1 /*
2     KWin - the KDE window manager
3     This file is part of the KDE project.
4 
5     SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
6     SPDX-FileCopyrightText: 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
7 
8     SPDX-License-Identifier: GPL-2.0-or-later
9 */
10 #include "abstract_client.h"
11 
12 #include "abstract_output.h"
13 #include "abstract_wayland_output.h"
14 #ifdef KWIN_BUILD_ACTIVITIES
15 #include "activities.h"
16 #endif
17 #include "appmenu.h"
18 #include "decorations/decoratedclient.h"
19 #include "decorations/decorationpalette.h"
20 #include "decorations/decorationbridge.h"
21 #include "effects.h"
22 #include "focuschain.h"
23 #include "outline.h"
24 #include "platform.h"
25 #include "screens.h"
26 #ifdef KWIN_BUILD_TABBOX
27 #include "tabbox.h"
28 #endif
29 #include "screenedge.h"
30 #include "useractions.h"
31 #include "virtualdesktops.h"
32 #include "workspace.h"
33 
34 #include "wayland_server.h"
35 #include <KWaylandServer/plasmawindowmanagement_interface.h>
36 #include <KWaylandServer/output_interface.h>
37 
38 #include <KDecoration2/DecoratedClient>
39 #include <KDecoration2/Decoration>
40 
41 #include <KDesktopFile>
42 
43 #include <QDir>
44 #include <QMouseEvent>
45 #include <QStyleHints>
46 
47 namespace KWin
48 {
49 
sign(int v)50 static inline int sign(int v)
51 {
52     return (v > 0) - (v < 0);
53 }
54 
55 QHash<QString, std::weak_ptr<Decoration::DecorationPalette>> AbstractClient::s_palettes;
56 std::shared_ptr<Decoration::DecorationPalette> AbstractClient::s_defaultPalette;
57 
AbstractClient()58 AbstractClient::AbstractClient()
59     : Toplevel()
60 #ifdef KWIN_BUILD_TABBOX
61     , m_tabBoxClient(QSharedPointer<TabBox::TabBoxClientImpl>(new TabBox::TabBoxClientImpl(this)))
62 #endif
63     , m_colorScheme(QStringLiteral("kdeglobals"))
64 {
65     connect(this, &AbstractClient::clientStartUserMovedResized,  this, &AbstractClient::moveResizedChanged);
66     connect(this, &AbstractClient::clientFinishUserMovedResized, this, &AbstractClient::moveResizedChanged);
67     connect(this, &AbstractClient::clientStartUserMovedResized,  this, &AbstractClient::removeCheckOutputConnection);
68     connect(this, &AbstractClient::clientFinishUserMovedResized, this, &AbstractClient::setupCheckOutputConnection);
69 
70     connect(this, &AbstractClient::paletteChanged, this, &AbstractClient::triggerDecorationRepaint);
71 
72     connect(Decoration::DecorationBridge::self(), &QObject::destroyed, this, &AbstractClient::destroyDecoration);
73 
74     // If the user manually moved the window, don't restore it after the keyboard closes
75     connect(this, &AbstractClient::clientFinishUserMovedResized, this, [this] () {
76         m_keyboardGeometryRestore = QRect();
77     });
78     connect(this, qOverload<AbstractClient *, bool, bool>(&AbstractClient::clientMaximizedStateChanged), this, [this] () {
79         m_keyboardGeometryRestore = QRect();
80     });
81     connect(this, &AbstractClient::fullScreenChanged, this, [this] () {
82         m_keyboardGeometryRestore = QRect();
83     });
84 
85     // replace on-screen-display on size changes
86     connect(this, &AbstractClient::frameGeometryChanged, this,
87         [this] (Toplevel *c, const QRect &old) {
88             Q_UNUSED(c)
89             if (isOnScreenDisplay() && !frameGeometry().isEmpty() && old.size() != frameGeometry().size() && isPlaceable()) {
90                 GeometryUpdatesBlocker blocker(this);
91                 placeIn(workspace()->clientArea(PlacementArea, this, workspace()->activeOutput()));
92             }
93         }
94     );
95 
96     connect(ApplicationMenu::self(), &ApplicationMenu::applicationMenuEnabledChanged, this, [this] {
97         Q_EMIT hasApplicationMenuChanged(hasApplicationMenu());
98     });
99 }
100 
~AbstractClient()101 AbstractClient::~AbstractClient()
102 {
103     Q_ASSERT(m_blockGeometryUpdates == 0);
104     Q_ASSERT(m_decoration.decoration == nullptr);
105 }
106 
updateMouseGrab()107 void AbstractClient::updateMouseGrab()
108 {
109 }
110 
belongToSameApplication(const AbstractClient * c1,const AbstractClient * c2,SameApplicationChecks checks)111 bool AbstractClient::belongToSameApplication(const AbstractClient *c1, const AbstractClient *c2, SameApplicationChecks checks)
112 {
113     return c1->belongsToSameApplication(c2, checks);
114 }
115 
isTransient() const116 bool AbstractClient::isTransient() const
117 {
118     return false;
119 }
120 
setClientShown(bool shown)121 void AbstractClient::setClientShown(bool shown)
122 {
123     Q_UNUSED(shown)
124 }
125 
userTime() const126 xcb_timestamp_t AbstractClient::userTime() const
127 {
128     return XCB_TIME_CURRENT_TIME;
129 }
130 
setSkipSwitcher(bool set)131 void AbstractClient::setSkipSwitcher(bool set)
132 {
133     set = rules()->checkSkipSwitcher(set);
134     if (set == skipSwitcher())
135         return;
136     m_skipSwitcher = set;
137     doSetSkipSwitcher();
138     updateWindowRules(Rules::SkipSwitcher);
139     Q_EMIT skipSwitcherChanged();
140 }
141 
setSkipPager(bool b)142 void AbstractClient::setSkipPager(bool b)
143 {
144     b = rules()->checkSkipPager(b);
145     if (b == skipPager())
146         return;
147     m_skipPager = b;
148     doSetSkipPager();
149     updateWindowRules(Rules::SkipPager);
150     Q_EMIT skipPagerChanged();
151 }
152 
doSetSkipPager()153 void AbstractClient::doSetSkipPager()
154 {
155 }
156 
setSkipTaskbar(bool b)157 void AbstractClient::setSkipTaskbar(bool b)
158 {
159     int was_wants_tab_focus = wantsTabFocus();
160     if (b == skipTaskbar())
161         return;
162     m_skipTaskbar = b;
163     doSetSkipTaskbar();
164     updateWindowRules(Rules::SkipTaskbar);
165     if (was_wants_tab_focus != wantsTabFocus()) {
166         FocusChain::self()->update(this, isActive() ? FocusChain::MakeFirst : FocusChain::Update);
167     }
168     Q_EMIT skipTaskbarChanged();
169 }
170 
setOriginalSkipTaskbar(bool b)171 void AbstractClient::setOriginalSkipTaskbar(bool b)
172 {
173     m_originalSkipTaskbar = rules()->checkSkipTaskbar(b);
174     setSkipTaskbar(m_originalSkipTaskbar);
175 }
176 
doSetSkipTaskbar()177 void AbstractClient::doSetSkipTaskbar()
178 {
179 
180 }
181 
doSetSkipSwitcher()182 void AbstractClient::doSetSkipSwitcher()
183 {
184 
185 }
186 
setIcon(const QIcon & icon)187 void AbstractClient::setIcon(const QIcon &icon)
188 {
189     m_icon = icon;
190     Q_EMIT iconChanged();
191 }
192 
setActive(bool act)193 void AbstractClient::setActive(bool act)
194 {
195     if (isZombie()) {
196         return;
197     }
198     if (m_active == act) {
199         return;
200     }
201     m_active = act;
202     const int ruledOpacity = m_active
203                              ? rules()->checkOpacityActive(qRound(opacity() * 100.0))
204                              : rules()->checkOpacityInactive(qRound(opacity() * 100.0));
205     setOpacity(ruledOpacity / 100.0);
206     workspace()->setActiveClient(act ? this : nullptr);
207 
208     if (!m_active)
209         cancelAutoRaise();
210 
211     if (!m_active && shadeMode() == ShadeActivated)
212         setShade(ShadeNormal);
213 
214     StackingUpdatesBlocker blocker(workspace());
215     updateLayer(); // active windows may get different layer
216     auto mainclients = mainClients();
217     for (auto it = mainclients.constBegin();
218             it != mainclients.constEnd();
219             ++it)
220         if ((*it)->isFullScreen())  // fullscreens go high even if their transient is active
221             (*it)->updateLayer();
222 
223     doSetActive();
224     Q_EMIT activeChanged();
225     updateMouseGrab();
226 }
227 
doSetActive()228 void AbstractClient::doSetActive()
229 {
230 }
231 
isZombie() const232 bool AbstractClient::isZombie() const
233 {
234     return m_zombie;
235 }
236 
markAsZombie()237 void AbstractClient::markAsZombie()
238 {
239     Q_ASSERT(!m_zombie);
240     m_zombie = true;
241 }
242 
layer() const243 Layer AbstractClient::layer() const
244 {
245     if (m_layer == UnknownLayer)
246         const_cast< AbstractClient* >(this)->m_layer = belongsToLayer();
247     return m_layer;
248 }
249 
updateLayer()250 void AbstractClient::updateLayer()
251 {
252     if (layer() == belongsToLayer())
253         return;
254     StackingUpdatesBlocker blocker(workspace());
255     invalidateLayer(); // invalidate, will be updated when doing restacking
256     for (auto it = transients().constBegin(),
257                                   end = transients().constEnd(); it != end; ++it)
258         (*it)->updateLayer();
259 }
260 
placeIn(const QRect & area)261 void AbstractClient::placeIn(const QRect &area)
262 {
263     // TODO: Get rid of this method eventually. We need to call setGeometryRestore() because
264     // checkWorkspacePosition() operates on geometryRestore() and because of quick tiling.
265     Placement::self()->place(this, area);
266     setGeometryRestore(moveResizeGeometry());
267 }
268 
invalidateLayer()269 void AbstractClient::invalidateLayer()
270 {
271     m_layer = UnknownLayer;
272 }
273 
belongsToLayer() const274 Layer AbstractClient::belongsToLayer() const
275 {
276     // NOTICE while showingDesktop, desktops move to the AboveLayer
277     // (interchangeable w/ eg. yakuake etc. which will at first remain visible)
278     // and the docks move into the NotificationLayer (which is between Above- and
279     // ActiveLayer, so that active fullscreen windows will still cover everything)
280     // Since the desktop is also activated, nothing should be in the ActiveLayer, though
281     if (isInternal())
282         return UnmanagedLayer;
283     if (isLockScreen())
284         return UnmanagedLayer;
285     if (isInputMethod())
286         return UnmanagedLayer;
287     if (isDesktop())
288         return workspace()->showingDesktop() ? AboveLayer : DesktopLayer;
289     if (isSplash())          // no damn annoying splashscreens
290         return NormalLayer; // getting in the way of everything else
291     if (isDock()) {
292         if (workspace()->showingDesktop())
293             return NotificationLayer;
294         return layerForDock();
295     }
296     if (isPopupWindow())
297         return PopupLayer;
298     if (isOnScreenDisplay())
299         return OnScreenDisplayLayer;
300     if (isNotification())
301         return NotificationLayer;
302     if (isCriticalNotification())
303         return CriticalNotificationLayer;
304     if (workspace()->showingDesktop() && belongsToDesktop()) {
305         return AboveLayer;
306     }
307     if (keepBelow())
308         return BelowLayer;
309     if (isActiveFullScreen())
310         return ActiveLayer;
311     if (keepAbove())
312         return AboveLayer;
313 
314     return NormalLayer;
315 }
316 
belongsToDesktop() const317 bool AbstractClient::belongsToDesktop() const
318 {
319     return false;
320 }
321 
layerForDock() const322 Layer AbstractClient::layerForDock() const
323 {
324     // slight hack for the 'allow window to cover panel' Kicker setting
325     // don't move keepbelow docks below normal window, but only to the same
326     // layer, so that both may be raised to cover the other
327     if (keepBelow())
328         return NormalLayer;
329     if (keepAbove()) // slight hack for the autohiding panels
330         return AboveLayer;
331     return DockLayer;
332 }
333 
setKeepAbove(bool b)334 void AbstractClient::setKeepAbove(bool b)
335 {
336     b = rules()->checkKeepAbove(b);
337     if (b && !rules()->checkKeepBelow(false))
338         setKeepBelow(false);
339     if (b == keepAbove()) {
340         return;
341     }
342     m_keepAbove = b;
343     doSetKeepAbove();
344     updateLayer();
345     updateWindowRules(Rules::Above);
346 
347     Q_EMIT keepAboveChanged(m_keepAbove);
348 }
349 
doSetKeepAbove()350 void AbstractClient::doSetKeepAbove()
351 {
352 }
353 
setKeepBelow(bool b)354 void AbstractClient::setKeepBelow(bool b)
355 {
356     b = rules()->checkKeepBelow(b);
357     if (b && !rules()->checkKeepAbove(false))
358         setKeepAbove(false);
359     if (b == keepBelow()) {
360         return;
361     }
362     m_keepBelow = b;
363     doSetKeepBelow();
364     updateLayer();
365     updateWindowRules(Rules::Below);
366 
367     Q_EMIT keepBelowChanged(m_keepBelow);
368 }
369 
doSetKeepBelow()370 void AbstractClient::doSetKeepBelow()
371 {
372 }
373 
startAutoRaise()374 void AbstractClient::startAutoRaise()
375 {
376     delete m_autoRaiseTimer;
377     m_autoRaiseTimer = new QTimer(this);
378     connect(m_autoRaiseTimer, &QTimer::timeout, this, &AbstractClient::autoRaise);
379     m_autoRaiseTimer->setSingleShot(true);
380     m_autoRaiseTimer->start(options->autoRaiseInterval());
381 }
382 
cancelAutoRaise()383 void AbstractClient::cancelAutoRaise()
384 {
385     delete m_autoRaiseTimer;
386     m_autoRaiseTimer = nullptr;
387 }
388 
autoRaise()389 void AbstractClient::autoRaise()
390 {
391     workspace()->raiseClient(this);
392     cancelAutoRaise();
393 }
394 
isMostRecentlyRaised() const395 bool AbstractClient::isMostRecentlyRaised() const
396 {
397     // The last toplevel in the unconstrained stacking order is the most recently raised one.
398     return workspace()->topClientOnDesktop(VirtualDesktopManager::self()->currentDesktop(), nullptr, true, false) == this;
399 }
400 
wantsTabFocus() const401 bool AbstractClient::wantsTabFocus() const
402 {
403     return (isNormalWindow() || isDialog()) && wantsInput();
404 }
405 
isSpecialWindow() const406 bool AbstractClient::isSpecialWindow() const
407 {
408     // TODO
409     return isDesktop() || isDock() || isSplash() || isToolbar() || isNotification() || isOnScreenDisplay() || isCriticalNotification();
410 }
411 
demandAttention(bool set)412 void AbstractClient::demandAttention(bool set)
413 {
414     if (isActive())
415         set = false;
416     if (m_demandsAttention == set)
417         return;
418     m_demandsAttention = set;
419     doSetDemandsAttention();
420     workspace()->clientAttentionChanged(this, set);
421     Q_EMIT demandsAttentionChanged();
422 }
423 
doSetDemandsAttention()424 void AbstractClient::doSetDemandsAttention()
425 {
426 }
427 
setDesktop(int desktop)428 void AbstractClient::setDesktop(int desktop)
429 {
430     const int numberOfDesktops = VirtualDesktopManager::self()->count();
431     if (desktop != NET::OnAllDesktops)   // Do range check
432         desktop = qMax(1, qMin(numberOfDesktops, desktop));
433 
434     QVector<VirtualDesktop *> desktops;
435     if (desktop != NET::OnAllDesktops) {
436        desktops << VirtualDesktopManager::self()->desktopForX11Id(desktop);
437     }
438     setDesktops(desktops);
439 }
440 
setDesktops(QVector<VirtualDesktop * > desktops)441 void AbstractClient::setDesktops(QVector<VirtualDesktop*> desktops)
442 {
443     //on x11 we can have only one desktop at a time
444     if (kwinApp()->operationMode() == Application::OperationModeX11 && desktops.size() > 1) {
445         desktops = QVector<VirtualDesktop*>({desktops.last()});
446     }
447 
448     desktops = rules()->checkDesktops(desktops);
449     if (desktops == m_desktops) {
450         return;
451     }
452 
453     int was_desk = AbstractClient::desktop();
454     const bool wasOnCurrentDesktop = isOnCurrentDesktop() && was_desk >= 0;
455 
456     m_desktops = desktops;
457 
458     if (windowManagementInterface()) {
459         if (m_desktops.isEmpty()) {
460             windowManagementInterface()->setOnAllDesktops(true);
461         } else {
462             windowManagementInterface()->setOnAllDesktops(false);
463             auto currentDesktops = windowManagementInterface()->plasmaVirtualDesktops();
464             for (auto desktop: qAsConst(m_desktops)) {
465                 if (!currentDesktops.contains(desktop->id())) {
466                     windowManagementInterface()->addPlasmaVirtualDesktop(desktop->id());
467                 } else {
468                     currentDesktops.removeOne(desktop->id());
469                 }
470             }
471             for (const auto &desktopId: qAsConst(currentDesktops)) {
472                 windowManagementInterface()->removePlasmaVirtualDesktop(desktopId);
473             }
474         }
475     }
476     if (info) {
477         info->setDesktop(desktop());
478     }
479 
480     if ((was_desk == NET::OnAllDesktops) != (desktop() == NET::OnAllDesktops)) {
481         // onAllDesktops changed
482         workspace()->updateOnAllDesktopsOfTransients(this);
483     }
484 
485     auto transients_stacking_order = workspace()->ensureStackingOrder(transients());
486     for (auto it = transients_stacking_order.constBegin();
487             it != transients_stacking_order.constEnd();
488             ++it)
489         (*it)->setDesktops(desktops);
490 
491     if (isModal())  // if a modal dialog is moved, move the mainwindow with it as otherwise
492         // the (just moved) modal dialog will confusingly return to the mainwindow with
493         // the next desktop change
494     {
495         Q_FOREACH (AbstractClient * c2, mainClients())
496         c2->setDesktops(desktops);
497     }
498 
499     doSetDesktop();
500 
501     FocusChain::self()->update(this, FocusChain::MakeFirst);
502     updateWindowRules(Rules::Desktops);
503 
504     Q_EMIT desktopChanged();
505     if (wasOnCurrentDesktop != isOnCurrentDesktop())
506         Q_EMIT desktopPresenceChanged(this, was_desk);
507     Q_EMIT x11DesktopIdsChanged();
508 }
509 
doSetDesktop()510 void AbstractClient::doSetDesktop()
511 {
512 }
513 
doSetOnActivities(const QStringList & activityList)514 void AbstractClient::doSetOnActivities(const QStringList &activityList)
515 {
516     Q_UNUSED(activityList);
517 }
518 
enterDesktop(VirtualDesktop * virtualDesktop)519 void AbstractClient::enterDesktop(VirtualDesktop *virtualDesktop)
520 {
521     if (m_desktops.contains(virtualDesktop)) {
522         return;
523     }
524     auto desktops = m_desktops;
525     desktops.append(virtualDesktop);
526     setDesktops(desktops);
527 }
528 
leaveDesktop(VirtualDesktop * virtualDesktop)529 void AbstractClient::leaveDesktop(VirtualDesktop *virtualDesktop)
530 {
531     QVector<VirtualDesktop*> currentDesktops;
532     if (m_desktops.isEmpty()) {
533         currentDesktops = VirtualDesktopManager::self()->desktops();
534     } else {
535         currentDesktops = m_desktops;
536     }
537 
538     if (!currentDesktops.contains(virtualDesktop)) {
539         return;
540     }
541     auto desktops = currentDesktops;
542     desktops.removeOne(virtualDesktop);
543     setDesktops(desktops);
544 }
545 
setOnAllDesktops(bool b)546 void AbstractClient::setOnAllDesktops(bool b)
547 {
548     if (b == isOnAllDesktops()) {
549         return;
550     }
551     if (b) {
552         setDesktops({});
553     } else {
554         setDesktops({VirtualDesktopManager::self()->currentDesktop()});
555     }
556 }
557 
desktop() const558 int AbstractClient::desktop() const
559 {
560     return m_desktops.isEmpty() ? (int)NET::OnAllDesktops : m_desktops.last()->x11DesktopNumber();
561 }
562 
x11DesktopIds() const563 QVector<uint> AbstractClient::x11DesktopIds() const
564 {
565     const auto desks = desktops();
566     QVector<uint> x11Ids;
567     x11Ids.reserve(desks.count());
568     std::transform(desks.constBegin(), desks.constEnd(),
569         std::back_inserter(x11Ids),
570         [] (const VirtualDesktop *vd) {
571             return vd->x11DesktopNumber();
572         }
573     );
574     return x11Ids;
575 }
576 
desktopIds() const577 QStringList AbstractClient::desktopIds() const
578 {
579     const auto desks = desktops();
580     QStringList ids;
581     ids.reserve(desks.count());
582     std::transform(desks.constBegin(), desks.constEnd(),
583                    std::back_inserter(ids),
584                    [] (const VirtualDesktop *vd) {
585                        return vd->id();
586                    }
587     );
588     return ids;
589 };
590 
shadeMode() const591 ShadeMode AbstractClient::shadeMode() const
592 {
593     return m_shadeMode;
594 }
595 
isShadeable() const596 bool AbstractClient::isShadeable() const
597 {
598     return false;
599 }
600 
setShade(bool set)601 void AbstractClient::setShade(bool set)
602 {
603     set ? setShade(ShadeNormal) : setShade(ShadeNone);
604 }
605 
setShade(ShadeMode mode)606 void AbstractClient::setShade(ShadeMode mode)
607 {
608     if (!isShadeable())
609         return;
610     if (mode == ShadeHover && isInteractiveMove())
611         return; // causes geometry breaks and is probably nasty
612     if (isSpecialWindow() || noBorder())
613         mode = ShadeNone;
614 
615     mode = rules()->checkShade(mode);
616     if (m_shadeMode == mode)
617         return;
618 
619     const bool wasShade = isShade();
620     const ShadeMode previousShadeMode = shadeMode();
621     m_shadeMode = mode;
622 
623     if (wasShade == isShade()) {
624         // Decoration may want to update after e.g. hover-shade changes
625         Q_EMIT shadeChanged();
626         return; // No real change in shaded state
627     }
628 
629     Q_ASSERT(isDecorated());
630     GeometryUpdatesBlocker blocker(this);
631 
632     doSetShade(previousShadeMode);
633     updateWindowRules(Rules::Shade);
634 
635     Q_EMIT shadeChanged();
636 }
637 
doSetShade(ShadeMode previousShadeMode)638 void AbstractClient::doSetShade(ShadeMode previousShadeMode)
639 {
640     Q_UNUSED(previousShadeMode)
641 }
642 
shadeHover()643 void AbstractClient::shadeHover()
644 {
645     setShade(ShadeHover);
646     cancelShadeHoverTimer();
647 }
648 
shadeUnhover()649 void AbstractClient::shadeUnhover()
650 {
651     setShade(ShadeNormal);
652     cancelShadeHoverTimer();
653 }
654 
startShadeHoverTimer()655 void AbstractClient::startShadeHoverTimer()
656 {
657     if (!isShade())
658         return;
659     m_shadeHoverTimer = new QTimer(this);
660     connect(m_shadeHoverTimer, &QTimer::timeout, this, &AbstractClient::shadeHover);
661     m_shadeHoverTimer->setSingleShot(true);
662     m_shadeHoverTimer->start(options->shadeHoverInterval());
663 }
664 
startShadeUnhoverTimer()665 void AbstractClient::startShadeUnhoverTimer()
666 {
667     if (m_shadeMode == ShadeHover && !isInteractiveMoveResize() && !isInteractiveMoveResizePointerButtonDown()) {
668         m_shadeHoverTimer = new QTimer(this);
669         connect(m_shadeHoverTimer, &QTimer::timeout, this, &AbstractClient::shadeUnhover);
670         m_shadeHoverTimer->setSingleShot(true);
671         m_shadeHoverTimer->start(options->shadeHoverInterval());
672     }
673 }
674 
cancelShadeHoverTimer()675 void AbstractClient::cancelShadeHoverTimer()
676 {
677     delete m_shadeHoverTimer;
678     m_shadeHoverTimer = nullptr;
679 }
680 
toggleShade()681 void AbstractClient::toggleShade()
682 {
683     // If the mode is ShadeHover or ShadeActive, cancel shade too.
684     setShade(shadeMode() == ShadeNone ? ShadeNormal : ShadeNone);
685 }
686 
titlebarPosition() const687 AbstractClient::Position AbstractClient::titlebarPosition() const
688 {
689     // TODO: still needed, remove?
690     return PositionTop;
691 }
692 
titlebarPositionUnderMouse() const693 bool AbstractClient::titlebarPositionUnderMouse() const
694 {
695     if (!isDecorated()) {
696         return false;
697     }
698     const auto sectionUnderMouse = decoration()->sectionUnderMouse();
699     if (sectionUnderMouse == Qt::TitleBarArea) {
700         return true;
701     }
702     // check other sections based on titlebarPosition
703     switch (titlebarPosition()) {
704     case AbstractClient::PositionTop:
705         return (sectionUnderMouse == Qt::TopLeftSection || sectionUnderMouse == Qt::TopSection || sectionUnderMouse == Qt::TopRightSection);
706     case AbstractClient::PositionLeft:
707         return (sectionUnderMouse == Qt::TopLeftSection || sectionUnderMouse == Qt::LeftSection || sectionUnderMouse == Qt::BottomLeftSection);
708     case AbstractClient::PositionRight:
709         return (sectionUnderMouse == Qt::BottomRightSection || sectionUnderMouse == Qt::RightSection || sectionUnderMouse == Qt::TopRightSection);
710     case AbstractClient::PositionBottom:
711         return (sectionUnderMouse == Qt::BottomLeftSection || sectionUnderMouse == Qt::BottomSection || sectionUnderMouse == Qt::BottomRightSection);
712     default:
713         // nothing
714         return false;
715     }
716 }
717 
setMinimized(bool set)718 void AbstractClient::setMinimized(bool set)
719 {
720     set ? minimize() : unminimize();
721 }
722 
minimize(bool avoid_animation)723 void AbstractClient::minimize(bool avoid_animation)
724 {
725     if (!isMinimizable() || isMinimized())
726         return;
727 
728     m_minimized = true;
729     doMinimize();
730 
731     updateWindowRules(Rules::Minimize);
732 
733     if (options->moveMinimizedWindowsToEndOfTabBoxFocusChain()) {
734         FocusChain::self()->update(this, FocusChain::MakeFirstMinimized);
735     }
736 
737     // TODO: merge signal with s_minimized
738     addWorkspaceRepaint(visibleGeometry());
739     Q_EMIT clientMinimized(this, !avoid_animation);
740     Q_EMIT minimizedChanged();
741 }
742 
unminimize(bool avoid_animation)743 void AbstractClient::unminimize(bool avoid_animation)
744 {
745     if (!isMinimized())
746         return;
747 
748     if (rules()->checkMinimize(false)) {
749         return;
750     }
751 
752     m_minimized = false;
753     doMinimize();
754 
755     updateWindowRules(Rules::Minimize);
756     Q_EMIT clientUnminimized(this, !avoid_animation);
757     Q_EMIT minimizedChanged();
758 }
759 
doMinimize()760 void AbstractClient::doMinimize()
761 {
762 }
763 
palette() const764 QPalette AbstractClient::palette() const
765 {
766     if (!m_palette) {
767         return QPalette();
768     }
769     return m_palette->palette();
770 }
771 
decorationPalette() const772 const Decoration::DecorationPalette *AbstractClient::decorationPalette() const
773 {
774     return m_palette.get();
775 }
776 
preferredColorScheme() const777 QString AbstractClient::preferredColorScheme() const
778 {
779     return rules()->checkDecoColor(QString());
780 }
781 
colorScheme() const782 QString AbstractClient::colorScheme() const
783 {
784     return m_colorScheme;
785 }
786 
setColorScheme(const QString & colorScheme)787 void AbstractClient::setColorScheme(const QString &colorScheme)
788 {
789     QString requestedColorScheme = colorScheme;
790     if (requestedColorScheme.isEmpty()) {
791         requestedColorScheme = QStringLiteral("kdeglobals");
792     }
793 
794     if (!m_palette || m_colorScheme != requestedColorScheme) {
795         m_colorScheme = requestedColorScheme;
796 
797         if (m_palette) {
798             disconnect(m_palette.get(), &Decoration::DecorationPalette::changed, this, &AbstractClient::handlePaletteChange);
799         }
800 
801         auto it = s_palettes.find(m_colorScheme);
802 
803         if (it == s_palettes.end() || it->expired()) {
804             m_palette = std::make_shared<Decoration::DecorationPalette>(m_colorScheme);
805             if (m_palette->isValid()) {
806                 s_palettes[m_colorScheme] = m_palette;
807             } else {
808                 if (!s_defaultPalette) {
809                     s_defaultPalette = std::make_shared<Decoration::DecorationPalette>(QStringLiteral("kdeglobals"));
810                     s_palettes[QStringLiteral("kdeglobals")] = s_defaultPalette;
811                 }
812 
813                 m_palette = s_defaultPalette;
814             }
815 
816             if (m_colorScheme == QStringLiteral("kdeglobals")) {
817                 s_defaultPalette = m_palette;
818             }
819         } else {
820             m_palette = it->lock();
821         }
822 
823         connect(m_palette.get(), &Decoration::DecorationPalette::changed, this, &AbstractClient::handlePaletteChange);
824 
825         Q_EMIT paletteChanged(palette());
826         Q_EMIT colorSchemeChanged();
827     }
828 }
829 
updateColorScheme()830 void AbstractClient::updateColorScheme()
831 {
832     setColorScheme(preferredColorScheme());
833 }
834 
handlePaletteChange()835 void AbstractClient::handlePaletteChange()
836 {
837     Q_EMIT paletteChanged(palette());
838 }
839 
keepInArea(QRect area,bool partial)840 void AbstractClient::keepInArea(QRect area, bool partial)
841 {
842     if (partial) {
843         // increase the area so that can have only 100 pixels in the area
844         const QRect geometry = moveResizeGeometry();
845         area.setLeft(std::min(area.left() - geometry.width() + 100, area.left()));
846         area.setTop(std::min(area.top() - geometry.height() + 100, area.top()));
847         area.setRight(std::max(area.right() + geometry.width() - 100, area.right()));
848         area.setBottom(std::max(area.bottom() + geometry.height() - 100, area.bottom()));
849     }
850     if (!partial) {
851         // resize to fit into area
852         const QRect geometry = moveResizeGeometry();
853         if (area.width() < geometry.width() || area.height() < geometry.height()) {
854             resizeWithChecks(geometry.size().boundedTo(area.size()));
855         }
856     }
857 
858     QRect geometry = moveResizeGeometry();
859     if (geometry.right() > area.right() && geometry.width() <= area.width()) {
860         geometry.moveRight(area.right());
861     }
862     if (geometry.bottom() > area.bottom() && geometry.height() <= area.height()) {
863         geometry.moveBottom(area.bottom());
864     }
865 
866     if (!area.contains(geometry.topLeft())) {
867         if (geometry.left() < area.left()) {
868             geometry.moveLeft(area.left());
869         }
870         if (geometry.top() < area.top()) {
871             geometry.moveTop(area.top());
872         }
873     }
874 
875     if (moveResizeGeometry().topLeft() != geometry.topLeft()) {
876         move(geometry.topLeft());
877     }
878 }
879 
880 /**
881  * Returns the maximum client size, not the maximum frame size.
882  */
maxSize() const883 QSize AbstractClient::maxSize() const
884 {
885     return rules()->checkMaxSize(QSize(INT_MAX, INT_MAX));
886 }
887 
888 /**
889  * Returns the minimum client size, not the minimum frame size.
890  */
minSize() const891 QSize AbstractClient::minSize() const
892 {
893     return rules()->checkMinSize(QSize(0, 0));
894 }
895 
blockGeometryUpdates(bool block)896 void AbstractClient::blockGeometryUpdates(bool block)
897 {
898     if (block) {
899         if (m_blockGeometryUpdates == 0) {
900             m_pendingMoveResizeMode = MoveResizeMode::None;
901         }
902         ++m_blockGeometryUpdates;
903     } else {
904         if (--m_blockGeometryUpdates == 0) {
905             if (m_pendingMoveResizeMode != MoveResizeMode::None) {
906                 if (isShade())
907                     moveResizeInternal(QRect(pos(), adjustedSize()), m_pendingMoveResizeMode);
908                 else
909                     moveResizeInternal(moveResizeGeometry(), m_pendingMoveResizeMode);
910                 m_pendingMoveResizeMode = MoveResizeMode::None;
911             }
912         }
913     }
914 }
915 
maximize(MaximizeMode m)916 void AbstractClient::maximize(MaximizeMode m)
917 {
918     setMaximize(m & MaximizeVertical, m & MaximizeHorizontal);
919 }
920 
setMaximize(bool vertically,bool horizontally)921 void AbstractClient::setMaximize(bool vertically, bool horizontally)
922 {
923     // changeMaximize() flips the state, so change from set->flip
924     const MaximizeMode oldMode = requestedMaximizeMode();
925     changeMaximize(
926         oldMode & MaximizeHorizontal ? !horizontally : horizontally,
927         oldMode & MaximizeVertical ? !vertically : vertically,
928         false);
929     const MaximizeMode newMode = maximizeMode();
930     if (oldMode != newMode) {
931         Q_EMIT clientMaximizedStateChanged(this, newMode);
932         Q_EMIT clientMaximizedStateChanged(this, vertically, horizontally);
933     }
934 }
935 
startInteractiveMoveResize()936 bool AbstractClient::startInteractiveMoveResize()
937 {
938     Q_ASSERT(!isInteractiveMoveResize());
939     Q_ASSERT(QWidget::keyboardGrabber() == nullptr);
940     Q_ASSERT(QWidget::mouseGrabber() == nullptr);
941     stopDelayedInteractiveMoveResize();
942     if (QApplication::activePopupWidget() != nullptr)
943         return false; // popups have grab
944     if (isFullScreen() && (screens()->count() < 2 || !isMovableAcrossScreens()))
945         return false;
946     if (!doStartInteractiveMoveResize()) {
947         return false;
948     }
949 
950     invalidateDecorationDoubleClickTimer();
951 
952     setInteractiveMoveResize(true);
953     workspace()->setMoveResizeClient(this);
954 
955     const Position mode = interactiveMoveResizePointerMode();
956     if (mode != PositionCenter) { // means "isResize()" but moveResizeMode = true is set below
957         if (maximizeMode() == MaximizeFull) { // partial is cond. reset in finishMoveResize
958             setGeometryRestore(moveResizeGeometry()); // "restore" to current geometry
959             setMaximize(false, false);
960         }
961     }
962 
963     if (quickTileMode() != QuickTileMode(QuickTileFlag::None) && mode != PositionCenter) { // Cannot use isResize() yet
964         // Exit quick tile mode when the user attempts to resize a tiled window
965         updateQuickTileMode(QuickTileFlag::None); // Do so without restoring original geometry
966         setGeometryRestore(moveResizeGeometry());
967         doSetQuickTileMode();
968         Q_EMIT quickTileModeChanged();
969     }
970 
971     updateHaveResizeEffect();
972     updateInitialMoveResizeGeometry();
973     checkUnrestrictedInteractiveMoveResize();
974     Q_EMIT clientStartUserMovedResized(this);
975     if (ScreenEdges::self()->isDesktopSwitchingMovingClients())
976         ScreenEdges::self()->reserveDesktopSwitching(true, Qt::Vertical|Qt::Horizontal);
977     return true;
978 }
979 
finishInteractiveMoveResize(bool cancel)980 void AbstractClient::finishInteractiveMoveResize(bool cancel)
981 {
982     GeometryUpdatesBlocker blocker(this);
983     const bool wasResize = isInteractiveResize(); // store across leaveMoveResize
984     leaveInteractiveMoveResize();
985 
986     doFinishInteractiveMoveResize();
987 
988     if (cancel)
989         moveResize(initialInteractiveMoveResizeGeometry());
990     else {
991         const QRect &moveResizeGeom = moveResizeGeometry();
992         if (wasResize) {
993             const bool restoreH = maximizeMode() == MaximizeHorizontal &&
994                                     moveResizeGeom.width() != initialInteractiveMoveResizeGeometry().width();
995             const bool restoreV = maximizeMode() == MaximizeVertical &&
996                                     moveResizeGeom.height() != initialInteractiveMoveResizeGeometry().height();
997             if (restoreH || restoreV) {
998                 changeMaximize(restoreH, restoreV, false);
999             }
1000         }
1001         moveResize(moveResizeGeom);
1002     }
1003     checkOutput(); // needs to be done because clientFinishUserMovedResized has not yet re-activated online alignment
1004     if (output() != interactiveMoveResizeStartOutput()) {
1005         if (isFullScreen() || isElectricBorderMaximizing()) {
1006             updateGeometryRestoresForFullscreen(output());
1007         }
1008         workspace()->sendClientToOutput(this, output()); // checks rule validity
1009         if (maximizeMode() != MaximizeRestore) {
1010             checkWorkspacePosition();
1011         }
1012     }
1013 
1014     if (isElectricBorderMaximizing()) {
1015         setQuickTileMode(electricBorderMode());
1016         setElectricBorderMaximizing(false);
1017     } else if (!cancel && !isFullScreen()) {
1018         QRect geom_restore = geometryRestore();
1019         if (!(maximizeMode() & MaximizeHorizontal)) {
1020             geom_restore.setX(moveResizeGeometry().x());
1021             geom_restore.setWidth(moveResizeGeometry().width());
1022         }
1023         if (!(maximizeMode() & MaximizeVertical)) {
1024             geom_restore.setY(moveResizeGeometry().y());
1025             geom_restore.setHeight(moveResizeGeometry().height());
1026         }
1027         setGeometryRestore(geom_restore);
1028     }
1029 // FRAME    update();
1030 
1031     Q_EMIT clientFinishUserMovedResized(this);
1032 }
1033 
1034 // This function checks if it actually makes sense to perform a restricted move/resize.
1035 // If e.g. the titlebar is already outside of the workarea, there's no point in performing
1036 // a restricted move resize, because then e.g. resize would also move the window (#74555).
1037 // NOTE: Most of it is duplicated from handleMoveResize().
checkUnrestrictedInteractiveMoveResize()1038 void AbstractClient::checkUnrestrictedInteractiveMoveResize()
1039 {
1040     if (isUnrestrictedInteractiveMoveResize())
1041         return;
1042     const QRect &moveResizeGeom = moveResizeGeometry();
1043     QRect desktopArea = workspace()->clientArea(WorkArea, this, moveResizeGeom.center());
1044     int left_marge, right_marge, top_marge, bottom_marge, titlebar_marge;
1045     // restricted move/resize - keep at least part of the titlebar always visible
1046     // how much must remain visible when moved away in that direction
1047     left_marge = qMin(100 + borderRight(), moveResizeGeom.width());
1048     right_marge = qMin(100 + borderLeft(), moveResizeGeom.width());
1049     // width/height change with opaque resizing, use the initial ones
1050     titlebar_marge = initialInteractiveMoveResizeGeometry().height();
1051     top_marge = borderBottom();
1052     bottom_marge = borderTop();
1053     if (isInteractiveResize()) {
1054         if (moveResizeGeom.bottom() < desktopArea.top() + top_marge)
1055             setUnrestrictedInteractiveMoveResize(true);
1056         if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge)
1057             setUnrestrictedInteractiveMoveResize(true);
1058         if (moveResizeGeom.right() < desktopArea.left() + left_marge)
1059             setUnrestrictedInteractiveMoveResize(true);
1060         if (moveResizeGeom.left() > desktopArea.right() - right_marge)
1061             setUnrestrictedInteractiveMoveResize(true);
1062         if (!isUnrestrictedInteractiveMoveResize() && moveResizeGeom.top() < desktopArea.top())   // titlebar mustn't go out
1063             setUnrestrictedInteractiveMoveResize(true);
1064     }
1065     if (isInteractiveMove()) {
1066         if (moveResizeGeom.bottom() < desktopArea.top() + titlebar_marge - 1)
1067             setUnrestrictedInteractiveMoveResize(true);
1068         // no need to check top_marge, titlebar_marge already handles it
1069         if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge + 1) // titlebar mustn't go out
1070             setUnrestrictedInteractiveMoveResize(true);
1071         if (moveResizeGeom.right() < desktopArea.left() + left_marge)
1072             setUnrestrictedInteractiveMoveResize(true);
1073         if (moveResizeGeom.left() > desktopArea.right() - right_marge)
1074             setUnrestrictedInteractiveMoveResize(true);
1075     }
1076 }
1077 
1078 // When the user pressed mouse on the titlebar, don't activate move immediately,
1079 // since it may be just a click. Activate instead after a delay. Move used to be
1080 // activated only after moving by several pixels, but that looks bad.
startDelayedInteractiveMoveResize()1081 void AbstractClient::startDelayedInteractiveMoveResize()
1082 {
1083     Q_ASSERT(!m_interactiveMoveResize.delayedTimer);
1084     m_interactiveMoveResize.delayedTimer = new QTimer(this);
1085     m_interactiveMoveResize.delayedTimer->setSingleShot(true);
1086     connect(m_interactiveMoveResize.delayedTimer, &QTimer::timeout, this,
1087         [this]() {
1088             Q_ASSERT(isInteractiveMoveResizePointerButtonDown());
1089             if (!startInteractiveMoveResize()) {
1090                 setInteractiveMoveResizePointerButtonDown(false);
1091             }
1092             updateCursor();
1093             stopDelayedInteractiveMoveResize();
1094         }
1095     );
1096     m_interactiveMoveResize.delayedTimer->start(QApplication::startDragTime());
1097 }
1098 
stopDelayedInteractiveMoveResize()1099 void AbstractClient::stopDelayedInteractiveMoveResize()
1100 {
1101     delete m_interactiveMoveResize.delayedTimer;
1102     m_interactiveMoveResize.delayedTimer = nullptr;
1103 }
1104 
updateInteractiveMoveResize(const QPointF & currentGlobalCursor)1105 void AbstractClient::updateInteractiveMoveResize(const QPointF &currentGlobalCursor)
1106 {
1107     handleInteractiveMoveResize(pos(), currentGlobalCursor.toPoint());
1108 }
1109 
handleInteractiveMoveResize(const QPoint & local,const QPoint & global)1110 void AbstractClient::handleInteractiveMoveResize(const QPoint &local, const QPoint &global)
1111 {
1112     const QRect oldGeo = moveResizeGeometry();
1113     handleInteractiveMoveResize(local.x(), local.y(), global.x(), global.y());
1114     if (!isFullScreen() && isInteractiveMove()) {
1115         if (quickTileMode() != QuickTileMode(QuickTileFlag::None) && oldGeo != moveResizeGeometry()) {
1116             GeometryUpdatesBlocker blocker(this);
1117             setQuickTileMode(QuickTileFlag::None);
1118             const QRect &geom_restore = geometryRestore();
1119             setInteractiveMoveOffset(QPoint(double(interactiveMoveOffset().x()) / double(oldGeo.width()) * double(geom_restore.width()),
1120                                  double(interactiveMoveOffset().y()) / double(oldGeo.height()) * double(geom_restore.height())));
1121             if (rules()->checkMaximize(MaximizeRestore) == MaximizeRestore)
1122                 setMoveResizeGeometry(geom_restore);
1123             handleInteractiveMoveResize(local.x(), local.y(), global.x(), global.y()); // fix position
1124         } else if (quickTileMode() == QuickTileMode(QuickTileFlag::None) && isResizable()) {
1125             checkQuickTilingMaximizationZones(global.x(), global.y());
1126         }
1127     }
1128 }
1129 
handleInteractiveMoveResize(int x,int y,int x_root,int y_root)1130 void AbstractClient::handleInteractiveMoveResize(int x, int y, int x_root, int y_root)
1131 {
1132     if (isWaitingForInteractiveMoveResizeSync())
1133         return; // we're still waiting for the client or the timeout
1134 
1135     const Position mode = interactiveMoveResizePointerMode();
1136     if ((mode == PositionCenter && !isMovableAcrossScreens())
1137             || (mode != PositionCenter && (isShade() || !isResizable())))
1138         return;
1139 
1140     if (!isInteractiveMoveResize()) {
1141         QPoint p(QPoint(x/* - padding_left*/, y/* - padding_top*/) - interactiveMoveOffset());
1142         if (p.manhattanLength() >= QApplication::startDragDistance()) {
1143             if (!startInteractiveMoveResize()) {
1144                 setInteractiveMoveResizePointerButtonDown(false);
1145                 updateCursor();
1146                 return;
1147             }
1148             updateCursor();
1149         } else
1150             return;
1151     }
1152 
1153     // ShadeHover or ShadeActive, ShadeNormal was already avoided above
1154     if (mode != PositionCenter && shadeMode() != ShadeNone)
1155         setShade(ShadeNone);
1156 
1157     QPoint globalPos(x_root, y_root);
1158     // these two points limit the geometry rectangle, i.e. if bottomleft resizing is done,
1159     // the bottomleft corner should be at is at (topleft.x(), bottomright().y())
1160     QPoint topleft = globalPos - interactiveMoveOffset();
1161     QPoint bottomright = globalPos + invertedInteractiveMoveOffset();
1162     QRect previousMoveResizeGeom = moveResizeGeometry();
1163 
1164     // TODO move whole group when moving its leader or when the leader is not mapped?
1165 
1166     auto titleBarRect = [this](bool &transposed, int &requiredPixels) -> QRect {
1167         const QRect &moveResizeGeom = moveResizeGeometry();
1168         QRect r(moveResizeGeom);
1169         r.moveTopLeft(QPoint(0,0));
1170         switch (titlebarPosition()) {
1171         default:
1172         case PositionTop:
1173             r.setHeight(borderTop());
1174             break;
1175         case PositionLeft:
1176             r.setWidth(borderLeft());
1177             transposed = true;
1178             break;
1179         case PositionBottom:
1180             r.setTop(r.bottom() - borderBottom());
1181             break;
1182         case PositionRight:
1183             r.setLeft(r.right() - borderRight());
1184             transposed = true;
1185             break;
1186         }
1187         // When doing a restricted move we must always keep 100px of the titlebar
1188         // visible to allow the user to be able to move it again.
1189         requiredPixels = qMin(100 * (transposed ? r.width() : r.height()),
1190                               moveResizeGeom.width() * moveResizeGeom.height());
1191         return r;
1192     };
1193 
1194     bool update = false;
1195     if (isInteractiveResize()) {
1196         QRect orig = initialInteractiveMoveResizeGeometry();
1197         SizeMode sizeMode = SizeModeAny;
1198         auto calculateMoveResizeGeom = [this, &topleft, &bottomright, &orig, &sizeMode, &mode]() {
1199             switch(mode) {
1200             case PositionTopLeft:
1201                 setMoveResizeGeometry(QRect(topleft, orig.bottomRight()));
1202                 break;
1203             case PositionBottomRight:
1204                 setMoveResizeGeometry(QRect(orig.topLeft(), bottomright));
1205                 break;
1206             case PositionBottomLeft:
1207                 setMoveResizeGeometry(QRect(QPoint(topleft.x(), orig.y()), QPoint(orig.right(), bottomright.y())));
1208                 break;
1209             case PositionTopRight:
1210                 setMoveResizeGeometry(QRect(QPoint(orig.x(), topleft.y()), QPoint(bottomright.x(), orig.bottom())));
1211                 break;
1212             case PositionTop:
1213                 setMoveResizeGeometry(QRect(QPoint(orig.left(), topleft.y()), orig.bottomRight()));
1214                 sizeMode = SizeModeFixedH; // try not to affect height
1215                 break;
1216             case PositionBottom:
1217                 setMoveResizeGeometry(QRect(orig.topLeft(), QPoint(orig.right(), bottomright.y())));
1218                 sizeMode = SizeModeFixedH;
1219                 break;
1220             case PositionLeft:
1221                 setMoveResizeGeometry(QRect(QPoint(topleft.x(), orig.top()), orig.bottomRight()));
1222                 sizeMode = SizeModeFixedW;
1223                 break;
1224             case PositionRight:
1225                 setMoveResizeGeometry(QRect(orig.topLeft(), QPoint(bottomright.x(), orig.bottom())));
1226                 sizeMode = SizeModeFixedW;
1227                 break;
1228             case PositionCenter:
1229             default:
1230                 abort();
1231                 break;
1232             }
1233         };
1234 
1235         // first resize (without checking constrains), then snap, then check bounds, then check constrains
1236         calculateMoveResizeGeom();
1237         // adjust new size to snap to other windows/borders
1238         setMoveResizeGeometry(workspace()->adjustClientSize(this, moveResizeGeometry(), mode));
1239 
1240         if (!isUnrestrictedInteractiveMoveResize()) {
1241             // Make sure the titlebar isn't behind a restricted area. We don't need to restrict
1242             // the other directions. If not visible enough, move the window to the closest valid
1243             // point. We bruteforce this by slowly moving the window back to its previous position
1244             QRegion availableArea(workspace()->clientArea(FullArea, this, workspace()->activeOutput()));
1245             availableArea -= workspace()->restrictedMoveArea(VirtualDesktopManager::self()->currentDesktop());
1246             bool transposed = false;
1247             int requiredPixels;
1248             QRect bTitleRect = titleBarRect(transposed, requiredPixels);
1249             int lastVisiblePixels = -1;
1250             QRect lastTry = moveResizeGeometry();
1251             bool titleFailed = false;
1252             for (;;) {
1253                 const QRect titleRect(bTitleRect.translated(moveResizeGeometry().topLeft()));
1254                 int visiblePixels = 0;
1255                 int realVisiblePixels = 0;
1256                 for (const QRect &rect : availableArea) {
1257                     const QRect r = rect & titleRect;
1258                     realVisiblePixels += r.width() * r.height();
1259                     if ((transposed && r.width() == titleRect.width()) || // Only the full size regions...
1260                         (!transposed && r.height() == titleRect.height())) // ...prevents long slim areas
1261                         visiblePixels += r.width() * r.height();
1262                 }
1263 
1264                 if (visiblePixels >= requiredPixels)
1265                     break; // We have reached a valid position
1266 
1267                 if (realVisiblePixels <= lastVisiblePixels) {
1268                     if (titleFailed && realVisiblePixels < lastVisiblePixels)
1269                         break; // we won't become better
1270                     else {
1271                         if (!titleFailed)
1272                             setMoveResizeGeometry(lastTry);
1273                         titleFailed = true;
1274                     }
1275                 }
1276                 lastVisiblePixels = realVisiblePixels;
1277                 QRect moveResizeGeom = moveResizeGeometry();
1278                 lastTry = moveResizeGeom;
1279 
1280                 // Not visible enough, move the window to the closest valid point. We bruteforce
1281                 // this by slowly moving the window back to its previous position.
1282                 // The geometry changes at up to two edges, the one with the title (if) shall take
1283                 // precedence. The opposing edge has no impact on visiblePixels and only one of
1284                 // the adjacent can alter at a time, ie. it's enough to ignore adjacent edges
1285                 // if the title edge altered
1286                 bool leftChanged  = previousMoveResizeGeom.left()   != moveResizeGeom.left();
1287                 bool rightChanged = previousMoveResizeGeom.right()  != moveResizeGeom.right();
1288                 bool topChanged   = previousMoveResizeGeom.top()    != moveResizeGeom.top();
1289                 bool btmChanged   = previousMoveResizeGeom.bottom() != moveResizeGeom.bottom();
1290                 auto fixChangedState = [titleFailed](bool &major, bool &counter, bool &ad1, bool &ad2) {
1291                     counter = false;
1292                     if (titleFailed)
1293                         major = false;
1294                     if (major)
1295                         ad1 = ad2 = false;
1296                 };
1297                 switch (titlebarPosition()) {
1298                 default:
1299                 case PositionTop:
1300                     fixChangedState(topChanged, btmChanged, leftChanged, rightChanged);
1301                     break;
1302                 case PositionLeft:
1303                     fixChangedState(leftChanged, rightChanged, topChanged, btmChanged);
1304                     break;
1305                 case PositionBottom:
1306                     fixChangedState(btmChanged, topChanged, leftChanged, rightChanged);
1307                     break;
1308                 case PositionRight:
1309                     fixChangedState(rightChanged, leftChanged, topChanged, btmChanged);
1310                     break;
1311                 }
1312                 if (topChanged)
1313                     moveResizeGeom.setTop(moveResizeGeom.y() + sign(previousMoveResizeGeom.y() - moveResizeGeom.y()));
1314                 else if (leftChanged)
1315                     moveResizeGeom.setLeft(moveResizeGeom.x() + sign(previousMoveResizeGeom.x() - moveResizeGeom.x()));
1316                 else if (btmChanged)
1317                     moveResizeGeom.setBottom(moveResizeGeom.bottom() + sign(previousMoveResizeGeom.bottom() - moveResizeGeom.bottom()));
1318                 else if (rightChanged)
1319                     moveResizeGeom.setRight(moveResizeGeom.right() + sign(previousMoveResizeGeom.right() - moveResizeGeom.right()));
1320                 else
1321                     break; // no position changed - that's certainly not good
1322                 setMoveResizeGeometry(moveResizeGeom);
1323             }
1324         }
1325 
1326         // Always obey size hints, even when in "unrestricted" mode
1327         QSize size = constrainFrameSize(moveResizeGeometry().size(), sizeMode);
1328         // the new topleft and bottomright corners (after checking size constrains), if they'll be needed
1329         topleft = QPoint(moveResizeGeometry().right() - size.width() + 1, moveResizeGeometry().bottom() - size.height() + 1);
1330         bottomright = QPoint(moveResizeGeometry().left() + size.width() - 1, moveResizeGeometry().top() + size.height() - 1);
1331         orig = moveResizeGeometry();
1332 
1333         // if aspect ratios are specified, both dimensions may change.
1334         // Therefore grow to the right/bottom if needed.
1335         // TODO it should probably obey gravity rather than always using right/bottom ?
1336         if (sizeMode == SizeModeFixedH)
1337             orig.setRight(bottomright.x());
1338         else if (sizeMode == SizeModeFixedW)
1339             orig.setBottom(bottomright.y());
1340 
1341         calculateMoveResizeGeom();
1342 
1343         if (moveResizeGeometry().size() != previousMoveResizeGeom.size())
1344             update = true;
1345     } else if (isInteractiveMove()) {
1346         Q_ASSERT(mode == PositionCenter);
1347         if (!isMovable()) { // isMovableAcrossScreens() must have been true to get here
1348             // Special moving of maximized windows on Xinerama screens
1349             AbstractOutput *output = kwinApp()->platform()->outputAt(globalPos);
1350             if (isFullScreen())
1351                 setMoveResizeGeometry(workspace()->clientArea(FullScreenArea, this, output));
1352             else {
1353                 QRect moveResizeGeom = workspace()->clientArea(MaximizeArea, this, output);
1354                 QSize adjSize = constrainFrameSize(moveResizeGeom.size(), SizeModeMax);
1355                 if (adjSize != moveResizeGeom.size()) {
1356                     QRect r(moveResizeGeom);
1357                     moveResizeGeom.setSize(adjSize);
1358                     moveResizeGeom.moveCenter(r.center());
1359                 }
1360                 setMoveResizeGeometry(moveResizeGeom);
1361             }
1362         } else {
1363             // first move, then snap, then check bounds
1364             QRect moveResizeGeom = moveResizeGeometry();
1365             moveResizeGeom.moveTopLeft(topleft);
1366             moveResizeGeom.moveTopLeft(workspace()->adjustClientPosition(this, moveResizeGeom.topLeft(),
1367                                        isUnrestrictedInteractiveMoveResize()));
1368             setMoveResizeGeometry(moveResizeGeom);
1369 
1370             if (!isUnrestrictedInteractiveMoveResize()) {
1371                 const QRegion strut = workspace()->restrictedMoveArea(VirtualDesktopManager::self()->currentDesktop());
1372                 QRegion availableArea(workspace()->clientArea(FullArea, this, workspace()->activeOutput()));
1373                 availableArea -= strut;   // Strut areas
1374                 bool transposed = false;
1375                 int requiredPixels;
1376                 QRect bTitleRect = titleBarRect(transposed, requiredPixels);
1377                 for (;;) {
1378                     QRect moveResizeGeom = moveResizeGeometry();
1379                     const QRect titleRect(bTitleRect.translated(moveResizeGeom.topLeft()));
1380                     int visiblePixels = 0;
1381                     for (const QRect &rect : availableArea) {
1382                         const QRect r = rect & titleRect;
1383                         if ((transposed && r.width() == titleRect.width()) || // Only the full size regions...
1384                             (!transposed && r.height() == titleRect.height())) // ...prevents long slim areas
1385                             visiblePixels += r.width() * r.height();
1386                     }
1387                     if (visiblePixels >= requiredPixels)
1388                         break; // We have reached a valid position
1389 
1390                     // (esp.) if there're more screens with different struts (panels) it the titlebar
1391                     // will be movable outside the movearea (covering one of the panels) until it
1392                     // crosses the panel "too much" (not enough visiblePixels) and then stucks because
1393                     // it's usually only pushed by 1px to either direction
1394                     // so we first check whether we intersect suc strut and move the window below it
1395                     // immediately (it's still possible to hit the visiblePixels >= titlebarArea break
1396                     // by moving the window slightly downwards, but it won't stuck)
1397                     // see bug #274466
1398                     // and bug #301805 for why we can't just match the titlearea against the screen
1399                     if (screens()->count() > 1) { // optimization
1400                         // TODO: could be useful on partial screen struts (half-width panels etc.)
1401                         int newTitleTop = -1;
1402                         for (const QRect &r : strut) {
1403                             if (r.top() == 0 && r.width() > r.height() && // "top panel"
1404                                 r.intersects(moveResizeGeom) && moveResizeGeom.top() < r.bottom()) {
1405                                 newTitleTop = r.bottom() + 1;
1406                                 break;
1407                             }
1408                         }
1409                         if (newTitleTop > -1) {
1410                             moveResizeGeom.moveTop(newTitleTop); // invalid position, possibly on screen change
1411                             setMoveResizeGeometry(moveResizeGeom);
1412                             break;
1413                         }
1414                     }
1415 
1416                     int dx = sign(previousMoveResizeGeom.x() - moveResizeGeom.x()),
1417                         dy = sign(previousMoveResizeGeom.y() - moveResizeGeom.y());
1418                     if (visiblePixels && dx) // means there's no full width cap -> favor horizontally
1419                         dy = 0;
1420                     else if (dy)
1421                         dx = 0;
1422 
1423                     // Move it back
1424                     moveResizeGeom.translate(dx, dy);
1425                     setMoveResizeGeometry(moveResizeGeom);
1426 
1427                     if (moveResizeGeom == previousMoveResizeGeom) {
1428                         break; // Prevent lockup
1429                     }
1430                 }
1431             }
1432         }
1433         if (moveResizeGeometry().topLeft() != previousMoveResizeGeom.topLeft())
1434             update = true;
1435     } else
1436         abort();
1437 
1438     if (!update)
1439         return;
1440 
1441     if (isInteractiveResize() && !haveResizeEffect()) {
1442         doInteractiveResizeSync();
1443     } else
1444         performInteractiveMoveResize();
1445 
1446     if (isInteractiveMove()) {
1447         ScreenEdges::self()->check(globalPos, QDateTime::fromMSecsSinceEpoch(xTime(), Qt::UTC));
1448     }
1449 }
1450 
performInteractiveMoveResize()1451 void AbstractClient::performInteractiveMoveResize()
1452 {
1453     const QRect &moveResizeGeom = moveResizeGeometry();
1454     if (isInteractiveMove()) {
1455         move(moveResizeGeom.topLeft());
1456     } else if (isInteractiveResize() && !haveResizeEffect()) {
1457         resize(moveResizeGeom.size());
1458     }
1459     positionGeometryTip();
1460     Q_EMIT clientStepUserMovedResized(this, moveResizeGeom);
1461 }
1462 
strutRect(StrutArea area) const1463 StrutRect AbstractClient::strutRect(StrutArea area) const
1464 {
1465     Q_UNUSED(area)
1466     return StrutRect();
1467 }
1468 
strutRects() const1469 StrutRects AbstractClient::strutRects() const
1470 {
1471     StrutRects region;
1472     region += strutRect(StrutAreaTop);
1473     region += strutRect(StrutAreaRight);
1474     region += strutRect(StrutAreaBottom);
1475     region += strutRect(StrutAreaLeft);
1476     return region;
1477 }
1478 
hasStrut() const1479 bool AbstractClient::hasStrut() const
1480 {
1481     return false;
1482 }
1483 
setupWindowManagementInterface()1484 void AbstractClient::setupWindowManagementInterface()
1485 {
1486     if (m_windowManagementInterface) {
1487         // already setup
1488         return;
1489     }
1490     if (!waylandServer() || !surface()) {
1491         return;
1492     }
1493     if (!waylandServer()->windowManagement()) {
1494         return;
1495     }
1496     using namespace KWaylandServer;
1497     auto w = waylandServer()->windowManagement()->createWindow(this, internalId());
1498     w->setTitle(caption());
1499     w->setVirtualDesktop(isOnAllDesktops() ? 0 : desktop() - 1);
1500     w->setActive(isActive());
1501     w->setFullscreen(isFullScreen());
1502     w->setKeepAbove(keepAbove());
1503     w->setKeepBelow(keepBelow());
1504     w->setMaximized(maximizeMode() == KWin::MaximizeFull);
1505     w->setMinimized(isMinimized());
1506     w->setOnAllDesktops(isOnAllDesktops());
1507     w->setDemandsAttention(isDemandingAttention());
1508     w->setCloseable(isCloseable());
1509     w->setMaximizeable(isMaximizable());
1510     w->setMinimizeable(isMinimizable());
1511     w->setFullscreenable(isFullScreenable());
1512     w->setApplicationMenuPaths(applicationMenuServiceName(), applicationMenuObjectPath());
1513     w->setIcon(icon());
1514     auto updateAppId = [this, w] {
1515         w->setAppId(QString::fromUtf8(m_desktopFileName.isEmpty() ? resourceClass() : m_desktopFileName));
1516     };
1517     updateAppId();
1518     w->setSkipTaskbar(skipTaskbar());
1519     w->setSkipSwitcher(skipSwitcher());
1520     w->setPid(pid());
1521     w->setShadeable(isShadeable());
1522     w->setShaded(isShade());
1523     w->setResizable(isResizable());
1524     w->setMovable(isMovable());
1525     w->setVirtualDesktopChangeable(true); // FIXME Matches X11Client::actionSupported(), but both should be implemented.
1526     w->setParentWindow(transientFor() ? transientFor()->windowManagementInterface() : nullptr);
1527     w->setGeometry(frameGeometry());
1528     connect(this, &AbstractClient::skipTaskbarChanged, w,
1529         [w, this] {
1530             w->setSkipTaskbar(skipTaskbar());
1531         }
1532     );
1533     connect(this, &AbstractClient::skipSwitcherChanged, w,
1534          [w, this] {
1535             w->setSkipSwitcher(skipSwitcher());
1536         }
1537     );
1538     connect(this, &AbstractClient::captionChanged, w, [w, this] { w->setTitle(caption()); });
1539 
1540     connect(this, &AbstractClient::activeChanged, w, [w, this] { w->setActive(isActive()); });
1541     connect(this, &AbstractClient::fullScreenChanged, w, [w, this] { w->setFullscreen(isFullScreen()); });
1542     connect(this, &AbstractClient::keepAboveChanged, w, &PlasmaWindowInterface::setKeepAbove);
1543     connect(this, &AbstractClient::keepBelowChanged, w, &PlasmaWindowInterface::setKeepBelow);
1544     connect(this, &AbstractClient::minimizedChanged, w, [w, this] { w->setMinimized(isMinimized()); });
1545     connect(this, static_cast<void (AbstractClient::*)(AbstractClient*,MaximizeMode)>(&AbstractClient::clientMaximizedStateChanged), w,
1546         [w] (KWin::AbstractClient *c, MaximizeMode mode) {
1547             Q_UNUSED(c);
1548             w->setMaximized(mode == KWin::MaximizeFull);
1549         }
1550     );
1551     connect(this, &AbstractClient::demandsAttentionChanged, w, [w, this] { w->setDemandsAttention(isDemandingAttention()); });
1552     connect(this, &AbstractClient::iconChanged, w,
1553         [w, this] {
1554             w->setIcon(icon());
1555         }
1556     );
1557     connect(this, &AbstractClient::windowClassChanged, w, updateAppId);
1558     connect(this, &AbstractClient::desktopFileNameChanged, w, updateAppId);
1559     connect(this, &AbstractClient::shadeChanged, w, [w, this] { w->setShaded(isShade()); });
1560     connect(this, &AbstractClient::transientChanged, w,
1561         [w, this] {
1562             w->setParentWindow(transientFor() ? transientFor()->windowManagementInterface() : nullptr);
1563         }
1564     );
1565     connect(this, &AbstractClient::frameGeometryChanged, w,
1566         [w, this] {
1567             w->setGeometry(frameGeometry());
1568         }
1569     );
1570     connect(this, &AbstractClient::applicationMenuChanged, w,
1571         [w, this] {
1572             w->setApplicationMenuPaths(applicationMenuServiceName(), applicationMenuObjectPath());
1573         }
1574     );
1575     connect(w, &PlasmaWindowInterface::closeRequested, this, [this] { closeWindow(); });
1576     connect(w, &PlasmaWindowInterface::moveRequested, this,
1577         [this] {
1578             Cursors::self()->mouse()->setPos(frameGeometry().center());
1579             performMouseCommand(Options::MouseMove, Cursors::self()->mouse()->pos());
1580         }
1581     );
1582     connect(w, &PlasmaWindowInterface::resizeRequested, this,
1583         [this] {
1584             Cursors::self()->mouse()->setPos(frameGeometry().bottomRight());
1585             performMouseCommand(Options::MouseResize, Cursors::self()->mouse()->pos());
1586         }
1587     );
1588     connect(w, &PlasmaWindowInterface::virtualDesktopRequested, this,
1589         [this] (quint32 desktop) {
1590             workspace()->sendClientToDesktop(this, desktop + 1, true);
1591         }
1592     );
1593     connect(w, &PlasmaWindowInterface::fullscreenRequested, this,
1594         [this] (bool set) {
1595             setFullScreen(set, false);
1596         }
1597     );
1598     connect(w, &PlasmaWindowInterface::minimizedRequested, this,
1599         [this] (bool set) {
1600             if (set) {
1601                 minimize();
1602             } else {
1603                 unminimize();
1604             }
1605         }
1606     );
1607     connect(w, &PlasmaWindowInterface::maximizedRequested, this,
1608         [this] (bool set) {
1609             maximize(set ? MaximizeFull : MaximizeRestore);
1610         }
1611     );
1612     connect(w, &PlasmaWindowInterface::keepAboveRequested, this,
1613         [this] (bool set) {
1614             setKeepAbove(set);
1615         }
1616     );
1617     connect(w, &PlasmaWindowInterface::keepBelowRequested, this,
1618         [this] (bool set) {
1619             setKeepBelow(set);
1620         }
1621     );
1622     connect(w, &PlasmaWindowInterface::demandsAttentionRequested, this,
1623         [this] (bool set) {
1624             demandAttention(set);
1625         }
1626     );
1627     connect(w, &PlasmaWindowInterface::activeRequested, this,
1628         [this] (bool set) {
1629             if (set) {
1630                 workspace()->activateClient(this, true);
1631             }
1632         }
1633     );
1634     connect(w, &PlasmaWindowInterface::shadedRequested, this,
1635         [this] (bool set) {
1636             setShade(set);
1637         }
1638     );
1639 
1640     for (const auto vd : qAsConst(m_desktops)) {
1641         w->addPlasmaVirtualDesktop(vd->id());
1642     }
1643 
1644     //this is only for the legacy
1645     connect(this, &AbstractClient::desktopChanged, w,
1646         [w, this] {
1647             if (isOnAllDesktops()) {
1648                 w->setOnAllDesktops(true);
1649                 return;
1650             }
1651             w->setVirtualDesktop(desktop() - 1);
1652             w->setOnAllDesktops(false);
1653         }
1654     );
1655 
1656     //Plasma Virtual desktop management
1657     //show/hide when the window enters/exits from desktop
1658     connect(w, &PlasmaWindowInterface::enterPlasmaVirtualDesktopRequested, this,
1659         [this] (const QString &desktopId) {
1660             VirtualDesktop *vd = VirtualDesktopManager::self()->desktopForId(desktopId);
1661             if (vd) {
1662                 enterDesktop(vd);
1663             }
1664         }
1665     );
1666     connect(w, &PlasmaWindowInterface::enterNewPlasmaVirtualDesktopRequested, this,
1667         [this] () {
1668             VirtualDesktopManager::self()->setCount(VirtualDesktopManager::self()->count() + 1);
1669             enterDesktop(VirtualDesktopManager::self()->desktops().last());
1670         }
1671     );
1672     connect(w, &PlasmaWindowInterface::leavePlasmaVirtualDesktopRequested, this,
1673         [this] (const QString &desktopId) {
1674             VirtualDesktop *vd = VirtualDesktopManager::self()->desktopForId(desktopId);
1675             if (vd) {
1676                 leaveDesktop(vd);
1677             }
1678         }
1679     );
1680 
1681     for (const auto &activity : qAsConst(m_activityList)) {
1682         w->addPlasmaActivity(activity);
1683     }
1684 
1685     // Notify clients on activities changes
1686     connect(this, &AbstractClient::activitiesChanged, w, [w, this] {
1687         const auto newActivities = m_activityList.toSet();
1688         const auto oldActivities = w->plasmaActivities().toSet();
1689 
1690         const auto activitiesToAdd = newActivities - oldActivities;
1691         for (const auto &activity : activitiesToAdd) {
1692             w->addPlasmaActivity(activity);
1693         }
1694 
1695         const auto activitiesToRemove = oldActivities - newActivities;
1696         for (const auto &activity : activitiesToRemove) {
1697             w->removePlasmaActivity(activity);
1698         }
1699     });
1700 
1701     //Plasma Activities management
1702     //show/hide when the window enters/exits activity
1703     connect(w, &PlasmaWindowInterface::enterPlasmaActivityRequested, this,
1704         [this] (const QString &activityId) {
1705             setOnActivity(activityId, true);
1706         }
1707     );
1708     connect(w, &PlasmaWindowInterface::leavePlasmaActivityRequested, this,
1709         [this] (const QString &activityId) {
1710             setOnActivity(activityId, false);
1711         }
1712     );
1713     connect(w, &PlasmaWindowInterface::sendToOutput, this,
1714         [this] (KWaylandServer::OutputInterface *output) {
1715             sendToOutput(waylandServer()->findOutput(output));
1716         }
1717     );
1718 
1719     m_windowManagementInterface = w;
1720 }
1721 
getMouseCommand(Qt::MouseButton button,bool * handled) const1722 Options::MouseCommand AbstractClient::getMouseCommand(Qt::MouseButton button, bool *handled) const
1723 {
1724     *handled = false;
1725     if (button == Qt::NoButton) {
1726         return Options::MouseNothing;
1727     }
1728     if (isActive()) {
1729         if (options->isClickRaise() && !isMostRecentlyRaised()) {
1730             *handled = true;
1731             return Options::MouseActivateRaiseAndPassClick;
1732         }
1733     } else {
1734         *handled = true;
1735         switch (button) {
1736         case Qt::LeftButton:
1737             return options->commandWindow1();
1738         case Qt::MiddleButton:
1739             return options->commandWindow2();
1740         case Qt::RightButton:
1741             return options->commandWindow3();
1742         default:
1743             // all other buttons pass Activate & Pass Client
1744             return Options::MouseActivateAndPassClick;
1745         }
1746     }
1747     return Options::MouseNothing;
1748 }
1749 
getWheelCommand(Qt::Orientation orientation,bool * handled) const1750 Options::MouseCommand AbstractClient::getWheelCommand(Qt::Orientation orientation, bool *handled) const
1751 {
1752     *handled = false;
1753     if (orientation != Qt::Vertical) {
1754         return Options::MouseNothing;
1755     }
1756     if (!isActive()) {
1757         *handled = true;
1758         return options->commandWindowWheel();
1759     }
1760     return Options::MouseNothing;
1761 }
1762 
performMouseCommand(Options::MouseCommand cmd,const QPoint & globalPos)1763 bool AbstractClient::performMouseCommand(Options::MouseCommand cmd, const QPoint &globalPos)
1764 {
1765     bool replay = false;
1766     switch(cmd) {
1767     case Options::MouseRaise:
1768         workspace()->raiseClient(this);
1769         break;
1770     case Options::MouseLower: {
1771         workspace()->lowerClient(this);
1772         // used to be activateNextClient(this), then topClientOnDesktop
1773         // since this is a mouseOp it's however safe to use the client under the mouse instead
1774         if (isActive() && options->focusPolicyIsReasonable()) {
1775             AbstractClient *next = workspace()->clientUnderMouse(output());
1776             if (next && next != this)
1777                 workspace()->requestFocus(next, false);
1778         }
1779         break;
1780     }
1781     case Options::MouseOperationsMenu:
1782         if (isActive() && options->isClickRaise())
1783             autoRaise();
1784         workspace()->showWindowMenu(QRect(globalPos, globalPos), this);
1785         break;
1786     case Options::MouseToggleRaiseAndLower:
1787         workspace()->raiseOrLowerClient(this);
1788         break;
1789     case Options::MouseActivateAndRaise: {
1790         replay = isActive(); // for clickraise mode
1791         bool mustReplay = !rules()->checkAcceptFocus(acceptsFocus());
1792         if (mustReplay) {
1793             auto it = workspace()->stackingOrder().constEnd(),
1794                                      begin = workspace()->stackingOrder().constBegin();
1795             while (mustReplay && --it != begin && *it != this) {
1796                 AbstractClient *c = qobject_cast<AbstractClient*>(*it);
1797                 if (!c || (c->keepAbove() && !keepAbove()) || (keepBelow() && !c->keepBelow()))
1798                     continue; // can never raise above "it"
1799                 mustReplay = !(c->isOnCurrentDesktop() && c->isOnCurrentActivity() && c->frameGeometry().intersects(frameGeometry()));
1800             }
1801         }
1802         workspace()->takeActivity(this, Workspace::ActivityFocus | Workspace::ActivityRaise);
1803         workspace()->setActiveOutput(globalPos);
1804         replay = replay || mustReplay;
1805         break;
1806     }
1807     case Options::MouseActivateAndLower:
1808         workspace()->requestFocus(this);
1809         workspace()->lowerClient(this);
1810         workspace()->setActiveOutput(globalPos);
1811         replay = replay || !rules()->checkAcceptFocus(acceptsFocus());
1812         break;
1813     case Options::MouseActivate:
1814         replay = isActive(); // for clickraise mode
1815         workspace()->takeActivity(this, Workspace::ActivityFocus);
1816         workspace()->setActiveOutput(globalPos);
1817         replay = replay || !rules()->checkAcceptFocus(acceptsFocus());
1818         break;
1819     case Options::MouseActivateRaiseAndPassClick:
1820         workspace()->takeActivity(this, Workspace::ActivityFocus | Workspace::ActivityRaise);
1821         workspace()->setActiveOutput(globalPos);
1822         replay = true;
1823         break;
1824     case Options::MouseActivateAndPassClick:
1825         workspace()->takeActivity(this, Workspace::ActivityFocus);
1826         workspace()->setActiveOutput(globalPos);
1827         replay = true;
1828         break;
1829     case Options::MouseMaximize:
1830         maximize(MaximizeFull);
1831         break;
1832     case Options::MouseRestore:
1833         maximize(MaximizeRestore);
1834         break;
1835     case Options::MouseMinimize:
1836         minimize();
1837         break;
1838     case Options::MouseAbove: {
1839         StackingUpdatesBlocker blocker(workspace());
1840         if (keepBelow())
1841             setKeepBelow(false);
1842         else
1843             setKeepAbove(true);
1844         break;
1845     }
1846     case Options::MouseBelow: {
1847         StackingUpdatesBlocker blocker(workspace());
1848         if (keepAbove())
1849             setKeepAbove(false);
1850         else
1851             setKeepBelow(true);
1852         break;
1853     }
1854     case Options::MousePreviousDesktop:
1855         workspace()->windowToPreviousDesktop(this);
1856         break;
1857     case Options::MouseNextDesktop:
1858         workspace()->windowToNextDesktop(this);
1859         break;
1860     case Options::MouseOpacityMore:
1861         if (!isDesktop())   // No point in changing the opacity of the desktop
1862             setOpacity(qMin(opacity() + 0.1, 1.0));
1863         break;
1864     case Options::MouseOpacityLess:
1865         if (!isDesktop())   // No point in changing the opacity of the desktop
1866             setOpacity(qMax(opacity() - 0.1, 0.1));
1867         break;
1868     case Options::MouseClose:
1869         closeWindow();
1870         break;
1871     case Options::MouseActivateRaiseAndMove:
1872     case Options::MouseActivateRaiseAndUnrestrictedMove:
1873         workspace()->raiseClient(this);
1874         workspace()->requestFocus(this);
1875         workspace()->setActiveOutput(globalPos);
1876         // fallthrough
1877     case Options::MouseMove:
1878     case Options::MouseUnrestrictedMove: {
1879         if (!isMovableAcrossScreens())
1880             break;
1881         if (isInteractiveMoveResize())
1882             finishInteractiveMoveResize(false);
1883         setInteractiveMoveResizePointerMode(PositionCenter);
1884         setInteractiveMoveResizePointerButtonDown(true);
1885         setInteractiveMoveOffset(QPoint(globalPos.x() - x(), globalPos.y() - y()));  // map from global
1886         setInvertedInteractiveMoveOffset(rect().bottomRight() - interactiveMoveOffset());
1887         setUnrestrictedInteractiveMoveResize((cmd == Options::MouseActivateRaiseAndUnrestrictedMove
1888                                   || cmd == Options::MouseUnrestrictedMove));
1889         if (!startInteractiveMoveResize())
1890             setInteractiveMoveResizePointerButtonDown(false);
1891         updateCursor();
1892         break;
1893     }
1894     case Options::MouseResize:
1895     case Options::MouseUnrestrictedResize: {
1896         if (!isResizable() || isShade())
1897             break;
1898         if (isInteractiveMoveResize())
1899             finishInteractiveMoveResize(false);
1900         setInteractiveMoveResizePointerButtonDown(true);
1901         const QPoint moveOffset = QPoint(globalPos.x() - x(), globalPos.y() - y());  // map from global
1902         setInteractiveMoveOffset(moveOffset);
1903         int x = moveOffset.x(), y = moveOffset.y();
1904         bool left = x < width() / 3;
1905         bool right = x >= 2 * width() / 3;
1906         bool top = y < height() / 3;
1907         bool bot = y >= 2 * height() / 3;
1908         Position mode;
1909         if (top)
1910             mode = left ? PositionTopLeft : (right ? PositionTopRight : PositionTop);
1911         else if (bot)
1912             mode = left ? PositionBottomLeft : (right ? PositionBottomRight : PositionBottom);
1913         else
1914             mode = (x < width() / 2) ? PositionLeft : PositionRight;
1915         setInteractiveMoveResizePointerMode(mode);
1916         setInvertedInteractiveMoveOffset(rect().bottomRight() - moveOffset);
1917         setUnrestrictedInteractiveMoveResize((cmd == Options::MouseUnrestrictedResize));
1918         if (!startInteractiveMoveResize())
1919             setInteractiveMoveResizePointerButtonDown(false);
1920         updateCursor();
1921         break;
1922     }
1923     case Options::MouseShade:
1924         toggleShade();
1925         cancelShadeHoverTimer();
1926         break;
1927     case Options::MouseSetShade:
1928         setShade(ShadeNormal);
1929         cancelShadeHoverTimer();
1930         break;
1931     case Options::MouseUnsetShade:
1932         setShade(ShadeNone);
1933         cancelShadeHoverTimer();
1934         break;
1935     case Options::MouseNothing:
1936     default:
1937         replay = true;
1938         break;
1939     }
1940     return replay;
1941 }
1942 
setTransientFor(AbstractClient * transientFor)1943 void AbstractClient::setTransientFor(AbstractClient *transientFor)
1944 {
1945     if (transientFor == this) {
1946         // cannot be transient for one self
1947         return;
1948     }
1949     if (m_transientFor == transientFor) {
1950         return;
1951     }
1952     m_transientFor = transientFor;
1953     Q_EMIT transientChanged();
1954 }
1955 
transientFor() const1956 const AbstractClient *AbstractClient::transientFor() const
1957 {
1958     return m_transientFor;
1959 }
1960 
transientFor()1961 AbstractClient *AbstractClient::transientFor()
1962 {
1963     return m_transientFor;
1964 }
1965 
hasTransientPlacementHint() const1966 bool AbstractClient::hasTransientPlacementHint() const
1967 {
1968     return false;
1969 }
1970 
transientPlacement(const QRect & bounds) const1971 QRect AbstractClient::transientPlacement(const QRect &bounds) const
1972 {
1973     Q_UNUSED(bounds);
1974     Q_UNREACHABLE();
1975     return QRect();
1976 }
1977 
hasTransient(const AbstractClient * c,bool indirect) const1978 bool AbstractClient::hasTransient(const AbstractClient *c, bool indirect) const
1979 {
1980     Q_UNUSED(indirect);
1981     return c->transientFor() == this;
1982 }
1983 
mainClients() const1984 QList< AbstractClient* > AbstractClient::mainClients() const
1985 {
1986     if (const AbstractClient *t = transientFor()) {
1987         return QList<AbstractClient*>{const_cast< AbstractClient* >(t)};
1988     }
1989     return QList<AbstractClient*>();
1990 }
1991 
allMainClients() const1992 QList<AbstractClient*> AbstractClient::allMainClients() const
1993 {
1994     auto result = mainClients();
1995     Q_FOREACH (const auto *cl, result) {
1996         result += cl->allMainClients();
1997     }
1998     return result;
1999 }
2000 
setModal(bool m)2001 void AbstractClient::setModal(bool m)
2002 {
2003     // Qt-3.2 can have even modal normal windows :(
2004     if (m_modal == m)
2005         return;
2006     m_modal = m;
2007     Q_EMIT modalChanged();
2008     // Changing modality for a mapped window is weird (?)
2009     // _NET_WM_STATE_MODAL should possibly rather be _NET_WM_WINDOW_TYPE_MODAL_DIALOG
2010 }
2011 
isModal() const2012 bool AbstractClient::isModal() const
2013 {
2014     return m_modal;
2015 }
2016 
2017 // check whether a transient should be actually kept above its mainwindow
2018 // there may be some special cases where this rule shouldn't be enfored
shouldKeepTransientAbove(const AbstractClient * parent,const AbstractClient * transient)2019 static bool shouldKeepTransientAbove(const AbstractClient *parent, const AbstractClient *transient)
2020 {
2021     // #93832 - don't keep splashscreens above dialogs
2022     if (transient->isSplash() && parent->isDialog()) {
2023         return false;
2024     }
2025     // This is rather a hack for #76026. Don't keep non-modal dialogs above
2026     // the mainwindow, but only if they're group transient (since only such dialogs
2027     // have taskbar entry in Kicker). A proper way of doing this (both kwin and kicker)
2028     // needs to be found.
2029     if (transient->isDialog() && !transient->isModal() && transient->groupTransient()) {
2030         return false;
2031     }
2032     // #63223 - don't keep transients above docks, because the dock is kept high,
2033     // and e.g. dialogs for them would be too high too
2034     // ignore this if the transient has a placement hint which indicates it should go above it's parent
2035     if (parent->isDock() && !transient->hasTransientPlacementHint()) {
2036         return false;
2037     }
2038     return true;
2039 }
2040 
addTransient(AbstractClient * cl)2041 void AbstractClient::addTransient(AbstractClient *cl)
2042 {
2043     Q_ASSERT(!m_transients.contains(cl));
2044     Q_ASSERT(cl != this);
2045     m_transients.append(cl);
2046     if (shouldKeepTransientAbove(this, cl)) {
2047         workspace()->constrain(this, cl);
2048     }
2049 }
2050 
removeTransient(AbstractClient * cl)2051 void AbstractClient::removeTransient(AbstractClient *cl)
2052 {
2053     m_transients.removeAll(cl);
2054     if (cl->transientFor() == this) {
2055         cl->setTransientFor(nullptr);
2056     }
2057     workspace()->unconstrain(this, cl);
2058 }
2059 
removeTransientFromList(AbstractClient * cl)2060 void AbstractClient::removeTransientFromList(AbstractClient *cl)
2061 {
2062     m_transients.removeAll(cl);
2063 }
2064 
isActiveFullScreen() const2065 bool AbstractClient::isActiveFullScreen() const
2066 {
2067     if (!isFullScreen())
2068         return false;
2069 
2070     const auto ac = workspace()->mostRecentlyActivatedClient(); // instead of activeClient() - avoids flicker
2071     // according to NETWM spec implementation notes suggests
2072     // "focused windows having state _NET_WM_STATE_FULLSCREEN" to be on the highest layer.
2073     // we'll also take the screen into account
2074     return ac && (ac == this || ac->output() != output()|| ac->allMainClients().contains(const_cast<AbstractClient*>(this)));
2075 }
2076 
2077 #define BORDER(which) \
2078     int AbstractClient::border##which() const \
2079     { \
2080         return isDecorated() ? decoration()->border##which() : 0; \
2081     }
2082 
2083 BORDER(Bottom)
BORDER(Left)2084 BORDER(Left)
2085 BORDER(Right)
2086 BORDER(Top)
2087 #undef BORDER
2088 
2089 void AbstractClient::updateInitialMoveResizeGeometry()
2090 {
2091     m_interactiveMoveResize.initialGeometry = frameGeometry();
2092     m_interactiveMoveResize.startOutput = output();
2093 }
2094 
updateCursor()2095 void AbstractClient::updateCursor()
2096 {
2097     Position m = interactiveMoveResizePointerMode();
2098     if (!isResizable() || isShade())
2099         m = PositionCenter;
2100     CursorShape c = Qt::ArrowCursor;
2101     switch(m) {
2102     case PositionTopLeft:
2103         c = KWin::ExtendedCursor::SizeNorthWest;
2104         break;
2105     case PositionBottomRight:
2106         c = KWin::ExtendedCursor::SizeSouthEast;
2107         break;
2108     case PositionBottomLeft:
2109         c = KWin::ExtendedCursor::SizeSouthWest;
2110         break;
2111     case PositionTopRight:
2112         c = KWin::ExtendedCursor::SizeNorthEast;
2113         break;
2114     case PositionTop:
2115         c = KWin::ExtendedCursor::SizeNorth;
2116         break;
2117     case PositionBottom:
2118         c = KWin::ExtendedCursor::SizeSouth;
2119         break;
2120     case PositionLeft:
2121         c = KWin::ExtendedCursor::SizeWest;
2122         break;
2123     case PositionRight:
2124         c = KWin::ExtendedCursor::SizeEast;
2125         break;
2126     default:
2127         if (isInteractiveMoveResize())
2128             c = Qt::SizeAllCursor;
2129         else
2130             c = Qt::ArrowCursor;
2131         break;
2132     }
2133     if (c == m_interactiveMoveResize.cursor)
2134         return;
2135     m_interactiveMoveResize.cursor = c;
2136     Q_EMIT moveResizeCursorChanged(c);
2137 }
2138 
leaveInteractiveMoveResize()2139 void AbstractClient::leaveInteractiveMoveResize()
2140 {
2141     workspace()->setMoveResizeClient(nullptr);
2142     setInteractiveMoveResize(false);
2143     if (ScreenEdges::self()->isDesktopSwitchingMovingClients())
2144         ScreenEdges::self()->reserveDesktopSwitching(false, Qt::Vertical|Qt::Horizontal);
2145     if (isElectricBorderMaximizing()) {
2146         outline()->hide();
2147         elevate(false);
2148     }
2149 }
2150 
2151 bool AbstractClient::s_haveResizeEffect = false;
2152 
updateHaveResizeEffect()2153 void AbstractClient::updateHaveResizeEffect()
2154 {
2155     s_haveResizeEffect = effects && static_cast<EffectsHandlerImpl*>(effects)->provides(Effect::Resize);
2156 }
2157 
doStartInteractiveMoveResize()2158 bool AbstractClient::doStartInteractiveMoveResize()
2159 {
2160     return true;
2161 }
2162 
doFinishInteractiveMoveResize()2163 void AbstractClient::doFinishInteractiveMoveResize()
2164 {
2165 }
2166 
positionGeometryTip()2167 void AbstractClient::positionGeometryTip()
2168 {
2169 }
2170 
isWaitingForInteractiveMoveResizeSync() const2171 bool AbstractClient::isWaitingForInteractiveMoveResizeSync() const
2172 {
2173     return false;
2174 }
2175 
doInteractiveResizeSync()2176 void AbstractClient::doInteractiveResizeSync()
2177 {
2178 }
2179 
checkQuickTilingMaximizationZones(int xroot,int yroot)2180 void AbstractClient::checkQuickTilingMaximizationZones(int xroot, int yroot)
2181 {
2182     QuickTileMode mode = QuickTileFlag::None;
2183     bool innerBorder = false;
2184 
2185     const auto outputs = kwinApp()->platform()->enabledOutputs();
2186     for (const AbstractOutput *output : outputs) {
2187         if (!output->geometry().contains(QPoint(xroot, yroot))) {
2188             continue;
2189         }
2190 
2191         auto isInScreen = [&output, &outputs](const QPoint &pt) {
2192             for (const AbstractOutput *other : outputs) {
2193                 if (other == output) {
2194                     continue;
2195                 }
2196                 if (other->geometry().contains(pt)) {
2197                     return true;
2198                 }
2199             }
2200             return false;
2201         };
2202 
2203         QRect area = workspace()->clientArea(MaximizeArea, this, QPoint(xroot, yroot));
2204         if (options->electricBorderTiling()) {
2205             if (xroot <= area.x() + 20) {
2206                 mode |= QuickTileFlag::Left;
2207                 innerBorder = isInScreen(QPoint(area.x() - 1, yroot));
2208             } else if (xroot >= area.x() + area.width() - 20) {
2209                 mode |= QuickTileFlag::Right;
2210                 innerBorder = isInScreen(QPoint(area.right() + 1, yroot));
2211             }
2212         }
2213 
2214         if (mode != QuickTileMode(QuickTileFlag::None)) {
2215             if (yroot <= area.y() + area.height() * options->electricBorderCornerRatio())
2216                 mode |= QuickTileFlag::Top;
2217             else if (yroot >= area.y() + area.height() - area.height()  * options->electricBorderCornerRatio())
2218                 mode |= QuickTileFlag::Bottom;
2219         } else if (options->electricBorderMaximize() && yroot <= area.y() + 5 && isMaximizable()) {
2220             mode = QuickTileFlag::Maximize;
2221             innerBorder = isInScreen(QPoint(xroot, area.y() - 1));
2222         }
2223         break; // no point in checking other screens to contain this... "point"...
2224     }
2225     if (mode != electricBorderMode()) {
2226         setElectricBorderMode(mode);
2227         if (innerBorder) {
2228             if (!m_electricMaximizingDelay) {
2229                 m_electricMaximizingDelay = new QTimer(this);
2230                 m_electricMaximizingDelay->setInterval(250);
2231                 m_electricMaximizingDelay->setSingleShot(true);
2232                 connect(m_electricMaximizingDelay, &QTimer::timeout, this, [this]() {
2233                     if (isInteractiveMove())
2234                         setElectricBorderMaximizing(electricBorderMode() != QuickTileMode(QuickTileFlag::None));
2235                 });
2236             }
2237             m_electricMaximizingDelay->start();
2238         } else {
2239             setElectricBorderMaximizing(mode != QuickTileMode(QuickTileFlag::None));
2240         }
2241     }
2242 }
2243 
keyPressEvent(uint key_code)2244 void AbstractClient::keyPressEvent(uint key_code)
2245 {
2246     if (!isInteractiveMove() && !isInteractiveResize())
2247         return;
2248     bool is_control = key_code & Qt::CTRL;
2249     bool is_alt = key_code & Qt::ALT;
2250     key_code = key_code & ~Qt::KeyboardModifierMask;
2251     int delta = is_control ? 1 : is_alt ? 32 : 8;
2252     QPoint pos = Cursors::self()->mouse()->pos();
2253     switch(key_code) {
2254     case Qt::Key_Left:
2255         pos.rx() -= delta;
2256         break;
2257     case Qt::Key_Right:
2258         pos.rx() += delta;
2259         break;
2260     case Qt::Key_Up:
2261         pos.ry() -= delta;
2262         break;
2263     case Qt::Key_Down:
2264         pos.ry() += delta;
2265         break;
2266     case Qt::Key_Space:
2267     case Qt::Key_Return:
2268     case Qt::Key_Enter:
2269         setInteractiveMoveResizePointerButtonDown(false);
2270         finishInteractiveMoveResize(false);
2271         updateCursor();
2272         break;
2273     case Qt::Key_Escape:
2274         setInteractiveMoveResizePointerButtonDown(false);
2275         finishInteractiveMoveResize(true);
2276         updateCursor();
2277         break;
2278     default:
2279         return;
2280     }
2281     Cursors::self()->mouse()->setPos(pos);
2282 }
2283 
resizeIncrements() const2284 QSize AbstractClient::resizeIncrements() const
2285 {
2286     return QSize(1, 1);
2287 }
2288 
dontInteractiveMoveResize()2289 void AbstractClient::dontInteractiveMoveResize()
2290 {
2291     setInteractiveMoveResizePointerButtonDown(false);
2292     stopDelayedInteractiveMoveResize();
2293     if (isInteractiveMoveResize())
2294         finishInteractiveMoveResize(false);
2295 }
2296 
mousePosition() const2297 AbstractClient::Position AbstractClient::mousePosition() const
2298 {
2299     if (isDecorated()) {
2300         switch (decoration()->sectionUnderMouse()) {
2301             case Qt::BottomLeftSection:
2302                 return PositionBottomLeft;
2303             case Qt::BottomRightSection:
2304                 return PositionBottomRight;
2305             case Qt::BottomSection:
2306                 return PositionBottom;
2307             case Qt::LeftSection:
2308                 return PositionLeft;
2309             case Qt::RightSection:
2310                 return PositionRight;
2311             case Qt::TopSection:
2312                 return PositionTop;
2313             case Qt::TopLeftSection:
2314                 return PositionTopLeft;
2315             case Qt::TopRightSection:
2316                 return PositionTopRight;
2317             default:
2318                 return PositionCenter;
2319         }
2320     }
2321     return PositionCenter;
2322 }
2323 
endInteractiveMoveResize()2324 void AbstractClient::endInteractiveMoveResize()
2325 {
2326     setInteractiveMoveResizePointerButtonDown(false);
2327     stopDelayedInteractiveMoveResize();
2328     if (isInteractiveMoveResize()) {
2329         finishInteractiveMoveResize(false);
2330         setInteractiveMoveResizePointerMode(mousePosition());
2331     }
2332     updateCursor();
2333 }
2334 
createDecoration(const QRect & oldGeometry)2335 void AbstractClient::createDecoration(const QRect &oldGeometry)
2336 {
2337     KDecoration2::Decoration *decoration = Decoration::DecorationBridge::self()->createDecoration(this);
2338     if (decoration) {
2339         QMetaObject::invokeMethod(decoration, QOverload<>::of(&KDecoration2::Decoration::update), Qt::QueuedConnection);
2340         connect(decoration, &KDecoration2::Decoration::shadowChanged, this, &Toplevel::updateShadow);
2341         connect(decoration, &KDecoration2::Decoration::bordersChanged,
2342                 this, &AbstractClient::updateDecorationInputShape);
2343         connect(decoration, &KDecoration2::Decoration::resizeOnlyBordersChanged,
2344                 this, &AbstractClient::updateDecorationInputShape);
2345         connect(decoration, &KDecoration2::Decoration::bordersChanged, this, [this]() {
2346             GeometryUpdatesBlocker blocker(this);
2347             const QRect oldGeometry = frameGeometry();
2348             if (!isShade()) {
2349                 checkWorkspacePosition(oldGeometry);
2350             }
2351             Q_EMIT geometryShapeChanged(this, oldGeometry);
2352         });
2353         connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::sizeChanged,
2354                 this, &AbstractClient::updateDecorationInputShape);
2355     }
2356     setDecoration(decoration);
2357     moveResize(QRect(oldGeometry.topLeft(), clientSizeToFrameSize(clientSize())));
2358 
2359     Q_EMIT geometryShapeChanged(this, oldGeometry);
2360 }
2361 
destroyDecoration()2362 void AbstractClient::destroyDecoration()
2363 {
2364     const QSize clientSize = frameSizeToClientSize(moveResizeGeometry().size());
2365     setDecoration(nullptr);
2366     resize(clientSize);
2367 }
2368 
setDecoration(KDecoration2::Decoration * decoration)2369 void AbstractClient::setDecoration(KDecoration2::Decoration *decoration)
2370 {
2371     m_decoration.decoration.reset(decoration);
2372     updateDecorationInputShape();
2373     Q_EMIT decorationChanged();
2374 }
2375 
updateDecorationInputShape()2376 void AbstractClient::updateDecorationInputShape()
2377 {
2378     if (!isDecorated()) {
2379         m_decoration.inputRegion = QRegion();
2380         return;
2381     }
2382 
2383     const QMargins borders = decoration()->borders();
2384     const QMargins resizeBorders = decoration()->resizeOnlyBorders();
2385 
2386     const QRect innerRect = QRect(QPoint(borderLeft(), borderTop()), decoratedClient()->size());
2387     const QRect outerRect = innerRect + borders + resizeBorders;
2388 
2389     m_decoration.inputRegion = QRegion(outerRect) - innerRect;
2390 }
2391 
decorationHasAlpha() const2392 bool AbstractClient::decorationHasAlpha() const
2393 {
2394     if (!isDecorated() || decoration()->isOpaque()) {
2395         // either no decoration or decoration has alpha disabled
2396         return false;
2397     }
2398     return true;
2399 }
2400 
triggerDecorationRepaint()2401 void AbstractClient::triggerDecorationRepaint()
2402 {
2403     if (isDecorated()) {
2404         decoration()->update();
2405     }
2406 }
2407 
layoutDecorationRects(QRect & left,QRect & top,QRect & right,QRect & bottom) const2408 void AbstractClient::layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom) const
2409 {
2410     if (!isDecorated()) {
2411         return;
2412     }
2413     QRect r = decoration()->rect();
2414 
2415     top = QRect(r.x(), r.y(), r.width(), borderTop());
2416     bottom = QRect(r.x(), r.y() + r.height() - borderBottom(),
2417                    r.width(), borderBottom());
2418     left = QRect(r.x(), r.y() + top.height(),
2419                  borderLeft(), r.height() - top.height() - bottom.height());
2420     right = QRect(r.x() + r.width() - borderRight(), r.y() + top.height(),
2421                   borderRight(), r.height() - top.height() - bottom.height());
2422 }
2423 
processDecorationMove(const QPoint & localPos,const QPoint & globalPos)2424 void AbstractClient::processDecorationMove(const QPoint &localPos, const QPoint &globalPos)
2425 {
2426     if (isInteractiveMoveResizePointerButtonDown()) {
2427         handleInteractiveMoveResize(localPos.x(), localPos.y(), globalPos.x(), globalPos.y());
2428         return;
2429     }
2430     // TODO: handle modifiers
2431     Position newmode = mousePosition();
2432     if (newmode != interactiveMoveResizePointerMode()) {
2433         setInteractiveMoveResizePointerMode(newmode);
2434         updateCursor();
2435     }
2436 }
2437 
processDecorationButtonPress(QMouseEvent * event,bool ignoreMenu)2438 bool AbstractClient::processDecorationButtonPress(QMouseEvent *event, bool ignoreMenu)
2439 {
2440     Options::MouseCommand com = Options::MouseNothing;
2441     bool active = isActive();
2442     if (!wantsInput())    // we cannot be active, use it anyway
2443         active = true;
2444 
2445     // check whether it is a double click
2446     if (event->button() == Qt::LeftButton && titlebarPositionUnderMouse()) {
2447         if (m_decoration.doubleClickTimer.isValid()) {
2448             const qint64 interval = m_decoration.doubleClickTimer.elapsed();
2449             m_decoration.doubleClickTimer.invalidate();
2450             if (interval > QGuiApplication::styleHints()->mouseDoubleClickInterval()) {
2451                 m_decoration.doubleClickTimer.start(); // expired -> new first click and pot. init
2452             } else {
2453                 Workspace::self()->performWindowOperation(this, options->operationTitlebarDblClick());
2454                 dontInteractiveMoveResize();
2455                 return false;
2456             }
2457         }
2458          else {
2459             m_decoration.doubleClickTimer.start(); // new first click and pot. init, could be invalidated by release - see below
2460         }
2461     }
2462 
2463     if (event->button() == Qt::LeftButton)
2464         com = active ? options->commandActiveTitlebar1() : options->commandInactiveTitlebar1();
2465     else if (event->button() == Qt::MiddleButton)
2466         com = active ? options->commandActiveTitlebar2() : options->commandInactiveTitlebar2();
2467     else if (event->button() == Qt::RightButton)
2468         com = active ? options->commandActiveTitlebar3() : options->commandInactiveTitlebar3();
2469     if (event->button() == Qt::LeftButton
2470             && com != Options::MouseOperationsMenu // actions where it's not possible to get the matching
2471             && com != Options::MouseMinimize)  // mouse release event
2472     {
2473         setInteractiveMoveResizePointerMode(mousePosition());
2474         setInteractiveMoveResizePointerButtonDown(true);
2475         setInteractiveMoveOffset(event->pos());
2476         setInvertedInteractiveMoveOffset(rect().bottomRight() - interactiveMoveOffset());
2477         setUnrestrictedInteractiveMoveResize(false);
2478         startDelayedInteractiveMoveResize();
2479         updateCursor();
2480     }
2481     // In the new API the decoration may process the menu action to display an inactive tab's menu.
2482     // If the event is unhandled then the core will create one for the active window in the group.
2483     if (!ignoreMenu || com != Options::MouseOperationsMenu)
2484         performMouseCommand(com, event->globalPos());
2485     return !( // Return events that should be passed to the decoration in the new API
2486                com == Options::MouseRaise ||
2487                com == Options::MouseOperationsMenu ||
2488                com == Options::MouseActivateAndRaise ||
2489                com == Options::MouseActivate ||
2490                com == Options::MouseActivateRaiseAndPassClick ||
2491                com == Options::MouseActivateAndPassClick ||
2492                com == Options::MouseNothing);
2493 }
2494 
processDecorationButtonRelease(QMouseEvent * event)2495 void AbstractClient::processDecorationButtonRelease(QMouseEvent *event)
2496 {
2497     if (isDecorated()) {
2498         if (event->isAccepted() || !titlebarPositionUnderMouse()) {
2499             invalidateDecorationDoubleClickTimer(); // click was for the deco and shall not init a doubleclick
2500         }
2501     }
2502 
2503     if (event->buttons() == Qt::NoButton) {
2504         setInteractiveMoveResizePointerButtonDown(false);
2505         stopDelayedInteractiveMoveResize();
2506         if (isInteractiveMoveResize()) {
2507             finishInteractiveMoveResize(false);
2508             setInteractiveMoveResizePointerMode(mousePosition());
2509         }
2510         updateCursor();
2511     }
2512 }
2513 
2514 
startDecorationDoubleClickTimer()2515 void AbstractClient::startDecorationDoubleClickTimer()
2516 {
2517     m_decoration.doubleClickTimer.start();
2518 }
2519 
invalidateDecorationDoubleClickTimer()2520 void AbstractClient::invalidateDecorationDoubleClickTimer()
2521 {
2522     m_decoration.doubleClickTimer.invalidate();
2523 }
2524 
providesContextHelp() const2525 bool AbstractClient::providesContextHelp() const
2526 {
2527     return false;
2528 }
2529 
showContextHelp()2530 void AbstractClient::showContextHelp()
2531 {
2532 }
2533 
decoratedClient() const2534 QPointer<Decoration::DecoratedClientImpl> AbstractClient::decoratedClient() const
2535 {
2536     return m_decoration.client;
2537 }
2538 
setDecoratedClient(QPointer<Decoration::DecoratedClientImpl> client)2539 void AbstractClient::setDecoratedClient(QPointer< Decoration::DecoratedClientImpl > client)
2540 {
2541     m_decoration.client = client;
2542 }
2543 
enterEvent(const QPoint & globalPos)2544 void AbstractClient::enterEvent(const QPoint &globalPos)
2545 {
2546     if (options->isShadeHover()) {
2547         cancelShadeHoverTimer();
2548         startShadeHoverTimer();
2549     }
2550 
2551     if (options->focusPolicy() == Options::ClickToFocus || workspace()->userActionsMenu()->isShown())
2552         return;
2553 
2554     if (options->isAutoRaise() && !isDesktop() &&
2555             !isDock() && workspace()->focusChangeEnabled() &&
2556             globalPos != workspace()->focusMousePosition() &&
2557             workspace()->topClientOnDesktop(VirtualDesktopManager::self()->currentDesktop(),
2558                                             options->isSeparateScreenFocus() ? output() : nullptr) != this) {
2559         startAutoRaise();
2560     }
2561 
2562     if (isDesktop() || isDock())
2563         return;
2564     // for FocusFollowsMouse, change focus only if the mouse has actually been moved, not if the focus
2565     // change came because of window changes (e.g. closing a window) - #92290
2566     if (options->focusPolicy() != Options::FocusFollowsMouse
2567             || globalPos != workspace()->focusMousePosition()) {
2568         workspace()->requestDelayFocus(this);
2569     }
2570 }
2571 
leaveEvent()2572 void AbstractClient::leaveEvent()
2573 {
2574     cancelAutoRaise();
2575     workspace()->cancelDelayFocus();
2576     cancelShadeHoverTimer();
2577     startShadeUnhoverTimer();
2578     // TODO: send hover leave to deco
2579     // TODO: handle Options::FocusStrictlyUnderMouse
2580 }
2581 
iconGeometry() const2582 QRect AbstractClient::iconGeometry() const
2583 {
2584     if (!windowManagementInterface() || !waylandServer()) {
2585         // window management interface is only available if the surface is mapped
2586         return QRect();
2587     }
2588 
2589     int minDistance = INT_MAX;
2590     AbstractClient *candidatePanel = nullptr;
2591     QRect candidateGeom;
2592 
2593     const auto minGeometries = windowManagementInterface()->minimizedGeometries();
2594     for (auto i = minGeometries.constBegin(), end = minGeometries.constEnd(); i != end; ++i) {
2595         AbstractClient *client = waylandServer()->findClient(i.key());
2596         if (!client) {
2597             continue;
2598         }
2599         const int distance = QPoint(client->pos() - pos()).manhattanLength();
2600         if (distance < minDistance) {
2601             minDistance = distance;
2602             candidatePanel = client;
2603             candidateGeom = i.value();
2604         }
2605     }
2606     if (!candidatePanel) {
2607         return QRect();
2608     }
2609     return candidateGeom.translated(candidatePanel->pos());
2610 }
2611 
inputGeometry() const2612 QRect AbstractClient::inputGeometry() const
2613 {
2614     if (isDecorated()) {
2615         return Toplevel::inputGeometry() + decoration()->resizeOnlyBorders();
2616     }
2617     return Toplevel::inputGeometry();
2618 }
2619 
hitTest(const QPoint & point) const2620 bool AbstractClient::hitTest(const QPoint &point) const
2621 {
2622     if (isDecorated()) {
2623         if (m_decoration.inputRegion.contains(mapToFrame(point))) {
2624             return true;
2625         }
2626     }
2627     return Toplevel::hitTest(point);
2628 }
2629 
virtualKeyboardGeometry() const2630 QRect AbstractClient::virtualKeyboardGeometry() const
2631 {
2632     return m_virtualKeyboardGeometry;
2633 }
2634 
setVirtualKeyboardGeometry(const QRect & geo)2635 void AbstractClient::setVirtualKeyboardGeometry(const QRect &geo)
2636 {
2637     // No keyboard anymore
2638     if (geo.isEmpty() && !m_keyboardGeometryRestore.isEmpty()) {
2639         const QRect availableArea = workspace()->clientArea(MaximizeArea, this);
2640         QRect newWindowGeometry = (maximizeMode() & MaximizeHorizontal) ? availableArea : m_keyboardGeometryRestore;
2641         moveResize(newWindowGeometry);
2642         m_keyboardGeometryRestore = QRect();
2643     } else if (geo.isEmpty()) {
2644         return;
2645     // The keyboard has just been opened (rather than resized) save client geometry for a restore
2646     } else if (m_keyboardGeometryRestore.isEmpty()) {
2647         m_keyboardGeometryRestore = moveResizeGeometry();
2648     }
2649 
2650     m_virtualKeyboardGeometry = geo;
2651 
2652     // Don't resize Desktop and fullscreen windows
2653     if (isFullScreen() || isDesktop()) {
2654         return;
2655     }
2656 
2657     if (!geo.intersects(m_keyboardGeometryRestore)) {
2658         return;
2659     }
2660 
2661     const QRect availableArea = workspace()->clientArea(MaximizeArea, this);
2662     QRect newWindowGeometry = (maximizeMode() & MaximizeHorizontal) ? availableArea : m_keyboardGeometryRestore;
2663     newWindowGeometry.moveBottom(geo.top());
2664     newWindowGeometry.setTop(qMax(newWindowGeometry.top(), availableArea.top()));
2665 
2666     moveResize(newWindowGeometry);
2667 }
2668 
keyboardGeometryRestore() const2669 QRect AbstractClient::keyboardGeometryRestore() const
2670 {
2671     return m_keyboardGeometryRestore;
2672 }
2673 
setKeyboardGeometryRestore(const QRect & geom)2674 void AbstractClient::setKeyboardGeometryRestore(const QRect &geom)
2675 {
2676     m_keyboardGeometryRestore = geom;
2677 }
2678 
dockWantsInput() const2679 bool AbstractClient::dockWantsInput() const
2680 {
2681     return false;
2682 }
2683 
setDesktopFileName(QByteArray name)2684 void AbstractClient::setDesktopFileName(QByteArray name)
2685 {
2686     name = rules()->checkDesktopFile(name).toUtf8();
2687     if (name == m_desktopFileName) {
2688         return;
2689     }
2690     m_desktopFileName = name;
2691     updateWindowRules(Rules::DesktopFile);
2692     Q_EMIT desktopFileNameChanged();
2693 }
2694 
iconFromDesktopFile() const2695 QString AbstractClient::iconFromDesktopFile() const
2696 {
2697     return iconFromDesktopFile(QFile::decodeName(m_desktopFileName));
2698 }
2699 
iconFromDesktopFile(const QString & desktopFileName)2700 QString AbstractClient::iconFromDesktopFile(const QString &desktopFileName)
2701 {
2702     if (desktopFileName.isEmpty()) {
2703         return {};
2704     }
2705 
2706     const QString desktopFileNameWithPrefix = desktopFileName + QLatin1String(".desktop");
2707     QString desktopFilePath;
2708 
2709     if (QDir::isAbsolutePath(desktopFileName)) {
2710         if (QFile::exists(desktopFileNameWithPrefix)) {
2711             desktopFilePath = desktopFileNameWithPrefix;
2712         } else {
2713             desktopFilePath = desktopFileName;
2714         }
2715     }
2716 
2717     if (desktopFilePath.isEmpty()) {
2718         desktopFilePath = QStandardPaths::locate(QStandardPaths::ApplicationsLocation,
2719                                                  desktopFileNameWithPrefix);
2720     }
2721     if (desktopFilePath.isEmpty()) {
2722         desktopFilePath = QStandardPaths::locate(QStandardPaths::ApplicationsLocation,
2723                                                  desktopFileName);
2724     }
2725     if (desktopFilePath.isEmpty()) {
2726         return {};
2727     }
2728 
2729     KDesktopFile df(desktopFilePath);
2730     return df.readIcon();
2731 }
2732 
hasApplicationMenu() const2733 bool AbstractClient::hasApplicationMenu() const
2734 {
2735     return ApplicationMenu::self()->applicationMenuEnabled() && !m_applicationMenuServiceName.isEmpty() && !m_applicationMenuObjectPath.isEmpty();
2736 }
2737 
updateApplicationMenuServiceName(const QString & serviceName)2738 void AbstractClient::updateApplicationMenuServiceName(const QString &serviceName)
2739 {
2740     const bool old_hasApplicationMenu = hasApplicationMenu();
2741 
2742     m_applicationMenuServiceName = serviceName;
2743 
2744     const bool new_hasApplicationMenu = hasApplicationMenu();
2745 
2746     Q_EMIT applicationMenuChanged();
2747     if (old_hasApplicationMenu != new_hasApplicationMenu) {
2748         Q_EMIT hasApplicationMenuChanged(new_hasApplicationMenu);
2749     }
2750 }
2751 
updateApplicationMenuObjectPath(const QString & objectPath)2752 void AbstractClient::updateApplicationMenuObjectPath(const QString &objectPath)
2753 {
2754     const bool old_hasApplicationMenu = hasApplicationMenu();
2755 
2756     m_applicationMenuObjectPath = objectPath;
2757 
2758     const bool new_hasApplicationMenu = hasApplicationMenu();
2759 
2760     Q_EMIT applicationMenuChanged();
2761     if (old_hasApplicationMenu != new_hasApplicationMenu) {
2762         Q_EMIT hasApplicationMenuChanged(new_hasApplicationMenu);
2763     }
2764 }
2765 
setApplicationMenuActive(bool applicationMenuActive)2766 void AbstractClient::setApplicationMenuActive(bool applicationMenuActive)
2767 {
2768     if (m_applicationMenuActive != applicationMenuActive) {
2769         m_applicationMenuActive = applicationMenuActive;
2770         Q_EMIT applicationMenuActiveChanged(applicationMenuActive);
2771     }
2772 }
2773 
showApplicationMenu(int actionId)2774 void AbstractClient::showApplicationMenu(int actionId)
2775 {
2776     if (isDecorated()) {
2777         decoration()->showApplicationMenu(actionId);
2778     } else {
2779         // we don't know where the application menu button will be, show it in the top left corner instead
2780         Workspace::self()->showApplicationMenu(QRect(), this, actionId);
2781     }
2782 }
2783 
unresponsive() const2784 bool AbstractClient::unresponsive() const
2785 {
2786     return m_unresponsive;
2787 }
2788 
setUnresponsive(bool unresponsive)2789 void AbstractClient::setUnresponsive(bool unresponsive)
2790 {
2791     if (m_unresponsive != unresponsive) {
2792         m_unresponsive = unresponsive;
2793         Q_EMIT unresponsiveChanged(m_unresponsive);
2794         Q_EMIT captionChanged();
2795     }
2796 }
2797 
shortcutCaptionSuffix() const2798 QString AbstractClient::shortcutCaptionSuffix() const
2799 {
2800     if (shortcut().isEmpty()) {
2801         return QString();
2802     }
2803     return QLatin1String(" {") + shortcut().toString() + QLatin1Char('}');
2804 }
2805 
findClientWithSameCaption() const2806 AbstractClient *AbstractClient::findClientWithSameCaption() const
2807 {
2808     auto fetchNameInternalPredicate = [this](const AbstractClient *cl) {
2809         return (!cl->isSpecialWindow() || cl->isToolbar()) && cl != this && cl->captionNormal() == captionNormal() && cl->captionSuffix() == captionSuffix();
2810     };
2811     return workspace()->findAbstractClient(fetchNameInternalPredicate);
2812 }
2813 
caption() const2814 QString AbstractClient::caption() const
2815 {
2816     QString cap = captionNormal() + captionSuffix();
2817     if (unresponsive()) {
2818         cap += QLatin1String(" ");
2819         cap += i18nc("Application is not responding, appended to window title", "(Not Responding)");
2820     }
2821     return cap;
2822 }
2823 
removeRule(Rules * rule)2824 void AbstractClient::removeRule(Rules* rule)
2825 {
2826     m_rules.remove(rule);
2827 }
2828 
discardTemporaryRules()2829 void AbstractClient::discardTemporaryRules()
2830 {
2831     m_rules.discardTemporary();
2832 }
2833 
evaluateWindowRules()2834 void AbstractClient::evaluateWindowRules()
2835 {
2836     setupWindowRules(true);
2837     applyWindowRules();
2838 }
2839 
2840 /**
2841  * Returns the list of activities the client window is on.
2842  * if it's on all activities, the list will be empty.
2843  * Don't use this, use isOnActivity() and friends (from class Toplevel)
2844  */
activities() const2845 QStringList AbstractClient::activities() const
2846 {
2847     return m_activityList;
2848 }
2849 
2850 /**
2851  * Sets whether the client is on @p activity.
2852  * If you remove it from its last activity, then it's on all activities.
2853  *
2854  * Note: If it was on all activities and you try to remove it from one, nothing will happen;
2855  * I don't think that's an important enough use case to handle here.
2856  */
setOnActivity(const QString & activity,bool enable)2857 void AbstractClient::setOnActivity(const QString &activity, bool enable)
2858 {
2859 #ifdef KWIN_BUILD_ACTIVITIES
2860     if (!Activities::self()) {
2861         return;
2862     }
2863     QStringList newActivitiesList = activities();
2864     if (newActivitiesList.contains(activity) == enable) {
2865         //nothing to do
2866         return;
2867     }
2868     if (enable) {
2869         QStringList allActivities = Activities::self()->all();
2870         if (!allActivities.contains(activity)) {
2871             //bogus ID
2872             return;
2873         }
2874         newActivitiesList.append(activity);
2875     } else {
2876         newActivitiesList.removeOne(activity);
2877     }
2878     setOnActivities(newActivitiesList);
2879 #else
2880     Q_UNUSED(activity)
2881     Q_UNUSED(enable)
2882 #endif
2883 }
2884 
2885 /**
2886  * set exactly which activities this client is on
2887  */
setOnActivities(const QStringList & newActivitiesList)2888 void AbstractClient::setOnActivities(const QStringList &newActivitiesList)
2889 {
2890 #ifdef KWIN_BUILD_ACTIVITIES
2891     if (!Activities::self()) {
2892         return;
2893     }
2894     const auto allActivities = Activities::self()->all();
2895     const auto activityList = [&] {
2896         auto result = rules()->checkActivity(newActivitiesList);
2897 
2898         const auto it = std::remove_if(result.begin(), result.end(), [=](const QString &activity) {
2899             return !allActivities.contains(activity);
2900         });
2901         result.erase(it, result.end());
2902         return result;
2903     }();
2904 
2905     const auto allActivityExplicitlyRequested = activityList.isEmpty() || activityList.contains(Activities::nullUuid());
2906     const auto allActivitiesCovered = activityList.size() > 1 && activityList.size() == allActivities.size();
2907 
2908     if (allActivityExplicitlyRequested || allActivitiesCovered) {
2909         if (!m_activityList.isEmpty()) {
2910             m_activityList.clear();
2911             doSetOnActivities(m_activityList);
2912         }
2913     } else {
2914         if (m_activityList != activityList) {
2915             m_activityList = activityList;
2916             doSetOnActivities(m_activityList);
2917         }
2918     }
2919 
2920     updateActivities(false);
2921 #else
2922     Q_UNUSED(newActivitiesList)
2923 #endif
2924 }
2925 
2926 /**
2927  * if @p all is true, sets on all activities.
2928  * if it's false, sets it to only be on the current activity
2929  */
setOnAllActivities(bool all)2930 void AbstractClient::setOnAllActivities(bool all)
2931 {
2932 #ifdef KWIN_BUILD_ACTIVITIES
2933     if (all == isOnAllActivities()) {
2934         return;
2935     }
2936     if (all) {
2937         setOnActivities(QStringList());
2938     } else {
2939         setOnActivity(Activities::self()->current(), true);
2940     }
2941 #else
2942     Q_UNUSED(on)
2943 #endif
2944 }
2945 
2946 /**
2947  * update after activities changed
2948  */
updateActivities(bool includeTransients)2949 void AbstractClient::updateActivities(bool includeTransients)
2950 {
2951     if (m_activityUpdatesBlocked) {
2952         m_blockedActivityUpdatesRequireTransients |= includeTransients;
2953         return;
2954     }
2955     Q_EMIT activitiesChanged(this);
2956     m_blockedActivityUpdatesRequireTransients = false; // reset
2957     FocusChain::self()->update(this, FocusChain::MakeFirst);
2958     updateWindowRules(Rules::Activity);
2959 }
2960 
blockActivityUpdates(bool b)2961 void AbstractClient::blockActivityUpdates(bool b)
2962 {
2963     if (b) {
2964         ++m_activityUpdatesBlocked;
2965     } else {
2966         Q_ASSERT(m_activityUpdatesBlocked);
2967         --m_activityUpdatesBlocked;
2968         if (!m_activityUpdatesBlocked) {
2969             updateActivities(m_blockedActivityUpdatesRequireTransients);
2970         }
2971     }
2972 }
2973 
checkNoBorder()2974 void AbstractClient::checkNoBorder()
2975 {
2976     setNoBorder(false);
2977 }
2978 
groupTransient() const2979 bool AbstractClient::groupTransient() const
2980 {
2981     return false;
2982 }
2983 
group() const2984 const Group *AbstractClient::group() const
2985 {
2986     return nullptr;
2987 }
2988 
group()2989 Group *AbstractClient::group()
2990 {
2991     return nullptr;
2992 }
2993 
isInternal() const2994 bool AbstractClient::isInternal() const
2995 {
2996     return false;
2997 }
2998 
supportsWindowRules() const2999 bool AbstractClient::supportsWindowRules() const
3000 {
3001     return false;
3002 }
3003 
frameMargins() const3004 QMargins AbstractClient::frameMargins() const
3005 {
3006     return QMargins(borderLeft(), borderTop(), borderRight(), borderBottom());
3007 }
3008 
framePosToClientPos(const QPoint & point) const3009 QPoint AbstractClient::framePosToClientPos(const QPoint &point) const
3010 {
3011     return point + QPoint(borderLeft(), borderTop());
3012 }
3013 
clientPosToFramePos(const QPoint & point) const3014 QPoint AbstractClient::clientPosToFramePos(const QPoint &point) const
3015 {
3016     return point - QPoint(borderLeft(), borderTop());
3017 }
3018 
frameSizeToClientSize(const QSize & size) const3019 QSize AbstractClient::frameSizeToClientSize(const QSize &size) const
3020 {
3021     const int width = size.width() - borderLeft() - borderRight();
3022     const int height = size.height() - borderTop() - borderBottom();
3023     return QSize(width, height);
3024 }
3025 
clientSizeToFrameSize(const QSize & size) const3026 QSize AbstractClient::clientSizeToFrameSize(const QSize &size) const
3027 {
3028     const int width = size.width() + borderLeft() + borderRight();
3029     const int height = size.height() + borderTop() + borderBottom();
3030     return QSize(width, height);
3031 }
3032 
frameRectToClientRect(const QRect & rect) const3033 QRect AbstractClient::frameRectToClientRect(const QRect &rect) const
3034 {
3035     const QPoint position = framePosToClientPos(rect.topLeft());
3036     const QSize size = frameSizeToClientSize(rect.size());
3037     return QRect(position, size);
3038 }
3039 
clientRectToFrameRect(const QRect & rect) const3040 QRect AbstractClient::clientRectToFrameRect(const QRect &rect) const
3041 {
3042     const QPoint position = clientPosToFramePos(rect.topLeft());
3043     const QSize size = clientSizeToFrameSize(rect.size());
3044     return QRect(position, size);
3045 }
3046 
moveResizeGeometry() const3047 QRect AbstractClient::moveResizeGeometry() const
3048 {
3049     return m_moveResizeGeometry;
3050 }
3051 
setMoveResizeGeometry(const QRect & geo)3052 void AbstractClient::setMoveResizeGeometry(const QRect &geo)
3053 {
3054     m_moveResizeGeometry = geo;
3055 }
3056 
move(const QPoint & point)3057 void AbstractClient::move(const QPoint &point)
3058 {
3059     m_moveResizeGeometry.moveTopLeft(point);
3060     moveResizeInternal(m_moveResizeGeometry, MoveResizeMode::Move);
3061 }
3062 
resize(const QSize & size)3063 void AbstractClient::resize(const QSize &size)
3064 {
3065     m_moveResizeGeometry.setSize(size);
3066     moveResizeInternal(m_moveResizeGeometry, MoveResizeMode::Resize);
3067 }
3068 
moveResize(const QRect & rect)3069 void AbstractClient::moveResize(const QRect &rect)
3070 {
3071     m_moveResizeGeometry = rect;
3072     moveResizeInternal(m_moveResizeGeometry, MoveResizeMode::MoveResize);
3073 }
3074 
setElectricBorderMode(QuickTileMode mode)3075 void AbstractClient::setElectricBorderMode(QuickTileMode mode)
3076 {
3077     if (mode != QuickTileMode(QuickTileFlag::Maximize)) {
3078         // sanitize the mode, ie. simplify "invalid" combinations
3079         if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Horizontal))
3080             mode &= ~QuickTileMode(QuickTileFlag::Horizontal);
3081         if ((mode & QuickTileFlag::Vertical) == QuickTileMode(QuickTileFlag::Vertical))
3082             mode &= ~QuickTileMode(QuickTileFlag::Vertical);
3083     }
3084     m_electricMode = mode;
3085 }
3086 
setElectricBorderMaximizing(bool maximizing)3087 void AbstractClient::setElectricBorderMaximizing(bool maximizing)
3088 {
3089     m_electricMaximizing = maximizing;
3090     if (maximizing)
3091         outline()->show(electricBorderMaximizeGeometry(Cursors::self()->mouse()->pos()), moveResizeGeometry());
3092     else
3093         outline()->hide();
3094     elevate(maximizing);
3095 }
3096 
electricBorderMaximizeGeometry(const QPoint & pos) const3097 QRect AbstractClient::electricBorderMaximizeGeometry(const QPoint &pos) const
3098 {
3099     if (electricBorderMode() == QuickTileMode(QuickTileFlag::Maximize)) {
3100         if (maximizeMode() == MaximizeFull)
3101             return geometryRestore();
3102         else
3103             return workspace()->clientArea(MaximizeArea, this, pos);
3104     }
3105 
3106     QRect ret = workspace()->clientArea(MaximizeArea, this, pos);
3107     if (electricBorderMode() & QuickTileFlag::Left)
3108         ret.setRight(ret.left()+ret.width()/2 - 1);
3109     else if (electricBorderMode() & QuickTileFlag::Right)
3110         ret.setLeft(ret.right()-(ret.width()-ret.width()/2) + 1);
3111     if (electricBorderMode() & QuickTileFlag::Top)
3112         ret.setBottom(ret.top()+ret.height()/2 - 1);
3113     else if (electricBorderMode() & QuickTileFlag::Bottom)
3114         ret.setTop(ret.bottom()-(ret.height()-ret.height()/2) + 1);
3115 
3116     return ret;
3117 }
3118 
setQuickTileMode(QuickTileMode mode,bool keyboard)3119 void AbstractClient::setQuickTileMode(QuickTileMode mode, bool keyboard)
3120 {
3121     // Only allow quick tile on a regular window.
3122     if (!isResizable()) {
3123         return;
3124     }
3125 
3126     workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event
3127 
3128     GeometryUpdatesBlocker blocker(this);
3129 
3130     if (mode == QuickTileMode(QuickTileFlag::Maximize)) {
3131         m_quickTileMode = int(QuickTileFlag::None);
3132         if (maximizeMode() == MaximizeFull) {
3133             setMaximize(false, false);
3134         } else {
3135             QRect prev_geom_restore = geometryRestore(); // setMaximize() would set moveResizeGeom as geom_restore
3136             m_quickTileMode = int(QuickTileFlag::Maximize);
3137             setMaximize(true, true);
3138             QRect clientArea = workspace()->clientArea(MaximizeArea, this);
3139             if (moveResizeGeometry().top() != clientArea.top()) {
3140                 QRect r(moveResizeGeometry());
3141                 r.moveTop(clientArea.top());
3142                 moveResize(r);
3143             }
3144             setGeometryRestore(prev_geom_restore);
3145         }
3146         doSetQuickTileMode();
3147         Q_EMIT quickTileModeChanged();
3148         return;
3149     }
3150 
3151     // sanitize the mode, ie. simplify "invalid" combinations
3152     if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Horizontal))
3153         mode &= ~QuickTileMode(QuickTileFlag::Horizontal);
3154     if ((mode & QuickTileFlag::Vertical) == QuickTileMode(QuickTileFlag::Vertical))
3155         mode &= ~QuickTileMode(QuickTileFlag::Vertical);
3156 
3157     setElectricBorderMode(mode); // used by ::electricBorderMaximizeGeometry(.)
3158 
3159     // restore from maximized so that it is possible to tile maximized windows with one hit or by dragging
3160     if (maximizeMode() != MaximizeRestore) {
3161 
3162         if (mode != QuickTileMode(QuickTileFlag::None)) {
3163             m_quickTileMode = int(QuickTileFlag::None); // Temporary, so the maximize code doesn't get all confused
3164 
3165             setMaximize(false, false);
3166 
3167             moveResize(electricBorderMaximizeGeometry(keyboard ? moveResizeGeometry().center() : Cursors::self()->mouse()->pos()));
3168             // Store the mode change
3169             m_quickTileMode = mode;
3170         } else {
3171             m_quickTileMode = mode;
3172             setMaximize(false, false);
3173         }
3174 
3175         doSetQuickTileMode();
3176         Q_EMIT quickTileModeChanged();
3177 
3178         return;
3179     }
3180 
3181     if (mode != QuickTileMode(QuickTileFlag::None)) {
3182         QPoint whichScreen = keyboard ? moveResizeGeometry().center() : Cursors::self()->mouse()->pos();
3183 
3184         // If trying to tile to the side that the window is already tiled to move the window to the next
3185         // screen if it exists, otherwise toggle the mode (set QuickTileFlag::None)
3186         if (quickTileMode() == mode) {
3187             const QVector<AbstractOutput *> outputs = kwinApp()->platform()->enabledOutputs();
3188             const AbstractOutput *currentOutput = output();
3189             const AbstractOutput *nextOutput = currentOutput;
3190 
3191             for (const AbstractOutput *output : outputs) {
3192                 if (output == currentOutput) {
3193                     continue;
3194                 }
3195 
3196                 if (output->geometry().bottom() <= currentOutput->geometry().top()
3197                     || output->geometry().top() >= currentOutput->geometry().bottom()) {
3198                     continue; // not in horizontal line
3199                 }
3200 
3201                 const int x = output->geometry().center().x();
3202                 if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Left)) {
3203                     if (x >= currentOutput->geometry().center().x()
3204                         || (currentOutput != nextOutput && x <= nextOutput->geometry().center().x())) {
3205                         continue; // not left of current or more left then found next
3206                     }
3207                 } else if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Right)) {
3208                     if (x <= currentOutput->geometry().center().x()
3209                         || (currentOutput != nextOutput && x >= nextOutput->geometry().center().x())) {
3210                         continue; // not right of current or more right then found next
3211                     }
3212                 }
3213 
3214                 nextOutput = output;
3215             }
3216 
3217             if (nextOutput == currentOutput) {
3218                 mode = QuickTileFlag::None; // No other screens, toggle tiling
3219             } else {
3220                 // Move to other screen
3221                 moveResize(geometryRestore().translated(nextOutput->geometry().topLeft() - currentOutput->geometry().topLeft()));
3222                 whichScreen = nextOutput->geometry().center();
3223 
3224                 // Swap sides
3225                 if (mode & QuickTileFlag::Horizontal) {
3226                     mode = (~mode & QuickTileFlag::Horizontal) | (mode & QuickTileFlag::Vertical);
3227                 }
3228             }
3229             setElectricBorderMode(mode); // used by ::electricBorderMaximizeGeometry(.)
3230         } else if (quickTileMode() == QuickTileMode(QuickTileFlag::None)) {
3231             // Not coming out of an existing tile, not shifting monitors, we're setting a brand new tile.
3232             // Store geometry first, so we can go out of this tile later.
3233             setGeometryRestore(moveResizeGeometry());
3234         }
3235 
3236         if (mode != QuickTileMode(QuickTileFlag::None)) {
3237             m_quickTileMode = mode;
3238             // Temporary, so the maximize code doesn't get all confused
3239             m_quickTileMode = int(QuickTileFlag::None);
3240             moveResize(electricBorderMaximizeGeometry(whichScreen));
3241         }
3242 
3243         // Store the mode change
3244         m_quickTileMode = mode;
3245     }
3246 
3247     if (mode == QuickTileMode(QuickTileFlag::None)) {
3248         m_quickTileMode = int(QuickTileFlag::None);
3249         // Untiling, so just restore geometry, and we're done.
3250         if (!geometryRestore().isValid()) // invalid if we started maximized and wait for placement
3251             setGeometryRestore(moveResizeGeometry());
3252         moveResize(geometryRestore());
3253         checkWorkspacePosition(); // Just in case it's a different screen
3254     }
3255     doSetQuickTileMode();
3256     Q_EMIT quickTileModeChanged();
3257 }
3258 
doSetQuickTileMode()3259 void AbstractClient::doSetQuickTileMode()
3260 {
3261 }
3262 
sendToOutput(AbstractOutput * newOutput)3263 void AbstractClient::sendToOutput(AbstractOutput *newOutput)
3264 {
3265     newOutput = rules()->checkOutput(newOutput);
3266     if (isActive()) {
3267         workspace()->setActiveOutput(newOutput);
3268         // might impact the layer of a fullscreen window
3269         Q_FOREACH (AbstractClient *cc, workspace()->allClientList()) {
3270             if (cc->isFullScreen() && cc->output() == newOutput) {
3271                 cc->updateLayer();
3272             }
3273         }
3274     }
3275     if (output() == newOutput && !isFullScreen())   // Don't use isOnScreen(), that's true even when only partially
3276         return;
3277 
3278     GeometryUpdatesBlocker blocker(this);
3279 
3280     // operating on the maximized / quicktiled window would leave the old geom_restore behind,
3281     // so we clear the state first
3282     MaximizeMode maxMode = maximizeMode();
3283     QuickTileMode qtMode = quickTileMode();
3284     if (maxMode != MaximizeRestore)
3285         maximize(MaximizeRestore);
3286     if (qtMode != QuickTileMode(QuickTileFlag::None))
3287         setQuickTileMode(QuickTileFlag::None, true);
3288 
3289     QRect oldScreenArea = workspace()->clientArea(MaximizeArea, this);
3290     QRect screenArea = workspace()->clientArea(MaximizeArea, this, newOutput);
3291 
3292     // the window can have its center so that the position correction moves the new center onto
3293     // the old screen, what will tile it where it is. Ie. the screen is not changed
3294     // this happens esp. with electric border quicktiling
3295     if (qtMode != QuickTileMode(QuickTileFlag::None))
3296         keepInArea(oldScreenArea);
3297 
3298     QRect oldGeom = moveResizeGeometry();
3299     QRect newGeom = oldGeom;
3300     // move the window to have the same relative position to the center of the screen
3301     // (i.e. one near the middle of the right edge will also end up near the middle of the right edge)
3302     QPoint center = newGeom.center() - oldScreenArea.center();
3303     center.setX(center.x() * screenArea.width() / oldScreenArea.width());
3304     center.setY(center.y() * screenArea.height() / oldScreenArea.height());
3305     center += screenArea.center();
3306     newGeom.moveCenter(center);
3307     moveResize(newGeom);
3308 
3309     // If the window was inside the old screen area, explicitly make sure its inside also the new screen area.
3310     // Calling checkWorkspacePosition() should ensure that, but when moving to a small screen the window could
3311     // be big enough to overlap outside of the new screen area, making struts from other screens come into effect,
3312     // which could alter the resulting geometry.
3313     if (oldScreenArea.contains(oldGeom)) {
3314         keepInArea(screenArea);
3315     }
3316 
3317     if (isFullScreen()) {
3318         updateGeometryRestoresForFullscreen(newOutput);
3319         checkWorkspacePosition(oldGeom);
3320     } else {
3321         // align geom_restore - checkWorkspacePosition operates on it
3322         setGeometryRestore(moveResizeGeometry());
3323 
3324         checkWorkspacePosition(oldGeom);
3325 
3326         // re-align geom_restore to constrained geometry
3327         setGeometryRestore(moveResizeGeometry());
3328     }
3329     // finally reset special states
3330     // NOTICE that MaximizeRestore/QuickTileFlag::None checks are required.
3331     // eg. setting QuickTileFlag::None would break maximization
3332     if (maxMode != MaximizeRestore)
3333         maximize(maxMode);
3334     if (qtMode != QuickTileMode(QuickTileFlag::None) && qtMode != quickTileMode())
3335         setQuickTileMode(qtMode, true);
3336 
3337     auto tso = workspace()->ensureStackingOrder(transients());
3338     for (auto it = tso.constBegin(), end = tso.constEnd(); it != end; ++it)
3339         (*it)->sendToOutput(newOutput);
3340 }
3341 
updateGeometryRestoresForFullscreen(AbstractOutput * output)3342 void AbstractClient::updateGeometryRestoresForFullscreen(AbstractOutput *output)
3343 {
3344     QRect screenArea = workspace()->clientArea(MaximizeArea, this, output);
3345     QRect newFullScreenGeometryRestore = screenArea;
3346     if (!(maximizeMode() & MaximizeVertical)) {
3347         newFullScreenGeometryRestore.setHeight(geometryRestore().height());
3348     }
3349     if (!(maximizeMode() & MaximizeHorizontal)) {
3350         newFullScreenGeometryRestore.setWidth(geometryRestore().width());
3351     }
3352     newFullScreenGeometryRestore.setSize(newFullScreenGeometryRestore.size().boundedTo(screenArea.size()));
3353     QSize move = (screenArea.size() - newFullScreenGeometryRestore.size()) / 2;
3354     newFullScreenGeometryRestore.translate(move.width(), move.height());
3355 
3356     QRect newGeometryRestore = QRect(screenArea.topLeft(), geometryRestore().size().boundedTo(screenArea.size()));
3357     move = (screenArea.size() - newGeometryRestore.size()) / 2;
3358     newGeometryRestore.translate(move.width(), move.height());
3359 
3360     setFullscreenGeometryRestore(newFullScreenGeometryRestore);
3361     setGeometryRestore(newGeometryRestore);
3362 }
3363 
checkWorkspacePosition(QRect oldGeometry,QRect oldClientGeometry,const VirtualDesktop * oldDesktop)3364 void AbstractClient::checkWorkspacePosition(QRect oldGeometry, QRect oldClientGeometry, const VirtualDesktop *oldDesktop)
3365 {
3366     if (isDock() || isDesktop() || !isPlaceable()) {
3367         return;
3368     }
3369     enum { Left = 0, Top, Right, Bottom };
3370     const int border[4] = { borderLeft(), borderTop(), borderRight(), borderBottom() };
3371     if( !oldGeometry.isValid())
3372         oldGeometry = moveResizeGeometry();
3373     if (!oldClientGeometry.isValid())
3374         oldClientGeometry = oldGeometry.adjusted(border[Left], border[Top], -border[Right], -border[Bottom]);
3375     if (isFullScreen()) {
3376         moveResize(workspace()->clientArea(FullScreenArea, this, fullscreenGeometryRestore().center()));
3377         return;
3378     }
3379 
3380     if (maximizeMode() != MaximizeRestore) {
3381         GeometryUpdatesBlocker block(this);
3382         changeMaximize(false, false, true);   // adjust size
3383         QRect geom = moveResizeGeometry();
3384         const QRect screenArea = workspace()->clientArea(ScreenArea, this, geom.center());
3385         checkOffscreenPosition(&geom, screenArea);
3386         moveResize(geom);
3387         return;
3388     }
3389 
3390     if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) {
3391         moveResize(electricBorderMaximizeGeometry(moveResizeGeometry().center()));
3392         return;
3393     }
3394 
3395     // this can be true only if this window was mapped before KWin
3396     // was started - in such case, don't adjust position to workarea,
3397     // because the window already had its position, and if a window
3398     // with a strut altering the workarea would be managed in initialization
3399     // after this one, this window would be moved
3400     if (!workspace() || workspace()->initializing())
3401         return;
3402 
3403     VirtualDesktop *desktop = !isOnCurrentDesktop() ? desktops().constLast() : VirtualDesktopManager::self()->currentDesktop();
3404     if (!oldDesktop) {
3405         oldDesktop = desktop;
3406     }
3407 
3408     // If the window was touching an edge before but not now move it so it is again.
3409     // Old and new maximums have different starting values so windows on the screen
3410     // edge will move when a new strut is placed on the edge.
3411     QRect oldScreenArea;
3412     if( workspace()->inUpdateClientArea()) {
3413         // we need to find the screen area as it was before the change
3414         oldScreenArea = QRect( 0, 0, workspace()->oldDisplayWidth(), workspace()->oldDisplayHeight());
3415         int distance = INT_MAX;
3416         Q_FOREACH(const QRect &r, workspace()->previousScreenSizes()) {
3417             int d = r.contains( oldGeometry.center()) ? 0 : ( r.center() - oldGeometry.center()).manhattanLength();
3418             if( d < distance ) {
3419                 distance = d;
3420                 oldScreenArea = r;
3421             }
3422         }
3423     } else {
3424         oldScreenArea = workspace()->clientArea(ScreenArea, kwinApp()->platform()->outputAt(oldGeometry.center()), oldDesktop);
3425     }
3426     const QRect oldGeomTall = QRect(oldGeometry.x(), oldScreenArea.y(), oldGeometry.width(), oldScreenArea.height());   // Full screen height
3427     const QRect oldGeomWide = QRect(oldScreenArea.x(), oldGeometry.y(), oldScreenArea.width(), oldGeometry.height());   // Full screen width
3428     int oldTopMax = oldScreenArea.y();
3429     int oldRightMax = oldScreenArea.x() + oldScreenArea.width();
3430     int oldBottomMax = oldScreenArea.y() + oldScreenArea.height();
3431     int oldLeftMax = oldScreenArea.x();
3432     const QRect screenArea = workspace()->clientArea(ScreenArea, this, geometryRestore().center());
3433     int topMax = screenArea.y();
3434     int rightMax = screenArea.x() + screenArea.width();
3435     int bottomMax = screenArea.y() + screenArea.height();
3436     int leftMax = screenArea.x();
3437     QRect newGeom = geometryRestore(); // geometry();
3438     QRect newClientGeom = newGeom.adjusted(border[Left], border[Top], -border[Right], -border[Bottom]);
3439     const QRect newGeomTall = QRect(newGeom.x(), screenArea.y(), newGeom.width(), screenArea.height());   // Full screen height
3440     const QRect newGeomWide = QRect(screenArea.x(), newGeom.y(), screenArea.width(), newGeom.height());   // Full screen width
3441     // Get the max strut point for each side where the window is (E.g. Highest point for
3442     // the bottom struts bounded by the window's left and right sides).
3443 
3444     // These 4 compute old bounds ...
3445     auto moveAreaFunc = workspace()->inUpdateClientArea() ?
3446                                 &Workspace::previousRestrictedMoveArea : //... the restricted areas changed
3447                                 &Workspace::restrictedMoveArea; //... when e.g. active desktop or screen changes
3448 
3449     for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaTop)) {
3450         QRect rect = r & oldGeomTall;
3451         if (!rect.isEmpty())
3452             oldTopMax = qMax(oldTopMax, rect.y() + rect.height());
3453     }
3454     for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaRight)) {
3455         QRect rect = r & oldGeomWide;
3456         if (!rect.isEmpty())
3457             oldRightMax = qMin(oldRightMax, rect.x());
3458     }
3459     for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaBottom)) {
3460         QRect rect = r & oldGeomTall;
3461         if (!rect.isEmpty())
3462             oldBottomMax = qMin(oldBottomMax, rect.y());
3463     }
3464     for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaLeft)) {
3465         QRect rect = r & oldGeomWide;
3466         if (!rect.isEmpty())
3467             oldLeftMax = qMax(oldLeftMax, rect.x() + rect.width());
3468     }
3469 
3470     // These 4 compute new bounds
3471     for (const QRect &r : workspace()->restrictedMoveArea(desktop, StrutAreaTop)) {
3472         QRect rect = r & newGeomTall;
3473         if (!rect.isEmpty())
3474             topMax = qMax(topMax, rect.y() + rect.height());
3475     }
3476     for (const QRect &r : workspace()->restrictedMoveArea(desktop, StrutAreaRight)) {
3477         QRect rect = r & newGeomWide;
3478         if (!rect.isEmpty())
3479             rightMax = qMin(rightMax, rect.x());
3480     }
3481     for (const QRect &r : workspace()->restrictedMoveArea(desktop, StrutAreaBottom)) {
3482         QRect rect = r & newGeomTall;
3483         if (!rect.isEmpty())
3484             bottomMax = qMin(bottomMax, rect.y());
3485     }
3486     for (const QRect &r : workspace()->restrictedMoveArea(desktop, StrutAreaLeft)) {
3487         QRect rect = r & newGeomWide;
3488         if (!rect.isEmpty())
3489             leftMax = qMax(leftMax, rect.x() + rect.width());
3490     }
3491 
3492 
3493     // Check if the sides were inside or touching but are no longer
3494     bool keep[4] = {false, false, false, false};
3495     bool save[4] = {false, false, false, false};
3496     int padding[4] = {0, 0, 0, 0};
3497     if (oldGeometry.x() >= oldLeftMax)
3498         save[Left] = newGeom.x() < leftMax;
3499     if (oldGeometry.x() == oldLeftMax)
3500         keep[Left] = newGeom.x() != leftMax;
3501     else if (oldClientGeometry.x() == oldLeftMax && newClientGeom.x() != leftMax) {
3502         padding[0] = border[Left];
3503         keep[Left] = true;
3504     }
3505     if (oldGeometry.y() >= oldTopMax)
3506         save[Top] = newGeom.y() < topMax;
3507     if (oldGeometry.y() == oldTopMax)
3508         keep[Top] = newGeom.y() != topMax;
3509     else if (oldClientGeometry.y() == oldTopMax && newClientGeom.y() != topMax) {
3510         padding[1] = border[Left];
3511         keep[Top] = true;
3512     }
3513     if (oldGeometry.right() <= oldRightMax - 1)
3514         save[Right] = newGeom.right() > rightMax - 1;
3515     if (oldGeometry.right() == oldRightMax - 1)
3516         keep[Right] = newGeom.right() != rightMax - 1;
3517     else if (oldClientGeometry.right() == oldRightMax - 1 && newClientGeom.right() != rightMax - 1) {
3518         padding[2] = border[Right];
3519         keep[Right] = true;
3520     }
3521     if (oldGeometry.bottom() <= oldBottomMax - 1)
3522         save[Bottom] = newGeom.bottom() > bottomMax - 1;
3523     if (oldGeometry.bottom() == oldBottomMax - 1)
3524         keep[Bottom] = newGeom.bottom() != bottomMax - 1;
3525     else if (oldClientGeometry.bottom() == oldBottomMax - 1 && newClientGeom.bottom() != bottomMax - 1) {
3526         padding[3] = border[Bottom];
3527         keep[Bottom] = true;
3528     }
3529 
3530     // if randomly touches opposing edges, do not favor either
3531     if (keep[Left] && keep[Right]) {
3532         keep[Left] = keep[Right] = false;
3533         padding[0] = padding[2] = 0;
3534     }
3535     if (keep[Top] && keep[Bottom]) {
3536         keep[Top] = keep[Bottom] = false;
3537         padding[1] = padding[3] = 0;
3538     }
3539 
3540     if (save[Left] || keep[Left])
3541         newGeom.moveLeft(qMax(leftMax, screenArea.x()) - padding[0]);
3542     if (padding[0] && screens()->intersecting(newGeom) > 1)
3543         newGeom.moveLeft(newGeom.left() + padding[0]);
3544     if (save[Top] || keep[Top])
3545         newGeom.moveTop(qMax(topMax, screenArea.y()) - padding[1]);
3546     if (padding[1] && screens()->intersecting(newGeom) > 1)
3547         newGeom.moveTop(newGeom.top() + padding[1]);
3548     if (save[Right] || keep[Right])
3549         newGeom.moveRight(qMin(rightMax - 1, screenArea.right()) + padding[2]);
3550     if (padding[2] && screens()->intersecting(newGeom) > 1)
3551         newGeom.moveRight(newGeom.right() - padding[2]);
3552     if (oldGeometry.x() >= oldLeftMax && newGeom.x() < leftMax)
3553         newGeom.setLeft(qMax(leftMax, screenArea.x()));
3554     else if (oldClientGeometry.x() >= oldLeftMax && newGeom.x() + border[Left] < leftMax) {
3555         newGeom.setLeft(qMax(leftMax, screenArea.x()) - border[Left]);
3556         if (screens()->intersecting(newGeom) > 1)
3557             newGeom.setLeft(newGeom.left() + border[Left]);
3558     }
3559     if (save[Bottom] || keep[Bottom])
3560         newGeom.moveBottom(qMin(bottomMax - 1, screenArea.bottom()) + padding[3]);
3561     if (padding[3] && screens()->intersecting(newGeom) > 1)
3562         newGeom.moveBottom(newGeom.bottom() - padding[3]);
3563     if (oldGeometry.y() >= oldTopMax && newGeom.y() < topMax)
3564         newGeom.setTop(qMax(topMax, screenArea.y()));
3565     else if (oldClientGeometry.y() >= oldTopMax && newGeom.y() + border[Top] < topMax) {
3566         newGeom.setTop(qMax(topMax, screenArea.y()) - border[Top]);
3567         if (screens()->intersecting(newGeom) > 1)
3568             newGeom.setTop(newGeom.top() + border[Top]);
3569     }
3570 
3571     checkOffscreenPosition(&newGeom, screenArea);
3572     // Obey size hints. TODO: We really should make sure it stays in the right place
3573     if (!isShade())
3574         newGeom.setSize(constrainFrameSize(newGeom.size()));
3575 
3576     moveResize(newGeom);
3577 }
3578 
checkOffscreenPosition(QRect * geom,const QRect & screenArea)3579 void AbstractClient::checkOffscreenPosition(QRect* geom, const QRect& screenArea)
3580 {
3581     if (geom->left() > screenArea.right()) {
3582         geom->moveLeft(screenArea.right() - screenArea.width()/4);
3583     } else if (geom->right() < screenArea.left()) {
3584         geom->moveRight(screenArea.left() + screenArea.width()/4);
3585     }
3586     if (geom->top() > screenArea.bottom()) {
3587         geom->moveTop(screenArea.bottom() - screenArea.height()/4);
3588     } else if (geom->bottom() < screenArea.top()) {
3589         geom->moveBottom(screenArea.top() + screenArea.width()/4);
3590     }
3591 }
3592 
3593 /**
3594  * Returns the appropriate frame size for the current client size.
3595  *
3596  * This is equivalent to clientSizeToFrameSize(constrainClientSize(clientSize())).
3597  */
adjustedSize() const3598 QSize AbstractClient::adjustedSize() const
3599 {
3600     QSize size = clientSize();
3601     // The client size is unknown until the window is mapped, don't constrain it.
3602     if (!size.isEmpty()) {
3603         size = constrainClientSize(size);
3604     }
3605     return clientSizeToFrameSize(size);
3606 }
3607 
3608 /**
3609  * Constrains the client size @p size according to a set of the window's size hints.
3610  *
3611  * Default implementation applies only minimum and maximum size constraints.
3612  */
constrainClientSize(const QSize & size,SizeMode mode) const3613 QSize AbstractClient::constrainClientSize(const QSize &size, SizeMode mode) const
3614 {
3615     Q_UNUSED(mode)
3616 
3617     int width = size.width();
3618     int height = size.height();
3619 
3620     // When user is resizing the window, the move resize geometry may have negative width or
3621     // height. In which case, we need to set negative dimensions to reasonable values.
3622     if (width < 1) {
3623         width = 1;
3624     }
3625     if (height < 1) {
3626         height = 1;
3627     }
3628 
3629     const QSize minimumSize = minSize();
3630     const QSize maximumSize = maxSize();
3631 
3632     width = qBound(minimumSize.width(), width, maximumSize.width());
3633     height = qBound(minimumSize.height(), height, maximumSize.height());
3634 
3635     return QSize(width, height);
3636 }
3637 
3638 /**
3639  * Constrains the frame size @p size according to a set of the window's size hints.
3640  */
constrainFrameSize(const QSize & size,SizeMode mode) const3641 QSize AbstractClient::constrainFrameSize(const QSize &size, SizeMode mode) const
3642 {
3643     const QSize unconstrainedClientSize = frameSizeToClientSize(size);
3644     const QSize constrainedClientSize = constrainClientSize(unconstrainedClientSize, mode);
3645     return clientSizeToFrameSize(constrainedClientSize);
3646 }
3647 
3648 /**
3649  * Returns @c true if the AbstractClient can be shown in full screen mode; otherwise @c false.
3650  *
3651  * Default implementation returns @c false.
3652  */
isFullScreenable() const3653 bool AbstractClient::isFullScreenable() const
3654 {
3655     return false;
3656 }
3657 
3658 /**
3659  * Returns @c true if the AbstractClient is currently being shown in full screen mode; otherwise @c false.
3660  *
3661  * A client in full screen mode occupies the entire screen with no window frame around it.
3662  *
3663  * Default implementation returns @c false.
3664  */
isFullScreen() const3665 bool AbstractClient::isFullScreen() const
3666 {
3667     return false;
3668 }
3669 
isRequestedFullScreen() const3670 bool AbstractClient::isRequestedFullScreen() const
3671 {
3672     return isFullScreen();
3673 }
3674 
3675 /**
3676  * Returns whether requests initiated by the user to enter or leave full screen mode are honored.
3677  *
3678  * Default implementation returns @c false.
3679  */
userCanSetFullScreen() const3680 bool AbstractClient::userCanSetFullScreen() const
3681 {
3682     return false;
3683 }
3684 
3685 /**
3686  * Asks the AbstractClient to enter or leave full screen mode.
3687  *
3688  * Default implementation does nothing.
3689  *
3690  * @param set @c true if the AbstractClient has to be shown in full screen mode, otherwise @c false
3691  * @param user @c true if the request is initiated by the user, otherwise @c false
3692  */
setFullScreen(bool set,bool user)3693 void AbstractClient::setFullScreen(bool set, bool user)
3694 {
3695     Q_UNUSED(set)
3696     Q_UNUSED(user)
3697     qCWarning(KWIN_CORE, "%s doesn't support setting fullscreen state", metaObject()->className());
3698 }
3699 
3700 /**
3701  * Returns @c true if the AbstractClient can be minimized; otherwise @c false.
3702  *
3703  * Default implementation returns @c false.
3704  */
isMinimizable() const3705 bool AbstractClient::isMinimizable() const
3706 {
3707     return false;
3708 }
3709 
3710 /**
3711  * Returns @c true if the AbstractClient can be maximized; otherwise @c false.
3712  *
3713  * Default implementation returns @c false.
3714  */
isMaximizable() const3715 bool AbstractClient::isMaximizable() const
3716 {
3717     return false;
3718 }
3719 
3720 /**
3721  * Returns the currently applied maximize mode.
3722  *
3723  * Default implementation returns MaximizeRestore.
3724  */
maximizeMode() const3725 MaximizeMode AbstractClient::maximizeMode() const
3726 {
3727     return MaximizeRestore;
3728 }
3729 
3730 /**
3731  * Returns the last requested maximize mode.
3732  *
3733  * On X11, this method always matches maximizeMode(). On Wayland, it is asynchronous.
3734  *
3735  * Default implementation matches maximizeMode().
3736  */
requestedMaximizeMode() const3737 MaximizeMode AbstractClient::requestedMaximizeMode() const
3738 {
3739     return maximizeMode();
3740 }
3741 
3742 /**
3743  * Returns the geometry of the AbstractClient before it was maximized or quick tiled.
3744  */
geometryRestore() const3745 QRect AbstractClient::geometryRestore() const
3746 {
3747     return m_maximizeGeometryRestore;
3748 }
3749 
3750 /**
3751  * Sets the geometry of the AbstractClient before it was maximized or quick tiled to @p rect.
3752  */
setGeometryRestore(const QRect & rect)3753 void AbstractClient::setGeometryRestore(const QRect &rect)
3754 {
3755     m_maximizeGeometryRestore = rect;
3756 }
3757 
3758 /**
3759  * Toggles the maximized state along specified dimensions @p horizontal and @p vertical.
3760  *
3761  * If @p adjust is @c true, only frame geometry will be updated to match requestedMaximizeMode().
3762  *
3763  * Default implementation does nothing.
3764  */
changeMaximize(bool horizontal,bool vertical,bool adjust)3765 void AbstractClient::changeMaximize(bool horizontal, bool vertical, bool adjust)
3766 {
3767     Q_UNUSED(horizontal)
3768     Q_UNUSED(vertical)
3769     Q_UNUSED(adjust)
3770     qCWarning(KWIN_CORE, "%s doesn't support setting maximized state", metaObject()->className());
3771 }
3772 
updateDecoration(bool check_workspace_pos,bool force)3773 void AbstractClient::updateDecoration(bool check_workspace_pos, bool force)
3774 {
3775     Q_UNUSED(check_workspace_pos)
3776     Q_UNUSED(force)
3777     qCWarning(KWIN_CORE, "%s doesn't support server side decorations", metaObject()->className());
3778 }
3779 
noBorder() const3780 bool AbstractClient::noBorder() const
3781 {
3782     return true;
3783 }
3784 
userCanSetNoBorder() const3785 bool AbstractClient::userCanSetNoBorder() const
3786 {
3787     return false;
3788 }
3789 
setNoBorder(bool set)3790 void AbstractClient::setNoBorder(bool set)
3791 {
3792     Q_UNUSED(set)
3793     qCWarning(KWIN_CORE, "%s doesn't support setting decorations", metaObject()->className());
3794 }
3795 
showOnScreenEdge()3796 void AbstractClient::showOnScreenEdge()
3797 {
3798     qCWarning(KWIN_CORE, "%s doesn't support screen edge activation", metaObject()->className());
3799 }
3800 
isPlaceable() const3801 bool AbstractClient::isPlaceable() const
3802 {
3803     return true;
3804 }
3805 
fullscreenGeometryRestore() const3806 QRect AbstractClient::fullscreenGeometryRestore() const
3807 {
3808     return m_fullscreenGeometryRestore;
3809 }
3810 
setFullscreenGeometryRestore(const QRect & geom)3811 void AbstractClient::setFullscreenGeometryRestore(const QRect &geom)
3812 {
3813     m_fullscreenGeometryRestore = geom;
3814 }
3815 
cleanTabBox()3816 void AbstractClient::cleanTabBox()
3817 {
3818 #ifdef KWIN_BUILD_TABBOX
3819     TabBox::TabBox *tabBox = TabBox::TabBox::self();
3820     if (tabBox && tabBox->isDisplayed() && tabBox->currentClient() == this) {
3821         tabBox->nextPrev(true);
3822     }
3823 #endif
3824 }
3825 
wantsShadowToBeRendered() const3826 bool AbstractClient::wantsShadowToBeRendered() const
3827 {
3828     return !isFullScreen() && maximizeMode() != MaximizeFull;
3829 }
3830 
3831 }
3832