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 ¤tGlobalCursor)
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