1 /*
2     KWin - the KDE window manager
3     This file is part of the KDE project.
4 
5     SPDX-FileCopyrightText: 2007 Rivo Laks <rivolaks@hot.ee>
6     SPDX-FileCopyrightText: 2008 Lucas Murray <lmurray@undefinedfire.com>
7 
8     SPDX-License-Identifier: GPL-2.0-or-later
9 */
10 
11 #include "presentwindows.h"
12 //KConfigSkeleton
13 #include "presentwindowsconfig.h"
14 #include <QAction>
15 #include <KGlobalAccel>
16 #include <KLocalizedString>
17 
18 #include <kwinglutils.h>
19 
20 #include <QMouseEvent>
21 #include <netwm_def.h>
22 
23 #include <QApplication>
24 #include <QDBusConnection>
25 #include <QQmlContext>
26 #include <QQmlEngine>
27 #include <QQuickItem>
28 #include <QQuickView>
29 #include <QGraphicsObject>
30 #include <QTimer>
31 #include <QVector2D>
32 #include <QVector4D>
33 
34 #include <climits>
35 #include <cmath>
36 
37 namespace KWin
38 {
39 
PresentWindowsEffect()40 PresentWindowsEffect::PresentWindowsEffect()
41     : m_proxy(this)
42     , m_activated(false)
43     , m_ignoreMinimized(false)
44     , m_decalOpacity(0.0)
45     , m_hasKeyboardGrab(false)
46     , m_mode(ModeCurrentDesktop)
47     , m_managerWindow(nullptr)
48     , m_needInitialSelection(false)
49     , m_highlightedWindow(nullptr)
50     , m_lastPresentTime(std::chrono::milliseconds::zero())
51     , m_filterFrame(nullptr)
52     , m_closeView(nullptr)
53     , m_exposeAction(new QAction(this))
54     , m_exposeAllAction(new QAction(this))
55     , m_exposeClassAction(new QAction(this))
56 {
57     initConfig<PresentWindowsConfig>();
58 
59     // TODO KF6 remove atom support
60     auto announceSupportProperties = [this] {
61         m_atomDesktop = effects->announceSupportProperty("_KDE_PRESENT_WINDOWS_DESKTOP", this);
62         m_atomWindows = effects->announceSupportProperty("_KDE_PRESENT_WINDOWS_GROUP", this);
63     };
64     announceSupportProperties();
65     connect(effects, &EffectsHandler::xcbConnectionChanged, this, announceSupportProperties);
66 
67     QAction* exposeAction = m_exposeAction;
68     exposeAction->setObjectName(QStringLiteral("Expose"));
69     exposeAction->setText(i18n("Toggle Present Windows (Current desktop)"));
70     KGlobalAccel::self()->setDefaultShortcut(exposeAction, QList<QKeySequence>() << Qt::CTRL + Qt::Key_F9);
71     KGlobalAccel::self()->setShortcut(exposeAction, QList<QKeySequence>() << Qt::CTRL + Qt::Key_F9);
72     shortcut = KGlobalAccel::self()->shortcut(exposeAction);
73     effects->registerGlobalShortcut(Qt::CTRL + Qt::Key_F9, exposeAction);
74     connect(exposeAction, &QAction::triggered, this, &PresentWindowsEffect::toggleActive);
75 
76     QAction* exposeAllAction = m_exposeAllAction;
77     exposeAllAction->setObjectName(QStringLiteral("ExposeAll"));
78     exposeAllAction->setText(i18n("Toggle Present Windows (All desktops)"));
79     KGlobalAccel::self()->setDefaultShortcut(exposeAllAction, QList<QKeySequence>() << Qt::CTRL + Qt::Key_F10 << Qt::Key_LaunchC);
80     KGlobalAccel::self()->setShortcut(exposeAllAction, QList<QKeySequence>() << Qt::CTRL + Qt::Key_F10 << Qt::Key_LaunchC);
81     shortcutAll = KGlobalAccel::self()->shortcut(exposeAllAction);
82     effects->registerGlobalShortcut(Qt::CTRL + Qt::Key_F10, exposeAllAction);
83     effects->registerTouchpadSwipeShortcut(SwipeDirection::Down, exposeAllAction);
84     connect(exposeAllAction, &QAction::triggered, this, &PresentWindowsEffect::toggleActiveAllDesktops);
85 
86     QAction* exposeClassAction = m_exposeClassAction;
87     exposeClassAction->setObjectName(QStringLiteral("ExposeClass"));
88     exposeClassAction->setText(i18n("Toggle Present Windows (Window class)"));
89     KGlobalAccel::self()->setDefaultShortcut(exposeClassAction, QList<QKeySequence>() << Qt::CTRL + Qt::Key_F7);
90     KGlobalAccel::self()->setShortcut(exposeClassAction, QList<QKeySequence>() << Qt::CTRL + Qt::Key_F7);
91     effects->registerGlobalShortcut(Qt::CTRL + Qt::Key_F7, exposeClassAction);
92     connect(exposeClassAction, &QAction::triggered, this, &PresentWindowsEffect::toggleActiveClass);
93     shortcutClass = KGlobalAccel::self()->shortcut(exposeClassAction);
94     connect(KGlobalAccel::self(), &KGlobalAccel::globalShortcutChanged, this, &PresentWindowsEffect::globalShortcutChanged);
95 
96     reconfigure(ReconfigureAll);
97     connect(effects, &EffectsHandler::windowAdded, this, &PresentWindowsEffect::slotWindowAdded);
98     connect(effects, &EffectsHandler::windowClosed, this, &PresentWindowsEffect::slotWindowClosed);
99     connect(effects, &EffectsHandler::windowDeleted, this, &PresentWindowsEffect::slotWindowDeleted);
100     connect(effects, &EffectsHandler::windowFrameGeometryChanged, this, &PresentWindowsEffect::slotWindowFrameGeometryChanged);
101     connect(effects, &EffectsHandler::propertyNotify, this, &PresentWindowsEffect::slotPropertyNotify);
102     connect(effects, &EffectsHandler::numberScreensChanged, this,
103         [this] {
104             if (isActive())
105                 reCreateGrids();
106         }
107     );
108     connect(effects, &EffectsHandler::screenAboutToLock, this, [this]() {
109         setActive(false);
110     });
111 
112     QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kde/KWin/PresentWindows"),
113                                                  QStringLiteral("org.kde.KWin.PresentWindows"),
114                                                  this,
115                                                  QDBusConnection::ExportScriptableSlots);
116     QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.KWin.PresentWindows"));
117 }
118 
~PresentWindowsEffect()119 PresentWindowsEffect::~PresentWindowsEffect()
120 {
121     QDBusConnection::sessionBus().unregisterService(QStringLiteral("org.kde.KWin.PresentWindows"));
122     delete m_filterFrame;
123     delete m_closeView;
124 }
125 
reconfigure(ReconfigureFlags)126 void PresentWindowsEffect::reconfigure(ReconfigureFlags)
127 {
128     PresentWindowsConfig::self()->read();
129     Q_FOREACH (ElectricBorder border, m_borderActivate) {
130         effects->unreserveElectricBorder(border, this);
131     }
132     Q_FOREACH (ElectricBorder border, m_borderActivateAll) {
133         effects->unreserveElectricBorder(border, this);
134     }
135     m_borderActivate.clear();
136     m_borderActivateAll.clear();
137 
138     Q_FOREACH (int i, PresentWindowsConfig::borderActivate()) {
139         m_borderActivate.append(ElectricBorder(i));
140         effects->reserveElectricBorder(ElectricBorder(i), this);
141     }
142     Q_FOREACH (int i, PresentWindowsConfig::borderActivateAll()) {
143         m_borderActivateAll.append(ElectricBorder(i));
144         effects->reserveElectricBorder(ElectricBorder(i), this);
145     }
146     Q_FOREACH (int i, PresentWindowsConfig::borderActivateClass()) {
147         m_borderActivateClass.append(ElectricBorder(i));
148         effects->reserveElectricBorder(ElectricBorder(i), this);
149     }
150 
151     m_layoutMode = PresentWindowsConfig::layoutMode();
152     m_showCaptions = PresentWindowsConfig::drawWindowCaptions();
153     m_showIcons = PresentWindowsConfig::drawWindowIcons();
154     m_doNotCloseWindows = !PresentWindowsConfig::allowClosingWindows();
155 
156     if (m_doNotCloseWindows) {
157         delete m_closeView;
158         m_closeView = nullptr;
159     }
160 
161     m_ignoreMinimized = PresentWindowsConfig::ignoreMinimized();
162     m_accuracy = PresentWindowsConfig::accuracy() * 20;
163     m_fillGaps = PresentWindowsConfig::fillGaps();
164     m_fadeDuration = double(animationTime(150));
165     m_showPanel = PresentWindowsConfig::showPanel();
166     m_leftButtonWindow = (WindowMouseAction)PresentWindowsConfig::leftButtonWindow();
167     m_middleButtonWindow = (WindowMouseAction)PresentWindowsConfig::middleButtonWindow();
168     m_rightButtonWindow = (WindowMouseAction)PresentWindowsConfig::rightButtonWindow();
169     m_leftButtonDesktop = (DesktopMouseAction)PresentWindowsConfig::leftButtonDesktop();
170     m_middleButtonDesktop = (DesktopMouseAction)PresentWindowsConfig::middleButtonDesktop();
171     m_rightButtonDesktop = (DesktopMouseAction)PresentWindowsConfig::rightButtonDesktop();
172 
173     // touch screen edges
174     const QVector<ElectricBorder> relevantBorders{ElectricLeft, ElectricTop, ElectricRight, ElectricBottom};
175     for (auto e : relevantBorders) {
176         effects->unregisterTouchBorder(e, m_exposeAction);
177         effects->unregisterTouchBorder(e, m_exposeAllAction);
178         effects->unregisterTouchBorder(e, m_exposeClassAction);
179     }
180     auto touchEdge = [&relevantBorders] (const QList<int> touchBorders, QAction *action) {
181         for (int i : touchBorders) {
182             if (!relevantBorders.contains(ElectricBorder(i))) {
183                 continue;
184             }
185             effects->registerTouchBorder(ElectricBorder(i), action);
186         }
187     };
188     touchEdge(PresentWindowsConfig::touchBorderActivate(), m_exposeAction);
189     touchEdge(PresentWindowsConfig::touchBorderActivateAll(), m_exposeAllAction);
190     touchEdge(PresentWindowsConfig::touchBorderActivateClass(), m_exposeClassAction);
191 }
192 
proxy()193 void* PresentWindowsEffect::proxy()
194 {
195     return &m_proxy;
196 }
197 
toggleActiveClass()198 void PresentWindowsEffect::toggleActiveClass()
199 {
200     if (!m_activated) {
201         if (!effects->activeWindow())
202             return;
203         m_mode = ModeWindowClass;
204         m_class = effects->activeWindow()->windowClass();
205     }
206     setActive(!m_activated);
207 }
208 
209 //-----------------------------------------------------------------------------
210 // Screen painting
211 
prePaintScreen(ScreenPrePaintData & data,std::chrono::milliseconds presentTime)212 void PresentWindowsEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
213 {
214     // The animation code assumes that the time diff cannot be 0, let's work around it.
215     int time;
216     if (m_lastPresentTime.count()) {
217         time = std::max(1, int((presentTime - m_lastPresentTime).count()));
218     } else {
219         time = 1;
220     }
221     m_lastPresentTime = presentTime;
222 
223     m_motionManager.calculate(time);
224 
225     // We need to mark the screen as having been transformed otherwise there will be no repainting
226     if (m_activated || m_motionManager.managingWindows())
227         data.mask |= Effect::PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS;
228 
229     if (m_activated)
230         m_decalOpacity = qMin(1.0, m_decalOpacity + time / m_fadeDuration);
231     else
232         m_decalOpacity = qMax(0.0, m_decalOpacity - time / m_fadeDuration);
233 
234     effects->prePaintScreen(data, presentTime);
235 }
236 
paintScreen(int mask,const QRegion & region,ScreenPaintData & data)237 void PresentWindowsEffect::paintScreen(int mask, const QRegion &region, ScreenPaintData &data)
238 {
239     effects->paintScreen(mask, region, data);
240 
241     // Display the filter box
242     if (!m_windowFilter.isEmpty())
243         m_filterFrame->render(region);
244 
245     if (m_closeView)
246         effects->renderEffectQuickView(m_closeView);
247 }
248 
postPaintScreen()249 void PresentWindowsEffect::postPaintScreen()
250 {
251     if (m_motionManager.areWindowsMoving()) {
252         effects->addRepaintFull();
253     } else {
254         m_lastPresentTime = std::chrono::milliseconds::zero();
255 
256         if (!m_activated && m_motionManager.managingWindows() && !(m_closeView && m_closeView->isVisible())) {
257             // We have finished moving them back, stop processing
258             m_motionManager.unmanageAll();
259 
260             DataHash::iterator i = m_windowData.begin();
261             while (i != m_windowData.end()) {
262                 delete i.value().textFrame;
263                 delete i.value().iconFrame;
264                 ++i;
265             }
266             m_windowData.clear();
267 
268             Q_FOREACH (EffectWindow * w, effects->stackingOrder()) {
269                 w->setData(WindowForceBlurRole, QVariant());
270                 w->setData(WindowForceBackgroundContrastRole, QVariant());
271             }
272             effects->setActiveFullScreenEffect(nullptr);
273             effects->addRepaintFull();
274         } else if (m_activated && m_needInitialSelection) {
275             m_needInitialSelection = false;
276             QMouseEvent me(QEvent::MouseMove, cursorPos(), Qt::NoButton, Qt::NoButton, Qt::NoModifier);
277             windowInputMouseEvent(&me);
278         }
279     }
280 
281     // Update windows that are changing brightness or opacity
282     for (auto i = m_windowData.begin(); i != m_windowData.end(); ++i) {
283         bool resetLastPresentTime = true;
284 
285         if (i.value().opacity > 0.0 && i.value().opacity < 1.0) {
286             i.key()->addRepaintFull();
287             resetLastPresentTime = false;
288         }
289         if (i.key()->isDesktop() && !m_motionManager.isManaging(i.key())) {
290             if (i.value().highlight != 0.3) {
291                 i.key()->addRepaintFull();
292                 resetLastPresentTime = false;
293             }
294         }
295         else if (i.value().highlight > 0.0 && i.value().highlight < 1.0) {
296             i.key()->addRepaintFull();
297             resetLastPresentTime = false;
298         }
299 
300         if (resetLastPresentTime) {
301             i->lastPresentTime = std::chrono::milliseconds::zero();
302         }
303     }
304 
305     effects->postPaintScreen();
306 }
307 
308 //-----------------------------------------------------------------------------
309 // Window painting
310 
prePaintWindow(EffectWindow * w,WindowPrePaintData & data,std::chrono::milliseconds presentTime)311 void PresentWindowsEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime)
312 {
313     // TODO: We should also check to see if any windows are fading just in case fading takes longer
314     //       than moving the windows when the effect is deactivated.
315     if (m_activated || m_motionManager.areWindowsMoving() || m_closeView) {
316         DataHash::iterator winData = m_windowData.find(w);
317         if (winData == m_windowData.end()) {
318             effects->prePaintWindow(w, data, presentTime);
319             return;
320         }
321         w->enablePainting(EffectWindow::PAINT_DISABLED_BY_MINIMIZE);   // Display always
322         w->enablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP);
323 
324         // The animation code assumes that the time diff cannot be 0, let's work around it.
325         int time;
326         if (winData->lastPresentTime.count()) {
327             time = std::max(1, int((presentTime - winData->lastPresentTime).count()));
328         } else {
329             time = 1;
330         }
331         winData->lastPresentTime = presentTime;
332 
333         // Calculate window's opacity
334         // TODO: Minimized windows or windows not on the current desktop are only 75% visible?
335         if (winData->visible) {
336             if (winData->deleted)
337                 winData->opacity = qMax(0.0, winData->opacity - time / m_fadeDuration);
338             else
339                 winData->opacity = qMin(/*(w->isMinimized() || !w->isOnCurrentDesktop()) ? 0.75 :*/ 1.0,
340                                           winData->opacity + time / m_fadeDuration);
341         } else
342             winData->opacity = qMax(0.0, winData->opacity - time / m_fadeDuration);
343 
344         if (winData->opacity <= 0.0) {
345             // don't disable painting for panels if show panel is set
346             if (!(m_showPanel && w->isDock()))
347                 w->disablePainting(EffectWindow::PAINT_DISABLED);
348         } else if (winData->opacity != 1.0)
349             data.setTranslucent();
350 
351         const bool isInMotion = m_motionManager.isManaging(w);
352         // Calculate window's brightness
353         if (w == m_highlightedWindow || !m_activated)
354             winData->highlight = qMin(1.0, winData->highlight + time / m_fadeDuration);
355         else if (!isInMotion && w->isDesktop())
356             winData->highlight = 0.3;
357         else
358             winData->highlight = qMax(0.0, winData->highlight - time / m_fadeDuration);
359 
360         // Closed windows
361         if (winData->deleted) {
362             data.setTranslucent();
363             if (winData->opacity <= 0.0 && winData->referenced) {
364                 // it's possible that another effect has referenced the window
365                 // we have to keep the window in the list to prevent flickering
366                 winData->referenced = false;
367                 w->unrefWindow();
368             } else
369                 w->enablePainting(EffectWindow::PAINT_DISABLED_BY_DELETE);
370         }
371 
372         // desktop windows on other desktops (Plasma activity per desktop) should not be painted
373         if (w->isDesktop() && !w->isOnCurrentDesktop())
374             w->disablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP);
375 
376         if (isInMotion)
377             data.setTransformed(); // We will be moving this window
378     }
379     effects->prePaintWindow(w, data, presentTime);
380 }
381 
paintWindow(EffectWindow * w,int mask,QRegion region,WindowPaintData & data)382 void PresentWindowsEffect::paintWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data)
383 {
384     if (m_activated || m_motionManager.areWindowsMoving()) {
385         DataHash::const_iterator winData = m_windowData.constFind(w);
386         if (winData == m_windowData.constEnd() || (w->isDock() && m_showPanel)) {
387 	    // we are darkening the panel to communicate that it's not interactive
388             data.multiplyBrightness(interpolate(0.40, 1.0, winData->highlight));
389             effects->paintWindow(w, mask, region, data);
390             return;
391         }
392 
393         mask |= PAINT_WINDOW_LANCZOS;
394         // Apply opacity and brightness
395         data.multiplyOpacity(winData->opacity);
396         data.multiplyBrightness(interpolate(0.40, 1.0, winData->highlight));
397 
398         if (m_motionManager.isManaging(w)) {
399             if (w->isDesktop()) {
400                 effects->paintWindow(w, mask, region, data);
401             }
402             m_motionManager.apply(w, data);
403             QRect rect = m_motionManager.transformedGeometry(w).toRect();
404 
405             if (m_activated && winData->highlight > 0.0) {
406                 // scale the window (interpolated by the highlight level) to at least 105% or to cover 1/16 of the screen size - yet keep it in screen bounds
407                 QRect area = effects->clientArea(FullScreenArea, w);
408 
409                 QSizeF effSize(w->width()*data.xScale(), w->height()*data.yScale());
410                 const float xr = area.width()/effSize.width();
411                 const float yr = area.height()/effSize.height();
412                 float tScale = 0.0;
413                 if (xr < yr) {
414                     tScale = qMax(xr/4.0, yr/32.0);
415                 } else {
416                     tScale = qMax(xr/32.0, yr/4.0);
417                 }
418                 if (tScale < 1.05) {
419                     tScale = 1.05;
420                 }
421                 if (effSize.width()*tScale > area.width())
422                     tScale = area.width() / effSize.width();
423                 if (effSize.height()*tScale > area.height())
424                     tScale = area.height() / effSize.height();
425 
426                 const qreal scale = interpolate(1.0, tScale, winData->highlight);
427                 if (scale > 1.0) {
428                     if (scale < tScale) // don't use lanczos during transition
429                         mask &= ~PAINT_WINDOW_LANCZOS;
430 
431                     const float df = (tScale-1.0f)*0.5f;
432                     int tx = qRound(rect.width()*df);
433                     int ty = qRound(rect.height()*df);
434                     QRect tRect(rect.adjusted(-tx, -ty, tx, ty));
435                     tx = qMax(tRect.x(), area.x()) + qMin(0, area.right()-tRect.right());
436                     ty = qMax(tRect.y(), area.y()) + qMin(0, area.bottom()-tRect.bottom());
437                     tx = qRound((tx-rect.x())*winData->highlight);
438                     ty = qRound((ty-rect.y())*winData->highlight);
439 
440                     rect.translate(tx,ty);
441                     rect.setWidth(rect.width()*scale);
442                     rect.setHeight(rect.height()*scale);
443 
444                     data *= QVector2D(scale, scale);
445                     data += QPoint(tx, ty);
446                 }
447             }
448 
449             if (m_motionManager.areWindowsMoving()) {
450                 mask &= ~PAINT_WINDOW_LANCZOS;
451             }
452             effects->paintWindow(w, mask, region, data);
453 
454             if (m_showIcons) {
455                 QPoint point(rect.x() + rect.width() / 2,
456                              rect.y() + rect.height() / 2);
457                 winData->iconFrame->setAlignment(Qt::AlignCenter);
458                 winData->iconFrame->setPosition(point);
459                 if (effects->compositingType() == KWin::OpenGLCompositing && data.shader) {
460                     const float a = 0.9 * data.opacity() * m_decalOpacity * 0.75;
461                     data.shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a));
462                 }
463                 winData->iconFrame->render(region, 0.9 * data.opacity() * m_decalOpacity, 0.75);
464             }
465             if (m_showCaptions) {
466                 QSize iconSize = winData->iconFrame->iconSize();
467                 QPoint point(rect.x() + rect.width() / 2,
468                              rect.y() + rect.height() / 2 + iconSize.height());
469                 winData->textFrame->setPosition(point);
470                 if (effects->compositingType() == KWin::OpenGLCompositing && data.shader) {
471                     const float a = 0.9 * data.opacity() * m_decalOpacity * 0.75;
472                     data.shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a));
473                 }
474                 winData->textFrame->render(region, 0.9 * data.opacity() * m_decalOpacity, 0.75);
475             }
476         } else {
477             effects->paintWindow(w, mask, region, data);
478         }
479     } else
480         effects->paintWindow(w, mask, region, data);
481 }
482 
483 //-----------------------------------------------------------------------------
484 // User interaction
485 
slotWindowAdded(EffectWindow * w)486 void PresentWindowsEffect::slotWindowAdded(EffectWindow *w)
487 {
488     if (!m_activated)
489         return;
490 
491     WindowData *winData = &m_windowData[w];
492     winData->visible = isVisibleWindow(w);
493     winData->opacity = 0.0;
494     winData->highlight = 0.0;
495     winData->textFrame = effects->effectFrame(EffectFrameUnstyled, false);
496 
497     QFont font;
498     font.setBold(true);
499     font.setPointSize(12);
500 
501     winData->textFrame->setFont(font);
502     winData->iconFrame = effects->effectFrame(EffectFrameUnstyled, false);
503     winData->iconFrame->setAlignment(Qt::AlignCenter);
504     winData->iconFrame->setIcon(w->icon());
505     winData->iconFrame->setIconSize(QSize(64, 64));
506 
507     if (isSelectableWindow(w)) {
508         m_motionManager.manage(w);
509         rearrangeWindows();
510     }
511 }
512 
slotWindowClosed(EffectWindow * w)513 void PresentWindowsEffect::slotWindowClosed(EffectWindow *w)
514 {
515     if (m_managerWindow == w)
516         m_managerWindow = nullptr;
517 
518     DataHash::iterator winData = m_windowData.find(w);
519     if (winData == m_windowData.end())
520         return;
521 
522     winData->deleted = true;
523     if (!winData->referenced) {
524         winData->referenced = true;
525         w->refWindow();
526     }
527 
528     if (m_highlightedWindow == w)
529         setHighlightedWindow(findFirstWindow());
530 
531     rearrangeWindows();
532 
533     Q_FOREACH (EffectWindow *w, m_motionManager.managedWindows()) {
534         winData = m_windowData.find(w);
535         if (winData != m_windowData.end() && !winData->deleted)
536            return; // found one that is not deleted? then we go on
537     }
538     setActive(false);     //else no need to keep this open
539 }
540 
slotWindowDeleted(EffectWindow * w)541 void PresentWindowsEffect::slotWindowDeleted(EffectWindow *w)
542 {
543     DataHash::iterator winData = m_windowData.find(w);
544     if (winData == m_windowData.end())
545         return;
546 
547     delete winData->textFrame;
548     delete winData->iconFrame;
549     m_windowData.erase(winData);
550     m_motionManager.unmanage(w);
551 }
552 
slotWindowFrameGeometryChanged(EffectWindow * w,const QRect & old)553 void PresentWindowsEffect::slotWindowFrameGeometryChanged(EffectWindow* w, const QRect& old)
554 {
555     Q_UNUSED(old)
556 
557     if (!m_activated)
558         return;
559     if (!m_windowData.contains(w))
560         return;
561 
562     rearrangeWindows();
563 }
564 
borderActivated(ElectricBorder border)565 bool PresentWindowsEffect::borderActivated(ElectricBorder border)
566 {
567     int mode = 0;
568     if (m_borderActivate.contains(border))
569         mode |= 1;
570     else if (m_borderActivateAll.contains(border))
571         mode |= 2;
572     else if (m_borderActivateClass.contains(border))
573         mode |= 4;
574 
575     if (!mode)
576         return false;
577 
578     if (effects->activeFullScreenEffect() && effects->activeFullScreenEffect() != this)
579         return true;
580 
581     if (mode & 1)
582         toggleActive();
583     else if (mode & 2)
584         toggleActiveAllDesktops();
585     else if (mode & 4)
586         toggleActiveClass();
587     return true;
588 }
589 
windowInputMouseEvent(QEvent * e)590 void PresentWindowsEffect::windowInputMouseEvent(QEvent *e)
591 {
592     QMouseEvent* me = dynamic_cast< QMouseEvent* >(e);
593     if (!me) {
594         return;
595     }
596     me->setAccepted(false);
597 
598     if (m_closeView) {
599         const bool contains = m_closeView->geometry().contains(me->pos());
600         if (!m_closeView->isVisible() && contains) {
601             updateCloseWindow();
602         }
603         m_closeView->forwardMouseEvent(e);
604     }
605     if (e->isAccepted()) {
606         return;
607     }
608     inputEventUpdate(me->pos(), me->type(), me->button());
609 }
610 
inputEventUpdate(const QPoint & pos,QEvent::Type type,Qt::MouseButton button)611 void PresentWindowsEffect::inputEventUpdate(const QPoint &pos, QEvent::Type type, Qt::MouseButton button)
612 {
613     // Which window are we hovering over? Always trigger as we don't always get move events before clicking
614     // We cannot use m_motionManager.windowAtPoint() as the window might not be visible
615     EffectWindowList windows = m_motionManager.managedWindows();
616     bool hovering = false;
617     EffectWindow *highlightCandidate = nullptr;
618     for (int i = 0; i < windows.size(); ++i) {
619         DataHash::const_iterator winData = m_windowData.constFind(windows.at(i));
620         if (winData == m_windowData.constEnd())
621             continue;
622 
623         if (m_motionManager.transformedGeometry(windows.at(i)).contains(pos) &&
624                 winData->visible && !winData->deleted) {
625             hovering = true;
626             if (windows.at(i) && m_highlightedWindow != windows.at(i))
627                 highlightCandidate = windows.at(i);
628             break;
629         }
630     }
631 
632     if (!hovering)
633         setHighlightedWindow(nullptr);
634     if (m_highlightedWindow && m_motionManager.transformedGeometry(m_highlightedWindow).contains(pos))
635         updateCloseWindow();
636     else if (m_closeView)
637         m_closeView->hide();
638 
639     if (type == QEvent::MouseButtonRelease) {
640         if (highlightCandidate)
641             setHighlightedWindow(highlightCandidate);
642         if (button == Qt::LeftButton) {
643             if (hovering) {
644                 // mouse is hovering above a window - use MouseActionsWindow
645                 mouseActionWindow(m_leftButtonWindow);
646             } else {
647                 // mouse is hovering above desktop - use MouseActionsDesktop
648                 mouseActionDesktop(m_leftButtonDesktop);
649             }
650         }
651         if (button == Qt::MiddleButton) {
652             if (hovering) {
653                 // mouse is hovering above a window - use MouseActionsWindow
654                 mouseActionWindow(m_middleButtonWindow);
655             } else {
656                 // mouse is hovering above desktop - use MouseActionsDesktop
657                 mouseActionDesktop(m_middleButtonDesktop);
658             }
659         }
660         if (button == Qt::RightButton) {
661             if (hovering) {
662                 // mouse is hovering above a window - use MouseActionsWindow
663                 mouseActionWindow(m_rightButtonWindow);
664             } else {
665                 // mouse is hovering above desktop - use MouseActionsDesktop
666                 mouseActionDesktop(m_rightButtonDesktop);
667             }
668         }
669     } else if (highlightCandidate && !m_motionManager.areWindowsMoving())
670         setHighlightedWindow(highlightCandidate);
671 }
672 
touchDown(qint32 id,const QPointF & pos,quint32 time)673 bool PresentWindowsEffect::touchDown(qint32 id, const QPointF &pos, quint32 time)
674 {
675     Q_UNUSED(time)
676 
677     if (!m_activated) {
678         return false;
679     }
680 
681     // only if we don't track a touch id yet
682     if (!m_touch.active) {
683         m_touch.active = true;
684         m_touch.id = id;
685         inputEventUpdate(pos.toPoint());
686     }
687     return true;
688 }
689 
touchMotion(qint32 id,const QPointF & pos,quint32 time)690 bool PresentWindowsEffect::touchMotion(qint32 id, const QPointF &pos, quint32 time)
691 {
692     Q_UNUSED(id)
693     Q_UNUSED(time)
694 
695     if (!m_activated) {
696         return false;
697     }
698     if (m_touch.active && m_touch.id == id) {
699         // only update for the touch id we track
700         inputEventUpdate(pos.toPoint());
701     }
702     return true;
703 }
704 
touchUp(qint32 id,quint32 time)705 bool PresentWindowsEffect::touchUp(qint32 id, quint32 time)
706 {
707     Q_UNUSED(id)
708     Q_UNUSED(time)
709 
710     if (!m_activated) {
711         return false;
712     }
713     if (m_touch.active && m_touch.id == id) {
714         m_touch.active = false;
715         m_touch.id = 0;
716         if (m_highlightedWindow) {
717             mouseActionWindow(m_leftButtonWindow);
718         }
719     }
720     return true;
721 }
722 
mouseActionWindow(WindowMouseAction & action)723 void PresentWindowsEffect::mouseActionWindow(WindowMouseAction& action)
724 {
725     switch(action) {
726     case WindowActivateAction:
727         if (m_highlightedWindow)
728             effects->activateWindow(m_highlightedWindow);
729         setActive(false);
730         break;
731     case WindowExitAction:
732         setActive(false);
733         break;
734     case WindowToCurrentDesktopAction:
735         if (m_highlightedWindow)
736             effects->windowToDesktop(m_highlightedWindow, effects->currentDesktop());
737         break;
738     case WindowToAllDesktopsAction:
739         if (m_highlightedWindow) {
740             if (m_highlightedWindow->isOnAllDesktops())
741                 effects->windowToDesktop(m_highlightedWindow, effects->currentDesktop());
742             else
743                 effects->windowToDesktop(m_highlightedWindow, NET::OnAllDesktops);
744         }
745         break;
746     case WindowMinimizeAction:
747         if (m_highlightedWindow) {
748             if (m_highlightedWindow->isMinimized())
749                 m_highlightedWindow->unminimize();
750             else
751                 m_highlightedWindow->minimize();
752         }
753         break;
754     case WindowCloseAction:
755         if (m_highlightedWindow) {
756             m_highlightedWindow->closeWindow();
757         }
758         break;
759     default:
760         break;
761     }
762 }
763 
mouseActionDesktop(DesktopMouseAction & action)764 void PresentWindowsEffect::mouseActionDesktop(DesktopMouseAction& action)
765 {
766     switch(action) {
767     case DesktopActivateAction:
768         if (m_highlightedWindow)
769             effects->activateWindow(m_highlightedWindow);
770         setActive(false);
771         break;
772     case DesktopExitAction:
773         setActive(false);
774         break;
775     case DesktopShowDesktopAction:
776         effects->setShowingDesktop(true);
777         setActive(false);
778     default:
779         break;
780     }
781 }
782 
grabbedKeyboardEvent(QKeyEvent * e)783 void PresentWindowsEffect::grabbedKeyboardEvent(QKeyEvent *e)
784 {
785     if (e->type() == QEvent::KeyPress) {
786         // check for global shortcuts
787         // HACK: keyboard grab disables the global shortcuts so we have to check for global shortcut (bug 156155)
788         if (m_mode == ModeCurrentDesktop && shortcut.contains(e->key() + e->modifiers())) {
789             toggleActive();
790             return;
791         }
792         if (m_mode == ModeAllDesktops && shortcutAll.contains(e->key() + e->modifiers())) {
793             toggleActiveAllDesktops();
794             return;
795         }
796         if (m_mode == ModeWindowClass && shortcutClass.contains(e->key() + e->modifiers())) {
797             toggleActiveClass();
798             return;
799         }
800 
801         switch(e->key()) {
802             // Wrap only if not auto-repeating
803         case Qt::Key_Left:
804             setHighlightedWindow(relativeWindow(m_highlightedWindow, -1, 0, !e->isAutoRepeat()));
805             break;
806         case Qt::Key_Right:
807             setHighlightedWindow(relativeWindow(m_highlightedWindow, 1, 0, !e->isAutoRepeat()));
808             break;
809         case Qt::Key_Up:
810             setHighlightedWindow(relativeWindow(m_highlightedWindow, 0, -1, !e->isAutoRepeat()));
811             break;
812         case Qt::Key_Down:
813             setHighlightedWindow(relativeWindow(m_highlightedWindow, 0, 1, !e->isAutoRepeat()));
814             break;
815         case Qt::Key_Home:
816             setHighlightedWindow(relativeWindow(m_highlightedWindow, -1000, 0, false));
817             break;
818         case Qt::Key_End:
819             setHighlightedWindow(relativeWindow(m_highlightedWindow, 1000, 0, false));
820             break;
821         case Qt::Key_PageUp:
822             setHighlightedWindow(relativeWindow(m_highlightedWindow, 0, -1000, false));
823             break;
824         case Qt::Key_PageDown:
825             setHighlightedWindow(relativeWindow(m_highlightedWindow, 0, 1000, false));
826             break;
827         case Qt::Key_Backspace:
828             if (!m_windowFilter.isEmpty()) {
829                 m_windowFilter.remove(m_windowFilter.length() - 1, 1);
830                 updateFilterFrame();
831                 rearrangeWindows();
832             }
833             return;
834         case Qt::Key_Escape:
835             setActive(false);
836             return;
837         case Qt::Key_Return:
838         case Qt::Key_Enter:
839             if (m_highlightedWindow)
840                 effects->activateWindow(m_highlightedWindow);
841             setActive(false);
842             return;
843         case Qt::Key_Tab:
844             return; // Nothing at the moment
845         case Qt::Key_Delete:
846             if (!m_windowFilter.isEmpty()) {
847                 m_windowFilter.clear();
848                 updateFilterFrame();
849                 rearrangeWindows();
850             }
851             break;
852         case 0:
853             return; // HACK: Workaround for Qt bug on unbound keys (#178547)
854         default:
855             if (!e->text().isEmpty()) {
856                 m_windowFilter.append(e->text());
857                 updateFilterFrame();
858                 rearrangeWindows();
859                 return;
860             }
861             break;
862         }
863     }
864 }
865 
866 //-----------------------------------------------------------------------------
867 // Atom handling
slotPropertyNotify(EffectWindow * w,long a)868 void PresentWindowsEffect::slotPropertyNotify(EffectWindow* w, long a)
869 {
870     if (m_atomDesktop == XCB_ATOM_NONE && m_atomWindows == XCB_ATOM_NONE) {
871         return;
872     }
873     if (!w || (a != m_atomDesktop && a != m_atomWindows))
874         return; // Not our atom
875 
876     if (a == m_atomDesktop) {
877         QByteArray byteData = w->readProperty(m_atomDesktop, m_atomDesktop, 32);
878         if (byteData.length() < 1) {
879             // Property was removed, end present windows
880             setActive(false);
881             return;
882         }
883         auto* data = reinterpret_cast<uint32_t*>(byteData.data());
884 
885         if (!data[0]) {
886             // Purposely ending present windows by issuing a NULL target
887             setActive(false);
888             return;
889         }
890         // present windows is active so don't do anything
891         if (m_activated)
892             return;
893 
894         int desktop = data[0];
895         if (desktop > effects->numberOfDesktops())
896             return;
897         if (desktop == -1)
898             toggleActiveAllDesktops();
899         else {
900             m_mode = ModeSelectedDesktop;
901             m_desktop = desktop;
902             m_managerWindow = w;
903             setActive(true);
904         }
905     } else if (a == m_atomWindows) {
906         QByteArray byteData = w->readProperty(m_atomWindows, m_atomWindows, 32);
907         if (byteData.length() < 1) {
908             // Property was removed, end present windows
909             setActive(false);
910             return;
911         }
912         auto* data = reinterpret_cast<uint32_t*>(byteData.data());
913 
914         if (!data[0]) {
915             // Purposely ending present windows by issuing a NULL target
916             setActive(false);
917             return;
918         }
919         // present windows is active so don't do anything
920         if (m_activated)
921             return;
922 
923         // for security clear selected windows
924         m_selectedWindows.clear();
925         int length = byteData.length() / sizeof(data[0]);
926         for (int i = 0; i < length; i++) {
927             EffectWindow* foundWin = effects->findWindow(data[i]);
928             if (!foundWin) {
929                 qCDebug(KWINEFFECTS) << "Invalid window targetted for present windows. Requested:" << data[i];
930                 continue;
931             }
932             m_selectedWindows.append(foundWin);
933         }
934         m_mode = ModeWindowGroup;
935         m_managerWindow = w;
936         setActive(true);
937     }
938 }
939 
presentWindows(const QStringList & windows)940 void PresentWindowsEffect::presentWindows(const QStringList &windows)
941 {
942     m_selectedWindows.clear();
943     for (const auto &window : windows) {
944         if (auto effectWindow = effects->findWindow(QUuid(window)); effectWindow) {
945             m_selectedWindows.append(effectWindow);
946         } else if (auto effectWindow = effects->findWindow(window.toLong()); effectWindow) {
947             m_selectedWindows.append(effectWindow);
948         }
949     }
950    m_mode = ModeWindowGroup;
951    setActive(true);
952 }
953 
954 
955 //-----------------------------------------------------------------------------
956 // Window rearranging
957 
rearrangeWindows()958 void PresentWindowsEffect::rearrangeWindows()
959 {
960     if (!m_activated)
961         return;
962 
963     effects->addRepaintFull(); // Trigger the first repaint
964     if (m_closeView)
965         m_closeView->hide();
966 
967     // Work out which windows are on which screens
968     EffectWindowList windowlist;
969     QList<EffectWindowList> windowlists;
970     for (int i = 0; i < effects->numScreens(); i++)
971         windowlists.append(EffectWindowList());
972 
973     if (m_windowFilter.isEmpty()) {
974         windowlist = m_motionManager.managedWindows();
975         Q_FOREACH (EffectWindow * w, m_motionManager.managedWindows()) {
976             DataHash::iterator winData = m_windowData.find(w);
977             if (winData == m_windowData.end() || winData->deleted)
978                 continue; // don't include closed windows
979             windowlists[w->screen()].append(w);
980             winData->visible = true;
981         }
982     } else {
983         // Can we move this filtering somewhere else?
984         Q_FOREACH (EffectWindow * w, m_motionManager.managedWindows()) {
985             DataHash::iterator winData = m_windowData.find(w);
986             if (winData == m_windowData.end() || winData->deleted)
987                 continue; // don't include closed windows
988 
989             if (w->caption().contains(m_windowFilter, Qt::CaseInsensitive) ||
990                     w->windowClass().contains(m_windowFilter, Qt::CaseInsensitive) ||
991                     w->windowRole().contains(m_windowFilter, Qt::CaseInsensitive)) {
992                 windowlist.append(w);
993                 windowlists[w->screen()].append(w);
994                 winData->visible = true;
995             } else
996                 winData->visible = false;
997         }
998     }
999     if (windowlist.isEmpty()) {
1000         setHighlightedWindow(nullptr);
1001         return;
1002     }
1003 
1004     // We filtered out the highlighted window
1005     if (m_highlightedWindow) {
1006         DataHash::iterator winData = m_windowData.find(m_highlightedWindow);
1007         if (winData != m_windowData.end() && !winData->visible)
1008             setHighlightedWindow(findFirstWindow());
1009     } else
1010         setHighlightedWindow(findFirstWindow());
1011 
1012     int screens = effects->numScreens();
1013     for (int screen = 0; screen < screens; screen++) {
1014         EffectWindowList windows;
1015         windows = windowlists[screen];
1016 
1017         // Don't rearrange if the grid is the same size as what it was before to prevent
1018         // windows moving to a better spot if one was filtered out.
1019         if (m_layoutMode == LayoutRegularGrid &&
1020                 m_gridSizes[screen].columns &&
1021                 m_gridSizes[screen].rows &&
1022                 windows.size() < m_gridSizes[screen].columns * m_gridSizes[screen].rows &&
1023                 windows.size() > (m_gridSizes[screen].columns - 1) * m_gridSizes[screen].rows &&
1024                 windows.size() > m_gridSizes[screen].columns *(m_gridSizes[screen].rows - 1))
1025             continue;
1026 
1027         // No point continuing if there is no windows to process
1028         if (!windows.count())
1029             continue;
1030 
1031         calculateWindowTransformations(windows, screen, m_motionManager);
1032     }
1033 
1034     // Resize text frames if required
1035     QFontMetrics* metrics = nullptr; // All fonts are the same
1036     Q_FOREACH (EffectWindow * w, m_motionManager.managedWindows()) {
1037         DataHash::iterator winData = m_windowData.find(w);
1038         if (winData == m_windowData.end())
1039             continue;
1040 
1041         if (!metrics)
1042             metrics = new QFontMetrics(winData->textFrame->font());
1043 
1044         QRect geom = m_motionManager.targetGeometry(w).toRect();
1045         QString string = metrics->elidedText(w->caption(), Qt::ElideRight, geom.width() * 0.9);
1046         if (string != winData->textFrame->text())
1047             winData->textFrame->setText(string);
1048     }
1049     delete metrics;
1050 }
1051 
calculateWindowTransformations(EffectWindowList windowlist,int screen,WindowMotionManager & motionManager,bool external)1052 void PresentWindowsEffect::calculateWindowTransformations(EffectWindowList windowlist, int screen,
1053         WindowMotionManager& motionManager, bool external)
1054 {
1055     if (m_layoutMode == LayoutRegularGrid)
1056         calculateWindowTransformationsClosest(windowlist, screen, motionManager);
1057     else if (m_layoutMode == LayoutFlexibleGrid)
1058         calculateWindowTransformationsKompose(windowlist, screen, motionManager);
1059     else
1060         calculateWindowTransformationsNatural(windowlist, screen, motionManager);
1061 
1062     // If called externally we don't need to remember this data
1063     if (external)
1064         m_windowData.clear();
1065 }
1066 
distance(QPoint & pos1,QPoint & pos2)1067 static inline int distance(QPoint &pos1, QPoint &pos2)
1068 {
1069     const int xdiff = pos1.x() - pos2.x();
1070     const int ydiff = pos1.y() - pos2.y();
1071     return int(sqrt(float(xdiff*xdiff + ydiff*ydiff)));
1072 }
1073 
calculateWindowTransformationsClosest(EffectWindowList windowlist,int screen,WindowMotionManager & motionManager)1074 void PresentWindowsEffect::calculateWindowTransformationsClosest(EffectWindowList windowlist, int screen,
1075         WindowMotionManager& motionManager)
1076 {
1077     // This layout mode requires at least one window visible
1078     if (windowlist.count() == 0)
1079         return;
1080 
1081     QRect area = effects->clientArea(ScreenArea, screen, effects->currentDesktop());
1082     if (m_showPanel)   // reserve space for the panel
1083         area = effects->clientArea(MaximizeArea, screen, effects->currentDesktop());
1084     int columns = int(ceil(sqrt(double(windowlist.count()))));
1085     int rows = int(ceil(windowlist.count() / double(columns)));
1086 
1087     // Remember the size for later
1088     // If we are using this layout externally we don't need to remember m_gridSizes.
1089     if (m_gridSizes.size() != 0) {
1090         m_gridSizes[screen].columns = columns;
1091         m_gridSizes[screen].rows = rows;
1092     }
1093 
1094     // Assign slots
1095     int slotWidth = area.width() / columns;
1096     int slotHeight = area.height() / rows;
1097     QVector<EffectWindow*> takenSlots;
1098     takenSlots.resize(rows*columns);
1099     takenSlots.fill(0);
1100 
1101     // precalculate all slot centers
1102     QVector<QPoint> slotCenters;
1103     slotCenters.resize(rows*columns);
1104     for (int x = 0; x < columns; ++x)
1105         for (int y = 0; y < rows; ++y) {
1106             slotCenters[x + y*columns] = QPoint(area.x() + slotWidth * x + slotWidth / 2,
1107                                                 area.y() + slotHeight * y + slotHeight / 2);
1108         }
1109 
1110     // Assign each window to the closest available slot
1111     EffectWindowList tmpList = windowlist; // use a QLinkedList copy instead?
1112     QPoint otherPos;
1113     while (!tmpList.isEmpty()) {
1114         EffectWindow *w = tmpList.first();
1115         int slotCandidate = -1, slotCandidateDistance = INT_MAX;
1116         QPoint pos = w->frameGeometry().center();
1117 
1118         for (int i = 0; i < columns*rows; ++i) { // all slots
1119             const int dist = distance(pos, slotCenters[i]);
1120             if (dist < slotCandidateDistance) { // window is interested in this slot
1121                 EffectWindow *occupier = takenSlots[i];
1122                 Q_ASSERT(occupier != w);
1123                 if (!occupier || dist < distance((otherPos = occupier->frameGeometry().center()), slotCenters[i])) {
1124                     // either nobody lives here, or we're better - takeover the slot if it's our best
1125                     slotCandidate = i;
1126                     slotCandidateDistance = dist;
1127                 }
1128             }
1129         }
1130         Q_ASSERT(slotCandidate != -1);
1131         if (takenSlots[slotCandidate])
1132             tmpList << takenSlots[slotCandidate]; // occupier needs a new home now :p
1133         tmpList.removeAll(w);
1134         takenSlots[slotCandidate] = w; // ...and we rumble in =)
1135     }
1136 
1137     for (int slot = 0; slot < columns*rows; ++slot) {
1138         EffectWindow *w = takenSlots[slot];
1139         if (!w) // some slots might be empty
1140             continue;
1141 
1142         // Work out where the slot is
1143         QRect target(
1144             area.x() + (slot % columns) * slotWidth,
1145             area.y() + (slot / columns) * slotHeight,
1146             slotWidth, slotHeight);
1147         target.adjust(10, 10, -10, -10);   // Borders
1148 
1149         double scale;
1150         if (target.width() / double(w->width()) < target.height() / double(w->height())) {
1151             // Center vertically
1152             scale = target.width() / double(w->width());
1153             target.moveTop(target.top() + (target.height() - int(w->height() * scale)) / 2);
1154             target.setHeight(int(w->height() * scale));
1155         } else {
1156             // Center horizontally
1157             scale = target.height() / double(w->height());
1158             target.moveLeft(target.left() + (target.width() - int(w->width() * scale)) / 2);
1159             target.setWidth(int(w->width() * scale));
1160         }
1161         // Don't scale the windows too much
1162         if (scale > 2.0 || (scale > 1.0 && (w->width() > 300 || w->height() > 300))) {
1163             scale = (w->width() > 300 || w->height() > 300) ? 1.0 : 2.0;
1164             target = QRect(
1165                          target.center().x() - int(w->width() * scale) / 2,
1166                          target.center().y() - int(w->height() * scale) / 2,
1167                          scale * w->width(), scale * w->height());
1168         }
1169         motionManager.moveWindow(w, target);
1170     }
1171 }
1172 
calculateWindowTransformationsKompose(EffectWindowList windowlist,int screen,WindowMotionManager & motionManager)1173 void PresentWindowsEffect::calculateWindowTransformationsKompose(EffectWindowList windowlist, int screen,
1174         WindowMotionManager& motionManager)
1175 {
1176     // This layout mode requires at least one window visible
1177     if (windowlist.count() == 0)
1178         return;
1179 
1180     QRect availRect = effects->clientArea(ScreenArea, screen, effects->currentDesktop());
1181     if (m_showPanel)   // reserve space for the panel
1182         availRect = effects->clientArea(MaximizeArea, screen, effects->currentDesktop());
1183     std::sort(windowlist.begin(), windowlist.end());   // The location of the windows should not depend on the stacking order
1184 
1185     // Following code is taken from Kompose 0.5.4, src/komposelayout.cpp
1186     int spacing = 10;
1187     int rows, columns;
1188     double parentRatio = availRect.width() / (double)availRect.height();
1189     // Use more columns than rows when parent's width > parent's height
1190     if (parentRatio > 1) {
1191         columns = (int)ceil(sqrt((double)windowlist.count()));
1192         rows = (int)ceil((double)windowlist.count() / (double)columns);
1193     } else {
1194         rows = (int)ceil(sqrt((double)windowlist.count()));
1195         columns = (int)ceil((double)windowlist.count() / (double)rows);
1196     }
1197     //qCDebug(KWINEFFECTS) << "Using " << rows << " rows & " << columns << " columns for " << windowlist.count() << " clients";
1198 
1199     // Calculate width & height
1200     int w = (availRect.width() - (columns + 1) * spacing) / columns;
1201     int h = (availRect.height() - (rows + 1) * spacing) / rows;
1202 
1203     EffectWindowList::iterator it(windowlist.begin());
1204     QList<QRect> geometryRects;
1205     QList<int> maxRowHeights;
1206     // Process rows
1207     for (int i = 0; i < rows; ++i) {
1208         int xOffsetFromLastCol = 0;
1209         int maxHeightInRow = 0;
1210         // Process columns
1211         for (int j = 0; j < columns; ++j) {
1212             EffectWindow* window;
1213 
1214             // Check for end of List
1215             if (it == windowlist.end())
1216                 break;
1217             window = *it;
1218 
1219             // Calculate width and height of widget
1220             double ratio = aspectRatio(window);
1221 
1222             int widgetw = 100;
1223             int widgeth = 100;
1224             int usableW = w;
1225             int usableH = h;
1226 
1227             // use width of two boxes if there is no right neighbour
1228             if (window == windowlist.last() && j != columns - 1) {
1229                 usableW = 2 * w;
1230             }
1231             ++it; // We need access to the neighbour in the following
1232             // expand if right neighbour has ratio < 1
1233             if (j != columns - 1 && it != windowlist.end() && aspectRatio(*it) < 1) {
1234                 int addW = w - widthForHeight(*it, h);
1235                 if (addW > 0) {
1236                     usableW = w + addW;
1237                 }
1238             }
1239 
1240             if (ratio == -1) {
1241                 widgetw = w;
1242                 widgeth = h;
1243             } else {
1244                 double widthByHeight = widthForHeight(window, usableH);
1245                 double heightByWidth = heightForWidth(window, usableW);
1246                 if ((ratio >= 1.0 && heightByWidth <= usableH) ||
1247                         (ratio < 1.0 && widthByHeight > usableW)) {
1248                     widgetw = usableW;
1249                     widgeth = (int)heightByWidth;
1250                 } else if ((ratio < 1.0 && widthByHeight <= usableW) ||
1251                           (ratio >= 1.0 && heightByWidth > usableH)) {
1252                     widgeth = usableH;
1253                     widgetw = (int)widthByHeight;
1254                 }
1255                 // Don't upscale large-ish windows
1256                 if (widgetw > window->width() && (window->width() > 300 || window->height() > 300)) {
1257                     widgetw = window->width();
1258                     widgeth = window->height();
1259                 }
1260             }
1261 
1262             // Set the Widget's size
1263 
1264             int alignmentXoffset = 0;
1265             int alignmentYoffset = 0;
1266             if (i == 0 && h > widgeth)
1267                 alignmentYoffset = h - widgeth;
1268             if (j == 0 && w > widgetw)
1269                 alignmentXoffset = w - widgetw;
1270             QRect geom(availRect.x() + j *(w + spacing) + spacing + alignmentXoffset + xOffsetFromLastCol,
1271                        availRect.y() + i *(h + spacing) + spacing + alignmentYoffset,
1272                        widgetw, widgeth);
1273             geometryRects.append(geom);
1274 
1275             // Set the x offset for the next column
1276             if (alignmentXoffset == 0)
1277                 xOffsetFromLastCol += widgetw - w;
1278             if (maxHeightInRow < widgeth)
1279                 maxHeightInRow = widgeth;
1280         }
1281         maxRowHeights.append(maxHeightInRow);
1282     }
1283 
1284     int topOffset = 0;
1285     for (int i = 0; i < rows; i++) {
1286         for (int j = 0; j < columns; j++) {
1287             int pos = i * columns + j;
1288             if (pos >= windowlist.count())
1289                 break;
1290 
1291             EffectWindow* window = windowlist[pos];
1292             QRect target = geometryRects[pos];
1293             target.setY(target.y() + topOffset);
1294             // @Marrtin: any idea what this is good for?
1295 //             DataHash::iterator winData = m_windowData.find(window);
1296 //             if (winData != m_windowData.end())
1297 //                 winData->slot = pos;
1298             motionManager.moveWindow(window, target);
1299 
1300             //qCDebug(KWINEFFECTS) << "Window '" << window->caption() << "' gets moved to (" <<
1301             //        mWindowData[window].area.left() << "; " << mWindowData[window].area.right() <<
1302             //        "), scale: " << mWindowData[window].scale << endl;
1303         }
1304         if (maxRowHeights[i] - h > 0)
1305             topOffset += maxRowHeights[i] - h;
1306     }
1307 }
1308 
calculateWindowTransformationsNatural(EffectWindowList windowlist,int screen,WindowMotionManager & motionManager)1309 void PresentWindowsEffect::calculateWindowTransformationsNatural(EffectWindowList windowlist, int screen,
1310         WindowMotionManager& motionManager)
1311 {
1312     // If windows do not overlap they scale into nothingness, fix by resetting. To reproduce
1313     // just have a single window on a Xinerama screen or have two windows that do not touch.
1314     // TODO: Work out why this happens, is most likely a bug in the manager.
1315     Q_FOREACH (EffectWindow * w, windowlist)
1316         if (motionManager.transformedGeometry(w) == w->frameGeometry())
1317             motionManager.reset(w);
1318 
1319     if (windowlist.count() == 1) {
1320         // Just move the window to its original location to save time
1321         if (effects->clientArea(FullScreenArea, windowlist[0]).contains(windowlist[0]->frameGeometry())) {
1322             motionManager.moveWindow(windowlist[0], windowlist[0]->frameGeometry());
1323             return;
1324         }
1325     }
1326 
1327     // As we are using pseudo-random movement (See "slot") we need to make sure the list
1328     // is always sorted the same way no matter which window is currently active.
1329     std::sort(windowlist.begin(), windowlist.end());
1330 
1331     QRect area = effects->clientArea(ScreenArea, screen, effects->currentDesktop());
1332     if (m_showPanel)   // reserve space for the panel
1333         area = effects->clientArea(MaximizeArea, screen, effects->currentDesktop());
1334     QRect bounds = area;
1335     int direction = 0;
1336     QHash<EffectWindow*, QRect> targets;
1337     QHash<EffectWindow*, int> directions;
1338     Q_FOREACH (EffectWindow * w, windowlist) {
1339         bounds = bounds.united(w->frameGeometry());
1340         targets[w] = w->frameGeometry();
1341         // Reuse the unused "slot" as a preferred direction attribute. This is used when the window
1342         // is on the edge of the screen to try to use as much screen real estate as possible.
1343         directions[w] = direction;
1344         direction++;
1345         if (direction == 4)
1346             direction = 0;
1347     }
1348 
1349     // Iterate over all windows, if two overlap push them apart _slightly_ as we try to
1350     // brute-force the most optimal positions over many iterations.
1351     bool overlap;
1352     do {
1353         overlap = false;
1354         Q_FOREACH (EffectWindow * w, windowlist) {
1355             QRect *target_w = &targets[w];
1356             Q_FOREACH (EffectWindow * e, windowlist) {
1357                 if (w == e)
1358                     continue;
1359 
1360                 QRect *target_e = &targets[e];
1361                 if (target_w->adjusted(-5, -5, 5, 5).intersects(target_e->adjusted(-5, -5, 5, 5))) {
1362                     overlap = true;
1363 
1364                     // Determine pushing direction
1365                     QPoint diff(target_e->center() - target_w->center());
1366                     // Prevent dividing by zero and non-movement
1367                     if (diff.x() == 0 && diff.y() == 0)
1368                         diff.setX(1);
1369                     // Try to keep screen aspect ratio
1370                     //if (bounds.height() / bounds.width() > area.height() / area.width())
1371                     //    diff.setY(diff.y() / 2);
1372                     //else
1373                     //    diff.setX(diff.x() / 2);
1374                     // Approximate a vector of between 10px and 20px in magnitude in the same direction
1375                     diff *= m_accuracy / double(diff.manhattanLength());
1376                     // Move both windows apart
1377                     target_w->translate(-diff);
1378                     target_e->translate(diff);
1379 
1380                     // Try to keep the bounding rect the same aspect as the screen so that more
1381                     // screen real estate is utilised. We do this by splitting the screen into nine
1382                     // equal sections, if the window center is in any of the corner sections pull the
1383                     // window towards the outer corner. If it is in any of the other edge sections
1384                     // alternate between each corner on that edge. We don't want to determine it
1385                     // randomly as it will not produce consistant locations when using the filter.
1386                     // Only move one window so we don't cause large amounts of unnecessary zooming
1387                     // in some situations. We need to do this even when expanding later just in case
1388                     // all windows are the same size.
1389                     // (We are using an old bounding rect for this, hopefully it doesn't matter)
1390                     int xSection = (target_w->x() - bounds.x()) / (bounds.width() / 3);
1391                     int ySection = (target_w->y() - bounds.y()) / (bounds.height() / 3);
1392                     diff = QPoint(0, 0);
1393                     if (xSection != 1 || ySection != 1) { // Remove this if you want the center to pull as well
1394                         if (xSection == 1)
1395                             xSection = (directions[w] / 2 ? 2 : 0);
1396                         if (ySection == 1)
1397                             ySection = (directions[w] % 2 ? 2 : 0);
1398                     }
1399                     if (xSection == 0 && ySection == 0)
1400                         diff = QPoint(bounds.topLeft() - target_w->center());
1401                     if (xSection == 2 && ySection == 0)
1402                         diff = QPoint(bounds.topRight() - target_w->center());
1403                     if (xSection == 2 && ySection == 2)
1404                         diff = QPoint(bounds.bottomRight() - target_w->center());
1405                     if (xSection == 0 && ySection == 2)
1406                         diff = QPoint(bounds.bottomLeft() - target_w->center());
1407                     if (diff.x() != 0 || diff.y() != 0) {
1408                         diff *= m_accuracy / double(diff.manhattanLength());
1409                         target_w->translate(diff);
1410                     }
1411 
1412                     // Update bounding rect
1413                     bounds = bounds.united(*target_w);
1414                     bounds = bounds.united(*target_e);
1415                 }
1416             }
1417         }
1418     } while (overlap);
1419 
1420     // Work out scaling by getting the most top-left and most bottom-right window coords.
1421     // The 20's and 10's are so that the windows don't touch the edge of the screen.
1422     double scale;
1423     if (bounds == area)
1424         scale = 1.0; // Don't add borders to the screen
1425     else if (area.width() / double(bounds.width()) < area.height() / double(bounds.height()))
1426         scale = (area.width() - 20) / double(bounds.width());
1427     else
1428         scale = (area.height() - 20) / double(bounds.height());
1429     // Make bounding rect fill the screen size for later steps
1430     bounds = QRect(
1431                  (bounds.x() * scale - (area.width() - 20 - bounds.width() * scale) / 2 - 10) / scale,
1432                  (bounds.y() * scale - (area.height() - 20 - bounds.height() * scale) / 2 - 10) / scale,
1433                  area.width() / scale,
1434                  area.height() / scale
1435              );
1436 
1437     // Move all windows back onto the screen and set their scale
1438     QHash<EffectWindow*, QRect>::iterator target = targets.begin();
1439     while (target != targets.end()) {
1440         target->setRect((target->x() - bounds.x()) * scale + area.x(),
1441                         (target->y() - bounds.y()) * scale + area.y(),
1442                         target->width() * scale,
1443                         target->height() * scale
1444                         );
1445         ++target;
1446     }
1447 
1448     // Try to fill the gaps by enlarging windows if they have the space
1449     if (m_fillGaps) {
1450         // Don't expand onto or over the border
1451         QRegion borderRegion(area.adjusted(-200, -200, 200, 200));
1452         borderRegion ^= area.adjusted(10 / scale, 10 / scale, -10 / scale, -10 / scale);
1453 
1454         bool moved;
1455         do {
1456             moved = false;
1457             Q_FOREACH (EffectWindow * w, windowlist) {
1458                 QRect oldRect;
1459                 QRect *target = &targets[w];
1460                 // This may cause some slight distortion if the windows are enlarged a large amount
1461                 int widthDiff = m_accuracy;
1462                 int heightDiff = heightForWidth(w, target->width() + widthDiff) - target->height();
1463                 int xDiff = widthDiff / 2;  // Also move a bit in the direction of the enlarge, allows the
1464                 int yDiff = heightDiff / 2; // center windows to be enlarged if there is gaps on the side.
1465 
1466                 // heightDiff (and yDiff) will be re-computed after each successful enlargement attempt
1467                 // so that the error introduced in the window's aspect ratio is minimized
1468 
1469                 // Attempt enlarging to the top-right
1470                 oldRect = *target;
1471                 target->setRect(target->x() + xDiff,
1472                                 target->y() - yDiff - heightDiff,
1473                                 target->width() + widthDiff,
1474                                 target->height() + heightDiff
1475                                 );
1476                 if (isOverlappingAny(w, targets, borderRegion))
1477                     *target = oldRect;
1478                 else {
1479                     moved = true;
1480                     heightDiff = heightForWidth(w, target->width() + widthDiff) - target->height();
1481                     yDiff = heightDiff / 2;
1482                 }
1483 
1484                 // Attempt enlarging to the bottom-right
1485                 oldRect = *target;
1486                 target->setRect(
1487                                  target->x() + xDiff,
1488                                  target->y() + yDiff,
1489                                  target->width() + widthDiff,
1490                                  target->height() + heightDiff
1491                              );
1492                 if (isOverlappingAny(w, targets, borderRegion))
1493                     *target = oldRect;
1494                 else {
1495                     moved = true;
1496                     heightDiff = heightForWidth(w, target->width() + widthDiff) - target->height();
1497                     yDiff = heightDiff / 2;
1498                 }
1499 
1500                 // Attempt enlarging to the bottom-left
1501                 oldRect = *target;
1502                 target->setRect(
1503                                  target->x() - xDiff - widthDiff,
1504                                  target->y() + yDiff,
1505                                  target->width() + widthDiff,
1506                                  target->height() + heightDiff
1507                              );
1508                 if (isOverlappingAny(w, targets, borderRegion))
1509                     *target = oldRect;
1510                 else {
1511                     moved = true;
1512                     heightDiff = heightForWidth(w, target->width() + widthDiff) - target->height();
1513                     yDiff = heightDiff / 2;
1514                 }
1515 
1516                 // Attempt enlarging to the top-left
1517                 oldRect = *target;
1518                 target->setRect(
1519                                  target->x() - xDiff - widthDiff,
1520                                  target->y() - yDiff - heightDiff,
1521                                  target->width() + widthDiff,
1522                                  target->height() + heightDiff
1523                              );
1524                 if (isOverlappingAny(w, targets, borderRegion))
1525                     *target = oldRect;
1526                 else
1527                     moved = true;
1528             }
1529         } while (moved);
1530 
1531         // The expanding code above can actually enlarge windows over 1.0/2.0 scale, we don't like this
1532         // We can't add this to the loop above as it would cause a never-ending loop so we have to make
1533         // do with the less-than-optimal space usage with using this method.
1534         Q_FOREACH (EffectWindow * w, windowlist) {
1535             QRect *target = &targets[w];
1536             double scale = target->width() / double(w->width());
1537             if (scale > 2.0 || (scale > 1.0 && (w->width() > 300 || w->height() > 300))) {
1538                 scale = (w->width() > 300 || w->height() > 300) ? 1.0 : 2.0;
1539                 target->setRect(
1540                                  target->center().x() - int(w->width() * scale) / 2,
1541                                  target->center().y() - int(w->height() * scale) / 2,
1542                                  w->width() * scale,
1543                                  w->height() * scale);
1544             }
1545         }
1546     }
1547 
1548     // Notify the motion manager of the targets
1549     Q_FOREACH (EffectWindow * w, windowlist)
1550         motionManager.moveWindow(w, targets.value(w));
1551 }
1552 
isOverlappingAny(EffectWindow * w,const QHash<EffectWindow *,QRect> & targets,const QRegion & border)1553 bool PresentWindowsEffect::isOverlappingAny(EffectWindow *w, const QHash<EffectWindow*, QRect> &targets, const QRegion &border)
1554 {
1555     QHash<EffectWindow*, QRect>::const_iterator winTarget = targets.find(w);
1556     if (winTarget == targets.constEnd())
1557         return false;
1558     if (border.intersects(*winTarget))
1559         return true;
1560 
1561     // Is there a better way to do this?
1562     QHash<EffectWindow*, QRect>::const_iterator target;
1563     for (target = targets.constBegin(); target != targets.constEnd(); ++target) {
1564         if (target == winTarget)
1565             continue;
1566         if (winTarget->adjusted(-5, -5, 5, 5).intersects(target->adjusted(-5, -5, 5, 5)))
1567             return true;
1568     }
1569     return false;
1570 }
1571 
1572 //-----------------------------------------------------------------------------
1573 // Activation
1574 
setActive(bool active)1575 void PresentWindowsEffect::setActive(bool active)
1576 {
1577     if (effects->activeFullScreenEffect() && effects->activeFullScreenEffect() != this)
1578         return;
1579     if (m_activated == active)
1580         return;
1581 
1582     m_activated = active;
1583     if (m_activated) {
1584         effects->setShowingDesktop(false);
1585         m_needInitialSelection = true;
1586         m_closeButtonCorner = (Qt::Corner)effects->kwinOption(KWin::CloseButtonCorner).toInt();
1587         m_decalOpacity = 0.0;
1588         m_highlightedWindow = nullptr;
1589         m_windowFilter.clear();
1590 
1591         if (!(m_doNotCloseWindows || m_closeView)) {
1592             m_closeView = new CloseWindowView();
1593             connect(m_closeView, &EffectQuickView::repaintNeeded, this, []() {
1594                 effects->addRepaintFull();
1595             });
1596             connect(m_closeView, &CloseWindowView::requestClose, this, &PresentWindowsEffect::closeWindow);
1597         }
1598 
1599         // Add every single window to m_windowData (Just calling [w] creates it)
1600         Q_FOREACH (EffectWindow * w, effects->stackingOrder()) {
1601             DataHash::iterator winData;
1602             if ((winData = m_windowData.find(w)) != m_windowData.end()) {
1603                 winData->visible = isVisibleWindow(w);
1604                 continue; // Happens if we reactivate before the ending animation finishes
1605             }
1606 
1607             winData = m_windowData.insert(w, WindowData());
1608             winData->visible = isVisibleWindow(w);
1609             winData->deleted = false;
1610             winData->referenced = false;
1611             winData->opacity = 0.0;
1612             if (w->isOnCurrentDesktop() && !w->isMinimized())
1613                 winData->opacity = 1.0;
1614 
1615             winData->highlight = 1.0;
1616             winData->textFrame = effects->effectFrame(EffectFrameUnstyled, false);
1617 
1618             QFont font;
1619             font.setBold(true);
1620             font.setPointSize(12);
1621 
1622             winData->textFrame->setFont(font);
1623             winData->iconFrame = effects->effectFrame(EffectFrameUnstyled, false);
1624             winData->iconFrame->setAlignment(Qt::AlignCenter);
1625             winData->iconFrame->setIcon(w->icon());
1626             winData->iconFrame->setIconSize(QSize(64, 64));
1627         }
1628 
1629         // Filter out special windows such as panels and taskbars
1630         Q_FOREACH (EffectWindow * w, effects->stackingOrder()) {
1631             if (isSelectableWindow(w)) {
1632                 m_motionManager.manage(w);
1633             }
1634         }
1635 
1636         if (m_motionManager.managedWindows().isEmpty()) {
1637             // No point triggering if there is nothing to do
1638             m_activated = false;
1639 
1640             DataHash::iterator i = m_windowData.begin();
1641             while (i != m_windowData.end()) {
1642                 delete i.value().textFrame;
1643                 delete i.value().iconFrame;
1644                 ++i;
1645             }
1646             m_windowData.clear();
1647 
1648             m_motionManager.unmanageAll();
1649             return;
1650         }
1651 
1652         // Create temporary input window to catch mouse events
1653         effects->startMouseInterception(this, Qt::ArrowCursor);
1654         m_hasKeyboardGrab = effects->grabKeyboard(this);
1655         effects->setActiveFullScreenEffect(this);
1656 
1657         reCreateGrids();
1658         rearrangeWindows();
1659         setHighlightedWindow(effects->activeWindow());
1660 
1661         Q_FOREACH (EffectWindow * w, effects->stackingOrder()) {
1662             w->setData(WindowForceBlurRole, QVariant(true));
1663             w->setData(WindowForceBackgroundContrastRole, QVariant(true));
1664         }
1665     } else {
1666         m_needInitialSelection = false;
1667         if (m_highlightedWindow)
1668             effects->setElevatedWindow(m_highlightedWindow, false);
1669 
1670         // Fade in/out all windows
1671         EffectWindow *activeWindow = effects->activeWindow();
1672         int desktop = effects->currentDesktop();
1673         if (activeWindow && !activeWindow->isOnAllDesktops())
1674             desktop = activeWindow->desktop();
1675         Q_FOREACH (EffectWindow * w, effects->stackingOrder()) {
1676             DataHash::iterator winData = m_windowData.find(w);
1677             if (winData != m_windowData.end())
1678                 winData->visible = (w->isOnDesktop(desktop) || w->isOnAllDesktops()) &&
1679                                     !w->isMinimized();
1680         }
1681         if (m_closeView)
1682             m_closeView->hide();
1683 
1684         // Move all windows back to their original position
1685         Q_FOREACH (EffectWindow * w, m_motionManager.managedWindows())
1686         m_motionManager.moveWindow(w, w->frameGeometry());
1687         if (m_filterFrame) {
1688             m_filterFrame->free();
1689         }
1690         m_windowFilter.clear();
1691         m_selectedWindows.clear();
1692 
1693         effects->stopMouseInterception(this);
1694         if (m_hasKeyboardGrab)
1695             effects->ungrabKeyboard();
1696         m_hasKeyboardGrab = false;
1697 
1698         // destroy atom on manager window
1699         if (m_managerWindow) {
1700             if (m_mode == ModeSelectedDesktop && m_atomDesktop != XCB_ATOM_NONE)
1701                 m_managerWindow->deleteProperty(m_atomDesktop);
1702             else if (m_mode == ModeWindowGroup && m_atomWindows != XCB_ATOM_NONE)
1703                 m_managerWindow->deleteProperty(m_atomWindows);
1704             m_managerWindow = nullptr;
1705         }
1706     }
1707 
1708     effects->addRepaintFull(); // Trigger the first repaint
1709 }
1710 
1711 //-----------------------------------------------------------------------------
1712 // Filter box
1713 
updateFilterFrame()1714 void PresentWindowsEffect::updateFilterFrame()
1715 {
1716     QRect area = effects->clientArea(ScreenArea, effects->activeScreen(), effects->currentDesktop());
1717     if (!m_filterFrame) {
1718         m_filterFrame = effects->effectFrame(EffectFrameStyled, false);
1719         QFont font;
1720         font.setPointSize(font.pointSize() * 2);
1721         font.setBold(true);
1722         m_filterFrame->setFont(font);
1723     }
1724     m_filterFrame->setPosition(QPoint(area.x() + area.width() / 2, area.y() + area.height() / 10));
1725     m_filterFrame->setText(m_windowFilter);
1726 }
1727 
1728 //-----------------------------------------------------------------------------
1729 // Helper functions
1730 
isSelectableWindow(EffectWindow * w)1731 bool PresentWindowsEffect::isSelectableWindow(EffectWindow *w)
1732 {
1733     if (!w->isOnCurrentActivity())
1734         return false;
1735     if (w->isSpecialWindow() || w->isUtility())
1736         return false;
1737     if (w->isDeleted())
1738         return false;
1739     if (!w->acceptsFocus())
1740         return false;
1741     if (w->isSkipSwitcher())
1742         return false;
1743     if (m_ignoreMinimized && w->isMinimized())
1744         return false;
1745 
1746     switch(m_mode) {
1747     default:
1748     case ModeAllDesktops:
1749         return true;
1750     case ModeCurrentDesktop:
1751         return w->isOnCurrentDesktop();
1752     case ModeSelectedDesktop:
1753         return w->isOnDesktop(m_desktop);
1754     case ModeWindowGroup:
1755         return m_selectedWindows.contains(w);
1756     case ModeWindowClass:
1757         return m_class == w->windowClass();
1758     }
1759 }
1760 
isVisibleWindow(EffectWindow * w)1761 bool PresentWindowsEffect::isVisibleWindow(EffectWindow *w)
1762 {
1763     if (w->isDesktop())
1764         return true;
1765     return isSelectableWindow(w);
1766 }
1767 
setHighlightedWindow(EffectWindow * w)1768 void PresentWindowsEffect::setHighlightedWindow(EffectWindow *w)
1769 {
1770     if (w == m_highlightedWindow || (w != nullptr && !m_motionManager.isManaging(w)))
1771         return;
1772 
1773     if (m_closeView)
1774         m_closeView->hide();
1775     if (m_highlightedWindow) {
1776         effects->setElevatedWindow(m_highlightedWindow, false);
1777         m_highlightedWindow->addRepaintFull(); // Trigger the first repaint
1778     }
1779     m_highlightedWindow = w;
1780     if (m_highlightedWindow) {
1781         effects->setElevatedWindow(m_highlightedWindow, true);
1782         m_highlightedWindow->addRepaintFull(); // Trigger the first repaint
1783     }
1784 
1785     updateCloseWindow();
1786 }
1787 
updateCloseWindow()1788 void PresentWindowsEffect::updateCloseWindow()
1789 {
1790     if (!m_closeView || m_doNotCloseWindows)
1791         return;
1792 
1793     if (!m_activated || !m_highlightedWindow || m_highlightedWindow->isDesktop()) {
1794         m_closeView->hide();
1795         return;
1796     }
1797     if (m_closeView->isVisible())
1798         return;
1799 
1800     const QRectF rect(m_motionManager.targetGeometry(m_highlightedWindow));
1801     if (2*m_closeView->geometry().width() > rect.width() && 2*m_closeView->geometry().height() > rect.height()) {
1802         // not for tiny windows (eg. with many windows) - they might become unselectable
1803         m_closeView->hide();
1804         return;
1805     }
1806 
1807     QRect cvr(QPoint(0,0), m_closeView->size());
1808     switch (m_closeButtonCorner)
1809     {
1810     case Qt::TopLeftCorner:
1811     default:
1812         cvr.moveTopLeft(rect.topLeft().toPoint()); break;
1813     case Qt::TopRightCorner:
1814         cvr.moveTopRight(rect.topRight().toPoint()); break;
1815     case Qt::BottomLeftCorner:
1816         cvr.moveBottomLeft(rect.bottomLeft().toPoint()); break;
1817     case Qt::BottomRightCorner:
1818         cvr.moveBottomRight(rect.bottomRight().toPoint()); break;
1819     }
1820 
1821     m_closeView->setGeometry(cvr);
1822 
1823     if (rect.contains(effects->cursorPos())) {
1824         m_closeView->show();
1825         m_closeView->disarm();
1826     }
1827     else
1828         m_closeView->hide();
1829 }
1830 
closeWindow()1831 void PresentWindowsEffect::closeWindow()
1832 {
1833     if (m_highlightedWindow)
1834         m_highlightedWindow->closeWindow();
1835 }
1836 
relativeWindow(EffectWindow * w,int xdiff,int ydiff,bool wrap) const1837 EffectWindow* PresentWindowsEffect::relativeWindow(EffectWindow *w, int xdiff, int ydiff, bool wrap) const
1838 {
1839     if (!w)
1840         return m_motionManager.managedWindows().constFirst();
1841 
1842     // TODO: Is it possible to select hidden windows?
1843     EffectWindow* next;
1844     QRect area = effects->clientArea(FullArea, 0, effects->currentDesktop());
1845     QRect detectRect;
1846 
1847     // Detect across the width of the desktop
1848     if (xdiff != 0) {
1849         if (xdiff > 0) {
1850             // Detect right
1851             for (int i = 0; i < xdiff; i++) {
1852                 QRectF wArea = m_motionManager.transformedGeometry(w);
1853                 detectRect = QRect(0, wArea.y(), area.width(), wArea.height());
1854                 next = nullptr;
1855 
1856                 Q_FOREACH (EffectWindow * e, m_motionManager.managedWindows()) {
1857                     DataHash::const_iterator winData = m_windowData.find(e);
1858                     if (winData == m_windowData.end() || !winData->visible)
1859                         continue;
1860 
1861                     QRectF eArea = m_motionManager.transformedGeometry(e);
1862                     if (eArea.intersects(detectRect) &&
1863                             eArea.x() > wArea.x()) {
1864                         if (next == nullptr)
1865                             next = e;
1866                         else {
1867                             QRectF nArea = m_motionManager.transformedGeometry(next);
1868                             if (eArea.x() < nArea.x())
1869                                 next = e;
1870                         }
1871                     }
1872                 }
1873                 if (next == nullptr) {
1874                     if (wrap)   // We are at the right-most window, now get the left-most one to wrap
1875                         return relativeWindow(w, -1000, 0, false);
1876                     break; // No more windows to the right
1877                 }
1878                 w = next;
1879             }
1880             return w;
1881         } else {
1882             // Detect left
1883             for (int i = 0; i < -xdiff; i++) {
1884                 QRectF wArea = m_motionManager.transformedGeometry(w);
1885                 detectRect = QRect(0, wArea.y(), area.width(), wArea.height());
1886                 next = nullptr;
1887 
1888                 Q_FOREACH (EffectWindow * e, m_motionManager.managedWindows()) {
1889                     DataHash::const_iterator winData = m_windowData.find(e);
1890                     if (winData == m_windowData.end() || !winData->visible)
1891                         continue;
1892 
1893                     QRectF eArea = m_motionManager.transformedGeometry(e);
1894                     if (eArea.intersects(detectRect) &&
1895                             eArea.x() + eArea.width() < wArea.x() + wArea.width()) {
1896                         if (next == nullptr)
1897                             next = e;
1898                         else {
1899                             QRectF nArea = m_motionManager.transformedGeometry(next);
1900                             if (eArea.x() + eArea.width() > nArea.x() + nArea.width())
1901                                 next = e;
1902                         }
1903                     }
1904                 }
1905                 if (next == nullptr) {
1906                     if (wrap)   // We are at the left-most window, now get the right-most one to wrap
1907                         return relativeWindow(w, 1000, 0, false);
1908                     break; // No more windows to the left
1909                 }
1910                 w = next;
1911             }
1912             return w;
1913         }
1914     }
1915 
1916     // Detect across the height of the desktop
1917     if (ydiff != 0) {
1918         if (ydiff > 0) {
1919             // Detect down
1920             for (int i = 0; i < ydiff; i++) {
1921                 QRectF wArea = m_motionManager.transformedGeometry(w);
1922                 detectRect = QRect(wArea.x(), 0, wArea.width(), area.height());
1923                 next = nullptr;
1924 
1925                 Q_FOREACH (EffectWindow * e, m_motionManager.managedWindows()) {
1926                     DataHash::const_iterator winData = m_windowData.find(e);
1927                     if (winData == m_windowData.end() || !winData->visible)
1928                         continue;
1929 
1930                     QRectF eArea = m_motionManager.transformedGeometry(e);
1931                     if (eArea.intersects(detectRect) &&
1932                             eArea.y() > wArea.y()) {
1933                         if (next == nullptr)
1934                             next = e;
1935                         else {
1936                             QRectF nArea = m_motionManager.transformedGeometry(next);
1937                             if (eArea.y() < nArea.y())
1938                                 next = e;
1939                         }
1940                     }
1941                 }
1942                 if (next == nullptr) {
1943                     if (wrap)   // We are at the bottom-most window, now get the top-most one to wrap
1944                         return relativeWindow(w, 0, -1000, false);
1945                     break; // No more windows to the bottom
1946                 }
1947                 w = next;
1948             }
1949             return w;
1950         } else {
1951             // Detect up
1952             for (int i = 0; i < -ydiff; i++) {
1953                 QRectF wArea = m_motionManager.transformedGeometry(w);
1954                 detectRect = QRect(wArea.x(), 0, wArea.width(), area.height());
1955                 next = nullptr;
1956 
1957                 Q_FOREACH (EffectWindow * e, m_motionManager.managedWindows()) {
1958                     DataHash::const_iterator winData = m_windowData.find(e);
1959                     if (winData == m_windowData.end() || !winData->visible)
1960                         continue;
1961 
1962                     QRectF eArea = m_motionManager.transformedGeometry(e);
1963                     if (eArea.intersects(detectRect) &&
1964                             eArea.y() + eArea.height() < wArea.y() + wArea.height()) {
1965                         if (next == nullptr)
1966                             next = e;
1967                         else {
1968                             QRectF nArea = m_motionManager.transformedGeometry(next);
1969                             if (eArea.y() + eArea.height() > nArea.y() + nArea.height())
1970                                 next = e;
1971                         }
1972                     }
1973                 }
1974                 if (next == nullptr) {
1975                     if (wrap)   // We are at the top-most window, now get the bottom-most one to wrap
1976                         return relativeWindow(w, 0, 1000, false);
1977                     break; // No more windows to the top
1978                 }
1979                 w = next;
1980             }
1981             return w;
1982         }
1983     }
1984 
1985     abort(); // Should never get here
1986 }
1987 
findFirstWindow() const1988 EffectWindow* PresentWindowsEffect::findFirstWindow() const
1989 {
1990     EffectWindow *topLeft = nullptr;
1991     QRectF topLeftGeometry;
1992 
1993     Q_FOREACH (EffectWindow * w, m_motionManager.managedWindows()) {
1994         DataHash::const_iterator winData = m_windowData.find(w);
1995         if (winData == m_windowData.end())
1996             continue;
1997         QRectF geometry = m_motionManager.transformedGeometry(w);
1998         if (winData->visible == false)
1999             continue; // Not visible
2000         if (winData->deleted)
2001             continue; // Window has been closed
2002         if (topLeft == nullptr) {
2003             topLeft = w;
2004             topLeftGeometry = geometry;
2005         } else if (geometry.x() < topLeftGeometry.x() || geometry.y() < topLeftGeometry.y()) {
2006             topLeft = w;
2007             topLeftGeometry = geometry;
2008         }
2009     }
2010     return topLeft;
2011 }
2012 
globalShortcutChanged(QAction * action,const QKeySequence & seq)2013 void PresentWindowsEffect::globalShortcutChanged(QAction *action, const QKeySequence& seq)
2014 {
2015     if (action->objectName() == QStringLiteral("Expose")) {
2016         shortcut.clear();
2017         shortcut.append(seq);
2018     } else if (action->objectName() == QStringLiteral("ExposeAll")) {
2019         shortcutAll.clear();
2020         shortcutAll.append(seq);
2021     } else if (action->objectName() == QStringLiteral("ExposeClass")) {
2022         shortcutClass.clear();
2023         shortcutClass.append(seq);
2024     }
2025 }
2026 
isActive() const2027 bool PresentWindowsEffect::isActive() const
2028 {
2029     return (m_activated || m_motionManager.managingWindows()) && !effects->isScreenLocked();
2030 }
2031 
reCreateGrids()2032 void PresentWindowsEffect::reCreateGrids()
2033 {
2034     m_gridSizes.clear();
2035     for (int i = 0; i < effects->numScreens(); ++i) {
2036         m_gridSizes.append(GridSize());
2037     }
2038     rearrangeWindows();
2039 }
2040 
CloseWindowView(QObject * parent)2041 CloseWindowView::CloseWindowView(QObject *parent)
2042     : EffectQuickScene(parent)
2043 {
2044     setSource(QUrl(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kwin/effects/presentwindows/main.qml"))));
2045     if (QQuickItem *item = rootItem()) {
2046         connect(item, SIGNAL(clicked()), this, SLOT(clicked()));
2047         setGeometry(QRect(QPoint(), QSize(item->implicitWidth(), item->implicitHeight())));
2048     }
2049     m_armTimer.restart();
2050 }
2051 
clicked()2052 void CloseWindowView::clicked()
2053 {
2054     // 50ms until the window is elevated (seen!) and 300ms more to be "realized" by the user.
2055     if (m_armTimer.hasExpired(350)) {
2056         Q_EMIT requestClose();
2057     }
2058 }
2059 
disarm()2060 void CloseWindowView::disarm()
2061 {
2062     m_armTimer.restart();
2063 }
2064 
2065 
2066 } // namespace
2067