File indexing completed on 2024-06-09 05:25:57

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2009 Lucas Murray <lmurray@undefinedfire.com>
0006     SPDX-FileCopyrightText: 2021 David Redondo <kde@david-redondo.de>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "highlightwindow.h"
0012 #include "effect/effecthandler.h"
0013 
0014 #include <QDBusConnection>
0015 
0016 Q_LOGGING_CATEGORY(KWIN_HIGHLIGHTWINDOW, "kwin_effect_highlightwindow", QtWarningMsg)
0017 
0018 namespace KWin
0019 {
0020 
0021 HighlightWindowEffect::HighlightWindowEffect()
0022     : m_easingCurve(QEasingCurve::Linear)
0023     , m_fadeDuration(animationTime(150))
0024     , m_monitorWindow(nullptr)
0025 {
0026     // TODO KF6 remove atom support
0027     m_atom = effects->announceSupportProperty("_KDE_WINDOW_HIGHLIGHT", this);
0028     connect(effects, &EffectsHandler::windowAdded, this, &HighlightWindowEffect::slotWindowAdded);
0029     connect(effects, &EffectsHandler::windowClosed, this, &HighlightWindowEffect::slotWindowClosed);
0030     connect(effects, &EffectsHandler::windowDeleted, this, &HighlightWindowEffect::slotWindowDeleted);
0031     connect(effects, &EffectsHandler::propertyNotify, this, [this](EffectWindow *w, long atom) {
0032         slotPropertyNotify(w, atom, nullptr);
0033     });
0034     connect(effects, &EffectsHandler::xcbConnectionChanged, this, [this]() {
0035         m_atom = effects->announceSupportProperty("_KDE_WINDOW_HIGHLIGHT", this);
0036     });
0037 
0038     QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kde/KWin/HighlightWindow"),
0039                                                  QStringLiteral("org.kde.KWin.HighlightWindow"),
0040                                                  this,
0041                                                  QDBusConnection::ExportScriptableContents);
0042     QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.KWin.HighlightWindow"));
0043 }
0044 
0045 HighlightWindowEffect::~HighlightWindowEffect()
0046 {
0047     QDBusConnection::sessionBus().unregisterService(QStringLiteral("org.kde.KWin.HighlightWindow"));
0048 }
0049 
0050 static bool isInitiallyHidden(EffectWindow *w)
0051 {
0052     // Is the window initially hidden until it is highlighted?
0053     return w->isMinimized() || !w->isOnCurrentDesktop();
0054 }
0055 
0056 static bool isHighlightWindow(EffectWindow *window)
0057 {
0058     return window->isNormalWindow() || window->isDialog();
0059 }
0060 
0061 void HighlightWindowEffect::highlightWindows(const QStringList &windows)
0062 {
0063     QList<EffectWindow *> effectWindows;
0064     effectWindows.reserve(windows.count());
0065     for (const auto &window : windows) {
0066         if (auto effectWindow = effects->findWindow(QUuid(window)); effectWindow) {
0067             effectWindows.append(effectWindow);
0068         } else if (auto effectWindow = effects->findWindow(window.toLong()); effectWindow) {
0069             effectWindows.append(effectWindow);
0070         }
0071     }
0072     highlightWindows(effectWindows);
0073 }
0074 
0075 void HighlightWindowEffect::slotWindowAdded(EffectWindow *w)
0076 {
0077     if (!m_highlightedWindows.isEmpty()) {
0078         // On X11, the tabbox may ask us to highlight itself before the windowAdded signal
0079         // is emitted because override-redirect windows are shown after synthetic 50ms delay.
0080         if (m_highlightedWindows.contains(w)) {
0081             return;
0082         }
0083         // This window was demanded to be highlighted before it appeared on the screen.
0084         for (const WId &id : std::as_const(m_highlightedIds)) {
0085             if (w == effects->findWindow(id)) {
0086                 const quint64 animationId = startHighlightAnimation(w);
0087                 complete(animationId);
0088                 return;
0089             }
0090         }
0091         if (isHighlightWindow(w)) {
0092             const quint64 animationId = startGhostAnimation(w); // this window is not currently highlighted
0093             complete(animationId);
0094         }
0095     }
0096     slotPropertyNotify(w, m_atom, w); // Check initial value
0097 }
0098 
0099 void HighlightWindowEffect::slotWindowClosed(EffectWindow *w)
0100 {
0101     if (m_monitorWindow == w) { // The monitoring window was destroyed
0102         finishHighlighting();
0103     }
0104 }
0105 
0106 void HighlightWindowEffect::slotWindowDeleted(EffectWindow *w)
0107 {
0108     m_animations.remove(w);
0109 }
0110 
0111 void HighlightWindowEffect::slotPropertyNotify(EffectWindow *w, long a, EffectWindow *addedWindow)
0112 {
0113     if (a != m_atom || m_atom == XCB_ATOM_NONE) {
0114         return; // Not our atom
0115     }
0116 
0117     // if the window is null, the property was set on the root window - see events.cpp
0118     QByteArray byteData = w ? w->readProperty(m_atom, m_atom, 32) : effects->readRootProperty(m_atom, m_atom, 32);
0119     if (byteData.length() < 1) {
0120         // Property was removed, clearing highlight
0121         if (!addedWindow || w != addedWindow) {
0122             finishHighlighting();
0123         }
0124         return;
0125     }
0126     auto *data = reinterpret_cast<uint32_t *>(byteData.data());
0127 
0128     if (!data[0]) {
0129         // Purposely clearing highlight by issuing a NULL target
0130         finishHighlighting();
0131         return;
0132     }
0133     m_monitorWindow = w;
0134     bool found = false;
0135     int length = byteData.length() / sizeof(data[0]);
0136     // foreach ( EffectWindow* e, m_highlightedWindows )
0137     //     effects->setElevatedWindow( e, false );
0138     m_highlightedWindows.clear();
0139     m_highlightedIds.clear();
0140     for (int i = 0; i < length; i++) {
0141         m_highlightedIds << data[i];
0142         EffectWindow *foundWin = effects->findWindow(data[i]);
0143         if (!foundWin) {
0144             qCDebug(KWIN_HIGHLIGHTWINDOW) << "Invalid window targetted for highlight. Requested:" << data[i];
0145             continue; // might come in later.
0146         }
0147         m_highlightedWindows.append(foundWin);
0148         // TODO: We cannot just simply elevate the window as this will elevate it over
0149         // Plasma tooltips and other such windows as well
0150         // effects->setElevatedWindow( foundWin, true );
0151         found = true;
0152     }
0153     if (!found) {
0154         finishHighlighting();
0155         return;
0156     }
0157     prepareHighlighting();
0158 }
0159 
0160 void HighlightWindowEffect::prepareHighlighting()
0161 {
0162     const QList<EffectWindow *> windows = effects->stackingOrder();
0163     for (EffectWindow *window : windows) {
0164         if (!isHighlightWindow(window)) {
0165             continue;
0166         }
0167         if (isHighlighted(window)) {
0168             startHighlightAnimation(window);
0169         } else {
0170             startGhostAnimation(window);
0171         }
0172     }
0173 }
0174 
0175 void HighlightWindowEffect::finishHighlighting()
0176 {
0177     const QList<EffectWindow *> windows = effects->stackingOrder();
0178     for (EffectWindow *window : windows) {
0179         if (isHighlightWindow(window)) {
0180             startRevertAnimation(window);
0181         }
0182     }
0183 
0184     // Sanity check, ideally, this should never happen.
0185     if (!m_animations.isEmpty()) {
0186         for (quint64 &animationId : m_animations) {
0187             cancel(animationId);
0188         }
0189         m_animations.clear();
0190     }
0191 
0192     m_monitorWindow = nullptr;
0193     m_highlightedWindows.clear();
0194 }
0195 
0196 void HighlightWindowEffect::highlightWindows(const QList<KWin::EffectWindow *> &windows)
0197 {
0198     if (windows.isEmpty()) {
0199         finishHighlighting();
0200         return;
0201     }
0202 
0203     m_monitorWindow = nullptr;
0204     m_highlightedWindows.clear();
0205     m_highlightedIds.clear();
0206     for (auto w : windows) {
0207         m_highlightedWindows << w;
0208     }
0209     prepareHighlighting();
0210 }
0211 
0212 quint64 HighlightWindowEffect::startGhostAnimation(EffectWindow *window)
0213 {
0214     quint64 &animationId = m_animations[window];
0215     if (animationId) {
0216         retarget(animationId, FPx2(m_ghostOpacity, m_ghostOpacity), m_fadeDuration);
0217     } else {
0218         const qreal startOpacity = isInitiallyHidden(window) ? 0 : 1;
0219         animationId = set(window, Opacity, 0, m_fadeDuration, FPx2(m_ghostOpacity, m_ghostOpacity),
0220                           m_easingCurve, 0, FPx2(startOpacity, startOpacity), false, false);
0221     }
0222     return animationId;
0223 }
0224 
0225 quint64 HighlightWindowEffect::startHighlightAnimation(EffectWindow *window)
0226 {
0227     quint64 &animationId = m_animations[window];
0228     if (animationId) {
0229         retarget(animationId, FPx2(1.0, 1.0), m_fadeDuration);
0230     } else {
0231         const qreal startOpacity = isInitiallyHidden(window) ? 0 : 1;
0232         animationId = set(window, Opacity, 0, m_fadeDuration, FPx2(1.0, 1.0),
0233                           m_easingCurve, 0, FPx2(startOpacity, startOpacity), false, false);
0234     }
0235     return animationId;
0236 }
0237 
0238 void HighlightWindowEffect::startRevertAnimation(EffectWindow *window)
0239 {
0240     const quint64 animationId = m_animations.take(window);
0241     if (animationId) {
0242         const qreal startOpacity = isHighlighted(window) ? 1 : m_ghostOpacity;
0243         const qreal endOpacity = isInitiallyHidden(window) ? 0 : 1;
0244         animate(window, Opacity, 0, m_fadeDuration, FPx2(endOpacity, endOpacity),
0245                 m_easingCurve, 0, FPx2(startOpacity, startOpacity), false, false);
0246         cancel(animationId);
0247     }
0248 }
0249 
0250 bool HighlightWindowEffect::isHighlighted(EffectWindow *window) const
0251 {
0252     return m_highlightedWindows.contains(window);
0253 }
0254 
0255 bool HighlightWindowEffect::provides(Feature feature)
0256 {
0257     switch (feature) {
0258     case HighlightWindows:
0259         return true;
0260     default:
0261         return false;
0262     }
0263 }
0264 
0265 bool HighlightWindowEffect::perform(Feature feature, const QVariantList &arguments)
0266 {
0267     if (feature != HighlightWindows) {
0268         return false;
0269     }
0270     if (arguments.size() != 1) {
0271         return false;
0272     }
0273     highlightWindows(arguments.first().value<QList<EffectWindow *>>());
0274     return true;
0275 }
0276 
0277 } // namespace
0278 
0279 #include "moc_highlightwindow.cpp"