File indexing completed on 2024-11-10 04:56:59

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2007 Philip Falkner <philip.falkner@gmail.com>
0006     SPDX-FileCopyrightText: 2009 Martin Gräßlin <mgraesslin@kde.org>
0007     SPDX-FileCopyrightText: 2010 Alexandre Pereira <pereira.alex@gmail.com>
0008     SPDX-FileCopyrightText: 2018 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0009 
0010     SPDX-License-Identifier: GPL-2.0-or-later
0011 */
0012 
0013 // own
0014 #include "glide.h"
0015 
0016 // KConfigSkeleton
0017 #include "glideconfig.h"
0018 
0019 #include "core/rendertarget.h"
0020 #include "core/renderviewport.h"
0021 #include "effect/effecthandler.h"
0022 
0023 // Qt
0024 #include <QMatrix4x4>
0025 #include <QSet>
0026 #include <qmath.h>
0027 
0028 #include <cmath>
0029 
0030 namespace KWin
0031 {
0032 
0033 static const QSet<QString> s_blacklist{
0034     QStringLiteral("ksmserver ksmserver"),
0035     QStringLiteral("ksmserver-logout-greeter ksmserver-logout-greeter"),
0036     QStringLiteral("ksplashqml ksplashqml"),
0037     // Spectacle needs to be blacklisted in order to stay out of its own screenshots.
0038     QStringLiteral("spectacle spectacle"), // x11
0039     QStringLiteral("spectacle org.kde.spectacle"), // wayland
0040 };
0041 
0042 static QMatrix4x4 createPerspectiveMatrix(const QRectF &rect, const qreal scale, const QMatrix4x4 &renderTargetTransformation)
0043 {
0044     QMatrix4x4 ret = renderTargetTransformation;
0045 
0046     const float fovY = std::tan(qDegreesToRadians(60.0f) / 2);
0047     const float aspect = 1.0f;
0048     const float zNear = 0.1f;
0049     const float zFar = 100.0f;
0050 
0051     const float yMax = zNear * fovY;
0052     const float yMin = -yMax;
0053     const float xMin = yMin * aspect;
0054     const float xMax = yMax * aspect;
0055 
0056     ret.frustum(xMin, xMax, yMin, yMax, zNear, zFar);
0057 
0058     const auto deviceRect = scaledRect(rect, scale);
0059 
0060     const float scaleFactor = 1.1 * fovY / yMax;
0061     ret.translate(xMin * scaleFactor, yMax * scaleFactor, -1.1);
0062     ret.scale((xMax - xMin) * scaleFactor / deviceRect.width(),
0063               -(yMax - yMin) * scaleFactor / deviceRect.height(),
0064               0.001);
0065     ret.translate(-deviceRect.x(), -deviceRect.y());
0066 
0067     return ret;
0068 }
0069 
0070 GlideEffect::GlideEffect()
0071 {
0072     GlideConfig::instance(effects->config());
0073     reconfigure(ReconfigureAll);
0074 
0075     connect(effects, &EffectsHandler::windowAdded, this, &GlideEffect::windowAdded);
0076     connect(effects, &EffectsHandler::windowClosed, this, &GlideEffect::windowClosed);
0077     connect(effects, &EffectsHandler::windowDataChanged, this, &GlideEffect::windowDataChanged);
0078 }
0079 
0080 GlideEffect::~GlideEffect() = default;
0081 
0082 void GlideEffect::reconfigure(ReconfigureFlags flags)
0083 {
0084     GlideConfig::self()->read();
0085     m_duration = std::chrono::milliseconds(animationTime<GlideConfig>(160));
0086 
0087     m_inParams.edge = static_cast<RotationEdge>(GlideConfig::inRotationEdge());
0088     m_inParams.angle.from = GlideConfig::inRotationAngle();
0089     m_inParams.angle.to = 0.0;
0090     m_inParams.distance.from = GlideConfig::inDistance();
0091     m_inParams.distance.to = 0.0;
0092     m_inParams.opacity.from = GlideConfig::inOpacity();
0093     m_inParams.opacity.to = 1.0;
0094 
0095     m_outParams.edge = static_cast<RotationEdge>(GlideConfig::outRotationEdge());
0096     m_outParams.angle.from = 0.0;
0097     m_outParams.angle.to = GlideConfig::outRotationAngle();
0098     m_outParams.distance.from = 0.0;
0099     m_outParams.distance.to = GlideConfig::outDistance();
0100     m_outParams.opacity.from = 1.0;
0101     m_outParams.opacity.to = GlideConfig::outOpacity();
0102 }
0103 
0104 void GlideEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
0105 {
0106     data.mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS;
0107 
0108     effects->prePaintScreen(data, presentTime);
0109 }
0110 
0111 void GlideEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime)
0112 {
0113     auto animationIt = m_animations.find(w);
0114     if (animationIt != m_animations.end()) {
0115         (*animationIt).timeLine.advance(presentTime);
0116         data.setTransformed();
0117     }
0118 
0119     effects->prePaintWindow(w, data, presentTime);
0120 }
0121 
0122 void GlideEffect::paintWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, QRegion region, WindowPaintData &data)
0123 {
0124     auto animationIt = m_animations.constFind(w);
0125     if (animationIt == m_animations.constEnd()) {
0126         effects->paintWindow(renderTarget, viewport, w, mask, region, data);
0127         return;
0128     }
0129 
0130     // Perspective projection distorts objects near edges
0131     // of the viewport. This is critical because distortions
0132     // near edges of the viewport are not desired with this effect.
0133     // To fix this, the center of the window will be moved to the origin,
0134     // after applying perspective projection, the center is moved back
0135     // to its "original" projected position. Overall, this is how the window
0136     // will be transformed:
0137     //  [move to the origin] -> [rotate] -> [translate] ->
0138     //    -> [perspective projection] -> [reverse "move to the origin"]
0139 
0140     const QMatrix4x4 oldProjMatrix = createPerspectiveMatrix(viewport.renderRect(), viewport.scale(), renderTarget.transform().toMatrix());
0141     const auto frame = w->frameGeometry();
0142     const QRectF windowGeo = scaledRect(frame, viewport.scale());
0143     const QVector3D invOffset = oldProjMatrix.map(QVector3D(windowGeo.center()));
0144     QMatrix4x4 invOffsetMatrix;
0145     invOffsetMatrix.translate(invOffset.x(), invOffset.y());
0146 
0147     data.setProjectionMatrix(invOffsetMatrix * oldProjMatrix);
0148 
0149     // Move the center of the window to the origin.
0150     const QPointF offset = viewport.renderRect().center() - w->frameGeometry().center();
0151     data.translate(offset.x(), offset.y());
0152 
0153     const GlideParams params = w->isDeleted() ? m_outParams : m_inParams;
0154     const qreal t = (*animationIt).timeLine.value();
0155 
0156     switch (params.edge) {
0157     case RotationEdge::Top:
0158         data.setRotationAxis(Qt::XAxis);
0159         data.setRotationOrigin(QVector3D(0, 0, 0));
0160         data.setRotationAngle(-interpolate(params.angle.from, params.angle.to, t));
0161         break;
0162 
0163     case RotationEdge::Right:
0164         data.setRotationAxis(Qt::YAxis);
0165         data.setRotationOrigin(QVector3D(w->width(), 0, 0));
0166         data.setRotationAngle(-interpolate(params.angle.from, params.angle.to, t));
0167         break;
0168 
0169     case RotationEdge::Bottom:
0170         data.setRotationAxis(Qt::XAxis);
0171         data.setRotationOrigin(QVector3D(0, w->height(), 0));
0172         data.setRotationAngle(interpolate(params.angle.from, params.angle.to, t));
0173         break;
0174 
0175     case RotationEdge::Left:
0176         data.setRotationAxis(Qt::YAxis);
0177         data.setRotationOrigin(QVector3D(0, 0, 0));
0178         data.setRotationAngle(interpolate(params.angle.from, params.angle.to, t));
0179         break;
0180 
0181     default:
0182         // Fallback to Top.
0183         data.setRotationAxis(Qt::XAxis);
0184         data.setRotationOrigin(QVector3D(0, 0, 0));
0185         data.setRotationAngle(-interpolate(params.angle.from, params.angle.to, t));
0186         break;
0187     }
0188 
0189     data.setZTranslation(-interpolate(params.distance.from, params.distance.to, t));
0190     data.multiplyOpacity(interpolate(params.opacity.from, params.opacity.to, t));
0191 
0192     effects->paintWindow(renderTarget, viewport, w, mask, region, data);
0193 }
0194 
0195 void GlideEffect::postPaintScreen()
0196 {
0197     auto animationIt = m_animations.begin();
0198     while (animationIt != m_animations.end()) {
0199         if ((*animationIt).timeLine.done()) {
0200             animationIt = m_animations.erase(animationIt);
0201         } else {
0202             ++animationIt;
0203         }
0204     }
0205 
0206     effects->addRepaintFull();
0207     effects->postPaintScreen();
0208 }
0209 
0210 bool GlideEffect::isActive() const
0211 {
0212     return !m_animations.isEmpty();
0213 }
0214 
0215 bool GlideEffect::supported()
0216 {
0217     return effects->isOpenGLCompositing()
0218         && effects->animationsSupported();
0219 }
0220 
0221 void GlideEffect::windowAdded(EffectWindow *w)
0222 {
0223     if (effects->activeFullScreenEffect()) {
0224         return;
0225     }
0226 
0227     if (!isGlideWindow(w)) {
0228         return;
0229     }
0230 
0231     if (!w->isVisible()) {
0232         return;
0233     }
0234 
0235     const void *addGrab = w->data(WindowAddedGrabRole).value<void *>();
0236     if (addGrab && addGrab != this) {
0237         return;
0238     }
0239 
0240     w->setData(WindowAddedGrabRole, QVariant::fromValue(static_cast<void *>(this)));
0241 
0242     GlideAnimation &animation = m_animations[w];
0243     animation.timeLine.reset();
0244     animation.timeLine.setDirection(TimeLine::Forward);
0245     animation.timeLine.setDuration(m_duration);
0246     animation.timeLine.setEasingCurve(QEasingCurve::InCurve);
0247 
0248     effects->addRepaintFull();
0249 }
0250 
0251 void GlideEffect::windowClosed(EffectWindow *w)
0252 {
0253     if (effects->activeFullScreenEffect()) {
0254         return;
0255     }
0256 
0257     if (!isGlideWindow(w)) {
0258         return;
0259     }
0260 
0261     if (!w->isVisible() || w->skipsCloseAnimation()) {
0262         return;
0263     }
0264 
0265     const void *closeGrab = w->data(WindowClosedGrabRole).value<void *>();
0266     if (closeGrab && closeGrab != this) {
0267         return;
0268     }
0269 
0270     w->setData(WindowClosedGrabRole, QVariant::fromValue(static_cast<void *>(this)));
0271 
0272     GlideAnimation &animation = m_animations[w];
0273     animation.deletedRef = EffectWindowDeletedRef(w);
0274     animation.timeLine.reset();
0275     animation.timeLine.setDirection(TimeLine::Forward);
0276     animation.timeLine.setDuration(m_duration);
0277     animation.timeLine.setEasingCurve(QEasingCurve::OutCurve);
0278 
0279     effects->addRepaintFull();
0280 }
0281 
0282 void GlideEffect::windowDataChanged(EffectWindow *w, int role)
0283 {
0284     if (role != WindowAddedGrabRole && role != WindowClosedGrabRole) {
0285         return;
0286     }
0287 
0288     if (w->data(role).value<void *>() == this) {
0289         return;
0290     }
0291 
0292     auto animationIt = m_animations.find(w);
0293     if (animationIt != m_animations.end()) {
0294         m_animations.erase(animationIt);
0295     }
0296 }
0297 
0298 bool GlideEffect::isGlideWindow(EffectWindow *w) const
0299 {
0300     // We don't want to animate most of plasmashell's windows, yet, some
0301     // of them we want to, for example, Task Manager Settings window.
0302     // The problem is that all those window share single window class.
0303     // So, the only way to decide whether a window should be animated is
0304     // to use a heuristic: if a window has decoration, then it's most
0305     // likely a dialog or a settings window so we have to animate it.
0306     if (w->windowClass() == QLatin1String("plasmashell plasmashell")
0307         || w->windowClass() == QLatin1String("plasmashell org.kde.plasmashell")) {
0308         return w->hasDecoration();
0309     }
0310 
0311     if (s_blacklist.contains(w->windowClass())) {
0312         return false;
0313     }
0314 
0315     if (w->hasDecoration()) {
0316         return true;
0317     }
0318 
0319     // Don't animate combobox popups, tooltips, popup menus, etc.
0320     if (w->isPopupWindow()) {
0321         return false;
0322     }
0323 
0324     // Don't animate the outline and the screenlocker as it looks bad.
0325     if (w->isLockScreen() || w->isOutline()) {
0326         return false;
0327     }
0328 
0329     // Override-redirect windows are usually used for user interface
0330     // concepts that are not expected to be animated by this effect.
0331     if (w->isX11Client() && !w->isManaged()) {
0332         return false;
0333     }
0334 
0335     return w->isNormalWindow()
0336         || w->isDialog();
0337 }
0338 
0339 } // namespace KWin
0340 
0341 #include "moc_glide.cpp"