KWin
Loading...
Searching...
No Matches
highlightwindow.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2009 Lucas Murray <lmurray@undefinedfire.com>
6 SPDX-FileCopyrightText: 2021 David Redondo <kde@david-redondo.de>
7
8 SPDX-License-Identifier: GPL-2.0-or-later
9*/
10
11#include "highlightwindow.h"
13
14#include <QDBusConnection>
15
16Q_LOGGING_CATEGORY(KWIN_HIGHLIGHTWINDOW, "kwin_effect_highlightwindow", QtWarningMsg)
17
18namespace KWin
19{
20
22 : m_easingCurve(QEasingCurve::Linear)
23 , m_fadeDuration(animationTime(150))
24 , m_monitorWindow(nullptr)
25{
26 // TODO KF6 remove atom support
27 m_atom = effects->announceSupportProperty("_KDE_WINDOW_HIGHLIGHT", this);
31 connect(effects, &EffectsHandler::propertyNotify, this, [this](EffectWindow *w, long atom) {
32 slotPropertyNotify(w, atom, nullptr);
33 });
34 connect(effects, &EffectsHandler::xcbConnectionChanged, this, [this]() {
35 m_atom = effects->announceSupportProperty("_KDE_WINDOW_HIGHLIGHT", this);
36 });
37
38 QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kde/KWin/HighlightWindow"),
39 QStringLiteral("org.kde.KWin.HighlightWindow"),
40 this,
41 QDBusConnection::ExportScriptableContents);
42 QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.KWin.HighlightWindow"));
43}
44
46{
47 QDBusConnection::sessionBus().unregisterService(QStringLiteral("org.kde.KWin.HighlightWindow"));
48}
49
50static bool isInitiallyHidden(EffectWindow *w)
51{
52 // Is the window initially hidden until it is highlighted?
53 return w->isMinimized() || !w->isOnCurrentDesktop();
54}
55
56static bool isHighlightWindow(EffectWindow *window)
57{
58 return window->isNormalWindow() || window->isDialog();
59}
60
61void HighlightWindowEffect::highlightWindows(const QStringList &windows)
62{
63 QList<EffectWindow *> effectWindows;
64 effectWindows.reserve(windows.count());
65 for (const auto &window : windows) {
66 if (auto effectWindow = effects->findWindow(QUuid(window)); effectWindow) {
67 effectWindows.append(effectWindow);
68 } else if (auto effectWindow = effects->findWindow(window.toLong()); effectWindow) {
69 effectWindows.append(effectWindow);
70 }
71 }
72 highlightWindows(effectWindows);
73}
74
76{
77 if (!m_highlightedWindows.isEmpty()) {
78 // On X11, the tabbox may ask us to highlight itself before the windowAdded signal
79 // is emitted because override-redirect windows are shown after synthetic 50ms delay.
80 if (m_highlightedWindows.contains(w)) {
81 return;
82 }
83 // This window was demanded to be highlighted before it appeared on the screen.
84 for (const WId &id : std::as_const(m_highlightedIds)) {
85 if (w == effects->findWindow(id)) {
86 const quint64 animationId = startHighlightAnimation(w);
87 complete(animationId);
88 return;
89 }
90 }
91 if (isHighlightWindow(w)) {
92 const quint64 animationId = startGhostAnimation(w); // this window is not currently highlighted
93 complete(animationId);
94 }
95 }
96 slotPropertyNotify(w, m_atom, w); // Check initial value
97}
98
100{
101 if (m_monitorWindow == w) { // The monitoring window was destroyed
102 finishHighlighting();
103 }
104}
105
107{
108 m_animations.remove(w);
109}
110
112{
113 if (a != m_atom || m_atom == XCB_ATOM_NONE) {
114 return; // Not our atom
115 }
116
117 // if the window is null, the property was set on the root window - see events.cpp
118 QByteArray byteData = w ? w->readProperty(m_atom, m_atom, 32) : effects->readRootProperty(m_atom, m_atom, 32);
119 if (byteData.length() < 1) {
120 // Property was removed, clearing highlight
121 if (!addedWindow || w != addedWindow) {
122 finishHighlighting();
123 }
124 return;
125 }
126 auto *data = reinterpret_cast<uint32_t *>(byteData.data());
127
128 if (!data[0]) {
129 // Purposely clearing highlight by issuing a NULL target
130 finishHighlighting();
131 return;
132 }
133 m_monitorWindow = w;
134 bool found = false;
135 int length = byteData.length() / sizeof(data[0]);
136 // foreach ( EffectWindow* e, m_highlightedWindows )
137 // effects->setElevatedWindow( e, false );
138 m_highlightedWindows.clear();
139 m_highlightedIds.clear();
140 for (int i = 0; i < length; i++) {
141 m_highlightedIds << data[i];
142 EffectWindow *foundWin = effects->findWindow(data[i]);
143 if (!foundWin) {
144 qCDebug(KWIN_HIGHLIGHTWINDOW) << "Invalid window targetted for highlight. Requested:" << data[i];
145 continue; // might come in later.
146 }
147 m_highlightedWindows.append(foundWin);
148 // TODO: We cannot just simply elevate the window as this will elevate it over
149 // Plasma tooltips and other such windows as well
150 // effects->setElevatedWindow( foundWin, true );
151 found = true;
152 }
153 if (!found) {
154 finishHighlighting();
155 return;
156 }
157 prepareHighlighting();
158}
159
160void HighlightWindowEffect::prepareHighlighting()
161{
162 const QList<EffectWindow *> windows = effects->stackingOrder();
163 for (EffectWindow *window : windows) {
164 if (!isHighlightWindow(window)) {
165 continue;
166 }
167 if (isHighlighted(window)) {
168 startHighlightAnimation(window);
169 } else {
170 startGhostAnimation(window);
171 }
172 }
173}
174
175void HighlightWindowEffect::finishHighlighting()
176{
177 const QList<EffectWindow *> windows = effects->stackingOrder();
178 for (EffectWindow *window : windows) {
179 if (isHighlightWindow(window)) {
180 startRevertAnimation(window);
181 }
182 }
183
184 // Sanity check, ideally, this should never happen.
185 if (!m_animations.isEmpty()) {
186 for (quint64 &animationId : m_animations) {
187 cancel(animationId);
188 }
189 m_animations.clear();
190 }
191
192 m_monitorWindow = nullptr;
193 m_highlightedWindows.clear();
194}
195
196void HighlightWindowEffect::highlightWindows(const QList<KWin::EffectWindow *> &windows)
197{
198 if (windows.isEmpty()) {
199 finishHighlighting();
200 return;
201 }
202
203 m_monitorWindow = nullptr;
204 m_highlightedWindows.clear();
205 m_highlightedIds.clear();
206 for (auto w : windows) {
207 m_highlightedWindows << w;
208 }
209 prepareHighlighting();
210}
211
212quint64 HighlightWindowEffect::startGhostAnimation(EffectWindow *window)
213{
214 quint64 &animationId = m_animations[window];
215 if (animationId) {
216 retarget(animationId, FPx2(m_ghostOpacity, m_ghostOpacity), m_fadeDuration);
217 } else {
218 const qreal startOpacity = isInitiallyHidden(window) ? 0 : 1;
219 animationId = set(window, Opacity, 0, m_fadeDuration, FPx2(m_ghostOpacity, m_ghostOpacity),
220 m_easingCurve, 0, FPx2(startOpacity, startOpacity), false, false);
221 }
222 return animationId;
223}
224
225quint64 HighlightWindowEffect::startHighlightAnimation(EffectWindow *window)
226{
227 quint64 &animationId = m_animations[window];
228 if (animationId) {
229 retarget(animationId, FPx2(1.0, 1.0), m_fadeDuration);
230 } else {
231 const qreal startOpacity = isInitiallyHidden(window) ? 0 : 1;
232 animationId = set(window, Opacity, 0, m_fadeDuration, FPx2(1.0, 1.0),
233 m_easingCurve, 0, FPx2(startOpacity, startOpacity), false, false);
234 }
235 return animationId;
236}
237
238void HighlightWindowEffect::startRevertAnimation(EffectWindow *window)
239{
240 const quint64 animationId = m_animations.take(window);
241 if (animationId) {
242 const qreal startOpacity = isHighlighted(window) ? 1 : m_ghostOpacity;
243 const qreal endOpacity = isInitiallyHidden(window) ? 0 : 1;
244 animate(window, Opacity, 0, m_fadeDuration, FPx2(endOpacity, endOpacity),
245 m_easingCurve, 0, FPx2(startOpacity, startOpacity), false, false);
246 cancel(animationId);
247 }
248}
249
250bool HighlightWindowEffect::isHighlighted(EffectWindow *window) const
251{
252 return m_highlightedWindows.contains(window);
253}
254
256{
257 switch (feature) {
258 case HighlightWindows:
259 return true;
260 default:
261 return false;
262 }
263}
264
265bool HighlightWindowEffect::perform(Feature feature, const QVariantList &arguments)
266{
267 if (feature != HighlightWindows) {
268 return false;
269 }
270 if (arguments.size() != 1) {
271 return false;
272 }
273 highlightWindows(arguments.first().value<QList<EffectWindow *>>());
274 return true;
275}
276
277} // namespace
278
279#include "moc_highlightwindow.cpp"
quint64 set(EffectWindow *w, Attribute a, uint meta, int ms, const FPx2 &to, const QEasingCurve &curve=QEasingCurve(), int delay=0, const FPx2 &from=FPx2(), bool fullScreen=false, bool keepAlive=true, GLShader *shader=nullptr)
bool complete(quint64 animationId)
bool retarget(quint64 animationId, FPx2 newTarget, int newRemainingTime=-1)
bool cancel(quint64 animationId)
quint64 animate(EffectWindow *w, Attribute a, uint meta, int ms, const FPx2 &to, const QEasingCurve &curve=QEasingCurve(), int delay=0, const FPx2 &from=FPx2(), bool fullScreen=false, bool keepAlive=true, GLShader *shader=nullptr)
Representation of a window used by/for Effect classes.
bool isMinimized() const
bool isOnCurrentDesktop() const
QByteArray readProperty(long atom, long type, int format) const
void propertyNotify(KWin::EffectWindow *w, long atom)
void windowDeleted(KWin::EffectWindow *w)
Q_SCRIPTABLE KWin::EffectWindow * findWindow(WId id) const
void windowClosed(KWin::EffectWindow *w)
QList< EffectWindow * > stackingOrder
QByteArray readRootProperty(long atom, long type, int format) const
xcb_atom_t announceSupportProperty(const QByteArray &propertyName, Effect *effect)
Announces support for the feature with the given name. If no other Effect has announced support for t...
void windowAdded(KWin::EffectWindow *w)
void slotWindowAdded(KWin::EffectWindow *w)
bool perform(Feature feature, const QVariantList &arguments) override
bool provides(Feature feature) override
void slotWindowDeleted(KWin::EffectWindow *w)
Q_SCRIPTABLE void highlightWindows(const QStringList &windows)
void slotPropertyNotify(KWin::EffectWindow *w, long atom, EffectWindow *addedWindow=nullptr)
void slotWindowClosed(KWin::EffectWindow *w)
@ HighlightWindows
Definition effect.h:579
EffectsHandler * effects