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: 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"