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 ®ion, 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