KWin
Loading...
Searching...
No Matches
slidingpopups.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 Marco Martin notmart @gmail.com
6 SPDX-FileCopyrightText: 2018 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
7
8 SPDX-License-Identifier: GPL-2.0-or-later
9*/
10
11#include "slidingpopups.h"
12#include "slidingpopupsconfig.h"
13
15#include "wayland/display.h"
16#include "wayland/slide.h"
17#include "wayland/surface.h"
18
19#include <QFontMetrics>
20#include <QGuiApplication>
21#include <QTimer>
22#include <QWindow>
23
24#include <KWindowEffects>
25
26Q_DECLARE_METATYPE(KWindowEffects::SlideFromLocation)
27
28namespace KWin
29{
30
31SlideManagerInterface *SlidingPopupsEffect::s_slideManager = nullptr;
32QTimer *SlidingPopupsEffect::s_slideManagerRemoveTimer = nullptr;
33
35{
36 SlidingPopupsConfig::instance(effects->config());
37
38 Display *display = effects->waylandDisplay();
39 if (display) {
40 if (!s_slideManagerRemoveTimer) {
41 s_slideManagerRemoveTimer = new QTimer(QCoreApplication::instance());
42 s_slideManagerRemoveTimer->setSingleShot(true);
43 s_slideManagerRemoveTimer->callOnTimeout([]() {
44 s_slideManager->remove();
45 s_slideManager = nullptr;
46 });
47 }
48 s_slideManagerRemoveTimer->stop();
49 if (!s_slideManager) {
50 s_slideManager = new SlideManagerInterface(display, s_slideManagerRemoveTimer);
51 }
52 }
53
54 m_slideLength = QFontMetrics(QGuiApplication::font()).height() * 8;
55
56 m_atom = effects->announceSupportProperty("_KDE_SLIDE", this);
57 connect(effects, &EffectsHandler::windowAdded, this, &SlidingPopupsEffect::slotWindowAdded);
58 connect(effects, &EffectsHandler::windowClosed, this, &SlidingPopupsEffect::slotWindowClosed);
59 connect(effects, &EffectsHandler::windowDeleted, this, &SlidingPopupsEffect::slotWindowDeleted);
60 connect(effects, &EffectsHandler::propertyNotify, this, &SlidingPopupsEffect::slotPropertyNotify);
61 connect(effects, &EffectsHandler::xcbConnectionChanged, this, [this]() {
62 m_atom = effects->announceSupportProperty(QByteArrayLiteral("_KDE_SLIDE"), this);
63 });
65 this, &SlidingPopupsEffect::stopAnimations);
67 this, &SlidingPopupsEffect::stopAnimations);
69 this, &SlidingPopupsEffect::stopAnimations);
70
72
73 const QList<EffectWindow *> windows = effects->stackingOrder();
74 for (EffectWindow *window : windows) {
75 setupSlideData(window);
76 }
77}
78
80{
81 // When compositing is restarted, avoid removing the manager immediately.
82 if (s_slideManager) {
83 s_slideManagerRemoveTimer->start(1000);
84 }
85
86 // Cancel animations here while both m_animations and m_animationsData are still valid.
87 // slotWindowDeleted may access m_animationsData when an animation is removed.
88 m_animations.clear();
89 m_animationsData.clear();
90}
91
96
97void SlidingPopupsEffect::reconfigure(ReconfigureFlags flags)
98{
99 SlidingPopupsConfig::self()->read();
100 m_slideInDuration = std::chrono::milliseconds(
101 static_cast<int>(animationTime(SlidingPopupsConfig::slideInTime() != 0 ? SlidingPopupsConfig::slideInTime() : 150)));
102 m_slideOutDuration = std::chrono::milliseconds(
103 static_cast<int>(animationTime(SlidingPopupsConfig::slideOutTime() != 0 ? SlidingPopupsConfig::slideOutTime() : 250)));
104
105 auto animationIt = m_animations.begin();
106 while (animationIt != m_animations.end()) {
107 const auto duration = ((*animationIt).kind == AnimationKind::In)
108 ? m_slideInDuration
109 : m_slideOutDuration;
110 (*animationIt).timeLine.setDuration(duration);
111 ++animationIt;
112 }
113
114 auto dataIt = m_animationsData.begin();
115 while (dataIt != m_animationsData.end()) {
116 (*dataIt).slideInDuration = m_slideInDuration;
117 (*dataIt).slideOutDuration = m_slideOutDuration;
118 ++dataIt;
119 }
120}
121
122void SlidingPopupsEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime)
123{
124 auto animationIt = m_animations.find(w);
125 if (animationIt == m_animations.end()) {
126 effects->prePaintWindow(w, data, presentTime);
127 return;
128 }
129
130 (*animationIt).timeLine.advance(presentTime);
131 data.setTransformed();
132
133 effects->prePaintWindow(w, data, presentTime);
134}
135
136void SlidingPopupsEffect::paintWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, QRegion region, WindowPaintData &data)
137{
138 auto animationIt = m_animations.constFind(w);
139 if (animationIt == m_animations.constEnd()) {
140 effects->paintWindow(renderTarget, viewport, w, mask, region, data);
141 return;
142 }
143
144 const AnimationData &animData = m_animationsData[w];
145 const qreal slideLength = (animData.slideLength > 0) ? animData.slideLength : m_slideLength;
146
147 const QRectF screenRect = effects->clientArea(FullScreenArea, w->screen(), effects->currentDesktop());
148 int splitPoint = 0;
149 const QRectF geo = w->expandedGeometry();
150 const qreal t = (*animationIt).timeLine.value();
151
152 switch (animData.location) {
153 case Location::Left:
154 if (slideLength < geo.width()) {
155 data.multiplyOpacity(t);
156 }
157 data.translate(-interpolate(std::min(geo.width(), slideLength), 0.0, t));
158 splitPoint = geo.width() - (geo.x() + geo.width() - screenRect.x() - animData.offset);
159 region &= QRegion(geo.x() + splitPoint, geo.y(), geo.width() - splitPoint, geo.height());
160 break;
161 case Location::Top:
162 if (slideLength < geo.height()) {
163 data.multiplyOpacity(t);
164 }
165 data.translate(0.0, -interpolate(std::min(geo.height(), slideLength), 0.0, t));
166 splitPoint = geo.height() - (geo.y() + geo.height() - screenRect.y() - animData.offset);
167 region &= QRegion(geo.x(), geo.y() + splitPoint, geo.width(), geo.height() - splitPoint);
168 break;
169 case Location::Right:
170 if (slideLength < geo.width()) {
171 data.multiplyOpacity(t);
172 }
173 data.translate(interpolate(std::min(geo.width(), slideLength), 0.0, t));
174 splitPoint = screenRect.x() + screenRect.width() - geo.x() - animData.offset;
175 region &= QRegion(geo.x(), geo.y(), splitPoint, geo.height());
176 break;
177 case Location::Bottom:
178 default:
179 if (slideLength < geo.height()) {
180 data.multiplyOpacity(t);
181 }
182 data.translate(0.0, interpolate(std::min(geo.height(), slideLength), 0.0, t));
183 splitPoint = screenRect.y() + screenRect.height() - geo.y() - animData.offset;
184 region &= QRegion(geo.x(), geo.y(), geo.width(), splitPoint);
185 }
186
187 effects->paintWindow(renderTarget, viewport, w, mask, region, data);
188}
189
191{
192 auto animationIt = m_animations.find(w);
193 if (animationIt != m_animations.end()) {
195 if ((*animationIt).timeLine.done()) {
196 if (!w->isDeleted()) {
198 w->setData(WindowForceBlurRole, QVariant());
199 }
200 m_animations.erase(animationIt);
201 }
202 }
203
205}
206
207void SlidingPopupsEffect::setupSlideData(EffectWindow *w)
208{
209 connect(w, &EffectWindow::windowFrameGeometryChanged, this, &SlidingPopupsEffect::slotWindowFrameGeometryChanged);
210 connect(w, &EffectWindow::windowShown, this, &SlidingPopupsEffect::slideIn);
211 connect(w, &EffectWindow::windowHidden, this, &SlidingPopupsEffect::slideOut);
212
213 // X11
214 if (m_atom != XCB_ATOM_NONE) {
215 slotPropertyNotify(w, m_atom);
216 }
217
218 // Wayland
219 if (auto surf = w->surface()) {
220 slotWaylandSlideOnShowChanged(w);
221 connect(surf, &SurfaceInterface::slideOnShowHideChanged, this, [this, surf] {
222 slotWaylandSlideOnShowChanged(effects->findWindow(surf));
223 });
224 }
225
226 if (auto internal = w->internalWindow()) {
227 internal->installEventFilter(this);
228 setupInternalWindowSlide(w);
229 }
230}
231
232void SlidingPopupsEffect::slotWindowAdded(EffectWindow *w)
233{
234 setupSlideData(w);
235 if (!w->isHidden()) {
236 slideIn(w);
237 }
238}
239
240void SlidingPopupsEffect::slotWindowClosed(EffectWindow *w)
241{
242 if (!w->isHidden()) {
243 slideOut(w);
244 }
245}
246
247void SlidingPopupsEffect::slotWindowDeleted(EffectWindow *w)
248{
249 m_animationsData.remove(w);
250}
251
252void SlidingPopupsEffect::slotPropertyNotify(EffectWindow *w, long atom)
253{
254 if (!w || atom != m_atom || m_atom == XCB_ATOM_NONE) {
255 return;
256 }
257
258 // _KDE_SLIDE atom format(each field is an uint32_t):
259 // <offset> <location> [<slide in duration>] [<slide out duration>] [<slide length>]
260 //
261 // If offset is equal to -1, this effect will decide what offset to use
262 // given edge of the screen, from which the window has to slide.
263 //
264 // If slide in duration is equal to 0 milliseconds, the default slide in
265 // duration will be used. Same with the slide out duration.
266 //
267 // NOTE: If only slide in duration has been provided, then it will be
268 // also used as slide out duration. I.e. if you provided only slide in
269 // duration, then slide in duration == slide out duration.
270
271 const QByteArray rawAtomData = w->readProperty(m_atom, m_atom, 32);
272
273 if (rawAtomData.isEmpty()) {
274 // Property was removed, thus also remove the effect for window
275 if (w->data(WindowClosedGrabRole).value<void *>() == this) {
276 w->setData(WindowClosedGrabRole, QVariant());
277 }
278 m_animations.remove(w);
279 m_animationsData.remove(w);
280 return;
281 }
282
283 // Offset and location are required.
284 if (static_cast<size_t>(rawAtomData.size()) < sizeof(uint32_t) * 2) {
285 return;
286 }
287
288 const auto *atomData = reinterpret_cast<const uint32_t *>(rawAtomData.data());
289 AnimationData &animData = m_animationsData[w];
290 animData.offset = atomData[0];
291
292 switch (atomData[1]) {
293 case 0: // West
294 animData.location = Location::Left;
295 break;
296 case 1: // North
297 animData.location = Location::Top;
298 break;
299 case 2: // East
300 animData.location = Location::Right;
301 break;
302 case 3: // South
303 default:
304 animData.location = Location::Bottom;
305 break;
306 }
307
308 if (static_cast<size_t>(rawAtomData.size()) >= sizeof(uint32_t) * 3) {
309 animData.slideInDuration = std::chrono::milliseconds(atomData[2]);
310 if (static_cast<size_t>(rawAtomData.size()) >= sizeof(uint32_t) * 4) {
311 animData.slideOutDuration = std::chrono::milliseconds(atomData[3]);
312 } else {
313 animData.slideOutDuration = animData.slideInDuration;
314 }
315 } else {
316 animData.slideInDuration = m_slideInDuration;
317 animData.slideOutDuration = m_slideOutDuration;
318 }
319
320 if (static_cast<size_t>(rawAtomData.size()) >= sizeof(uint32_t) * 5) {
321 animData.slideLength = atomData[4];
322 } else {
323 animData.slideLength = 0;
324 }
325
326 setupAnimData(w);
327}
328
329void SlidingPopupsEffect::slotWindowFrameGeometryChanged(EffectWindow *w, const QRectF &)
330{
331 if (w == effects->inputPanel()) {
332 setupInputPanelSlide();
333 }
334}
335
336void SlidingPopupsEffect::setupAnimData(EffectWindow *w)
337{
338 const QRectF screenRect = effects->clientArea(FullScreenArea, w->screen(), effects->currentDesktop());
339 const QRectF windowGeo = w->frameGeometry();
340 AnimationData &animData = m_animationsData[w];
341
342 if (animData.offset == -1) {
343 switch (animData.location) {
344 case Location::Left:
345 animData.offset = std::max<qreal>(windowGeo.left() - screenRect.left(), 0);
346 break;
347 case Location::Top:
348 animData.offset = std::max<qreal>(windowGeo.top() - screenRect.top(), 0);
349 break;
350 case Location::Right:
351 animData.offset = std::max<qreal>(screenRect.right() - windowGeo.right(), 0);
352 break;
353 case Location::Bottom:
354 default:
355 animData.offset = std::max<qreal>(screenRect.bottom() - windowGeo.bottom(), 0);
356 break;
357 }
358 }
359 // sanitize
360 switch (animData.location) {
361 case Location::Left:
362 animData.offset = std::max<qreal>(windowGeo.left() - screenRect.left(), animData.offset);
363 break;
364 case Location::Top:
365 animData.offset = std::max<qreal>(windowGeo.top() - screenRect.top(), animData.offset);
366 break;
367 case Location::Right:
368 animData.offset = std::max<qreal>(screenRect.right() - windowGeo.right(), animData.offset);
369 break;
370 case Location::Bottom:
371 default:
372 animData.offset = std::max<qreal>(screenRect.bottom() - windowGeo.bottom(), animData.offset);
373 break;
374 }
375
376 animData.slideInDuration = (animData.slideInDuration.count() != 0)
377 ? animData.slideInDuration
378 : m_slideInDuration;
379
380 animData.slideOutDuration = (animData.slideOutDuration.count() != 0)
381 ? animData.slideOutDuration
382 : m_slideOutDuration;
383
384 // Grab the window, so other windowClosed effects will ignore it
385 w->setData(WindowClosedGrabRole, QVariant::fromValue(static_cast<void *>(this)));
386}
387
388void SlidingPopupsEffect::slotWaylandSlideOnShowChanged(EffectWindow *w)
389{
390 if (!w) {
391 return;
392 }
393
394 SurfaceInterface *surf = w->surface();
395 if (!surf) {
396 return;
397 }
398
399 if (surf->slideOnShowHide()) {
400 AnimationData &animData = m_animationsData[w];
401
402 animData.offset = surf->slideOnShowHide()->offset();
403
404 switch (surf->slideOnShowHide()->location()) {
406 animData.location = Location::Top;
407 break;
409 animData.location = Location::Left;
410 break;
412 animData.location = Location::Right;
413 break;
415 default:
416 animData.location = Location::Bottom;
417 break;
418 }
419 animData.slideLength = 0;
420 animData.slideInDuration = m_slideInDuration;
421 animData.slideOutDuration = m_slideOutDuration;
422
423 setupAnimData(w);
424 }
425}
426
427void SlidingPopupsEffect::setupInternalWindowSlide(EffectWindow *w)
428{
429 if (!w) {
430 return;
431 }
432 auto internal = w->internalWindow();
433 if (!internal) {
434 return;
435 }
436 const QVariant slideProperty = internal->property("kwin_slide");
437 if (!slideProperty.isValid()) {
438 return;
439 }
440 Location location;
441 switch (slideProperty.value<KWindowEffects::SlideFromLocation>()) {
442 case KWindowEffects::BottomEdge:
443 location = Location::Bottom;
444 break;
445 case KWindowEffects::TopEdge:
446 location = Location::Top;
447 break;
448 case KWindowEffects::RightEdge:
449 location = Location::Right;
450 break;
451 case KWindowEffects::LeftEdge:
452 location = Location::Left;
453 break;
454 default:
455 return;
456 }
457 AnimationData &animData = m_animationsData[w];
458 animData.location = location;
459 bool intOk = false;
460 animData.offset = internal->property("kwin_slide_offset").toInt(&intOk);
461 if (!intOk) {
462 animData.offset = -1;
463 }
464 animData.slideLength = 0;
465 animData.slideInDuration = m_slideInDuration;
466 animData.slideOutDuration = m_slideOutDuration;
467
468 setupAnimData(w);
469}
470
471void SlidingPopupsEffect::setupInputPanelSlide()
472{
473 auto w = effects->inputPanel();
474
475 if (!w || effects->isInputPanelOverlay()) {
476 return;
477 }
478
479 AnimationData &animData = m_animationsData[w];
480 animData.location = Location::Bottom;
481 animData.offset = 0;
482 animData.slideLength = 0;
483 animData.slideInDuration = m_slideInDuration;
484 animData.slideOutDuration = m_slideOutDuration;
485
486 setupAnimData(w);
487
488 slideIn(w);
489}
490
491bool SlidingPopupsEffect::eventFilter(QObject *watched, QEvent *event)
492{
493 auto internal = qobject_cast<QWindow *>(watched);
494 if (internal && event->type() == QEvent::DynamicPropertyChange) {
495 QDynamicPropertyChangeEvent *pe = static_cast<QDynamicPropertyChangeEvent *>(event);
496 if (pe->propertyName() == "kwin_slide" || pe->propertyName() == "kwin_slide_offset") {
497 if (auto w = effects->findWindow(internal)) {
498 setupInternalWindowSlide(w);
499 }
500 }
501 }
502 return false;
503}
504
505void SlidingPopupsEffect::slideIn(EffectWindow *w)
506{
508 return;
509 }
510
511 if (!w->isVisible()) {
512 return;
513 }
514
515 auto dataIt = m_animationsData.constFind(w);
516 if (dataIt == m_animationsData.constEnd()) {
517 return;
518 }
519
520 Animation &animation = m_animations[w];
521 animation.kind = AnimationKind::In;
522 animation.timeLine.setDirection(TimeLine::Forward);
523 animation.timeLine.setDuration((*dataIt).slideInDuration);
524 animation.timeLine.setEasingCurve(QEasingCurve::OutCubic);
525
526 // If the opposite animation (Out) was active and it had shorter duration,
527 // at this point, the timeline can end up in the "done" state. Thus, we have
528 // to reset it.
529 if (animation.timeLine.done()) {
530 animation.timeLine.reset();
531 }
532
533 w->setData(WindowAddedGrabRole, QVariant::fromValue(static_cast<void *>(this)));
534 w->setData(WindowForceBackgroundContrastRole, QVariant(true));
535 w->setData(WindowForceBlurRole, QVariant(true));
536
537 w->addRepaintFull();
538}
539
540void SlidingPopupsEffect::slideOut(EffectWindow *w)
541{
543 return;
544 }
545
546 if (!w->isVisible()) {
547 return;
548 }
549
550 auto dataIt = m_animationsData.constFind(w);
551 if (dataIt == m_animationsData.constEnd()) {
552 return;
553 }
554
555 Animation &animation = m_animations[w];
556 animation.deletedRef = EffectWindowDeletedRef(w);
557 animation.visibleRef = EffectWindowVisibleRef(w, EffectWindow::PAINT_DISABLED);
558 animation.kind = AnimationKind::Out;
559 animation.timeLine.setDirection(TimeLine::Backward);
560 animation.timeLine.setDuration((*dataIt).slideOutDuration);
561 // this is effectively InCubic because the direction is reversed
562 animation.timeLine.setEasingCurve(QEasingCurve::OutCubic);
563
564 // If the opposite animation (In) was active and it had shorter duration,
565 // at this point, the timeline can end up in the "done" state. Thus, we have
566 // to reset it.
567 if (animation.timeLine.done()) {
568 animation.timeLine.reset();
569 }
570
571 w->setData(WindowClosedGrabRole, QVariant::fromValue(static_cast<void *>(this)));
572 w->setData(WindowForceBackgroundContrastRole, QVariant(true));
573 w->setData(WindowForceBlurRole, QVariant(true));
574
575 w->addRepaintFull();
576}
577
578void SlidingPopupsEffect::stopAnimations()
579{
580 for (auto it = m_animations.constBegin(); it != m_animations.constEnd(); ++it) {
581 EffectWindow *w = it.key();
582
583 if (!w->isDeleted()) {
584 w->setData(WindowForceBackgroundContrastRole, QVariant());
585 w->setData(WindowForceBlurRole, QVariant());
586 }
587 }
588
589 m_animations.clear();
590}
591
593{
594 return !m_animations.isEmpty();
595}
596
597} // namespace
598
599#include "moc_slidingpopups.cpp"
Class holding the Wayland server display loop.
Definition display.h:34
Representation of a window used by/for Effect classes.
void windowHidden(KWin::EffectWindow *w)
bool isVisible() const
Q_SCRIPTABLE void addRepaintFull()
void windowFrameGeometryChanged(KWin::EffectWindow *window, const QRectF &oldGeometry)
Q_SCRIPTABLE void setData(int role, const QVariant &data)
SurfaceInterface * surface() const
KWin::Output * screen
QWindow * internalWindow
void windowShown(KWin::EffectWindow *w)
bool isDeleted() const
Display * waylandDisplay() const
void propertyNotify(KWin::EffectWindow *w, long atom)
void screenLockingChanged(bool locked)
void windowDeleted(KWin::EffectWindow *w)
bool animationsSupported() const
Q_SCRIPTABLE KWin::EffectWindow * findWindow(WId id) const
void windowClosed(KWin::EffectWindow *w)
QList< EffectWindow * > stackingOrder
Q_SCRIPTABLE void addRepaint(const QRectF &r)
void paintWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion &region, WindowPaintData &data)
KWin::VirtualDesktop * currentDesktop
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...
KWin::EffectWindow * inputPanel
void prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime)
void desktopChanged(KWin::VirtualDesktop *oldDesktop, KWin::VirtualDesktop *newDesktop, KWin::EffectWindow *with)
QRectF clientArea(clientAreaOption, const Output *screen, const VirtualDesktop *desktop) const
bool isInputPanelOverlay() const
KSharedConfigPtr config() const
void windowAdded(KWin::EffectWindow *w)
Effect * activeFullScreenEffect() const
void activeFullScreenEffectChanged()
void postPaintWindow(EffectWindow *w)
void reconfigure(ReconfigureFlags flags) override
bool isActive() const override
void paintWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, QRegion region, WindowPaintData &data) override
void postPaintWindow(EffectWindow *w) override
bool eventFilter(QObject *watched, QEvent *event) override
void prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime) override
qreal multiplyOpacity(qreal factor)
Definition effect.cpp:309
static double interpolate(double x, double y, double a)
Definition effect.h:910
static double animationTime(const KConfigGroup &cfg, const QString &key, int defaultTime)
Definition effect.cpp:483
void translate(qreal x, qreal y=0.0, qreal z=0.0)
Definition effect.cpp:116
@ ReconfigureAll
Definition effect.h:601
Q_DECLARE_METATYPE(KWin::SwitchEvent::State)
@ WindowAddedGrabRole
@ WindowForceBackgroundContrastRole
For fullscreen effects to enforce the background contrast,.
@ WindowClosedGrabRole
@ WindowForceBlurRole
For fullscreen effects to enforce blurring of windows,.
@ FullScreenArea
Definition globals.h:53
EffectsHandler * effects