File indexing completed on 2024-04-28 05:30:36

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
0006     SPDX-FileCopyrightText: 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 #include "window.h"
0011 
0012 #if KWIN_BUILD_ACTIVITIES
0013 #include "activities.h"
0014 #endif
0015 #include "appmenu.h"
0016 #include "client_machine.h"
0017 #include "compositor.h"
0018 #include "core/output.h"
0019 #include "decorations/decoratedclient.h"
0020 #include "decorations/decorationpalette.h"
0021 #include "focuschain.h"
0022 #include "input.h"
0023 #include "outline.h"
0024 #include "placement.h"
0025 #include "scene/windowitem.h"
0026 #include "scene/workspacescene.h"
0027 #include "screenedge.h"
0028 #include "shadow.h"
0029 #if KWIN_BUILD_TABBOX
0030 #include "tabbox/tabbox.h"
0031 #endif
0032 #include "tiles/tilemanager.h"
0033 #include "useractions.h"
0034 #include "virtualdesktops.h"
0035 #include "wayland/output.h"
0036 #include "wayland/plasmawindowmanagement.h"
0037 #include "wayland/surface.h"
0038 #include "wayland_server.h"
0039 #include "workspace.h"
0040 
0041 #include <KDecoration2/DecoratedClient>
0042 #include <KDecoration2/Decoration>
0043 #include <KDesktopFile>
0044 
0045 #include <QDebug>
0046 #include <QDir>
0047 #include <QMouseEvent>
0048 #include <QStyleHints>
0049 
0050 namespace KWin
0051 {
0052 
0053 static inline int sign(int v)
0054 {
0055     return (v > 0) - (v < 0);
0056 }
0057 
0058 QHash<QString, std::weak_ptr<Decoration::DecorationPalette>> Window::s_palettes;
0059 std::shared_ptr<Decoration::DecorationPalette> Window::s_defaultPalette;
0060 
0061 Window::Window()
0062     : m_output(workspace()->activeOutput())
0063     , ready_for_painting(false)
0064     , m_internalId(QUuid::createUuid())
0065     , m_clientMachine(new ClientMachine(this))
0066     , m_skipCloseAnimation(false)
0067     , m_colorScheme(QStringLiteral("kdeglobals"))
0068     , m_moveResizeOutput(workspace()->activeOutput())
0069 {
0070     connect(this, &Window::bufferGeometryChanged, this, &Window::inputTransformationChanged);
0071 
0072     connect(this, &Window::interactiveMoveResizeStarted, this, &Window::moveResizedChanged);
0073     connect(this, &Window::interactiveMoveResizeFinished, this, &Window::moveResizedChanged);
0074 
0075     connect(this, &Window::windowShown, this, &Window::hiddenChanged);
0076     connect(this, &Window::windowHidden, this, &Window::hiddenChanged);
0077 
0078     connect(this, &Window::paletteChanged, this, &Window::triggerDecorationRepaint);
0079 
0080     // If the user manually moved the window, don't restore it after the keyboard closes
0081     connect(this, &Window::interactiveMoveResizeFinished, this, [this]() {
0082         m_keyboardGeometryRestore = QRectF();
0083     });
0084     connect(this, &Window::maximizedChanged, this, [this]() {
0085         m_keyboardGeometryRestore = QRectF();
0086     });
0087     connect(this, &Window::fullScreenChanged, this, [this]() {
0088         m_keyboardGeometryRestore = QRectF();
0089     });
0090 
0091     // replace on-screen-display on size changes
0092     connect(this, &Window::frameGeometryChanged, this, [this](const QRectF &old) {
0093         if (isOnScreenDisplay() && !frameGeometry().isEmpty() && old.size() != frameGeometry().size() && isPlaceable()) {
0094             GeometryUpdatesBlocker blocker(this);
0095             workspace()->placement()->place(this, workspace()->clientArea(PlacementArea, this, workspace()->activeOutput()));
0096         }
0097     });
0098 
0099     connect(Workspace::self()->applicationMenu(), &ApplicationMenu::applicationMenuEnabledChanged, this, [this] {
0100         Q_EMIT hasApplicationMenuChanged(hasApplicationMenu());
0101     });
0102     connect(&m_offscreenFramecallbackTimer, &QTimer::timeout, this, &Window::maybeSendFrameCallback);
0103 }
0104 
0105 Window::~Window()
0106 {
0107     if (m_tile) {
0108         m_tile->removeWindow(this);
0109     }
0110     Q_ASSERT(m_blockGeometryUpdates == 0);
0111 }
0112 
0113 void Window::ref()
0114 {
0115     ++m_refCount;
0116 }
0117 
0118 void Window::unref()
0119 {
0120     --m_refCount;
0121     if (m_refCount) {
0122         return;
0123     }
0124     if (m_deleted) {
0125         workspace()->removeDeleted(this);
0126     }
0127     delete this;
0128 }
0129 
0130 QDebug operator<<(QDebug debug, const Window *window)
0131 {
0132     QDebugStateSaver saver(debug);
0133     debug.nospace();
0134     if (window) {
0135         debug << window->metaObject()->className() << '(' << static_cast<const void *>(window);
0136         if (const SurfaceInterface *surface = window->surface()) {
0137             debug << ", surface=" << surface;
0138         }
0139         if (window->isClient()) {
0140             if (!window->isPopupWindow()) {
0141                 debug << ", caption=" << window->caption();
0142             }
0143             if (window->transientFor()) {
0144                 debug << ", transientFor=" << window->transientFor();
0145             }
0146         }
0147         if (debug.verbosity() > 2) {
0148             debug << ", frameGeometry=" << window->frameGeometry();
0149             debug << ", resourceName=" << window->resourceName();
0150             debug << ", resourceClass=" << window->resourceClass();
0151         }
0152         debug << ')';
0153     } else {
0154         debug << "Window(0x0)";
0155     }
0156     return debug;
0157 }
0158 
0159 QRectF Window::visibleGeometry() const
0160 {
0161     if (const WindowItem *item = windowItem()) {
0162         return item->mapToGlobal(item->boundingRect());
0163     }
0164     return QRectF();
0165 }
0166 
0167 /**
0168  * Returns client machine for this window,
0169  * taken either from its window or from the leader window.
0170  */
0171 QString Window::wmClientMachine(bool use_localhost) const
0172 {
0173     if (!m_clientMachine) {
0174         // this should never happen
0175         return QString();
0176     }
0177     if (use_localhost && m_clientMachine->isLocal()) {
0178         // special name for the local machine (localhost)
0179         return ClientMachine::localhost();
0180     }
0181     return m_clientMachine->hostName();
0182 }
0183 
0184 void Window::setResourceClass(const QString &name, const QString &className)
0185 {
0186     resource_name = name;
0187     resource_class = className;
0188     Q_EMIT windowClassChanged();
0189 }
0190 
0191 qreal Window::opacity() const
0192 {
0193     return m_opacity;
0194 }
0195 
0196 void Window::setOpacity(qreal opacity)
0197 {
0198     opacity = std::clamp(opacity, 0.0, 1.0);
0199     if (m_opacity == opacity) {
0200         return;
0201     }
0202     const qreal oldOpacity = m_opacity;
0203     m_opacity = opacity;
0204     Q_EMIT opacityChanged(this, oldOpacity);
0205 }
0206 
0207 bool Window::setupCompositing()
0208 {
0209     WorkspaceScene *scene = Compositor::self()->scene();
0210     if (!scene) {
0211         return false;
0212     }
0213 
0214     m_windowItem = createItem(scene);
0215     m_windowItem->setParentItem(scene->containerItem());
0216 
0217     connect(windowItem(), &WindowItem::positionChanged, this, &Window::visibleGeometryChanged);
0218     connect(windowItem(), &WindowItem::boundingRectChanged, this, &Window::visibleGeometryChanged);
0219 
0220     return true;
0221 }
0222 
0223 void Window::finishCompositing()
0224 {
0225     m_windowItem.reset();
0226 }
0227 
0228 void Window::setReadyForPainting()
0229 {
0230     if (!ready_for_painting) {
0231         ready_for_painting = true;
0232         Q_EMIT readyForPaintingChanged();
0233     }
0234 }
0235 
0236 Output *Window::output() const
0237 {
0238     return m_output;
0239 }
0240 
0241 void Window::setOutput(Output *output)
0242 {
0243     if (m_output != output) {
0244         m_output = output;
0245         Q_EMIT outputChanged();
0246     }
0247 }
0248 
0249 bool Window::isOnActiveOutput() const
0250 {
0251     return isOnOutput(workspace()->activeOutput());
0252 }
0253 
0254 bool Window::isOnOutput(Output *output) const
0255 {
0256     return output->geometry().intersects(frameGeometry().toRect());
0257 }
0258 
0259 Shadow *Window::shadow() const
0260 {
0261     return m_shadow.get();
0262 }
0263 
0264 void Window::updateShadow()
0265 {
0266     if (m_shadow) {
0267         if (!m_shadow->updateShadow()) {
0268             m_shadow.reset();
0269         }
0270         Q_EMIT shadowChanged();
0271     } else {
0272         m_shadow = Shadow::createShadow(this);
0273         if (m_shadow) {
0274             Q_EMIT shadowChanged();
0275         }
0276     }
0277 }
0278 
0279 EffectWindow *Window::effectWindow()
0280 {
0281     return m_windowItem ? m_windowItem->effectWindow() : nullptr;
0282 }
0283 
0284 const EffectWindow *Window::effectWindow() const
0285 {
0286     return m_windowItem ? m_windowItem->effectWindow() : nullptr;
0287 }
0288 
0289 SurfaceItem *Window::surfaceItem() const
0290 {
0291     if (m_windowItem) {
0292         return m_windowItem->surfaceItem();
0293     }
0294     return nullptr;
0295 }
0296 
0297 bool Window::wantsShadowToBeRendered() const
0298 {
0299     return !isFullScreen() && maximizeMode() != MaximizeFull;
0300 }
0301 
0302 bool Window::isClient() const
0303 {
0304     return false;
0305 }
0306 
0307 bool Window::isUnmanaged() const
0308 {
0309     return false;
0310 }
0311 
0312 void Window::elevate(bool elevate)
0313 {
0314     if (m_windowItem) {
0315         if (elevate) {
0316             m_windowItem->elevate();
0317         } else {
0318             m_windowItem->deelevate();
0319         }
0320     }
0321 }
0322 
0323 pid_t Window::pid() const
0324 {
0325     return -1;
0326 }
0327 
0328 bool Window::skipsCloseAnimation() const
0329 {
0330     return m_skipCloseAnimation;
0331 }
0332 
0333 void Window::setSkipCloseAnimation(bool set)
0334 {
0335     if (set == m_skipCloseAnimation) {
0336         return;
0337     }
0338     m_skipCloseAnimation = set;
0339     Q_EMIT skipCloseAnimationChanged();
0340 }
0341 
0342 SurfaceInterface *Window::surface() const
0343 {
0344     return m_surface;
0345 }
0346 
0347 void Window::setSurface(SurfaceInterface *surface)
0348 {
0349     if (m_surface == surface) {
0350         return;
0351     }
0352     m_surface = surface;
0353     Q_EMIT surfaceChanged();
0354 }
0355 
0356 int Window::stackingOrder() const
0357 {
0358     return m_stackingOrder;
0359 }
0360 
0361 void Window::setStackingOrder(int order)
0362 {
0363     if (m_stackingOrder != order) {
0364         m_stackingOrder = order;
0365         Q_EMIT stackingOrderChanged();
0366     }
0367 }
0368 
0369 QString Window::windowRole() const
0370 {
0371     return QString();
0372 }
0373 
0374 QMatrix4x4 Window::inputTransformation() const
0375 {
0376     QMatrix4x4 m;
0377     m.translate(-m_bufferGeometry.x(), -m_bufferGeometry.y());
0378     return m;
0379 }
0380 
0381 bool Window::hitTest(const QPointF &point) const
0382 {
0383     if (isDecorated()) {
0384         if (m_decoration.inputRegion.contains(flooredPoint(mapToFrame(point)))) {
0385             return true;
0386         }
0387     }
0388     if (m_surface && m_surface->isMapped()) {
0389         return m_surface->inputSurfaceAt(mapToLocal(point));
0390     }
0391     return exclusiveContains(m_bufferGeometry, point);
0392 }
0393 
0394 QPointF Window::mapToFrame(const QPointF &point) const
0395 {
0396     return point - frameGeometry().topLeft();
0397 }
0398 
0399 QPointF Window::mapToLocal(const QPointF &point) const
0400 {
0401     return point - bufferGeometry().topLeft();
0402 }
0403 
0404 QPointF Window::mapFromLocal(const QPointF &point) const
0405 {
0406     return point + bufferGeometry().topLeft();
0407 }
0408 
0409 bool Window::isLocalhost() const
0410 {
0411     if (!m_clientMachine) {
0412         return true;
0413     }
0414     return m_clientMachine->isLocal();
0415 }
0416 
0417 QMargins Window::frameMargins() const
0418 {
0419     return QMargins(borderLeft(), borderTop(), borderRight(), borderBottom());
0420 }
0421 
0422 void Window::updateMouseGrab()
0423 {
0424 }
0425 
0426 bool Window::belongToSameApplication(const Window *c1, const Window *c2, SameApplicationChecks checks)
0427 {
0428     return c1->belongsToSameApplication(c2, checks);
0429 }
0430 
0431 xcb_timestamp_t Window::userTime() const
0432 {
0433     return XCB_TIME_CURRENT_TIME;
0434 }
0435 
0436 void Window::setSkipSwitcher(bool set)
0437 {
0438     set = rules()->checkSkipSwitcher(set);
0439     if (set == skipSwitcher()) {
0440         return;
0441     }
0442     m_skipSwitcher = set;
0443     doSetSkipSwitcher();
0444     updateWindowRules(Rules::SkipSwitcher);
0445     Q_EMIT skipSwitcherChanged();
0446 }
0447 
0448 void Window::setSkipPager(bool b)
0449 {
0450     b = rules()->checkSkipPager(b);
0451     if (b == skipPager()) {
0452         return;
0453     }
0454     m_skipPager = b;
0455     doSetSkipPager();
0456     updateWindowRules(Rules::SkipPager);
0457     Q_EMIT skipPagerChanged();
0458 }
0459 
0460 void Window::doSetSkipPager()
0461 {
0462 }
0463 
0464 void Window::setSkipTaskbar(bool b)
0465 {
0466     int was_wants_tab_focus = wantsTabFocus();
0467     if (b == skipTaskbar()) {
0468         return;
0469     }
0470     m_skipTaskbar = b;
0471     doSetSkipTaskbar();
0472     updateWindowRules(Rules::SkipTaskbar);
0473     if (was_wants_tab_focus != wantsTabFocus()) {
0474         Workspace::self()->focusChain()->update(this, isActive() ? FocusChain::MakeFirst : FocusChain::Update);
0475     }
0476     Q_EMIT skipTaskbarChanged();
0477 }
0478 
0479 void Window::setOriginalSkipTaskbar(bool b)
0480 {
0481     m_originalSkipTaskbar = rules()->checkSkipTaskbar(b);
0482     setSkipTaskbar(m_originalSkipTaskbar);
0483 }
0484 
0485 void Window::doSetSkipTaskbar()
0486 {
0487 }
0488 
0489 void Window::doSetSkipSwitcher()
0490 {
0491 }
0492 
0493 void Window::setIcon(const QIcon &icon)
0494 {
0495     m_icon = icon;
0496     Q_EMIT iconChanged();
0497 }
0498 
0499 void Window::setActive(bool act)
0500 {
0501     if (isDeleted()) {
0502         return;
0503     }
0504     if (m_active == act) {
0505         return;
0506     }
0507     m_active = act;
0508     const int ruledOpacity = m_active
0509         ? rules()->checkOpacityActive(qRound(opacity() * 100.0))
0510         : rules()->checkOpacityInactive(qRound(opacity() * 100.0));
0511     setOpacity(ruledOpacity / 100.0);
0512     workspace()->setActiveWindow(act ? this : nullptr);
0513 
0514     if (!m_active) {
0515         cancelAutoRaise();
0516     }
0517 
0518     if (!m_active && shadeMode() == ShadeActivated) {
0519         setShade(ShadeNormal);
0520     }
0521 
0522     StackingUpdatesBlocker blocker(workspace());
0523     updateLayer(); // active windows may get different layer
0524     auto mainwindows = mainWindows();
0525     for (auto it = mainwindows.constBegin(); it != mainwindows.constEnd(); ++it) {
0526         if ((*it)->isFullScreen()) { // fullscreens go high even if their transient is active
0527             (*it)->updateLayer();
0528         }
0529     }
0530 
0531     doSetActive();
0532     Q_EMIT activeChanged();
0533     updateMouseGrab();
0534 }
0535 
0536 void Window::doSetActive()
0537 {
0538 }
0539 
0540 bool Window::isDeleted() const
0541 {
0542     return m_deleted;
0543 }
0544 
0545 void Window::markAsDeleted()
0546 {
0547     Q_ASSERT(!m_deleted);
0548     m_deleted = true;
0549     workspace()->addDeleted(this);
0550 }
0551 
0552 Layer Window::layer() const
0553 {
0554     if (m_layer == UnknownLayer) {
0555         const_cast<Window *>(this)->m_layer = rules()->checkLayer(belongsToLayer());
0556     }
0557     return m_layer;
0558 }
0559 
0560 void Window::updateLayer()
0561 {
0562     if (isDeleted()) {
0563         return;
0564     }
0565     if (layer() == rules()->checkLayer(belongsToLayer())) {
0566         return;
0567     }
0568     StackingUpdatesBlocker blocker(workspace());
0569     m_layer = UnknownLayer; // invalidate, will be updated when doing restacking
0570     for (auto it = transients().constBegin(), end = transients().constEnd(); it != end; ++it) {
0571         (*it)->updateLayer();
0572     }
0573 }
0574 
0575 Layer Window::belongsToLayer() const
0576 {
0577     if (isUnmanaged() || isInternal()) {
0578         return OverlayLayer;
0579     }
0580     if (isLockScreen() && !waylandServer()) {
0581         return OverlayLayer;
0582     }
0583     if (isInputMethod()) {
0584         return OverlayLayer;
0585     }
0586     if (isLockScreenOverlay() && waylandServer() && waylandServer()->isScreenLocked()) {
0587         return OverlayLayer;
0588     }
0589     if (isDesktop()) {
0590         return DesktopLayer;
0591     }
0592     if (isSplash()) { // no damn annoying splashscreens
0593         return NormalLayer; // getting in the way of everything else
0594     }
0595     if (isDock() || isAppletPopup()) {
0596         return AboveLayer;
0597     }
0598     if (isPopupWindow()) {
0599         return PopupLayer;
0600     }
0601     if (isOnScreenDisplay()) {
0602         return OnScreenDisplayLayer;
0603     }
0604     if (isNotification()) {
0605         return NotificationLayer;
0606     }
0607     if (isCriticalNotification()) {
0608         return CriticalNotificationLayer;
0609     }
0610     if (keepBelow()) {
0611         return BelowLayer;
0612     }
0613     if (isActiveFullScreen()) {
0614         return ActiveLayer;
0615     }
0616     if (keepAbove()) {
0617         return AboveLayer;
0618     }
0619 
0620     return NormalLayer;
0621 }
0622 
0623 bool Window::belongsToDesktop() const
0624 {
0625     return false;
0626 }
0627 
0628 void Window::setKeepAbove(bool b)
0629 {
0630     b = rules()->checkKeepAbove(b);
0631     if (b && !rules()->checkKeepBelow(false)) {
0632         setKeepBelow(false);
0633     }
0634     if (b == keepAbove()) {
0635         return;
0636     }
0637     m_keepAbove = b;
0638     doSetKeepAbove();
0639     updateLayer();
0640     updateWindowRules(Rules::Above);
0641 
0642     Q_EMIT keepAboveChanged(m_keepAbove);
0643 }
0644 
0645 void Window::doSetKeepAbove()
0646 {
0647 }
0648 
0649 void Window::setKeepBelow(bool b)
0650 {
0651     b = rules()->checkKeepBelow(b);
0652     if (b && !rules()->checkKeepAbove(false)) {
0653         setKeepAbove(false);
0654     }
0655     if (b == keepBelow()) {
0656         return;
0657     }
0658     m_keepBelow = b;
0659     doSetKeepBelow();
0660     updateLayer();
0661     updateWindowRules(Rules::Below);
0662 
0663     Q_EMIT keepBelowChanged(m_keepBelow);
0664 }
0665 
0666 void Window::doSetKeepBelow()
0667 {
0668 }
0669 
0670 void Window::startAutoRaise()
0671 {
0672     delete m_autoRaiseTimer;
0673     m_autoRaiseTimer = new QTimer(this);
0674     connect(m_autoRaiseTimer, &QTimer::timeout, this, &Window::autoRaise);
0675     m_autoRaiseTimer->setSingleShot(true);
0676     m_autoRaiseTimer->start(options->autoRaiseInterval());
0677 }
0678 
0679 void Window::cancelAutoRaise()
0680 {
0681     delete m_autoRaiseTimer;
0682     m_autoRaiseTimer = nullptr;
0683 }
0684 
0685 void Window::autoRaise()
0686 {
0687     workspace()->raiseWindow(this);
0688     cancelAutoRaise();
0689 }
0690 
0691 bool Window::isMostRecentlyRaised() const
0692 {
0693     // The last window in the unconstrained stacking order is the most recently raised one.
0694     return workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop(), nullptr, true, false) == this;
0695 }
0696 
0697 bool Window::wantsTabFocus() const
0698 {
0699     return (isNormalWindow() || isDialog() || isAppletPopup()) && wantsInput();
0700 }
0701 
0702 bool Window::isSpecialWindow() const
0703 {
0704     // TODO
0705     return isDesktop() || isDock() || isSplash() || isToolbar() || isNotification() || isOnScreenDisplay() || isCriticalNotification();
0706 }
0707 
0708 void Window::demandAttention(bool set)
0709 {
0710     if (isActive()) {
0711         set = false;
0712     }
0713     if (m_demandsAttention == set) {
0714         return;
0715     }
0716     m_demandsAttention = set;
0717     doSetDemandsAttention();
0718     workspace()->windowAttentionChanged(this, set);
0719     Q_EMIT demandsAttentionChanged();
0720 }
0721 
0722 void Window::doSetDemandsAttention()
0723 {
0724 }
0725 
0726 void Window::setDesktops(QList<VirtualDesktop *> desktops)
0727 {
0728     // on x11 we can have only one desktop at a time
0729     if (kwinApp()->operationMode() == Application::OperationModeX11 && desktops.size() > 1) {
0730         desktops = QList<VirtualDesktop *>({desktops.last()});
0731     }
0732 
0733     desktops = rules()->checkDesktops(desktops);
0734     if (desktops == m_desktops) {
0735         return;
0736     }
0737 
0738     m_desktops = desktops;
0739 
0740     if (windowManagementInterface()) {
0741         if (m_desktops.isEmpty()) {
0742             windowManagementInterface()->setOnAllDesktops(true);
0743         } else {
0744             windowManagementInterface()->setOnAllDesktops(false);
0745             auto currentDesktops = windowManagementInterface()->plasmaVirtualDesktops();
0746             for (auto desktop : std::as_const(m_desktops)) {
0747                 if (!currentDesktops.contains(desktop->id())) {
0748                     windowManagementInterface()->addPlasmaVirtualDesktop(desktop->id());
0749                 } else {
0750                     currentDesktops.removeOne(desktop->id());
0751                 }
0752             }
0753             for (const auto &desktopId : std::as_const(currentDesktops)) {
0754                 windowManagementInterface()->removePlasmaVirtualDesktop(desktopId);
0755             }
0756         }
0757     }
0758 
0759     auto transients_stacking_order = workspace()->ensureStackingOrder(transients());
0760     for (auto it = transients_stacking_order.constBegin(); it != transients_stacking_order.constEnd(); ++it) {
0761         (*it)->setDesktops(desktops);
0762     }
0763 
0764     if (isModal()) // if a modal dialog is moved, move the mainwindow with it as otherwise
0765                    // the (just moved) modal dialog will confusingly return to the mainwindow with
0766                    // the next desktop change
0767     {
0768         const auto windows = mainWindows();
0769         for (Window *other : windows) {
0770             other->setDesktops(desktops);
0771         }
0772     }
0773 
0774     doSetDesktop();
0775 
0776     Workspace::self()->focusChain()->update(this, FocusChain::MakeFirst);
0777     updateWindowRules(Rules::Desktops);
0778 
0779     Q_EMIT desktopsChanged();
0780 }
0781 
0782 void Window::doSetDesktop()
0783 {
0784 }
0785 
0786 void Window::enterDesktop(VirtualDesktop *virtualDesktop)
0787 {
0788     if (m_desktops.contains(virtualDesktop)) {
0789         return;
0790     }
0791     auto desktops = m_desktops;
0792     desktops.append(virtualDesktop);
0793     setDesktops(desktops);
0794 }
0795 
0796 void Window::leaveDesktop(VirtualDesktop *virtualDesktop)
0797 {
0798     QList<VirtualDesktop *> currentDesktops;
0799     if (m_desktops.isEmpty()) {
0800         currentDesktops = VirtualDesktopManager::self()->desktops();
0801     } else {
0802         currentDesktops = m_desktops;
0803     }
0804 
0805     if (!currentDesktops.contains(virtualDesktop)) {
0806         return;
0807     }
0808     auto desktops = currentDesktops;
0809     desktops.removeOne(virtualDesktop);
0810     setDesktops(desktops);
0811 }
0812 
0813 void Window::setOnAllDesktops(bool b)
0814 {
0815     if (b == isOnAllDesktops()) {
0816         return;
0817     }
0818     if (b) {
0819         setDesktops({});
0820     } else {
0821         setDesktops({VirtualDesktopManager::self()->currentDesktop()});
0822     }
0823 }
0824 
0825 QList<VirtualDesktop *> Window::desktops() const
0826 {
0827     return m_desktops;
0828 }
0829 
0830 QStringList Window::desktopIds() const
0831 {
0832     const auto desks = desktops();
0833     QStringList ids;
0834     ids.reserve(desks.count());
0835     std::transform(desks.constBegin(), desks.constEnd(),
0836                    std::back_inserter(ids),
0837                    [](const VirtualDesktop *vd) {
0838                        return vd->id();
0839                    });
0840     return ids;
0841 }
0842 
0843 bool Window::isOnDesktop(VirtualDesktop *desktop) const
0844 {
0845     return isOnAllDesktops() || desktops().contains(desktop);
0846 }
0847 
0848 bool Window::isOnCurrentDesktop() const
0849 {
0850     return isOnDesktop(VirtualDesktopManager::self()->currentDesktop());
0851 }
0852 
0853 ShadeMode Window::shadeMode() const
0854 {
0855     return m_shadeMode;
0856 }
0857 
0858 bool Window::isShadeable() const
0859 {
0860     return false;
0861 }
0862 
0863 void Window::setShade(bool set)
0864 {
0865     set ? setShade(ShadeNormal) : setShade(ShadeNone);
0866 }
0867 
0868 void Window::setShade(ShadeMode mode)
0869 {
0870     if (!isShadeable()) {
0871         return;
0872     }
0873     if (mode == ShadeHover && isInteractiveMove()) {
0874         return; // causes geometry breaks and is probably nasty
0875     }
0876     if (isSpecialWindow() || !isDecorated()) {
0877         mode = ShadeNone;
0878     }
0879 
0880     mode = rules()->checkShade(mode);
0881     if (m_shadeMode == mode) {
0882         return;
0883     }
0884 
0885     const bool wasShade = isShade();
0886     const ShadeMode previousShadeMode = shadeMode();
0887     m_shadeMode = mode;
0888 
0889     if (wasShade == isShade()) {
0890         // Decoration may want to update after e.g. hover-shade changes
0891         Q_EMIT shadeChanged();
0892         return; // No real change in shaded state
0893     }
0894 
0895     Q_ASSERT(isDecorated());
0896     GeometryUpdatesBlocker blocker(this);
0897 
0898     doSetShade(previousShadeMode);
0899     updateWindowRules(Rules::Shade);
0900 
0901     Q_EMIT shadeChanged();
0902 }
0903 
0904 void Window::doSetShade(ShadeMode previousShadeMode)
0905 {
0906 }
0907 
0908 void Window::shadeHover()
0909 {
0910     setShade(ShadeHover);
0911     cancelShadeHoverTimer();
0912 }
0913 
0914 void Window::shadeUnhover()
0915 {
0916     setShade(ShadeNormal);
0917     cancelShadeHoverTimer();
0918 }
0919 
0920 void Window::startShadeHoverTimer()
0921 {
0922     if (!isShade()) {
0923         return;
0924     }
0925     m_shadeHoverTimer = new QTimer(this);
0926     connect(m_shadeHoverTimer, &QTimer::timeout, this, &Window::shadeHover);
0927     m_shadeHoverTimer->setSingleShot(true);
0928     m_shadeHoverTimer->start(options->shadeHoverInterval());
0929 }
0930 
0931 void Window::startShadeUnhoverTimer()
0932 {
0933     if (m_shadeMode == ShadeHover && !isInteractiveMoveResize() && !isInteractiveMoveResizePointerButtonDown()) {
0934         m_shadeHoverTimer = new QTimer(this);
0935         connect(m_shadeHoverTimer, &QTimer::timeout, this, &Window::shadeUnhover);
0936         m_shadeHoverTimer->setSingleShot(true);
0937         m_shadeHoverTimer->start(options->shadeHoverInterval());
0938     }
0939 }
0940 
0941 void Window::cancelShadeHoverTimer()
0942 {
0943     delete m_shadeHoverTimer;
0944     m_shadeHoverTimer = nullptr;
0945 }
0946 
0947 void Window::toggleShade()
0948 {
0949     // If the mode is ShadeHover or ShadeActive, cancel shade too.
0950     setShade(shadeMode() == ShadeNone ? ShadeNormal : ShadeNone);
0951 }
0952 
0953 Qt::Edge Window::titlebarPosition() const
0954 {
0955     // TODO: still needed, remove?
0956     return Qt::TopEdge;
0957 }
0958 
0959 bool Window::titlebarPositionUnderMouse() const
0960 {
0961     if (!isDecorated()) {
0962         return false;
0963     }
0964     const auto sectionUnderMouse = decoration()->sectionUnderMouse();
0965     if (sectionUnderMouse == Qt::TitleBarArea) {
0966         return true;
0967     }
0968     // check other sections based on titlebarPosition
0969     switch (titlebarPosition()) {
0970     case Qt::TopEdge:
0971         return (sectionUnderMouse == Qt::TopLeftSection || sectionUnderMouse == Qt::TopSection || sectionUnderMouse == Qt::TopRightSection);
0972     case Qt::LeftEdge:
0973         return (sectionUnderMouse == Qt::TopLeftSection || sectionUnderMouse == Qt::LeftSection || sectionUnderMouse == Qt::BottomLeftSection);
0974     case Qt::RightEdge:
0975         return (sectionUnderMouse == Qt::BottomRightSection || sectionUnderMouse == Qt::RightSection || sectionUnderMouse == Qt::TopRightSection);
0976     case Qt::BottomEdge:
0977         return (sectionUnderMouse == Qt::BottomLeftSection || sectionUnderMouse == Qt::BottomSection || sectionUnderMouse == Qt::BottomRightSection);
0978     default:
0979         // nothing
0980         return false;
0981     }
0982 }
0983 
0984 void Window::setMinimized(bool set)
0985 {
0986     const bool effectiveSet = rules()->checkMinimize(set);
0987     if (m_minimized == effectiveSet) {
0988         return;
0989     }
0990 
0991     if (effectiveSet && !isMinimizable()) {
0992         return;
0993     }
0994 
0995     m_minimized = effectiveSet;
0996     doMinimize();
0997 
0998     updateWindowRules(Rules::Minimize);
0999     Q_EMIT minimizedChanged();
1000 }
1001 
1002 void Window::doMinimize()
1003 {
1004 }
1005 
1006 QPalette Window::palette()
1007 {
1008     ensurePalette();
1009     return m_palette->palette();
1010 }
1011 
1012 const Decoration::DecorationPalette *Window::decorationPalette()
1013 {
1014     ensurePalette();
1015     return m_palette.get();
1016 }
1017 
1018 QString Window::preferredColorScheme() const
1019 {
1020     return rules()->checkDecoColor(QString());
1021 }
1022 
1023 QString Window::colorScheme() const
1024 {
1025     return m_colorScheme;
1026 }
1027 
1028 void Window::setColorScheme(const QString &colorScheme)
1029 {
1030     QString requestedColorScheme = colorScheme;
1031     if (requestedColorScheme.isEmpty()) {
1032         requestedColorScheme = QStringLiteral("kdeglobals");
1033     }
1034 
1035     if (m_colorScheme == requestedColorScheme) {
1036         return;
1037     }
1038 
1039     m_colorScheme = requestedColorScheme;
1040 
1041     if (m_palette) {
1042         disconnect(m_palette.get(), &Decoration::DecorationPalette::changed, this, &Window::handlePaletteChange);
1043         m_palette.reset();
1044 
1045         // If there already was a palette, re-create it right away
1046         // so the signals for repainting the decoration are emitted.
1047         ensurePalette();
1048     }
1049 
1050     Q_EMIT colorSchemeChanged();
1051 }
1052 
1053 void Window::updateColorScheme()
1054 {
1055     setColorScheme(preferredColorScheme());
1056 }
1057 
1058 void Window::ensurePalette()
1059 {
1060     if (m_palette) {
1061         return;
1062     }
1063 
1064     auto it = s_palettes.find(m_colorScheme);
1065 
1066     if (it == s_palettes.end() || it->expired()) {
1067         m_palette = std::make_shared<Decoration::DecorationPalette>(m_colorScheme);
1068         if (m_palette->isValid()) {
1069             s_palettes[m_colorScheme] = m_palette;
1070         } else {
1071             if (!s_defaultPalette) {
1072                 s_defaultPalette = std::make_shared<Decoration::DecorationPalette>(QStringLiteral("kdeglobals"));
1073                 s_palettes[QStringLiteral("kdeglobals")] = s_defaultPalette;
1074             }
1075 
1076             m_palette = s_defaultPalette;
1077         }
1078 
1079         if (m_colorScheme == QStringLiteral("kdeglobals")) {
1080             s_defaultPalette = m_palette;
1081         }
1082     } else {
1083         m_palette = it->lock();
1084     }
1085 
1086     connect(m_palette.get(), &Decoration::DecorationPalette::changed, this, &Window::handlePaletteChange);
1087 
1088     handlePaletteChange();
1089 }
1090 
1091 void Window::handlePaletteChange()
1092 {
1093     Q_EMIT paletteChanged(palette());
1094 }
1095 
1096 QRectF Window::keepInArea(QRectF geometry, QRectF area, bool partial)
1097 {
1098     if (partial) {
1099         // increase the area so that can have only 100 pixels in the area
1100         const QRectF geometry = moveResizeGeometry();
1101         area.setLeft(std::min(area.left() - geometry.width() + 100, area.left()));
1102         area.setTop(std::min(area.top() - geometry.height() + 100, area.top()));
1103         area.setRight(std::max(area.right() + geometry.width() - 100, area.right()));
1104         area.setBottom(std::max(area.bottom() + geometry.height() - 100, area.bottom()));
1105     }
1106     if (!partial) {
1107         // resize to fit into area
1108         if (area.width() < geometry.width() || area.height() < geometry.height()) {
1109             geometry = resizeWithChecks(geometry, geometry.size().boundedTo(area.size()));
1110         }
1111     }
1112 
1113     if (geometry.right() > area.right() && geometry.width() <= area.width()) {
1114         geometry.moveRight(area.right());
1115     }
1116     if (geometry.bottom() > area.bottom() && geometry.height() <= area.height()) {
1117         geometry.moveBottom(area.bottom());
1118     }
1119 
1120     if (geometry.left() < area.left()) {
1121         geometry.moveLeft(area.left());
1122     }
1123     if (geometry.top() < area.top()) {
1124         geometry.moveTop(area.top());
1125     }
1126     return geometry;
1127 }
1128 
1129 void Window::keepInArea(QRectF area, bool partial)
1130 {
1131     moveResize(keepInArea(moveResizeGeometry(), area, partial));
1132 }
1133 
1134 /**
1135  * Returns the maximum client size, not the maximum frame size.
1136  */
1137 QSizeF Window::maxSize() const
1138 {
1139     return rules()->checkMaxSize(QSize(INT_MAX, INT_MAX));
1140 }
1141 
1142 /**
1143  * Returns the minimum client size, not the minimum frame size.
1144  */
1145 QSizeF Window::minSize() const
1146 {
1147     return rules()->checkMinSize(QSize(0, 0));
1148 }
1149 
1150 void Window::blockGeometryUpdates(bool block)
1151 {
1152     if (block) {
1153         if (m_blockGeometryUpdates == 0) {
1154             m_pendingMoveResizeMode = MoveResizeMode::None;
1155         }
1156         ++m_blockGeometryUpdates;
1157     } else {
1158         if (--m_blockGeometryUpdates == 0) {
1159             if (m_pendingMoveResizeMode != MoveResizeMode::None) {
1160                 moveResizeInternal(moveResizeGeometry(), m_pendingMoveResizeMode);
1161                 m_pendingMoveResizeMode = MoveResizeMode::None;
1162             }
1163         }
1164     }
1165 }
1166 
1167 void Window::maximize(MaximizeMode mode)
1168 {
1169     qCWarning(KWIN_CORE, "%s doesn't support setting maximized state", metaObject()->className());
1170 }
1171 
1172 void Window::setMaximize(bool vertically, bool horizontally)
1173 {
1174     MaximizeMode mode = MaximizeRestore;
1175     if (vertically) {
1176         mode = MaximizeMode(mode | MaximizeVertical);
1177     }
1178     if (horizontally) {
1179         mode = MaximizeMode(mode | MaximizeHorizontal);
1180     }
1181 
1182     maximize(mode);
1183 }
1184 
1185 bool Window::startInteractiveMoveResize()
1186 {
1187     Q_ASSERT(!isInteractiveMoveResize());
1188     Q_ASSERT(QWidget::keyboardGrabber() == nullptr);
1189     Q_ASSERT(QWidget::mouseGrabber() == nullptr);
1190     stopDelayedInteractiveMoveResize();
1191     if (QApplication::activePopupWidget() != nullptr) {
1192         return false; // popups have grab
1193     }
1194     if (isRequestedFullScreen() && (workspace()->outputs().count() < 2 || !isMovableAcrossScreens())) {
1195         return false;
1196     }
1197     if (!doStartInteractiveMoveResize()) {
1198         return false;
1199     }
1200 
1201     invalidateDecorationDoubleClickTimer();
1202 
1203     setInteractiveMoveResize(true);
1204     workspace()->setMoveResizeWindow(this);
1205 
1206     m_interactiveMoveResize.initialGeometry = moveResizeGeometry();
1207     m_interactiveMoveResize.startOutput = moveResizeOutput();
1208     m_interactiveMoveResize.initialMaximizeMode = requestedMaximizeMode();
1209     m_interactiveMoveResize.initialQuickTileMode = quickTileMode();
1210     m_interactiveMoveResize.initialGeometryRestore = geometryRestore();
1211 
1212     if (requestedMaximizeMode() != MaximizeRestore) {
1213         switch (interactiveMoveResizeGravity()) {
1214         case Gravity::Left:
1215         case Gravity::Right:
1216             // Quit maximized horizontally state if the window is resized horizontally.
1217             if (requestedMaximizeMode() & MaximizeHorizontal) {
1218                 QRectF originalGeometry = geometryRestore();
1219                 originalGeometry.setX(moveResizeGeometry().x());
1220                 originalGeometry.setWidth(moveResizeGeometry().width());
1221                 setGeometryRestore(originalGeometry);
1222                 maximize(requestedMaximizeMode() ^ MaximizeHorizontal);
1223             }
1224             break;
1225         case Gravity::Top:
1226         case Gravity::Bottom:
1227             // Quit maximized vertically state if the window is resized vertically.
1228             if (requestedMaximizeMode() & MaximizeVertical) {
1229                 QRectF originalGeometry = geometryRestore();
1230                 originalGeometry.setY(moveResizeGeometry().y());
1231                 originalGeometry.setHeight(moveResizeGeometry().height());
1232                 setGeometryRestore(originalGeometry);
1233                 maximize(requestedMaximizeMode() ^ MaximizeVertical);
1234             }
1235             break;
1236         case Gravity::TopLeft:
1237         case Gravity::BottomLeft:
1238         case Gravity::TopRight:
1239         case Gravity::BottomRight:
1240             // Quit the maximized mode if the window is resized by dragging one of its corners.
1241             setGeometryRestore(moveResizeGeometry());
1242             maximize(MaximizeRestore);
1243             break;
1244         default:
1245             break;
1246         }
1247     }
1248 
1249     if (m_tile && !m_tile->supportsResizeGravity(interactiveMoveResizeGravity())) {
1250         setQuickTileMode(QuickTileFlag::None);
1251     }
1252 
1253     updateElectricGeometryRestore();
1254     checkUnrestrictedInteractiveMoveResize();
1255     Q_EMIT interactiveMoveResizeStarted();
1256     if (workspace()->screenEdges()->isDesktopSwitchingMovingClients()) {
1257         workspace()->screenEdges()->reserveDesktopSwitching(true, Qt::Vertical | Qt::Horizontal);
1258     }
1259     return true;
1260 }
1261 
1262 void Window::finishInteractiveMoveResize(bool cancel)
1263 {
1264     const bool wasMove = isInteractiveMove();
1265     GeometryUpdatesBlocker blocker(this);
1266     leaveInteractiveMoveResize();
1267 
1268     doFinishInteractiveMoveResize();
1269 
1270     if (cancel) {
1271         moveResize(initialInteractiveMoveResizeGeometry());
1272         if (m_interactiveMoveResize.initialMaximizeMode != MaximizeMode::MaximizeRestore) {
1273             setMaximize(m_interactiveMoveResize.initialMaximizeMode & MaximizeMode::MaximizeVertical, m_interactiveMoveResize.initialMaximizeMode & MaximizeMode::MaximizeHorizontal);
1274             setGeometryRestore(m_interactiveMoveResize.initialGeometryRestore);
1275         } else if (m_interactiveMoveResize.initialQuickTileMode) {
1276             setQuickTileMode(m_interactiveMoveResize.initialQuickTileMode, true);
1277             setGeometryRestore(m_interactiveMoveResize.initialGeometryRestore);
1278         }
1279     } else if (moveResizeOutput() != interactiveMoveResizeStartOutput()) {
1280         workspace()->sendWindowToOutput(this, moveResizeOutput()); // checks rule validity
1281         if (isRequestedFullScreen() || requestedMaximizeMode() != MaximizeRestore) {
1282             checkWorkspacePosition();
1283         }
1284     }
1285 
1286     if (isElectricBorderMaximizing()) {
1287         setQuickTileMode(electricBorderMode());
1288         setElectricBorderMaximizing(false);
1289     } else if (wasMove && (input()->modifiersRelevantForGlobalShortcuts() & Qt::ShiftModifier)) {
1290         setQuickTileMode(QuickTileFlag::Custom);
1291     }
1292     setElectricBorderMode(QuickTileMode(QuickTileFlag::None));
1293     workspace()->outline()->hide();
1294 
1295     m_interactiveMoveResize.counter++;
1296     Q_EMIT interactiveMoveResizeFinished();
1297 }
1298 
1299 // This function checks if it actually makes sense to perform a restricted move/resize.
1300 // If e.g. the titlebar is already outside of the workarea, there's no point in performing
1301 // a restricted move resize, because then e.g. resize would also move the window (#74555).
1302 // NOTE: Most of it is duplicated from handleMoveResize().
1303 void Window::checkUnrestrictedInteractiveMoveResize()
1304 {
1305     if (isUnrestrictedInteractiveMoveResize()) {
1306         return;
1307     }
1308     const QRectF &moveResizeGeom = moveResizeGeometry();
1309     QRectF desktopArea = workspace()->clientArea(WorkArea, this, moveResizeGeom.center());
1310     int left_marge, right_marge, top_marge, bottom_marge, titlebar_marge;
1311     // restricted move/resize - keep at least part of the titlebar always visible
1312     // how much must remain visible when moved away in that direction
1313     left_marge = std::min(100. + borderRight(), moveResizeGeom.width());
1314     right_marge = std::min(100. + borderLeft(), moveResizeGeom.width());
1315     // width/height change with opaque resizing, use the initial ones
1316     titlebar_marge = initialInteractiveMoveResizeGeometry().height();
1317     top_marge = borderBottom();
1318     bottom_marge = borderTop();
1319     if (isInteractiveResize()) {
1320         if (moveResizeGeom.bottom() < desktopArea.top() + top_marge) {
1321             setUnrestrictedInteractiveMoveResize(true);
1322         }
1323         if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge) {
1324             setUnrestrictedInteractiveMoveResize(true);
1325         }
1326         if (moveResizeGeom.right() < desktopArea.left() + left_marge) {
1327             setUnrestrictedInteractiveMoveResize(true);
1328         }
1329         if (moveResizeGeom.left() > desktopArea.right() - right_marge) {
1330             setUnrestrictedInteractiveMoveResize(true);
1331         }
1332         if (!isUnrestrictedInteractiveMoveResize() && moveResizeGeom.top() < desktopArea.top()) { // titlebar mustn't go out
1333             setUnrestrictedInteractiveMoveResize(true);
1334         }
1335     }
1336     if (isInteractiveMove()) {
1337         if (moveResizeGeom.bottom() < desktopArea.top() + titlebar_marge) {
1338             setUnrestrictedInteractiveMoveResize(true);
1339         }
1340         // no need to check top_marge, titlebar_marge already handles it
1341         if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge) { // titlebar mustn't go out
1342             setUnrestrictedInteractiveMoveResize(true);
1343         }
1344         if (moveResizeGeom.right() < desktopArea.left() + left_marge) {
1345             setUnrestrictedInteractiveMoveResize(true);
1346         }
1347         if (moveResizeGeom.left() > desktopArea.right() - right_marge) {
1348             setUnrestrictedInteractiveMoveResize(true);
1349         }
1350     }
1351 }
1352 
1353 // When the user pressed mouse on the titlebar, don't activate move immediately,
1354 // since it may be just a click. Activate instead after a delay. Move used to be
1355 // activated only after moving by several pixels, but that looks bad.
1356 void Window::startDelayedInteractiveMoveResize()
1357 {
1358     Q_ASSERT(!m_interactiveMoveResize.delayedTimer);
1359     m_interactiveMoveResize.delayedTimer = new QTimer(this);
1360     m_interactiveMoveResize.delayedTimer->setSingleShot(true);
1361     connect(m_interactiveMoveResize.delayedTimer, &QTimer::timeout, this, [this]() {
1362         Q_ASSERT(isInteractiveMoveResizePointerButtonDown());
1363         if (!startInteractiveMoveResize()) {
1364             setInteractiveMoveResizePointerButtonDown(false);
1365         }
1366         updateCursor();
1367         stopDelayedInteractiveMoveResize();
1368     });
1369     m_interactiveMoveResize.delayedTimer->start(QApplication::startDragTime());
1370 }
1371 
1372 void Window::stopDelayedInteractiveMoveResize()
1373 {
1374     delete m_interactiveMoveResize.delayedTimer;
1375     m_interactiveMoveResize.delayedTimer = nullptr;
1376 }
1377 
1378 void Window::updateInteractiveMoveResize(const QPointF &currentGlobalCursor)
1379 {
1380     handleInteractiveMoveResize(pos(), currentGlobalCursor);
1381 }
1382 
1383 void Window::handleInteractiveMoveResize(const QPointF &local, const QPointF &global)
1384 {
1385     const QRectF oldGeo = moveResizeGeometry();
1386     handleInteractiveMoveResize(local.x(), local.y(), global.x(), global.y());
1387     if (!isRequestedFullScreen() && isInteractiveMove()) {
1388         if (quickTileMode() != QuickTileMode(QuickTileFlag::None) && oldGeo != moveResizeGeometry()) {
1389             GeometryUpdatesBlocker blocker(this);
1390             setQuickTileMode(QuickTileFlag::None);
1391             const QRectF &geom_restore = geometryRestore();
1392             setInteractiveMoveOffset(QPointF(double(interactiveMoveOffset().x()) / double(oldGeo.width()) * double(geom_restore.width()),
1393                                              double(interactiveMoveOffset().y()) / double(oldGeo.height()) * double(geom_restore.height())));
1394             if (rules()->checkMaximize(MaximizeRestore) == MaximizeRestore) {
1395                 setMoveResizeGeometry(geom_restore);
1396             }
1397             handleInteractiveMoveResize(local.x(), local.y(), global.x(), global.y()); // fix position
1398         }
1399 
1400         if (input()->modifiersRelevantForGlobalShortcuts() & Qt::ShiftModifier) {
1401             resetQuickTilingMaximizationZones();
1402             const auto &r = quickTileGeometry(QuickTileFlag::Custom, global);
1403             if (r.isEmpty()) {
1404                 workspace()->outline()->hide();
1405             } else {
1406                 if (!workspace()->outline()->isActive() || workspace()->outline()->geometry() != r.toRect()) {
1407                     workspace()->outline()->show(r.toRect(), moveResizeGeometry().toRect());
1408                 }
1409             }
1410         } else {
1411             if (quickTileMode() == QuickTileMode(QuickTileFlag::None) && isResizable()) {
1412                 checkQuickTilingMaximizationZones(global.x(), global.y());
1413             }
1414             if (!m_electricMaximizing) {
1415                 // Only if we are in an electric maximizing gesture we should keep the outline,
1416                 // otherwise we must make sure it's hidden
1417                 workspace()->outline()->hide();
1418             }
1419         }
1420     }
1421 }
1422 
1423 void Window::handleInteractiveMoveResize(qreal x, qreal y, qreal x_root, qreal y_root)
1424 {
1425     if (isWaitingForInteractiveMoveResizeSync()) {
1426         return; // we're still waiting for the client or the timeout
1427     }
1428 
1429     const Gravity gravity = interactiveMoveResizeGravity();
1430     if ((gravity == Gravity::None && !isMovableAcrossScreens())
1431         || (gravity != Gravity::None && (isShade() || !isResizable()))) {
1432         return;
1433     }
1434 
1435     if (!isInteractiveMoveResize()) {
1436         QPointF p(QPointF(x /* - padding_left*/, y /* - padding_top*/) - interactiveMoveOffset());
1437         if (p.manhattanLength() >= QApplication::startDragDistance()) {
1438             if (!startInteractiveMoveResize()) {
1439                 setInteractiveMoveResizePointerButtonDown(false);
1440                 updateCursor();
1441                 return;
1442             }
1443             updateCursor();
1444         } else {
1445             return;
1446         }
1447     }
1448 
1449     // ShadeHover or ShadeActive, ShadeNormal was already avoided above
1450     if (gravity != Gravity::None && shadeMode() != ShadeNone) {
1451         setShade(ShadeNone);
1452     }
1453 
1454     QPointF globalPos(x_root, y_root);
1455     // these two points limit the geometry rectangle, i.e. if bottomleft resizing is done,
1456     // the bottomleft corner should be at is at (topleft.x(), bottomright().y())
1457     QPointF topleft = globalPos - interactiveMoveOffset();
1458     QPointF bottomright = globalPos + invertedInteractiveMoveOffset();
1459     const QRectF currentMoveResizeGeom = moveResizeGeometry();
1460     QRectF nextMoveResizeGeom = moveResizeGeometry();
1461 
1462     // TODO move whole group when moving its leader or when the leader is not mapped?
1463 
1464     auto titleBarRect = [this](const QRectF &rect, bool &transposed, int &requiredPixels) -> QRectF {
1465         QRectF titleRect = rect;
1466         titleRect.moveTopLeft(QPointF(0, 0));
1467         switch (titlebarPosition()) {
1468         default:
1469         case Qt::TopEdge:
1470             titleRect.setHeight(borderTop());
1471             break;
1472         case Qt::LeftEdge:
1473             titleRect.setWidth(borderLeft());
1474             transposed = true;
1475             break;
1476         case Qt::BottomEdge:
1477             titleRect.setTop(titleRect.bottom() - borderBottom());
1478             break;
1479         case Qt::RightEdge:
1480             titleRect.setLeft(titleRect.right() - borderRight());
1481             transposed = true;
1482             break;
1483         }
1484         // When doing a restricted move we must always keep 100px of the titlebar
1485         // visible to allow the user to be able to move it again.
1486         requiredPixels = std::min(100 * (transposed ? titleRect.width() : titleRect.height()),
1487                                   rect.width() * rect.height());
1488         return titleRect;
1489     };
1490 
1491     if (isInteractiveResize()) {
1492         if (m_tile && m_tile->supportsResizeGravity(gravity)) {
1493             m_tile->resizeFromGravity(gravity, x_root, y_root);
1494             return;
1495         }
1496 
1497         QRectF orig = initialInteractiveMoveResizeGeometry();
1498         SizeMode sizeMode = SizeModeAny;
1499         auto calculateMoveResizeGeom = [&topleft, &bottomright, &orig, &nextMoveResizeGeom, &sizeMode, &gravity]() {
1500             switch (gravity) {
1501             case Gravity::TopLeft:
1502                 nextMoveResizeGeom = QRectF(topleft, orig.bottomRight());
1503                 break;
1504             case Gravity::BottomRight:
1505                 nextMoveResizeGeom = QRectF(orig.topLeft(), bottomright);
1506                 break;
1507             case Gravity::BottomLeft:
1508                 nextMoveResizeGeom = QRectF(QPointF(topleft.x(), orig.y()), QPointF(orig.right(), bottomright.y()));
1509                 break;
1510             case Gravity::TopRight:
1511                 nextMoveResizeGeom = QRectF(QPointF(orig.x(), topleft.y()), QPointF(bottomright.x(), orig.bottom()));
1512                 break;
1513             case Gravity::Top:
1514                 nextMoveResizeGeom = QRectF(QPointF(orig.left(), topleft.y()), orig.bottomRight());
1515                 sizeMode = SizeModeFixedH; // try not to affect height
1516                 break;
1517             case Gravity::Bottom:
1518                 nextMoveResizeGeom = QRectF(orig.topLeft(), QPointF(orig.right(), bottomright.y()));
1519                 sizeMode = SizeModeFixedH;
1520                 break;
1521             case Gravity::Left:
1522                 nextMoveResizeGeom = QRectF(QPointF(topleft.x(), orig.top()), orig.bottomRight());
1523                 sizeMode = SizeModeFixedW;
1524                 break;
1525             case Gravity::Right:
1526                 nextMoveResizeGeom = QRectF(orig.topLeft(), QPointF(bottomright.x(), orig.bottom()));
1527                 sizeMode = SizeModeFixedW;
1528                 break;
1529             case Gravity::None:
1530                 Q_UNREACHABLE();
1531                 break;
1532             }
1533         };
1534 
1535         // first resize (without checking constrains), then snap, then check bounds, then check constrains
1536         calculateMoveResizeGeom();
1537         // adjust new size to snap to other windows/borders
1538         nextMoveResizeGeom = workspace()->adjustWindowSize(this, nextMoveResizeGeom, gravity);
1539 
1540         if (!isUnrestrictedInteractiveMoveResize()) {
1541             // Make sure the titlebar isn't behind a restricted area. We don't need to restrict
1542             // the other directions. If not visible enough, move the window to the closest valid
1543             // point. We bruteforce this by slowly moving the window back to its previous position
1544             const StrutRects strut = workspace()->restrictedMoveArea(VirtualDesktopManager::self()->currentDesktop());
1545             QRegion availableArea(workspace()->clientArea(FullArea, this, workspace()->activeOutput()).toRect());
1546             for (const QRect &rect : strut) {
1547                 availableArea -= rect;
1548             }
1549             bool transposed = false;
1550             int requiredPixels;
1551             QRectF bTitleRect = titleBarRect(nextMoveResizeGeom, transposed, requiredPixels);
1552             int lastVisiblePixels = -1;
1553             QRectF lastTry = nextMoveResizeGeom;
1554             bool titleFailed = false;
1555             for (;;) {
1556                 const QRect titleRect = bTitleRect.translated(nextMoveResizeGeom.topLeft()).toRect();
1557                 int visiblePixels = 0;
1558                 int realVisiblePixels = 0;
1559                 for (const QRect &rect : availableArea) {
1560                     const QRect r = rect & titleRect;
1561                     realVisiblePixels += r.width() * r.height();
1562                     if ((transposed && r.width() == titleRect.width()) || // Only the full size regions...
1563                         (!transposed && r.height() == titleRect.height())) { // ...prevents long slim areas
1564                         visiblePixels += r.width() * r.height();
1565                     }
1566                 }
1567 
1568                 if (visiblePixels >= requiredPixels) {
1569                     break; // We have reached a valid position
1570                 }
1571 
1572                 if (realVisiblePixels <= lastVisiblePixels) {
1573                     if (titleFailed && realVisiblePixels < lastVisiblePixels) {
1574                         break; // we won't become better
1575                     } else {
1576                         if (!titleFailed) {
1577                             nextMoveResizeGeom = lastTry;
1578                         }
1579                         titleFailed = true;
1580                     }
1581                 }
1582                 lastVisiblePixels = realVisiblePixels;
1583                 QRectF currentTry = nextMoveResizeGeom;
1584                 lastTry = currentTry;
1585 
1586                 // Not visible enough, move the window to the closest valid point. We bruteforce
1587                 // this by slowly moving the window back to its previous position.
1588                 // The geometry changes at up to two edges, the one with the title (if) shall take
1589                 // precedence. The opposing edge has no impact on visiblePixels and only one of
1590                 // the adjacent can alter at a time, ie. it's enough to ignore adjacent edges
1591                 // if the title edge altered
1592                 bool leftChanged = !qFuzzyCompare(currentMoveResizeGeom.left(), currentTry.left());
1593                 bool rightChanged = !qFuzzyCompare(currentMoveResizeGeom.right(), currentTry.right());
1594                 bool topChanged = !qFuzzyCompare(currentMoveResizeGeom.top(), currentTry.top());
1595                 bool btmChanged = !qFuzzyCompare(currentMoveResizeGeom.bottom(), currentTry.bottom());
1596                 auto fixChangedState = [titleFailed](bool &major, bool &counter, bool &ad1, bool &ad2) {
1597                     counter = false;
1598                     if (titleFailed) {
1599                         major = false;
1600                     }
1601                     if (major) {
1602                         ad1 = ad2 = false;
1603                     }
1604                 };
1605                 switch (titlebarPosition()) {
1606                 default:
1607                 case Qt::TopEdge:
1608                     fixChangedState(topChanged, btmChanged, leftChanged, rightChanged);
1609                     break;
1610                 case Qt::LeftEdge:
1611                     fixChangedState(leftChanged, rightChanged, topChanged, btmChanged);
1612                     break;
1613                 case Qt::BottomEdge:
1614                     fixChangedState(btmChanged, topChanged, leftChanged, rightChanged);
1615                     break;
1616                 case Qt::RightEdge:
1617                     fixChangedState(rightChanged, leftChanged, topChanged, btmChanged);
1618                     break;
1619                 }
1620                 if (topChanged) {
1621                     currentTry.setTop(currentTry.y() + qBound(-1.0, currentMoveResizeGeom.y() - currentTry.y(), 1.0));
1622                 } else if (leftChanged) {
1623                     currentTry.setLeft(currentTry.x() + qBound(-1.0, currentMoveResizeGeom.x() - currentTry.x(), 1.0));
1624                 } else if (btmChanged) {
1625                     currentTry.setBottom(currentTry.bottom() + qBound(-1.0, currentMoveResizeGeom.bottom() - currentTry.bottom(), 1.0));
1626                 } else if (rightChanged) {
1627                     currentTry.setRight(currentTry.right() + qBound(-1.0, currentMoveResizeGeom.right() - currentTry.right(), 1.0));
1628                 } else {
1629                     break; // no position changed - that's certainly not good
1630                 }
1631                 nextMoveResizeGeom = currentTry;
1632             }
1633         }
1634 
1635         // Always obey size hints, even when in "unrestricted" mode
1636         QSizeF size = constrainFrameSize(nextMoveResizeGeom.size(), sizeMode);
1637         // the new topleft and bottomright corners (after checking size constrains), if they'll be needed
1638         topleft = QPointF(nextMoveResizeGeom.right() - size.width(), nextMoveResizeGeom.bottom() - size.height());
1639         bottomright = QPointF(nextMoveResizeGeom.left() + size.width(), nextMoveResizeGeom.top() + size.height());
1640         orig = nextMoveResizeGeom;
1641 
1642         // if aspect ratios are specified, both dimensions may change.
1643         // Therefore grow to the right/bottom if needed.
1644         // TODO it should probably obey gravity rather than always using right/bottom ?
1645         if (sizeMode == SizeModeFixedH) {
1646             orig.setRight(bottomright.x());
1647         } else if (sizeMode == SizeModeFixedW) {
1648             orig.setBottom(bottomright.y());
1649         }
1650 
1651         calculateMoveResizeGeom();
1652     } else if (isInteractiveMove()) {
1653         Q_ASSERT(gravity == Gravity::None);
1654         if (!isMovable()) { // isMovableAcrossScreens() must have been true to get here
1655             // Special moving of maximized windows on Xinerama screens
1656             Output *output = workspace()->outputAt(globalPos);
1657             if (isRequestedFullScreen()) {
1658                 nextMoveResizeGeom = workspace()->clientArea(FullScreenArea, this, output);
1659             } else {
1660                 nextMoveResizeGeom = workspace()->clientArea(MaximizeArea, this, output);
1661                 const QSizeF adjSize = constrainFrameSize(nextMoveResizeGeom.size(), SizeModeMax);
1662                 if (adjSize != nextMoveResizeGeom.size()) {
1663                     QRectF r(nextMoveResizeGeom);
1664                     nextMoveResizeGeom.setSize(adjSize);
1665                     nextMoveResizeGeom.moveCenter(r.center());
1666                 }
1667             }
1668         } else {
1669             // first move, then snap, then check bounds
1670             QRectF geometry = nextMoveResizeGeom;
1671             geometry.moveTopLeft(topleft);
1672             geometry.moveTopLeft(workspace()->adjustWindowPosition(this, geometry.topLeft(),
1673                                                                    isUnrestrictedInteractiveMoveResize()));
1674             nextMoveResizeGeom = geometry;
1675 
1676             if (!isUnrestrictedInteractiveMoveResize()) {
1677                 const StrutRects strut = workspace()->restrictedMoveArea(VirtualDesktopManager::self()->currentDesktop());
1678                 QRegion availableArea(workspace()->clientArea(FullArea, this, workspace()->activeOutput()).toRect());
1679                 for (const QRect &rect : strut) {
1680                     availableArea -= rect; // Strut areas
1681                 }
1682                 bool transposed = false;
1683                 int requiredPixels;
1684                 QRectF bTitleRect = titleBarRect(nextMoveResizeGeom, transposed, requiredPixels);
1685                 for (;;) {
1686                     QRectF currentTry = nextMoveResizeGeom;
1687                     const QRect titleRect = bTitleRect.translated(currentTry.topLeft()).toRect();
1688                     int visiblePixels = 0;
1689                     for (const QRect &rect : availableArea) {
1690                         const QRect r = rect & titleRect;
1691                         if ((transposed && r.width() == titleRect.width()) || // Only the full size regions...
1692                             (!transposed && r.height() == titleRect.height())) { // ...prevents long slim areas
1693                             visiblePixels += r.width() * r.height();
1694                         }
1695                     }
1696                     if (visiblePixels >= requiredPixels) {
1697                         break; // We have reached a valid position
1698                     }
1699 
1700                     // (esp.) if there're more screens with different struts (panels) it the titlebar
1701                     // will be movable outside the movearea (covering one of the panels) until it
1702                     // crosses the panel "too much" (not enough visiblePixels) and then stucks because
1703                     // it's usually only pushed by 1px to either direction
1704                     // so we first check whether we intersect suc strut and move the window below it
1705                     // immediately (it's still possible to hit the visiblePixels >= titlebarArea break
1706                     // by moving the window slightly downwards, but it won't stuck)
1707                     // see bug #274466
1708                     // and bug #301805 for why we can't just match the titlearea against the screen
1709                     if (workspace()->outputs().count() > 1) { // optimization
1710                         // TODO: could be useful on partial screen struts (half-width panels etc.)
1711                         int newTitleTop = -1;
1712                         for (const QRect &region : strut) {
1713                             QRectF r = region;
1714                             if (r.top() == 0 && r.width() > r.height() && // "top panel"
1715                                 r.intersects(currentTry) && currentTry.top() < r.bottom()) {
1716                                 newTitleTop = r.bottom();
1717                                 break;
1718                             }
1719                         }
1720                         if (newTitleTop > -1) {
1721                             currentTry.moveTop(newTitleTop); // invalid position, possibly on screen change
1722                             nextMoveResizeGeom = currentTry;
1723                             break;
1724                         }
1725                     }
1726 
1727                     int dx = sign(currentMoveResizeGeom.x() - currentTry.x()),
1728                         dy = sign(currentMoveResizeGeom.y() - currentTry.y());
1729                     if (visiblePixels && dx) { // means there's no full width cap -> favor horizontally
1730                         dy = 0;
1731                     } else if (dy) {
1732                         dx = 0;
1733                     }
1734 
1735                     // Move it back
1736                     currentTry.translate(dx, dy);
1737                     nextMoveResizeGeom = currentTry;
1738 
1739                     // sinces nextMoveResizeGeom is fractional, at best it is within 1 unit of currentMoveResizeGeom
1740                     if (std::abs(currentMoveResizeGeom.left() - nextMoveResizeGeom.left()) <= 1.0
1741                         && std::abs(currentMoveResizeGeom.right() - nextMoveResizeGeom.right()) <= 1.0
1742                         && std::abs(currentMoveResizeGeom.top() - nextMoveResizeGeom.top()) <= 1.0
1743                         && std::abs(currentMoveResizeGeom.bottom() - nextMoveResizeGeom.bottom()) <= 1.0) {
1744                         break; // Prevent lockup
1745                     }
1746                 }
1747             }
1748         }
1749     } else {
1750         Q_UNREACHABLE();
1751     }
1752 
1753     if (nextMoveResizeGeom != currentMoveResizeGeom) {
1754         if (isInteractiveMove()) {
1755             move(nextMoveResizeGeom.topLeft());
1756         } else {
1757             doInteractiveResizeSync(nextMoveResizeGeom);
1758         }
1759 
1760         Q_EMIT interactiveMoveResizeStepped(nextMoveResizeGeom);
1761     }
1762 }
1763 
1764 StrutRect Window::strutRect(StrutArea area) const
1765 {
1766     return StrutRect();
1767 }
1768 
1769 StrutRects Window::strutRects() const
1770 {
1771     StrutRects region;
1772     if (const StrutRect strut = strutRect(StrutAreaTop); strut.isValid()) {
1773         region += strut;
1774     }
1775     if (const StrutRect strut = strutRect(StrutAreaRight); strut.isValid()) {
1776         region += strut;
1777     }
1778     if (const StrutRect strut = strutRect(StrutAreaBottom); strut.isValid()) {
1779         region += strut;
1780     }
1781     if (const StrutRect strut = strutRect(StrutAreaLeft); strut.isValid()) {
1782         region += strut;
1783     }
1784     return region;
1785 }
1786 
1787 bool Window::hasStrut() const
1788 {
1789     return false;
1790 }
1791 
1792 void Window::setupWindowManagementInterface()
1793 {
1794     if (m_windowManagementInterface) {
1795         // already setup
1796         return;
1797     }
1798     if (!waylandServer() || !waylandServer()->windowManagement()) {
1799         return;
1800     }
1801     auto w = waylandServer()->windowManagement()->createWindow(this, internalId());
1802     w->setTitle(caption());
1803     w->setActive(isActive());
1804     w->setFullscreen(isFullScreen());
1805     w->setKeepAbove(keepAbove());
1806     w->setKeepBelow(keepBelow());
1807     w->setMaximized(maximizeMode() == KWin::MaximizeFull);
1808     w->setMinimized(isMinimized());
1809     w->setDemandsAttention(isDemandingAttention());
1810     w->setCloseable(isCloseable());
1811     w->setMaximizeable(isMaximizable());
1812     w->setMinimizeable(isMinimizable());
1813     w->setFullscreenable(isFullScreenable());
1814     w->setApplicationMenuPaths(applicationMenuServiceName(), applicationMenuObjectPath());
1815     w->setIcon(icon());
1816     auto updateAppId = [this, w] {
1817         w->setResourceName(resourceName());
1818         w->setAppId(m_desktopFileName.isEmpty() ? resourceClass() : m_desktopFileName);
1819     };
1820     updateAppId();
1821     w->setSkipTaskbar(skipTaskbar());
1822     w->setSkipSwitcher(skipSwitcher());
1823     w->setPid(pid());
1824     w->setShadeable(isShadeable());
1825     w->setShaded(isShade());
1826     w->setResizable(isResizable());
1827     w->setMovable(isMovable());
1828     w->setVirtualDesktopChangeable(true); // FIXME Matches X11Window::actionSupported(), but both should be implemented.
1829     w->setParentWindow(transientFor() ? transientFor()->windowManagementInterface() : nullptr);
1830     w->setGeometry(frameGeometry().toRect());
1831     connect(this, &Window::skipTaskbarChanged, w, [w, this]() {
1832         w->setSkipTaskbar(skipTaskbar());
1833     });
1834     connect(this, &Window::skipSwitcherChanged, w, [w, this]() {
1835         w->setSkipSwitcher(skipSwitcher());
1836     });
1837     connect(this, &Window::captionChanged, w, [w, this] {
1838         w->setTitle(caption());
1839     });
1840 
1841     connect(this, &Window::activeChanged, w, [w, this] {
1842         w->setActive(isActive());
1843     });
1844     connect(this, &Window::fullScreenChanged, w, [w, this] {
1845         w->setFullscreen(isFullScreen());
1846     });
1847     connect(this, &Window::keepAboveChanged, w, &PlasmaWindowInterface::setKeepAbove);
1848     connect(this, &Window::keepBelowChanged, w, &PlasmaWindowInterface::setKeepBelow);
1849     connect(this, &Window::minimizedChanged, w, [w, this] {
1850         w->setMinimized(isMinimized());
1851     });
1852     connect(this, &Window::maximizedChanged, w, [w, this]() {
1853         w->setMaximized(maximizeMode() == MaximizeFull);
1854     });
1855     connect(this, &Window::demandsAttentionChanged, w, [w, this] {
1856         w->setDemandsAttention(isDemandingAttention());
1857     });
1858     connect(this, &Window::iconChanged, w, [w, this]() {
1859         w->setIcon(icon());
1860     });
1861     connect(this, &Window::windowClassChanged, w, updateAppId);
1862     connect(this, &Window::desktopFileNameChanged, w, updateAppId);
1863     connect(this, &Window::shadeChanged, w, [w, this] {
1864         w->setShaded(isShade());
1865     });
1866     connect(this, &Window::transientChanged, w, [w, this]() {
1867         w->setParentWindow(transientFor() ? transientFor()->windowManagementInterface() : nullptr);
1868     });
1869     connect(this, &Window::frameGeometryChanged, w, [w, this]() {
1870         w->setGeometry(frameGeometry().toRect());
1871     });
1872     connect(this, &Window::applicationMenuChanged, w, [w, this]() {
1873         w->setApplicationMenuPaths(applicationMenuServiceName(), applicationMenuObjectPath());
1874     });
1875     connect(w, &PlasmaWindowInterface::closeRequested, this, [this] {
1876         closeWindow();
1877     });
1878     connect(w, &PlasmaWindowInterface::moveRequested, this, [this]() {
1879         Cursors::self()->mouse()->setPos(frameGeometry().center());
1880         performMouseCommand(Options::MouseMove, Cursors::self()->mouse()->pos());
1881     });
1882     connect(w, &PlasmaWindowInterface::resizeRequested, this, [this]() {
1883         Cursors::self()->mouse()->setPos(frameGeometry().bottomRight());
1884         performMouseCommand(Options::MouseResize, Cursors::self()->mouse()->pos());
1885     });
1886     connect(w, &PlasmaWindowInterface::fullscreenRequested, this, [this](bool set) {
1887         setFullScreen(set);
1888     });
1889     connect(w, &PlasmaWindowInterface::minimizedRequested, this, [this](bool set) {
1890         setMinimized(set);
1891     });
1892     connect(w, &PlasmaWindowInterface::maximizedRequested, this, [this](bool set) {
1893         maximize(set ? MaximizeFull : MaximizeRestore);
1894     });
1895     connect(w, &PlasmaWindowInterface::keepAboveRequested, this, [this](bool set) {
1896         setKeepAbove(set);
1897     });
1898     connect(w, &PlasmaWindowInterface::keepBelowRequested, this, [this](bool set) {
1899         setKeepBelow(set);
1900     });
1901     connect(w, &PlasmaWindowInterface::demandsAttentionRequested, this, [this](bool set) {
1902         demandAttention(set);
1903     });
1904     connect(w, &PlasmaWindowInterface::activeRequested, this, [this](bool set) {
1905         if (set) {
1906             workspace()->activateWindow(this, true);
1907         }
1908     });
1909     connect(w, &PlasmaWindowInterface::shadedRequested, this, [this](bool set) {
1910         setShade(set);
1911     });
1912 
1913     for (const auto vd : std::as_const(m_desktops)) {
1914         w->addPlasmaVirtualDesktop(vd->id());
1915     }
1916     // We need to set `OnAllDesktops` after the actual VD list has been added.
1917     // Otherwise it will unconditionally add the current desktop to the interface
1918     // which may not be the case, for example, when using rules
1919     w->setOnAllDesktops(isOnAllDesktops());
1920 
1921     // Plasma Virtual desktop management
1922     // show/hide when the window enters/exits from desktop
1923     connect(w, &PlasmaWindowInterface::enterPlasmaVirtualDesktopRequested, this, [this](const QString &desktopId) {
1924         VirtualDesktop *vd = VirtualDesktopManager::self()->desktopForId(desktopId);
1925         if (vd) {
1926             enterDesktop(vd);
1927         }
1928     });
1929     connect(w, &PlasmaWindowInterface::enterNewPlasmaVirtualDesktopRequested, this, [this]() {
1930         VirtualDesktopManager::self()->setCount(VirtualDesktopManager::self()->count() + 1);
1931         enterDesktop(VirtualDesktopManager::self()->desktops().last());
1932     });
1933     connect(w, &PlasmaWindowInterface::leavePlasmaVirtualDesktopRequested, this, [this](const QString &desktopId) {
1934         VirtualDesktop *vd = VirtualDesktopManager::self()->desktopForId(desktopId);
1935         if (vd) {
1936             leaveDesktop(vd);
1937         }
1938     });
1939 
1940     for (const auto &activity : std::as_const(m_activityList)) {
1941         w->addPlasmaActivity(activity);
1942     }
1943 
1944     connect(this, &Window::activitiesChanged, w, [w, this] {
1945         const auto newActivities = QSet<QString>(m_activityList.begin(), m_activityList.end());
1946         const auto oldActivitiesList = w->plasmaActivities();
1947         const auto oldActivities = QSet<QString>(oldActivitiesList.begin(), oldActivitiesList.end());
1948 
1949         const auto activitiesToAdd = newActivities - oldActivities;
1950         for (const auto &activity : activitiesToAdd) {
1951             w->addPlasmaActivity(activity);
1952         }
1953 
1954         const auto activitiesToRemove = oldActivities - newActivities;
1955         for (const auto &activity : activitiesToRemove) {
1956             w->removePlasmaActivity(activity);
1957         }
1958     });
1959 
1960     // Plasma Activities management
1961     // show/hide when the window enters/exits activity
1962     connect(w, &PlasmaWindowInterface::enterPlasmaActivityRequested, this, [this](const QString &activityId) {
1963         setOnActivity(activityId, true);
1964     });
1965     connect(w, &PlasmaWindowInterface::leavePlasmaActivityRequested, this, [this](const QString &activityId) {
1966         setOnActivity(activityId, false);
1967     });
1968     connect(w, &PlasmaWindowInterface::sendToOutput, this, [this](OutputInterface *output) {
1969         sendToOutput(output->handle());
1970     });
1971 
1972     m_windowManagementInterface = w;
1973 }
1974 
1975 void Window::destroyWindowManagementInterface()
1976 {
1977     delete m_windowManagementInterface;
1978     m_windowManagementInterface = nullptr;
1979 }
1980 
1981 Options::MouseCommand Window::getMouseCommand(Qt::MouseButton button, bool *handled) const
1982 {
1983     *handled = false;
1984     if (button == Qt::NoButton) {
1985         return Options::MouseNothing;
1986     }
1987     if (isActive()) {
1988         if (options->isClickRaise() && !isMostRecentlyRaised()) {
1989             *handled = true;
1990             return Options::MouseActivateRaiseAndPassClick;
1991         }
1992     } else {
1993         *handled = true;
1994         switch (button) {
1995         case Qt::LeftButton:
1996             return options->commandWindow1();
1997         case Qt::MiddleButton:
1998             return options->commandWindow2();
1999         case Qt::RightButton:
2000             return options->commandWindow3();
2001         default:
2002             // all other buttons pass Activate & Pass Client
2003             return Options::MouseActivateAndPassClick;
2004         }
2005     }
2006     return Options::MouseNothing;
2007 }
2008 
2009 Options::MouseCommand Window::getWheelCommand(Qt::Orientation orientation, bool *handled) const
2010 {
2011     *handled = false;
2012     if (orientation != Qt::Vertical) {
2013         return Options::MouseNothing;
2014     }
2015     if (!isActive()) {
2016         *handled = true;
2017         return options->commandWindowWheel();
2018     }
2019     return Options::MouseNothing;
2020 }
2021 
2022 bool Window::performMouseCommand(Options::MouseCommand cmd, const QPointF &globalPos)
2023 {
2024     bool replay = false;
2025     switch (cmd) {
2026     case Options::MouseRaise:
2027         workspace()->raiseWindow(this);
2028         break;
2029     case Options::MouseLower: {
2030         workspace()->lowerWindow(this);
2031         // used to be activateNextWindow(this), then topWindowOnDesktop
2032         // since this is a mouseOp it's however safe to use the window under the mouse instead
2033         if (isActive() && options->focusPolicyIsReasonable()) {
2034             Window *next = workspace()->windowUnderMouse(output());
2035             if (next && next != this) {
2036                 workspace()->requestFocus(next, false);
2037             }
2038         }
2039         break;
2040     }
2041     case Options::MouseOperationsMenu:
2042         if (isActive() && options->isClickRaise()) {
2043             autoRaise();
2044         }
2045         workspace()->showWindowMenu(QRect(globalPos.toPoint(), globalPos.toPoint()), this);
2046         break;
2047     case Options::MouseToggleRaiseAndLower:
2048         workspace()->raiseOrLowerWindow(this);
2049         break;
2050     case Options::MouseActivateAndRaise: {
2051         replay = isActive(); // for clickraise mode
2052         bool mustReplay = !rules()->checkAcceptFocus(acceptsFocus());
2053         if (mustReplay) {
2054             auto it = workspace()->stackingOrder().constEnd(),
2055                  begin = workspace()->stackingOrder().constBegin();
2056             while (mustReplay && --it != begin && *it != this) {
2057                 auto c = *it;
2058                 if (!c->isClient() || (c->keepAbove() && !keepAbove()) || (keepBelow() && !c->keepBelow())) {
2059                     continue; // can never raise above "it"
2060                 }
2061                 mustReplay = !(c->isOnCurrentDesktop() && c->isOnCurrentActivity() && c->frameGeometry().intersects(frameGeometry()));
2062             }
2063         }
2064         workspace()->takeActivity(this, Workspace::ActivityFocus | Workspace::ActivityRaise);
2065         workspace()->setActiveOutput(globalPos);
2066         replay = replay || mustReplay;
2067         break;
2068     }
2069     case Options::MouseActivateAndLower:
2070         workspace()->requestFocus(this);
2071         workspace()->lowerWindow(this);
2072         workspace()->setActiveOutput(globalPos);
2073         replay = replay || !rules()->checkAcceptFocus(acceptsFocus());
2074         break;
2075     case Options::MouseActivate:
2076         replay = isActive(); // for clickraise mode
2077         workspace()->takeActivity(this, Workspace::ActivityFocus);
2078         workspace()->setActiveOutput(globalPos);
2079         replay = replay || !rules()->checkAcceptFocus(acceptsFocus());
2080         break;
2081     case Options::MouseActivateRaiseAndPassClick:
2082         workspace()->takeActivity(this, Workspace::ActivityFocus | Workspace::ActivityRaise);
2083         workspace()->setActiveOutput(globalPos);
2084         replay = true;
2085         break;
2086     case Options::MouseActivateAndPassClick:
2087         workspace()->takeActivity(this, Workspace::ActivityFocus);
2088         workspace()->setActiveOutput(globalPos);
2089         replay = true;
2090         break;
2091     case Options::MouseMaximize:
2092         maximize(MaximizeFull);
2093         break;
2094     case Options::MouseRestore:
2095         maximize(MaximizeRestore);
2096         break;
2097     case Options::MouseMinimize:
2098         setMinimized(true);
2099         break;
2100     case Options::MouseAbove: {
2101         StackingUpdatesBlocker blocker(workspace());
2102         if (keepBelow()) {
2103             setKeepBelow(false);
2104         } else {
2105             setKeepAbove(true);
2106         }
2107         break;
2108     }
2109     case Options::MouseBelow: {
2110         StackingUpdatesBlocker blocker(workspace());
2111         if (keepAbove()) {
2112             setKeepAbove(false);
2113         } else {
2114             setKeepBelow(true);
2115         }
2116         break;
2117     }
2118     case Options::MousePreviousDesktop:
2119         workspace()->windowToPreviousDesktop(this);
2120         break;
2121     case Options::MouseNextDesktop:
2122         workspace()->windowToNextDesktop(this);
2123         break;
2124     case Options::MouseOpacityMore:
2125         if (!isDesktop()) { // No point in changing the opacity of the desktop
2126             setOpacity(std::min(opacity() + 0.1, 1.0));
2127         }
2128         break;
2129     case Options::MouseOpacityLess:
2130         if (!isDesktop()) { // No point in changing the opacity of the desktop
2131             setOpacity(std::max(opacity() - 0.1, 0.1));
2132         }
2133         break;
2134     case Options::MouseClose:
2135         closeWindow();
2136         break;
2137     case Options::MouseActivateRaiseAndMove:
2138     case Options::MouseActivateRaiseAndUnrestrictedMove:
2139         workspace()->raiseWindow(this);
2140         workspace()->requestFocus(this);
2141         workspace()->setActiveOutput(globalPos);
2142         // fallthrough
2143     case Options::MouseMove:
2144     case Options::MouseUnrestrictedMove: {
2145         if (!isMovableAcrossScreens()) {
2146             replay = true;
2147             break;
2148         }
2149         if (isInteractiveMoveResize()) {
2150             finishInteractiveMoveResize(false);
2151         }
2152         setInteractiveMoveResizeGravity(Gravity::None);
2153         setInteractiveMoveResizePointerButtonDown(true);
2154         setInteractiveMoveOffset(QPointF(globalPos.x() - x(), globalPos.y() - y())); // map from global
2155         setInvertedInteractiveMoveOffset(rect().bottomRight() - interactiveMoveOffset());
2156         setUnrestrictedInteractiveMoveResize((cmd == Options::MouseActivateRaiseAndUnrestrictedMove
2157                                               || cmd == Options::MouseUnrestrictedMove));
2158         if (!startInteractiveMoveResize()) {
2159             setInteractiveMoveResizePointerButtonDown(false);
2160         }
2161         updateCursor();
2162         break;
2163     }
2164     case Options::MouseResize:
2165     case Options::MouseUnrestrictedResize: {
2166         if (!isResizable() || isShade()) {
2167             break;
2168         }
2169         if (isInteractiveMoveResize()) {
2170             finishInteractiveMoveResize(false);
2171         }
2172         setInteractiveMoveResizePointerButtonDown(true);
2173         const QPointF moveOffset = QPointF(globalPos.x() - x(), globalPos.y() - y()); // map from global
2174         setInteractiveMoveOffset(moveOffset);
2175         int x = moveOffset.x(), y = moveOffset.y();
2176         bool left = x < width() / 3;
2177         bool right = x >= 2 * width() / 3;
2178         bool top = y < height() / 3;
2179         bool bot = y >= 2 * height() / 3;
2180         Gravity gravity;
2181         if (top) {
2182             gravity = left ? Gravity::TopLeft : (right ? Gravity::TopRight : Gravity::Top);
2183         } else if (bot) {
2184             gravity = left ? Gravity::BottomLeft : (right ? Gravity::BottomRight : Gravity::Bottom);
2185         } else {
2186             gravity = (x < width() / 2) ? Gravity::Left : Gravity::Right;
2187         }
2188         setInteractiveMoveResizeGravity(gravity);
2189         setInvertedInteractiveMoveOffset(rect().bottomRight() - moveOffset);
2190         setUnrestrictedInteractiveMoveResize((cmd == Options::MouseUnrestrictedResize));
2191         if (!startInteractiveMoveResize()) {
2192             setInteractiveMoveResizePointerButtonDown(false);
2193         }
2194         updateCursor();
2195         break;
2196     }
2197     case Options::MouseShade:
2198         toggleShade();
2199         cancelShadeHoverTimer();
2200         break;
2201     case Options::MouseSetShade:
2202         setShade(ShadeNormal);
2203         cancelShadeHoverTimer();
2204         break;
2205     case Options::MouseUnsetShade:
2206         setShade(ShadeNone);
2207         cancelShadeHoverTimer();
2208         break;
2209     case Options::MouseNothing:
2210     default:
2211         replay = true;
2212         break;
2213     }
2214     return replay;
2215 }
2216 
2217 void Window::setTransientFor(Window *transientFor)
2218 {
2219     if (transientFor == this) {
2220         // cannot be transient for one self
2221         return;
2222     }
2223     if (m_transientFor == transientFor) {
2224         return;
2225     }
2226     m_transientFor = transientFor;
2227     Q_EMIT transientChanged();
2228 }
2229 
2230 const Window *Window::transientFor() const
2231 {
2232     return m_transientFor;
2233 }
2234 
2235 Window *Window::transientFor()
2236 {
2237     return m_transientFor;
2238 }
2239 
2240 bool Window::hasTransientPlacementHint() const
2241 {
2242     return false;
2243 }
2244 
2245 QRectF Window::transientPlacement() const
2246 {
2247     Q_UNREACHABLE();
2248     return QRectF();
2249 }
2250 
2251 bool Window::hasTransient(const Window *c, bool indirect) const
2252 {
2253     return c->transientFor() == this;
2254 }
2255 
2256 QList<Window *> Window::mainWindows() const
2257 {
2258     if (const Window *t = transientFor()) {
2259         return QList<Window *>{const_cast<Window *>(t)};
2260     }
2261     return QList<Window *>();
2262 }
2263 
2264 QList<Window *> Window::allMainWindows() const
2265 {
2266     auto result = mainWindows();
2267     for (const auto *window : result) {
2268         result += window->allMainWindows();
2269     }
2270     return result;
2271 }
2272 
2273 void Window::setModal(bool m)
2274 {
2275     // Qt-3.2 can have even modal normal windows :(
2276     if (m_modal == m) {
2277         return;
2278     }
2279     m_modal = m;
2280     Q_EMIT modalChanged();
2281     // Changing modality for a mapped window is weird (?)
2282     // _NET_WM_STATE_MODAL should possibly rather be _NET_WM_WINDOW_TYPE_MODAL_DIALOG
2283 }
2284 
2285 bool Window::isModal() const
2286 {
2287     return m_modal;
2288 }
2289 
2290 bool Window::isTransient() const
2291 {
2292     return false;
2293 }
2294 
2295 // check whether a transient should be actually kept above its mainwindow
2296 // there may be some special cases where this rule shouldn't be enfored
2297 static bool shouldKeepTransientAbove(const Window *parent, const Window *transient)
2298 {
2299     // #93832 - don't keep splashscreens above dialogs
2300     if (transient->isSplash() && parent->isDialog()) {
2301         return false;
2302     }
2303     // This is rather a hack for #76026. Don't keep non-modal dialogs above
2304     // the mainwindow, but only if they're group transient (since only such dialogs
2305     // have taskbar entry in Kicker). A proper way of doing this (both kwin and kicker)
2306     // needs to be found.
2307     if (transient->isDialog() && !transient->isModal() && transient->groupTransient()) {
2308         return false;
2309     }
2310     // #63223 - don't keep transients above docks, because the dock is kept high,
2311     // and e.g. dialogs for them would be too high too
2312     // ignore this if the transient has a placement hint which indicates it should go above it's parent
2313     if (parent->isDock() && !transient->hasTransientPlacementHint()) {
2314         return false;
2315     }
2316     return true;
2317 }
2318 
2319 void Window::addTransient(Window *cl)
2320 {
2321     Q_ASSERT(!m_transients.contains(cl));
2322     Q_ASSERT(cl != this);
2323     m_transients.append(cl);
2324     if (shouldKeepTransientAbove(this, cl)) {
2325         workspace()->constrain(this, cl);
2326     }
2327 }
2328 
2329 void Window::removeTransient(Window *cl)
2330 {
2331     m_transients.removeAll(cl);
2332     if (cl->transientFor() == this) {
2333         cl->setTransientFor(nullptr);
2334     }
2335     workspace()->unconstrain(this, cl);
2336 }
2337 
2338 void Window::removeTransientFromList(Window *cl)
2339 {
2340     m_transients.removeAll(cl);
2341 }
2342 
2343 bool Window::isActiveFullScreen() const
2344 {
2345     if (!isFullScreen()) {
2346         return false;
2347     }
2348 
2349     const auto ac = workspace()->mostRecentlyActivatedWindow(); // instead of activeWindow() - avoids flicker
2350     // according to NETWM spec implementation notes suggests
2351     // "focused windows having state _NET_WM_STATE_FULLSCREEN" to be on the highest layer.
2352     // we'll also take the screen into account
2353     return ac && (ac == this || !ac->isOnOutput(output()) || ac->allMainWindows().contains(const_cast<Window *>(this)));
2354 }
2355 
2356 int Window::borderBottom() const
2357 {
2358     return isDecorated() ? decoration()->borderBottom() : 0;
2359 }
2360 
2361 int Window::borderLeft() const
2362 {
2363     return isDecorated() ? decoration()->borderLeft() : 0;
2364 }
2365 
2366 int Window::borderRight() const
2367 {
2368     return isDecorated() ? decoration()->borderRight() : 0;
2369 }
2370 
2371 int Window::borderTop() const
2372 {
2373     return isDecorated() ? decoration()->borderTop() : 0;
2374 }
2375 
2376 void Window::updateCursor()
2377 {
2378     Gravity gravity = interactiveMoveResizeGravity();
2379     if (!isResizable() || isShade()) {
2380         gravity = Gravity::None;
2381     }
2382     CursorShape c = Qt::ArrowCursor;
2383     switch (gravity) {
2384     case Gravity::TopLeft:
2385         c = KWin::ExtendedCursor::SizeNorthWest;
2386         break;
2387     case Gravity::BottomRight:
2388         c = KWin::ExtendedCursor::SizeSouthEast;
2389         break;
2390     case Gravity::BottomLeft:
2391         c = KWin::ExtendedCursor::SizeSouthWest;
2392         break;
2393     case Gravity::TopRight:
2394         c = KWin::ExtendedCursor::SizeNorthEast;
2395         break;
2396     case Gravity::Top:
2397         c = KWin::ExtendedCursor::SizeNorth;
2398         break;
2399     case Gravity::Bottom:
2400         c = KWin::ExtendedCursor::SizeSouth;
2401         break;
2402     case Gravity::Left:
2403         c = KWin::ExtendedCursor::SizeWest;
2404         break;
2405     case Gravity::Right:
2406         c = KWin::ExtendedCursor::SizeEast;
2407         break;
2408     default:
2409         if (isInteractiveMoveResize()) {
2410             c = Qt::SizeAllCursor;
2411         } else {
2412             c = Qt::ArrowCursor;
2413         }
2414         break;
2415     }
2416     if (c == m_interactiveMoveResize.cursor) {
2417         return;
2418     }
2419     m_interactiveMoveResize.cursor = c;
2420     Q_EMIT moveResizeCursorChanged(c);
2421 }
2422 
2423 void Window::leaveInteractiveMoveResize()
2424 {
2425     workspace()->setMoveResizeWindow(nullptr);
2426     setInteractiveMoveResize(false);
2427     if (workspace()->screenEdges()->isDesktopSwitchingMovingClients()) {
2428         workspace()->screenEdges()->reserveDesktopSwitching(false, Qt::Vertical | Qt::Horizontal);
2429     }
2430     if (isElectricBorderMaximizing()) {
2431         workspace()->outline()->hide();
2432         elevate(false);
2433     }
2434 }
2435 
2436 bool Window::doStartInteractiveMoveResize()
2437 {
2438     return true;
2439 }
2440 
2441 void Window::doFinishInteractiveMoveResize()
2442 {
2443 }
2444 
2445 bool Window::isWaitingForInteractiveMoveResizeSync() const
2446 {
2447     return false;
2448 }
2449 
2450 void Window::doInteractiveResizeSync(const QRectF &)
2451 {
2452 }
2453 
2454 void Window::checkQuickTilingMaximizationZones(int xroot, int yroot)
2455 {
2456     QuickTileMode mode = QuickTileFlag::None;
2457     bool innerBorder = false;
2458 
2459     const auto outputs = workspace()->outputs();
2460     for (const Output *output : outputs) {
2461         if (!output->geometry().contains(QPoint(xroot, yroot))) {
2462             continue;
2463         }
2464 
2465         auto isInScreen = [&output, &outputs](const QPoint &pt) {
2466             for (const Output *other : outputs) {
2467                 if (other == output) {
2468                     continue;
2469                 }
2470                 if (other->geometry().contains(pt)) {
2471                     return true;
2472                 }
2473             }
2474             return false;
2475         };
2476 
2477         QRectF area = workspace()->clientArea(MaximizeArea, this, QPointF(xroot, yroot));
2478         if (options->electricBorderTiling()) {
2479             if (xroot <= area.x() + 20) {
2480                 mode |= QuickTileFlag::Left;
2481                 innerBorder = isInScreen(QPoint(area.x() - 1, yroot));
2482             } else if (xroot >= area.x() + area.width() - 20) {
2483                 mode |= QuickTileFlag::Right;
2484                 innerBorder = isInScreen(QPoint(area.right() + 1, yroot));
2485             }
2486         }
2487 
2488         if (mode != QuickTileMode(QuickTileFlag::None)) {
2489             if (yroot <= area.y() + area.height() * options->electricBorderCornerRatio()) {
2490                 mode |= QuickTileFlag::Top;
2491             } else if (yroot >= area.y() + area.height() - area.height() * options->electricBorderCornerRatio()) {
2492                 mode |= QuickTileFlag::Bottom;
2493             }
2494         } else if (options->electricBorderMaximize() && yroot <= area.y() + 5 && isMaximizable()) {
2495             mode = QuickTileFlag::Maximize;
2496             innerBorder = isInScreen(QPoint(xroot, area.y() - 1));
2497         }
2498         break; // no point in checking other screens to contain this... "point"...
2499     }
2500     if (mode != electricBorderMode()) {
2501         setElectricBorderMode(mode);
2502         if (innerBorder) {
2503             if (!m_electricMaximizingDelay) {
2504                 m_electricMaximizingDelay = new QTimer(this);
2505                 m_electricMaximizingDelay->setInterval(250);
2506                 m_electricMaximizingDelay->setSingleShot(true);
2507                 connect(m_electricMaximizingDelay, &QTimer::timeout, this, [this]() {
2508                     if (isInteractiveMove()) {
2509                         setElectricBorderMaximizing(electricBorderMode() != QuickTileMode(QuickTileFlag::None));
2510                     }
2511                 });
2512             }
2513             m_electricMaximizingDelay->start();
2514         } else {
2515             setElectricBorderMaximizing(mode != QuickTileMode(QuickTileFlag::None));
2516         }
2517     }
2518 }
2519 
2520 void Window::resetQuickTilingMaximizationZones()
2521 {
2522     if (electricBorderMode() != QuickTileMode(QuickTileFlag::None)) {
2523         if (m_electricMaximizingDelay) {
2524             m_electricMaximizingDelay->stop();
2525         }
2526         setElectricBorderMaximizing(false);
2527         setElectricBorderMode(QuickTileFlag::None);
2528     }
2529 }
2530 
2531 void Window::keyPressEvent(uint key_code)
2532 {
2533     if (!isInteractiveMove() && !isInteractiveResize()) {
2534         return;
2535     }
2536     bool is_control = key_code & Qt::CTRL;
2537     bool is_alt = key_code & Qt::ALT;
2538     key_code = key_code & ~Qt::KeyboardModifierMask;
2539     int delta = is_control ? 1 : is_alt ? 32
2540                                         : 8;
2541     QPointF pos = Cursors::self()->mouse()->pos();
2542     switch (key_code) {
2543     case Qt::Key_Left:
2544         pos.rx() -= delta;
2545         break;
2546     case Qt::Key_Right:
2547         pos.rx() += delta;
2548         break;
2549     case Qt::Key_Up:
2550         pos.ry() -= delta;
2551         break;
2552     case Qt::Key_Down:
2553         pos.ry() += delta;
2554         break;
2555     case Qt::Key_Space:
2556     case Qt::Key_Return:
2557     case Qt::Key_Enter:
2558         setInteractiveMoveResizePointerButtonDown(false);
2559         finishInteractiveMoveResize(false);
2560         updateCursor();
2561         break;
2562     case Qt::Key_Escape:
2563         setInteractiveMoveResizePointerButtonDown(false);
2564         finishInteractiveMoveResize(true);
2565         updateCursor();
2566         break;
2567     default:
2568         return;
2569     }
2570     Cursors::self()->mouse()->setPos(pos);
2571 }
2572 
2573 QSizeF Window::resizeIncrements() const
2574 {
2575     return QSizeF(1, 1);
2576 }
2577 
2578 void Window::dontInteractiveMoveResize()
2579 {
2580     setInteractiveMoveResizePointerButtonDown(false);
2581     stopDelayedInteractiveMoveResize();
2582     if (isInteractiveMoveResize()) {
2583         finishInteractiveMoveResize(false);
2584     }
2585 }
2586 
2587 Gravity Window::mouseGravity() const
2588 {
2589     if (isDecorated()) {
2590         switch (decoration()->sectionUnderMouse()) {
2591         case Qt::BottomLeftSection:
2592             return Gravity::BottomLeft;
2593         case Qt::BottomRightSection:
2594             return Gravity::BottomRight;
2595         case Qt::BottomSection:
2596             return Gravity::Bottom;
2597         case Qt::LeftSection:
2598             return Gravity::Left;
2599         case Qt::RightSection:
2600             return Gravity::Right;
2601         case Qt::TopSection:
2602             return Gravity::Top;
2603         case Qt::TopLeftSection:
2604             return Gravity::TopLeft;
2605         case Qt::TopRightSection:
2606             return Gravity::TopRight;
2607         default:
2608             return Gravity::None;
2609         }
2610     }
2611     return Gravity::None;
2612 }
2613 
2614 void Window::endInteractiveMoveResize()
2615 {
2616     setInteractiveMoveResizePointerButtonDown(false);
2617     stopDelayedInteractiveMoveResize();
2618     if (isInteractiveMoveResize()) {
2619         finishInteractiveMoveResize(false);
2620         setInteractiveMoveResizeGravity(mouseGravity());
2621     }
2622     updateCursor();
2623 }
2624 
2625 void Window::setDecoration(std::shared_ptr<KDecoration2::Decoration> decoration)
2626 {
2627     if (m_decoration.decoration == decoration) {
2628         return;
2629     }
2630     if (decoration) {
2631         QMetaObject::invokeMethod(decoration.get(), QOverload<>::of(&KDecoration2::Decoration::update), Qt::QueuedConnection);
2632         connect(decoration.get(), &KDecoration2::Decoration::shadowChanged, this, [this]() {
2633             if (!isDeleted()) {
2634                 updateShadow();
2635             }
2636         });
2637         connect(decoration.get(), &KDecoration2::Decoration::bordersChanged, this, [this]() {
2638             if (!isDeleted()) {
2639                 updateDecorationInputShape();
2640             }
2641         });
2642         connect(decoration.get(), &KDecoration2::Decoration::resizeOnlyBordersChanged, this, [this]() {
2643             if (!isDeleted()) {
2644                 updateDecorationInputShape();
2645             }
2646         });
2647         connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::sizeChanged, this, [this]() {
2648             if (!isDeleted()) {
2649                 updateDecorationInputShape();
2650             }
2651         });
2652     }
2653     m_decoration.decoration = decoration;
2654     updateDecorationInputShape();
2655     Q_EMIT decorationChanged();
2656 }
2657 
2658 void Window::updateDecorationInputShape()
2659 {
2660     if (!isDecorated()) {
2661         m_decoration.inputRegion = QRegion();
2662         return;
2663     }
2664 
2665     const QMargins borders = decoration()->borders();
2666     const QMargins resizeBorders = decoration()->resizeOnlyBorders();
2667 
2668     const QRectF innerRect = QRectF(QPointF(borderLeft(), borderTop()), decoratedClient()->size());
2669     const QRectF outerRect = innerRect + borders + resizeBorders;
2670 
2671     m_decoration.inputRegion = QRegion(outerRect.toAlignedRect()) - innerRect.toAlignedRect();
2672 }
2673 
2674 bool Window::decorationHasAlpha() const
2675 {
2676     if (!isDecorated() || decoration()->isOpaque()) {
2677         // either no decoration or decoration has alpha disabled
2678         return false;
2679     }
2680     return true;
2681 }
2682 
2683 void Window::triggerDecorationRepaint()
2684 {
2685     if (isDecorated()) {
2686         decoration()->update();
2687     }
2688 }
2689 
2690 void Window::layoutDecorationRects(QRectF &left, QRectF &top, QRectF &right, QRectF &bottom) const
2691 {
2692     if (!isDecorated()) {
2693         return;
2694     }
2695     QRectF r = decoration()->rect();
2696 
2697     top = QRectF(r.x(), r.y(), r.width(), borderTop());
2698     bottom = QRectF(r.x(), r.y() + r.height() - borderBottom(),
2699                     r.width(), borderBottom());
2700     left = QRectF(r.x(), r.y() + top.height(),
2701                   borderLeft(), r.height() - top.height() - bottom.height());
2702     right = QRectF(r.x() + r.width() - borderRight(), r.y() + top.height(),
2703                    borderRight(), r.height() - top.height() - bottom.height());
2704 }
2705 
2706 void Window::processDecorationMove(const QPointF &localPos, const QPointF &globalPos)
2707 {
2708     if (isInteractiveMoveResizePointerButtonDown()) {
2709         handleInteractiveMoveResize(localPos.x(), localPos.y(), globalPos.x(), globalPos.y());
2710         return;
2711     }
2712     // TODO: handle modifiers
2713     Gravity newGravity = mouseGravity();
2714     if (newGravity != interactiveMoveResizeGravity()) {
2715         setInteractiveMoveResizeGravity(newGravity);
2716         updateCursor();
2717     }
2718 }
2719 
2720 bool Window::processDecorationButtonPress(QMouseEvent *event, bool ignoreMenu)
2721 {
2722     Options::MouseCommand com = Options::MouseNothing;
2723     bool active = isActive();
2724     if (!wantsInput()) { // we cannot be active, use it anyway
2725         active = true;
2726     }
2727 
2728     // check whether it is a double click
2729     if (event->button() == Qt::LeftButton && titlebarPositionUnderMouse()) {
2730         if (m_decoration.doubleClickTimer.isValid()) {
2731             const qint64 interval = m_decoration.doubleClickTimer.elapsed();
2732             m_decoration.doubleClickTimer.invalidate();
2733             if (interval > QGuiApplication::styleHints()->mouseDoubleClickInterval()) {
2734                 m_decoration.doubleClickTimer.start(); // expired -> new first click and pot. init
2735             } else {
2736                 Workspace::self()->performWindowOperation(this, options->operationTitlebarDblClick());
2737                 dontInteractiveMoveResize();
2738                 return false;
2739             }
2740         } else {
2741             m_decoration.doubleClickTimer.start(); // new first click and pot. init, could be invalidated by release - see below
2742         }
2743     }
2744 
2745     if (event->button() == Qt::LeftButton) {
2746         com = active ? options->commandActiveTitlebar1() : options->commandInactiveTitlebar1();
2747     } else if (event->button() == Qt::MiddleButton) {
2748         com = active ? options->commandActiveTitlebar2() : options->commandInactiveTitlebar2();
2749     } else if (event->button() == Qt::RightButton) {
2750         com = active ? options->commandActiveTitlebar3() : options->commandInactiveTitlebar3();
2751     }
2752     if (event->button() == Qt::LeftButton
2753         && com != Options::MouseOperationsMenu // actions where it's not possible to get the matching
2754         && com != Options::MouseMinimize) // mouse release event
2755     {
2756         setInteractiveMoveResizeGravity(mouseGravity());
2757         setInteractiveMoveResizePointerButtonDown(true);
2758         setInteractiveMoveOffset(event->pos());
2759         setInvertedInteractiveMoveOffset(rect().bottomRight() - interactiveMoveOffset());
2760         setUnrestrictedInteractiveMoveResize(false);
2761         startDelayedInteractiveMoveResize();
2762         updateCursor();
2763     }
2764     // In the new API the decoration may process the menu action to display an inactive tab's menu.
2765     // If the event is unhandled then the core will create one for the active window in the group.
2766     if (!ignoreMenu || com != Options::MouseOperationsMenu) {
2767         performMouseCommand(com, event->globalPos());
2768     }
2769     return !( // Return events that should be passed to the decoration in the new API
2770         com == Options::MouseRaise || com == Options::MouseOperationsMenu || com == Options::MouseActivateAndRaise || com == Options::MouseActivate || com == Options::MouseActivateRaiseAndPassClick || com == Options::MouseActivateAndPassClick || com == Options::MouseNothing);
2771 }
2772 
2773 void Window::processDecorationButtonRelease(QMouseEvent *event)
2774 {
2775     if (isDecorated()) {
2776         if (event->isAccepted() || !titlebarPositionUnderMouse()) {
2777             invalidateDecorationDoubleClickTimer(); // click was for the deco and shall not init a doubleclick
2778         }
2779     }
2780 
2781     if (event->buttons() == Qt::NoButton) {
2782         setInteractiveMoveResizePointerButtonDown(false);
2783         stopDelayedInteractiveMoveResize();
2784         if (isInteractiveMoveResize()) {
2785             finishInteractiveMoveResize(false);
2786             setInteractiveMoveResizeGravity(mouseGravity());
2787         }
2788         updateCursor();
2789     }
2790 }
2791 
2792 void Window::startDecorationDoubleClickTimer()
2793 {
2794     m_decoration.doubleClickTimer.start();
2795 }
2796 
2797 void Window::invalidateDecorationDoubleClickTimer()
2798 {
2799     m_decoration.doubleClickTimer.invalidate();
2800 }
2801 
2802 bool Window::providesContextHelp() const
2803 {
2804     return false;
2805 }
2806 
2807 void Window::showContextHelp()
2808 {
2809 }
2810 
2811 Decoration::DecoratedClientImpl *Window::decoratedClient() const
2812 {
2813     return m_decoration.client;
2814 }
2815 
2816 void Window::setDecoratedClient(Decoration::DecoratedClientImpl *client)
2817 {
2818     m_decoration.client = client;
2819 }
2820 
2821 void Window::pointerEnterEvent(const QPointF &globalPos)
2822 {
2823     if (options->isShadeHover()) {
2824         cancelShadeHoverTimer();
2825         startShadeHoverTimer();
2826     }
2827 
2828     if (options->focusPolicy() == Options::ClickToFocus || workspace()->userActionsMenu()->isShown()) {
2829         return;
2830     }
2831 
2832     if (options->isAutoRaise() && !isDesktop() && !isDock() && workspace()->focusChangeEnabled() && globalPos != workspace()->focusMousePosition() && workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop(), options->isSeparateScreenFocus() ? output() : nullptr) != this) {
2833         startAutoRaise();
2834     }
2835 
2836     if (isDesktop() || isDock()) {
2837         return;
2838     }
2839     // for FocusFollowsMouse, change focus only if the mouse has actually been moved, not if the focus
2840     // change came because of window changes (e.g. closing a window) - #92290
2841     if (options->focusPolicy() != Options::FocusFollowsMouse
2842         || globalPos != workspace()->focusMousePosition()) {
2843         workspace()->requestDelayFocus(this);
2844     }
2845 }
2846 
2847 void Window::pointerLeaveEvent()
2848 {
2849     cancelAutoRaise();
2850     workspace()->cancelDelayFocus();
2851     cancelShadeHoverTimer();
2852     startShadeUnhoverTimer();
2853     // TODO: send hover leave to deco
2854     // TODO: handle Options::FocusStrictlyUnderMouse
2855 }
2856 
2857 QRectF Window::iconGeometry() const
2858 {
2859     if (!windowManagementInterface() || !waylandServer()) {
2860         // window management interface is only available if the surface is mapped
2861         return QRectF();
2862     }
2863 
2864     int minDistance = INT_MAX;
2865     Window *candidatePanel = nullptr;
2866     QRectF candidateGeom;
2867 
2868     const auto minGeometries = windowManagementInterface()->minimizedGeometries();
2869     for (auto i = minGeometries.constBegin(), end = minGeometries.constEnd(); i != end; ++i) {
2870         Window *panel = waylandServer()->findWindow(i.key());
2871         if (!panel) {
2872             continue;
2873         }
2874         const int distance = QPointF(panel->pos() - pos()).manhattanLength();
2875         if (distance < minDistance) {
2876             minDistance = distance;
2877             candidatePanel = panel;
2878             candidateGeom = i.value();
2879         }
2880     }
2881     if (!candidatePanel) {
2882         // Check all mainwindows of this window.
2883         const auto windows = mainWindows();
2884         for (Window *mainWindow : windows) {
2885             const auto geom = mainWindow->iconGeometry();
2886             if (geom.isValid()) {
2887                 return geom;
2888             }
2889         }
2890         return QRectF();
2891     }
2892     return candidateGeom.translated(candidatePanel->pos());
2893 }
2894 
2895 QRectF Window::virtualKeyboardGeometry() const
2896 {
2897     return m_virtualKeyboardGeometry;
2898 }
2899 
2900 void Window::setVirtualKeyboardGeometry(const QRectF &geo)
2901 {
2902     // No keyboard anymore
2903     if (geo.isEmpty() && !m_keyboardGeometryRestore.isEmpty()) {
2904         const QRectF availableArea = workspace()->clientArea(MaximizeArea, this);
2905         QRectF newWindowGeometry = (requestedMaximizeMode() & MaximizeHorizontal) ? availableArea : m_keyboardGeometryRestore;
2906         moveResize(newWindowGeometry);
2907         m_keyboardGeometryRestore = QRectF();
2908     } else if (geo.isEmpty()) {
2909         return;
2910         // The keyboard has just been opened (rather than resized) save window geometry for a restore
2911     } else if (m_keyboardGeometryRestore.isEmpty()) {
2912         m_keyboardGeometryRestore = moveResizeGeometry();
2913     }
2914 
2915     m_virtualKeyboardGeometry = geo;
2916 
2917     // Don't resize Desktop and fullscreen windows
2918     if (isRequestedFullScreen() || isDesktop()) {
2919         return;
2920     }
2921 
2922     if (!geo.intersects(m_keyboardGeometryRestore)) {
2923         return;
2924     }
2925 
2926     const QRectF availableArea = workspace()->clientArea(MaximizeArea, this);
2927     QRectF newWindowGeometry = (requestedMaximizeMode() & MaximizeHorizontal) ? availableArea : m_keyboardGeometryRestore;
2928     newWindowGeometry.setHeight(std::min(newWindowGeometry.height(), geo.top() - availableArea.top()));
2929     newWindowGeometry.moveTop(std::max(geo.top() - newWindowGeometry.height(), availableArea.top()));
2930     newWindowGeometry = newWindowGeometry.intersected(availableArea);
2931     moveResize(newWindowGeometry);
2932 }
2933 
2934 QRectF Window::keyboardGeometryRestore() const
2935 {
2936     return m_keyboardGeometryRestore;
2937 }
2938 
2939 void Window::setKeyboardGeometryRestore(const QRectF &geom)
2940 {
2941     m_keyboardGeometryRestore = geom;
2942 }
2943 
2944 bool Window::dockWantsInput() const
2945 {
2946     return false;
2947 }
2948 
2949 void Window::setDesktopFileName(const QString &name)
2950 {
2951     const QString effectiveName = rules()->checkDesktopFile(name);
2952     if (effectiveName == m_desktopFileName) {
2953         return;
2954     }
2955     m_desktopFileName = effectiveName;
2956     updateWindowRules(Rules::DesktopFile);
2957     Q_EMIT desktopFileNameChanged();
2958 }
2959 
2960 QString Window::iconFromDesktopFile(const QString &desktopFileName)
2961 {
2962     const QString absolutePath = findDesktopFile(desktopFileName);
2963     if (absolutePath.isEmpty()) {
2964         return {};
2965     }
2966 
2967     KDesktopFile df(absolutePath);
2968     return df.readIcon();
2969 }
2970 
2971 QString Window::iconFromDesktopFile() const
2972 {
2973     return iconFromDesktopFile(m_desktopFileName);
2974 }
2975 
2976 QString Window::findDesktopFile(const QString &desktopFileName)
2977 {
2978     if (desktopFileName.isEmpty()) {
2979         return {};
2980     }
2981 
2982     const QString desktopFileNameWithPrefix = desktopFileName + QLatin1String(".desktop");
2983     QString desktopFilePath;
2984 
2985     if (QDir::isAbsolutePath(desktopFileName)) {
2986         if (QFile::exists(desktopFileNameWithPrefix)) {
2987             desktopFilePath = desktopFileNameWithPrefix;
2988         } else {
2989             desktopFilePath = desktopFileName;
2990         }
2991     }
2992 
2993     if (desktopFilePath.isEmpty()) {
2994         desktopFilePath = QStandardPaths::locate(QStandardPaths::ApplicationsLocation,
2995                                                  desktopFileNameWithPrefix);
2996     }
2997     if (desktopFilePath.isEmpty()) {
2998         desktopFilePath = QStandardPaths::locate(QStandardPaths::ApplicationsLocation,
2999                                                  desktopFileName);
3000     }
3001     return desktopFilePath;
3002 }
3003 
3004 bool Window::hasApplicationMenu() const
3005 {
3006     return Workspace::self()->applicationMenu()->applicationMenuEnabled() && !m_applicationMenuServiceName.isEmpty() && !m_applicationMenuObjectPath.isEmpty();
3007 }
3008 
3009 void Window::updateApplicationMenuServiceName(const QString &serviceName)
3010 {
3011     const bool old_hasApplicationMenu = hasApplicationMenu();
3012 
3013     m_applicationMenuServiceName = serviceName;
3014 
3015     const bool new_hasApplicationMenu = hasApplicationMenu();
3016 
3017     Q_EMIT applicationMenuChanged();
3018     if (old_hasApplicationMenu != new_hasApplicationMenu) {
3019         Q_EMIT hasApplicationMenuChanged(new_hasApplicationMenu);
3020     }
3021 }
3022 
3023 void Window::updateApplicationMenuObjectPath(const QString &objectPath)
3024 {
3025     const bool old_hasApplicationMenu = hasApplicationMenu();
3026 
3027     m_applicationMenuObjectPath = objectPath;
3028 
3029     const bool new_hasApplicationMenu = hasApplicationMenu();
3030 
3031     Q_EMIT applicationMenuChanged();
3032     if (old_hasApplicationMenu != new_hasApplicationMenu) {
3033         Q_EMIT hasApplicationMenuChanged(new_hasApplicationMenu);
3034     }
3035 }
3036 
3037 void Window::setApplicationMenuActive(bool applicationMenuActive)
3038 {
3039     if (m_applicationMenuActive != applicationMenuActive) {
3040         m_applicationMenuActive = applicationMenuActive;
3041         Q_EMIT applicationMenuActiveChanged(applicationMenuActive);
3042     }
3043 }
3044 
3045 void Window::showApplicationMenu(int actionId)
3046 {
3047     if (isDecorated()) {
3048         decoration()->showApplicationMenu(actionId);
3049     } else {
3050         // we don't know where the application menu button will be, show it in the top left corner instead
3051         Workspace::self()->showApplicationMenu(QRect(), this, actionId);
3052     }
3053 }
3054 
3055 bool Window::unresponsive() const
3056 {
3057     return m_unresponsive;
3058 }
3059 
3060 void Window::setUnresponsive(bool unresponsive)
3061 {
3062     if (m_unresponsive != unresponsive) {
3063         m_unresponsive = unresponsive;
3064         Q_EMIT unresponsiveChanged(m_unresponsive);
3065         Q_EMIT captionChanged();
3066     }
3067 }
3068 
3069 QString Window::shortcutCaptionSuffix() const
3070 {
3071     if (shortcut().isEmpty()) {
3072         return QString();
3073     }
3074     return QLatin1String(" {") + shortcut().toString() + QLatin1Char('}');
3075 }
3076 
3077 QString Window::caption() const
3078 {
3079     QString cap = captionNormal() + captionSuffix();
3080     if (unresponsive()) {
3081         cap += QLatin1String(" ");
3082         cap += i18nc("Application is not responding, appended to window title", "(Not Responding)");
3083     }
3084     return cap;
3085 }
3086 
3087 /**
3088  * Returns the list of activities the window window is on.
3089  * if it's on all activities, the list will be empty.
3090  * Don't use this, use isOnActivity() and friends (from class Window)
3091  */
3092 QStringList Window::activities() const
3093 {
3094     return m_activityList;
3095 }
3096 
3097 bool Window::isOnCurrentActivity() const
3098 {
3099 #if KWIN_BUILD_ACTIVITIES
3100     if (!Workspace::self()->activities()) {
3101         return true;
3102     }
3103     return isOnActivity(Workspace::self()->activities()->current());
3104 #else
3105     return true;
3106 #endif
3107 }
3108 
3109 /**
3110  * Sets whether the window is on @p activity.
3111  * If you remove it from its last activity, then it's on all activities.
3112  *
3113  * Note: If it was on all activities and you try to remove it from one, nothing will happen;
3114  * I don't think that's an important enough use case to handle here.
3115  */
3116 void Window::setOnActivity(const QString &activity, bool enable)
3117 {
3118 #if KWIN_BUILD_ACTIVITIES
3119     if (!Workspace::self()->activities()) {
3120         return;
3121     }
3122     QStringList newActivitiesList = activities();
3123     if (newActivitiesList.contains(activity) == enable) {
3124         // nothing to do
3125         return;
3126     }
3127     if (enable) {
3128         QStringList allActivities = Workspace::self()->activities()->all();
3129         if (!allActivities.contains(activity)) {
3130             // bogus ID
3131             return;
3132         }
3133         newActivitiesList.append(activity);
3134     } else {
3135         newActivitiesList.removeOne(activity);
3136     }
3137     setOnActivities(newActivitiesList);
3138 #endif
3139 }
3140 
3141 /**
3142  * set exactly which activities this window is on
3143  */
3144 void Window::setOnActivities(const QStringList &newActivitiesList)
3145 {
3146 #if KWIN_BUILD_ACTIVITIES
3147     if (!Workspace::self()->activities()) {
3148         return;
3149     }
3150     if (Workspace::self()->activities()->serviceStatus() != KActivities::Consumer::Running) {
3151         return;
3152     }
3153     const auto allActivities = Workspace::self()->activities()->all();
3154     const auto activityList = [&] {
3155         auto result = rules()->checkActivity(newActivitiesList);
3156 
3157         const auto it = std::remove_if(result.begin(), result.end(), [=](const QString &activity) {
3158             return !allActivities.contains(activity);
3159         });
3160         result.erase(it, result.end());
3161         return result;
3162     }();
3163 
3164     const auto allActivityExplicitlyRequested = activityList.isEmpty() || activityList.contains(Activities::nullUuid());
3165     const auto allActivitiesCovered = activityList.size() > 1 && activityList.size() == allActivities.size();
3166 
3167     if (allActivityExplicitlyRequested || allActivitiesCovered) {
3168         if (!m_activityList.isEmpty()) {
3169             m_activityList.clear();
3170             doSetOnActivities(m_activityList);
3171         }
3172     } else {
3173         if (m_activityList != activityList) {
3174             m_activityList = activityList;
3175             doSetOnActivities(m_activityList);
3176         }
3177     }
3178 
3179     updateActivities(false);
3180 #endif
3181 }
3182 
3183 void Window::doSetOnActivities(const QStringList &activityList)
3184 {
3185 }
3186 
3187 /**
3188  * if @p all is true, sets on all activities.
3189  * if it's false, sets it to only be on the current activity
3190  */
3191 void Window::setOnAllActivities(bool all)
3192 {
3193 #if KWIN_BUILD_ACTIVITIES
3194     if (all == isOnAllActivities()) {
3195         return;
3196     }
3197     if (all) {
3198         setOnActivities(QStringList());
3199     } else {
3200         setOnActivity(Workspace::self()->activities()->current(), true);
3201     }
3202 #endif
3203 }
3204 
3205 /**
3206  * update after activities changed
3207  */
3208 void Window::updateActivities(bool includeTransients)
3209 {
3210     if (m_activityUpdatesBlocked) {
3211         m_blockedActivityUpdatesRequireTransients |= includeTransients;
3212         return;
3213     }
3214     Q_EMIT activitiesChanged();
3215     m_blockedActivityUpdatesRequireTransients = false; // reset
3216     Workspace::self()->focusChain()->update(this, FocusChain::MakeFirst);
3217     updateWindowRules(Rules::Activity);
3218 }
3219 
3220 void Window::blockActivityUpdates(bool b)
3221 {
3222     if (b) {
3223         ++m_activityUpdatesBlocked;
3224     } else {
3225         Q_ASSERT(m_activityUpdatesBlocked);
3226         --m_activityUpdatesBlocked;
3227         if (!m_activityUpdatesBlocked) {
3228             updateActivities(m_blockedActivityUpdatesRequireTransients);
3229         }
3230     }
3231 }
3232 
3233 bool Window::groupTransient() const
3234 {
3235     return false;
3236 }
3237 
3238 const Group *Window::group() const
3239 {
3240     return nullptr;
3241 }
3242 
3243 Group *Window::group()
3244 {
3245     return nullptr;
3246 }
3247 
3248 QPointF Window::framePosToClientPos(const QPointF &point) const
3249 {
3250     return point + QPointF(borderLeft(), borderTop());
3251 }
3252 
3253 QPointF Window::clientPosToFramePos(const QPointF &point) const
3254 {
3255     return point - QPointF(borderLeft(), borderTop());
3256 }
3257 
3258 QSizeF Window::frameSizeToClientSize(const QSizeF &size) const
3259 {
3260     const qreal width = size.width() - borderLeft() - borderRight();
3261     const qreal height = size.height() - borderTop() - borderBottom();
3262     return QSizeF(width, height);
3263 }
3264 
3265 QSizeF Window::clientSizeToFrameSize(const QSizeF &size) const
3266 {
3267     const qreal width = size.width() + borderLeft() + borderRight();
3268     const qreal height = size.height() + borderTop() + borderBottom();
3269     return QSizeF(width, height);
3270 }
3271 
3272 QRectF Window::frameRectToClientRect(const QRectF &rect) const
3273 {
3274     const QPointF position = framePosToClientPos(rect.topLeft());
3275     const QSizeF size = frameSizeToClientSize(rect.size());
3276     return QRectF(position, size);
3277 }
3278 
3279 QRectF Window::clientRectToFrameRect(const QRectF &rect) const
3280 {
3281     const QPointF position = clientPosToFramePos(rect.topLeft());
3282     const QSizeF size = clientSizeToFrameSize(rect.size());
3283     return QRectF(position, size);
3284 }
3285 
3286 QRectF Window::moveResizeGeometry() const
3287 {
3288     return m_moveResizeGeometry;
3289 }
3290 
3291 void Window::setMoveResizeGeometry(const QRectF &geo)
3292 {
3293     m_moveResizeGeometry = geo;
3294     m_moveResizeOutput = workspace()->outputAt(geo.center());
3295 }
3296 
3297 Output *Window::moveResizeOutput() const
3298 {
3299     return m_moveResizeOutput;
3300 }
3301 
3302 void Window::setMoveResizeOutput(Output *output)
3303 {
3304     m_moveResizeOutput = output;
3305 }
3306 
3307 void Window::move(const QPointF &point)
3308 {
3309     const QRectF rect = QRectF(point, m_moveResizeGeometry.size());
3310 
3311     setMoveResizeGeometry(rect);
3312     moveResizeInternal(rect, MoveResizeMode::Move);
3313 }
3314 
3315 void Window::resize(const QSizeF &size)
3316 {
3317     const QRectF rect = QRectF(m_moveResizeGeometry.topLeft(), size);
3318 
3319     setMoveResizeGeometry(rect);
3320     moveResizeInternal(rect, MoveResizeMode::Resize);
3321 }
3322 
3323 void Window::moveResize(const QRectF &rect)
3324 {
3325     setMoveResizeGeometry(rect);
3326     moveResizeInternal(rect, MoveResizeMode::MoveResize);
3327 }
3328 
3329 void Window::setElectricBorderMode(QuickTileMode mode)
3330 {
3331     if (mode != QuickTileMode(QuickTileFlag::Maximize)) {
3332         // sanitize the mode, ie. simplify "invalid" combinations
3333         if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Horizontal)) {
3334             mode &= ~QuickTileMode(QuickTileFlag::Horizontal);
3335         }
3336         if ((mode & QuickTileFlag::Vertical) == QuickTileMode(QuickTileFlag::Vertical)) {
3337             mode &= ~QuickTileMode(QuickTileFlag::Vertical);
3338         }
3339     }
3340     m_electricMode = mode;
3341 }
3342 
3343 void Window::setElectricBorderMaximizing(bool maximizing)
3344 {
3345     m_electricMaximizing = maximizing;
3346     if (maximizing) {
3347         workspace()->outline()->show(quickTileGeometry(electricBorderMode(), Cursors::self()->mouse()->pos()).toRect(), moveResizeGeometry().toRect());
3348     } else {
3349         workspace()->outline()->hide();
3350     }
3351     elevate(maximizing);
3352 }
3353 
3354 QRectF Window::quickTileGeometry(QuickTileMode mode, const QPointF &pos) const
3355 {
3356     if (mode == QuickTileMode(QuickTileFlag::Maximize)) {
3357         if (requestedMaximizeMode() == MaximizeFull) {
3358             return geometryRestore();
3359         } else {
3360             return workspace()->clientArea(MaximizeArea, this, pos);
3361         }
3362     }
3363 
3364     Output *output = workspace()->outputAt(pos);
3365 
3366     if (mode & QuickTileFlag::Custom) {
3367         Tile *tile = workspace()->tileManager(output)->bestTileForPosition(pos);
3368         if (tile) {
3369             return tile->windowGeometry();
3370         } else {
3371             return QRectF();
3372         }
3373     }
3374 
3375     Tile *tile = workspace()->tileManager(output)->quickTile(mode);
3376     if (tile) {
3377         return tile->windowGeometry();
3378     }
3379     return workspace()->clientArea(MaximizeArea, this, pos);
3380 }
3381 
3382 void Window::updateElectricGeometryRestore()
3383 {
3384     m_electricGeometryRestore = geometryRestore();
3385     if (m_interactiveMoveResize.initialQuickTileMode == QuickTileMode(QuickTileFlag::None)) {
3386         if (!(requestedMaximizeMode() & MaximizeHorizontal)) {
3387             m_electricGeometryRestore.setX(x());
3388             m_electricGeometryRestore.setWidth(width());
3389         }
3390         if (!(requestedMaximizeMode() & MaximizeVertical)) {
3391             m_electricGeometryRestore.setY(y());
3392             m_electricGeometryRestore.setHeight(height());
3393         }
3394     }
3395 }
3396 
3397 QRectF Window::quickTileGeometryRestore() const
3398 {
3399     if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) {
3400         // If the window is tiled, geometryRestore() already has a good value.
3401         return geometryRestore();
3402     }
3403 
3404     if (isElectricBorderMaximizing()) {
3405         return m_electricGeometryRestore;
3406     } else {
3407         return moveResizeGeometry();
3408     }
3409 }
3410 
3411 void Window::setQuickTileMode(QuickTileMode mode, bool keyboard)
3412 {
3413     // Only allow quick tile on a regular window.
3414     if (!isResizable()) {
3415         return;
3416     }
3417     if (isAppletPopup()) {
3418         return;
3419     }
3420 
3421     workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event
3422 
3423     GeometryUpdatesBlocker blocker(this);
3424 
3425     setTile(nullptr);
3426 
3427     if (mode == QuickTileMode(QuickTileFlag::Maximize)) {
3428         if (requestedMaximizeMode() == MaximizeFull) {
3429             m_quickTileMode = int(QuickTileFlag::None);
3430             setMaximize(false, false);
3431         } else {
3432             QRectF effectiveGeometryRestore = quickTileGeometryRestore();
3433             m_quickTileMode = int(QuickTileFlag::Maximize);
3434             setMaximize(true, true);
3435             setGeometryRestore(effectiveGeometryRestore);
3436         }
3437         doSetQuickTileMode();
3438         Q_EMIT quickTileModeChanged();
3439         return;
3440     }
3441 
3442     // sanitize the mode, ie. simplify "invalid" combinations
3443     if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Horizontal)) {
3444         mode &= ~QuickTileMode(QuickTileFlag::Horizontal);
3445     }
3446     if ((mode & QuickTileFlag::Vertical) == QuickTileMode(QuickTileFlag::Vertical)) {
3447         mode &= ~QuickTileMode(QuickTileFlag::Vertical);
3448     }
3449 
3450     // restore from maximized so that it is possible to tile maximized windows with one hit or by dragging
3451     if (requestedMaximizeMode() != MaximizeRestore) {
3452 
3453         if (mode != QuickTileMode(QuickTileFlag::None)) {
3454             m_quickTileMode = int(QuickTileFlag::None); // Temporary, so the maximize code doesn't get all confused
3455 
3456             setMaximize(false, false);
3457 
3458             moveResize(quickTileGeometry(mode, keyboard ? moveResizeGeometry().center() : Cursors::self()->mouse()->pos()));
3459             // Store the mode change
3460             m_quickTileMode = mode;
3461         } else {
3462             m_quickTileMode = mode;
3463             setMaximize(false, false);
3464         }
3465 
3466         doSetQuickTileMode();
3467         Q_EMIT quickTileModeChanged();
3468 
3469         return;
3470     }
3471 
3472     QPointF whichScreen = keyboard ? moveResizeGeometry().center() : Cursors::self()->mouse()->pos();
3473     if (mode != QuickTileMode(QuickTileFlag::None)) {
3474         // If trying to tile to the side that the window is already tiled to move the window to the next
3475         // screen near the tile if it exists and swap the tile side, otherwise toggle the mode (set QuickTileFlag::None)
3476         if (quickTileMode() == mode) {
3477             Output *currentOutput = moveResizeOutput();
3478             Output *nextOutput = currentOutput;
3479             Output *candidateOutput = currentOutput;
3480             if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Left)) {
3481                 candidateOutput = workspace()->findOutput(nextOutput, Workspace::DirectionWest);
3482             } else if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Right)) {
3483                 candidateOutput = workspace()->findOutput(nextOutput, Workspace::DirectionEast);
3484             }
3485             bool shiftHorizontal = candidateOutput != nextOutput;
3486             nextOutput = candidateOutput;
3487             if ((mode & QuickTileFlag::Vertical) == QuickTileMode(QuickTileFlag::Top)) {
3488                 candidateOutput = workspace()->findOutput(nextOutput, Workspace::DirectionNorth);
3489             } else if ((mode & QuickTileFlag::Vertical) == QuickTileMode(QuickTileFlag::Bottom)) {
3490                 candidateOutput = workspace()->findOutput(nextOutput, Workspace::DirectionSouth);
3491             }
3492             bool shiftVertical = candidateOutput != nextOutput;
3493             nextOutput = candidateOutput;
3494 
3495             if (nextOutput == currentOutput) {
3496                 mode = QuickTileFlag::None; // No other screens in the tile direction, toggle tiling
3497             } else {
3498                 // Move to other screen
3499                 moveResize(geometryRestore().translated(nextOutput->geometry().topLeft() - currentOutput->geometry().topLeft()));
3500                 whichScreen = nextOutput->geometry().center();
3501 
3502                 // Swap sides
3503                 if (shiftHorizontal) {
3504                     mode = (~mode & QuickTileFlag::Horizontal) | (mode & QuickTileFlag::Vertical);
3505                 }
3506                 if (shiftVertical) {
3507                     mode = (~mode & QuickTileFlag::Vertical) | (mode & QuickTileFlag::Horizontal);
3508                 }
3509             }
3510         } else if (quickTileMode() == QuickTileMode(QuickTileFlag::None)) {
3511             // Not coming out of an existing tile, not shifting monitors, we're setting a brand new tile.
3512             // Store geometry first, so we can go out of this tile later.
3513             setGeometryRestore(quickTileGeometryRestore());
3514         }
3515 
3516         m_quickTileMode = mode;
3517     }
3518 
3519     if (mode == QuickTileMode(QuickTileFlag::None)) {
3520         setTile(nullptr);
3521         m_quickTileMode = int(QuickTileFlag::None);
3522         // Untiling, so just restore geometry, and we're done.
3523         if (geometryRestore().isValid()) { // invalid if we started maximized and wait for placement
3524             moveResize(geometryRestore());
3525         }
3526         checkWorkspacePosition(); // Just in case it's a different screen
3527     } else if (mode == QuickTileMode(QuickTileFlag::Custom)) {
3528         Tile *tile = nullptr;
3529         if (keyboard) {
3530             tile = workspace()->tileManager(output())->bestTileForPosition(moveResizeGeometry().center());
3531         } else {
3532             Output *output = workspace()->outputAt(Cursors::self()->mouse()->pos());
3533             tile = workspace()->tileManager(output)->bestTileForPosition(Cursors::self()->mouse()->pos());
3534         }
3535         setTile(tile);
3536     } else {
3537         // Use whichScreen to move to next screen when retiling to the same edge as the old behavior
3538         Output *output = workspace()->outputAt(whichScreen);
3539         Tile *tile = workspace()->tileManager(output)->quickTile(mode);
3540         setTile(tile);
3541     }
3542 
3543     doSetQuickTileMode();
3544     Q_EMIT quickTileModeChanged();
3545 }
3546 
3547 void Window::setTile(Tile *tile)
3548 {
3549     if (m_tile == tile) {
3550         return;
3551     } else if (m_tile) {
3552         m_tile->removeWindow(this);
3553     }
3554 
3555     m_tile = tile;
3556 
3557     if (m_tile) {
3558         m_tile->addWindow(this);
3559     }
3560 
3561     Q_EMIT tileChanged(tile);
3562 }
3563 
3564 Tile *Window::tile() const
3565 {
3566     return m_tile;
3567 }
3568 
3569 void Window::doSetQuickTileMode()
3570 {
3571 }
3572 
3573 void Window::doSetHidden()
3574 {
3575 }
3576 
3577 void Window::doSetHiddenByShowDesktop()
3578 {
3579 }
3580 
3581 QRectF Window::moveToArea(const QRectF &geometry, const QRectF &oldArea, const QRectF &newArea)
3582 {
3583     QRectF ret = geometry;
3584     // move the window to have the same relative position to the center of the screen
3585     // (i.e. one near the middle of the right edge will also end up near the middle of the right edge)
3586     QPointF center = geometry.center() - oldArea.center();
3587     center.setX(center.x() * newArea.width() / oldArea.width());
3588     center.setY(center.y() * newArea.height() / oldArea.height());
3589     center += newArea.center();
3590     ret.moveCenter(center);
3591 
3592     // If the window was inside the old screen area, explicitly make sure its inside also the new screen area
3593     if (oldArea.contains(geometry)) {
3594         ret = keepInArea(ret, newArea);
3595     }
3596     return ret;
3597 }
3598 
3599 QRectF Window::ensureSpecialStateGeometry(const QRectF &geometry)
3600 {
3601     if (isRequestedFullScreen()) {
3602         return workspace()->clientArea(FullScreenArea, this, geometry.center());
3603     } else if (requestedMaximizeMode() != MaximizeRestore) {
3604         const QRectF maximizeArea = workspace()->clientArea(MaximizeArea, this, geometry.center());
3605         QRectF ret = geometry;
3606         if (requestedMaximizeMode() & MaximizeHorizontal) {
3607             ret.setX(maximizeArea.x());
3608             ret.setWidth(maximizeArea.width());
3609         }
3610         if (requestedMaximizeMode() & MaximizeVertical) {
3611             ret.setY(maximizeArea.y());
3612             ret.setHeight(maximizeArea.height());
3613         }
3614         return ret;
3615     } else if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) {
3616         return quickTileGeometry(quickTileMode(), geometry.center());
3617     } else {
3618         return geometry;
3619     }
3620 }
3621 
3622 void Window::sendToOutput(Output *newOutput)
3623 {
3624     newOutput = rules()->checkOutput(newOutput);
3625     if (isActive()) {
3626         workspace()->setActiveOutput(newOutput);
3627         // might impact the layer of a fullscreen window
3628         const auto windows = workspace()->windows();
3629         for (Window *other : windows) {
3630             if (other->isFullScreen() && other->output() == newOutput) {
3631                 other->updateLayer();
3632             }
3633         }
3634     }
3635     if (moveResizeOutput() == newOutput) {
3636         return;
3637     }
3638 
3639     const QRectF oldGeom = moveResizeGeometry();
3640     const QRectF oldScreenArea = workspace()->clientArea(MaximizeArea, this, moveResizeOutput());
3641     const QRectF screenArea = workspace()->clientArea(MaximizeArea, this, newOutput);
3642 
3643     if (m_quickTileMode == QuickTileMode(QuickTileFlag::Custom)) {
3644         setTile(nullptr);
3645     }
3646 
3647     QRectF newGeom = moveToArea(oldGeom, oldScreenArea, screenArea);
3648     newGeom = ensureSpecialStateGeometry(newGeom);
3649     moveResize(newGeom);
3650 
3651     // move geometry restores to the new output as well
3652     m_fullscreenGeometryRestore = moveToArea(m_fullscreenGeometryRestore, oldScreenArea, screenArea);
3653     m_maximizeGeometryRestore = moveToArea(m_maximizeGeometryRestore, oldScreenArea, screenArea);
3654 
3655     auto tso = workspace()->ensureStackingOrder(transients());
3656     for (auto it = tso.constBegin(), end = tso.constEnd(); it != end; ++it) {
3657         (*it)->sendToOutput(newOutput);
3658     }
3659 }
3660 
3661 void Window::checkWorkspacePosition(QRectF oldGeometry, const VirtualDesktop *oldDesktop)
3662 {
3663     if (isDeleted()) {
3664         qCWarning(KWIN_CORE) << "Window::checkWorkspacePosition: called for a closed window. Consider this a bug";
3665         return;
3666     }
3667     if (isDock() || isDesktop() || !isPlaceable()) {
3668         return;
3669     }
3670 
3671     QRectF newGeom = moveResizeGeometry();
3672 
3673     if (!oldGeometry.isValid()) {
3674         oldGeometry = newGeom;
3675     }
3676 
3677     VirtualDesktop *desktop = !isOnCurrentDesktop() ? desktops().constLast() : VirtualDesktopManager::self()->currentDesktop();
3678     if (!oldDesktop) {
3679         oldDesktop = desktop;
3680     }
3681 
3682     // If the window was touching an edge before but not now move it so it is again.
3683     // Old and new maximums have different starting values so windows on the screen
3684     // edge will move when a new strut is placed on the edge.
3685     QRect oldScreenArea;
3686     QRect screenArea;
3687     if (workspace()->inUpdateClientArea()) {
3688         // check if the window is on an about to be destroyed output
3689         Output *newOutput = moveResizeOutput();
3690         if (!workspace()->outputs().contains(newOutput)) {
3691             newOutput = workspace()->outputAt(newGeom.center());
3692         }
3693         // we need to find the screen area as it was before the change
3694         oldScreenArea = workspace()->previousScreenSizes().value(moveResizeOutput());
3695         if (oldScreenArea.isNull()) {
3696             oldScreenArea = newOutput->geometry();
3697         }
3698         screenArea = newOutput->geometry();
3699         newGeom.translate(screenArea.topLeft() - oldScreenArea.topLeft());
3700     } else {
3701         oldScreenArea = workspace()->clientArea(ScreenArea, workspace()->outputAt(oldGeometry.center()), oldDesktop).toRect();
3702         screenArea = workspace()->clientArea(ScreenArea, this, newGeom.center()).toRect();
3703     }
3704 
3705     if (isRequestedFullScreen() || requestedMaximizeMode() != MaximizeRestore || quickTileMode() != QuickTileMode(QuickTileFlag::None)) {
3706         moveResize(ensureSpecialStateGeometry(newGeom));
3707         m_fullscreenGeometryRestore = moveToArea(m_fullscreenGeometryRestore, oldScreenArea, screenArea);
3708         m_maximizeGeometryRestore = moveToArea(m_maximizeGeometryRestore, oldScreenArea, screenArea);
3709         return;
3710     }
3711 
3712     const QRect oldGeomTall = QRect(oldGeometry.x(), oldScreenArea.y(), oldGeometry.width(), oldScreenArea.height()); // Full screen height
3713     const QRect oldGeomWide = QRect(oldScreenArea.x(), oldGeometry.y(), oldScreenArea.width(), oldGeometry.height()); // Full screen width
3714     int oldTopMax = oldScreenArea.y();
3715     int oldRightMax = oldScreenArea.x() + oldScreenArea.width();
3716     int oldBottomMax = oldScreenArea.y() + oldScreenArea.height();
3717     int oldLeftMax = oldScreenArea.x();
3718     int topMax = screenArea.y();
3719     int rightMax = screenArea.x() + screenArea.width();
3720     int bottomMax = screenArea.y() + screenArea.height();
3721     int leftMax = screenArea.x();
3722     const QRect newGeomTall = QRect(newGeom.x(), screenArea.y(), newGeom.width(), screenArea.height()); // Full screen height
3723     const QRect newGeomWide = QRect(screenArea.x(), newGeom.y(), screenArea.width(), newGeom.height()); // Full screen width
3724     // Get the max strut point for each side where the window is (E.g. Highest point for
3725     // the bottom struts bounded by the window's left and right sides).
3726 
3727     // These 4 compute old bounds ...
3728     auto moveAreaFunc = workspace()->inUpdateClientArea() ? &Workspace::previousRestrictedMoveArea : //... the restricted areas changed
3729         &Workspace::restrictedMoveArea; //... when e.g. active desktop or screen changes
3730 
3731     for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaTop)) {
3732         QRect rect = r & oldGeomTall;
3733         if (!rect.isEmpty()) {
3734             oldTopMax = std::max(oldTopMax, rect.y() + rect.height());
3735         }
3736     }
3737     for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaRight)) {
3738         QRect rect = r & oldGeomWide;
3739         if (!rect.isEmpty()) {
3740             oldRightMax = std::min(oldRightMax, rect.x());
3741         }
3742     }
3743     for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaBottom)) {
3744         QRect rect = r & oldGeomTall;
3745         if (!rect.isEmpty()) {
3746             oldBottomMax = std::min(oldBottomMax, rect.y());
3747         }
3748     }
3749     for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaLeft)) {
3750         QRect rect = r & oldGeomWide;
3751         if (!rect.isEmpty()) {
3752             oldLeftMax = std::max(oldLeftMax, rect.x() + rect.width());
3753         }
3754     }
3755 
3756     // These 4 compute new bounds
3757     for (const QRect &r : workspace()->restrictedMoveArea(desktop, StrutAreaTop)) {
3758         QRect rect = r & newGeomTall;
3759         if (!rect.isEmpty()) {
3760             topMax = std::max(topMax, rect.y() + rect.height());
3761         }
3762     }
3763     for (const QRect &r : workspace()->restrictedMoveArea(desktop, StrutAreaRight)) {
3764         QRect rect = r & newGeomWide;
3765         if (!rect.isEmpty()) {
3766             rightMax = std::min(rightMax, rect.x());
3767         }
3768     }
3769     for (const QRect &r : workspace()->restrictedMoveArea(desktop, StrutAreaBottom)) {
3770         QRect rect = r & newGeomTall;
3771         if (!rect.isEmpty()) {
3772             bottomMax = std::min(bottomMax, rect.y());
3773         }
3774     }
3775     for (const QRect &r : workspace()->restrictedMoveArea(desktop, StrutAreaLeft)) {
3776         QRect rect = r & newGeomWide;
3777         if (!rect.isEmpty()) {
3778             leftMax = std::max(leftMax, rect.x() + rect.width());
3779         }
3780     }
3781 
3782     // Check if the sides were inside or touching but are no longer
3783     enum {
3784         Left = 0,
3785         Top,
3786         Right,
3787         Bottom,
3788     };
3789     bool keep[4] = {false, false, false, false};
3790     bool save[4] = {false, false, false, false};
3791     if (oldGeometry.x() >= oldLeftMax) {
3792         save[Left] = newGeom.x() < leftMax;
3793     }
3794     if (oldGeometry.x() == oldLeftMax) {
3795         keep[Left] = newGeom.x() != leftMax;
3796     }
3797 
3798     if (oldGeometry.y() >= oldTopMax) {
3799         save[Top] = newGeom.y() < topMax;
3800     }
3801     if (oldGeometry.y() == oldTopMax) {
3802         keep[Top] = newGeom.y() != topMax;
3803     }
3804 
3805     if (oldGeometry.right() <= oldRightMax) {
3806         save[Right] = newGeom.right() > rightMax;
3807     }
3808     if (oldGeometry.right() == oldRightMax) {
3809         keep[Right] = newGeom.right() != rightMax;
3810     }
3811 
3812     if (oldGeometry.bottom() <= oldBottomMax) {
3813         save[Bottom] = newGeom.bottom() > bottomMax;
3814     }
3815     if (oldGeometry.bottom() == oldBottomMax) {
3816         keep[Bottom] = newGeom.bottom() != bottomMax;
3817     }
3818 
3819     // if randomly touches opposing edges, do not favor either
3820     if (keep[Left] && keep[Right]) {
3821         keep[Left] = keep[Right] = false;
3822     }
3823     if (keep[Top] && keep[Bottom]) {
3824         keep[Top] = keep[Bottom] = false;
3825     }
3826 
3827     if (save[Left] || keep[Left]) {
3828         newGeom.moveLeft(std::max(leftMax, screenArea.x()));
3829     }
3830     if (save[Top] || keep[Top]) {
3831         newGeom.moveTop(std::max(topMax, screenArea.y()));
3832     }
3833     if (save[Right] || keep[Right]) {
3834         newGeom.moveRight(std::min(rightMax, screenArea.right()) + 1);
3835     }
3836     if (save[Bottom] || keep[Bottom]) {
3837         newGeom.moveBottom(std::min(bottomMax, screenArea.bottom()) + 1);
3838     }
3839 
3840     if (oldGeometry.x() >= oldLeftMax && newGeom.x() < leftMax) {
3841         newGeom.setLeft(std::max(leftMax, screenArea.x()));
3842     }
3843     if (oldGeometry.y() >= oldTopMax && newGeom.y() < topMax) {
3844         newGeom.setTop(std::max(topMax, screenArea.y()));
3845     }
3846 
3847     checkOffscreenPosition(&newGeom, screenArea);
3848     // Obey size hints. TODO: We really should make sure it stays in the right place
3849     if (!isShade()) {
3850         newGeom.setSize(constrainFrameSize(newGeom.size()));
3851     }
3852 
3853     moveResize(newGeom);
3854 }
3855 
3856 void Window::checkOffscreenPosition(QRectF *geom, const QRectF &screenArea)
3857 {
3858     if (geom->left() > screenArea.right()) {
3859         geom->moveLeft(screenArea.right() - screenArea.width() / 4);
3860     } else if (geom->right() < screenArea.left()) {
3861         geom->moveRight(screenArea.left() + screenArea.width() / 4);
3862     }
3863     if (geom->top() > screenArea.bottom()) {
3864         geom->moveTop(screenArea.bottom() - screenArea.height() / 4);
3865     } else if (geom->bottom() < screenArea.top()) {
3866         geom->moveBottom(screenArea.top() + screenArea.width() / 4);
3867     }
3868 }
3869 
3870 /**
3871  * Constrains the client size @p size according to a set of the window's size hints.
3872  *
3873  * Default implementation applies only minimum and maximum size constraints.
3874  */
3875 QSizeF Window::constrainClientSize(const QSizeF &size, SizeMode mode) const
3876 {
3877     qreal width = size.width();
3878     qreal height = size.height();
3879 
3880     // When user is resizing the window, the move resize geometry may have negative width or
3881     // height. In which case, we need to set negative dimensions to reasonable values.
3882     if (width < 1) {
3883         width = 1;
3884     }
3885     if (height < 1) {
3886         height = 1;
3887     }
3888 
3889     const QSizeF minimumSize = minSize();
3890     const QSizeF maximumSize = maxSize();
3891 
3892     width = std::clamp(width, minimumSize.width(), maximumSize.width());
3893     height = std::clamp(height, minimumSize.height(), maximumSize.height());
3894 
3895     return QSizeF(width, height);
3896 }
3897 
3898 /**
3899  * Constrains the frame size @p size according to a set of the window's size hints.
3900  */
3901 QSizeF Window::constrainFrameSize(const QSizeF &size, SizeMode mode) const
3902 {
3903     const QSizeF unconstrainedClientSize = frameSizeToClientSize(size);
3904     const QSizeF constrainedClientSize = constrainClientSize(unconstrainedClientSize, mode);
3905     return clientSizeToFrameSize(constrainedClientSize);
3906 }
3907 
3908 QRectF Window::fullscreenGeometryRestore() const
3909 {
3910     return m_fullscreenGeometryRestore;
3911 }
3912 
3913 void Window::setFullscreenGeometryRestore(const QRectF &geom)
3914 {
3915     m_fullscreenGeometryRestore = geom;
3916 }
3917 
3918 /**
3919  * Returns @c true if the Window can be shown in full screen mode; otherwise @c false.
3920  *
3921  * Default implementation returns @c false.
3922  */
3923 bool Window::isFullScreenable() const
3924 {
3925     return false;
3926 }
3927 
3928 /**
3929  * Returns @c true if the Window is currently being shown in full screen mode; otherwise @c false.
3930  *
3931  * A window in full screen mode occupies the entire screen with no window frame around it.
3932  *
3933  * Default implementation returns @c false.
3934  */
3935 bool Window::isFullScreen() const
3936 {
3937     return false;
3938 }
3939 
3940 bool Window::isRequestedFullScreen() const
3941 {
3942     return isFullScreen();
3943 }
3944 
3945 /**
3946  * Asks the Window to enter or leave full screen mode.
3947  *
3948  * Default implementation does nothing.
3949  *
3950  * @param set @c true if the Window has to be shown in full screen mode, otherwise @c false
3951  */
3952 void Window::setFullScreen(bool set)
3953 {
3954     qCWarning(KWIN_CORE, "%s doesn't support setting fullscreen state", metaObject()->className());
3955 }
3956 
3957 /**
3958  * Returns @c true if the Window can be minimized; otherwise @c false.
3959  *
3960  * Default implementation returns @c false.
3961  */
3962 bool Window::isMinimizable() const
3963 {
3964     return false;
3965 }
3966 
3967 /**
3968  * Returns @c true if the Window can be maximized; otherwise @c false.
3969  *
3970  * Default implementation returns @c false.
3971  */
3972 bool Window::isMaximizable() const
3973 {
3974     return false;
3975 }
3976 
3977 /**
3978  * Returns the currently applied maximize mode.
3979  *
3980  * Default implementation returns MaximizeRestore.
3981  */
3982 MaximizeMode Window::maximizeMode() const
3983 {
3984     return MaximizeRestore;
3985 }
3986 
3987 /**
3988  * Returns the last requested maximize mode.
3989  *
3990  * On X11, this method always matches maximizeMode(). On Wayland, it is asynchronous.
3991  *
3992  * Default implementation matches maximizeMode().
3993  */
3994 MaximizeMode Window::requestedMaximizeMode() const
3995 {
3996     return maximizeMode();
3997 }
3998 
3999 /**
4000  * Returns the geometry of the Window before it was maximized or quick tiled.
4001  */
4002 QRectF Window::geometryRestore() const
4003 {
4004     return m_maximizeGeometryRestore;
4005 }
4006 
4007 /**
4008  * Sets the geometry of the Window before it was maximized or quick tiled to @p rect.
4009  */
4010 void Window::setGeometryRestore(const QRectF &rect)
4011 {
4012     m_maximizeGeometryRestore = rect;
4013 }
4014 
4015 void Window::invalidateDecoration()
4016 {
4017 }
4018 
4019 bool Window::noBorder() const
4020 {
4021     return true;
4022 }
4023 
4024 bool Window::userCanSetNoBorder() const
4025 {
4026     return false;
4027 }
4028 
4029 void Window::setNoBorder(bool set)
4030 {
4031     qCWarning(KWIN_CORE, "%s doesn't support setting decorations", metaObject()->className());
4032 }
4033 
4034 void Window::checkNoBorder()
4035 {
4036     setNoBorder(false);
4037 }
4038 
4039 void Window::showOnScreenEdge()
4040 {
4041     qCWarning(KWIN_CORE, "%s doesn't support screen edge activation", metaObject()->className());
4042 }
4043 
4044 bool Window::isPlaceable() const
4045 {
4046     return true;
4047 }
4048 
4049 void Window::cleanTabBox()
4050 {
4051 #if KWIN_BUILD_TABBOX
4052     TabBox::TabBox *tabBox = workspace()->tabbox();
4053     if (tabBox && tabBox->isDisplayed() && tabBox->currentClient() == this) {
4054         tabBox->nextPrev(true);
4055     }
4056 #endif
4057 }
4058 
4059 bool Window::supportsWindowRules() const
4060 {
4061     return false;
4062 }
4063 
4064 void Window::removeRule(Rules *rule)
4065 {
4066     m_rules.remove(rule);
4067 }
4068 
4069 void Window::evaluateWindowRules()
4070 {
4071     setupWindowRules();
4072     applyWindowRules();
4073 }
4074 
4075 void Window::setupWindowRules()
4076 {
4077     disconnect(this, &Window::captionNormalChanged, this, &Window::evaluateWindowRules);
4078     m_rules = workspace()->rulebook()->find(this);
4079     // check only after getting the rules, because there may be a rule forcing window type
4080 }
4081 
4082 void Window::updateWindowRules(Rules::Types selection)
4083 {
4084     if (workspace()->rulebook()->areUpdatesDisabled()) {
4085         return;
4086     }
4087     m_rules.update(this, selection);
4088 }
4089 
4090 void Window::finishWindowRules()
4091 {
4092     disconnect(this, &Window::captionNormalChanged, this, &Window::evaluateWindowRules);
4093     updateWindowRules(Rules::All);
4094     m_rules = WindowRules();
4095 }
4096 
4097 // Applies Force, ForceTemporarily and ApplyNow rules
4098 // Used e.g. after the rules have been modified using the kcm.
4099 void Window::applyWindowRules()
4100 {
4101     Q_ASSERT(!isDeleted());
4102     // apply force rules
4103     // Placement - does need explicit update, just like some others below
4104     // Geometry : setGeometry() doesn't check rules
4105     auto client_rules = rules();
4106     const QRectF oldGeometry = moveResizeGeometry();
4107     const QRectF geometry = client_rules->checkGeometrySafe(oldGeometry);
4108     if (geometry != oldGeometry) {
4109         moveResize(geometry);
4110     }
4111     // MinSize, MaxSize handled by Geometry
4112     // IgnoreGeometry
4113     setDesktops(desktops());
4114     workspace()->sendWindowToOutput(this, moveResizeOutput());
4115     setOnActivities(activities());
4116     // Type
4117     maximize(requestedMaximizeMode());
4118     setMinimized(isMinimized());
4119     setShade(shadeMode());
4120     setOriginalSkipTaskbar(skipTaskbar());
4121     setSkipPager(skipPager());
4122     setSkipSwitcher(skipSwitcher());
4123     setKeepAbove(keepAbove());
4124     setKeepBelow(keepBelow());
4125     setFullScreen(isRequestedFullScreen());
4126     setNoBorder(noBorder());
4127     updateColorScheme();
4128     updateLayer();
4129     // FSP
4130     // AcceptFocus :
4131     if (workspace()->mostRecentlyActivatedWindow() == this
4132         && !client_rules->checkAcceptFocus(true)) {
4133         workspace()->activateNextWindow(this);
4134     }
4135     // Autogrouping : Only checked on window manage
4136     // AutogroupInForeground : Only checked on window manage
4137     // AutogroupById : Only checked on window manage
4138     // StrictGeometry
4139     setShortcut(rules()->checkShortcut(shortcut().toString()));
4140     // see also X11Window::setActive()
4141     if (isActive()) {
4142         setOpacity(rules()->checkOpacityActive(qRound(opacity() * 100.0)) / 100.0);
4143         workspace()->disableGlobalShortcutsForClient(rules()->checkDisableGlobalShortcuts(false));
4144     } else {
4145         setOpacity(rules()->checkOpacityInactive(qRound(opacity() * 100.0)) / 100.0);
4146     }
4147     setDesktopFileName(rules()->checkDesktopFile(desktopFileName()));
4148 }
4149 
4150 void Window::setLastUsageSerial(quint32 serial)
4151 {
4152     if (m_lastUsageSerial < serial) {
4153         m_lastUsageSerial = serial;
4154     }
4155 }
4156 
4157 quint32 Window::lastUsageSerial() const
4158 {
4159     return m_lastUsageSerial;
4160 }
4161 
4162 uint32_t Window::interactiveMoveResizeCount() const
4163 {
4164     return m_interactiveMoveResize.counter;
4165 }
4166 
4167 void Window::setLockScreenOverlay(bool allowed)
4168 {
4169     if (m_lockScreenOverlay == allowed) {
4170         return;
4171     }
4172     m_lockScreenOverlay = allowed;
4173     Q_EMIT lockScreenOverlayChanged();
4174 }
4175 
4176 bool Window::isLockScreenOverlay() const
4177 {
4178     return m_lockScreenOverlay;
4179 }
4180 
4181 void Window::refOffscreenRendering()
4182 {
4183     if (m_offscreenRenderCount == 0) {
4184         m_offscreenFramecallbackTimer.start(1'000'000 / output()->refreshRate());
4185     }
4186     m_offscreenRenderCount++;
4187 }
4188 
4189 void Window::unrefOffscreenRendering()
4190 {
4191     Q_ASSERT(m_offscreenRenderCount);
4192     m_offscreenRenderCount--;
4193     if (m_offscreenRenderCount == 0) {
4194         m_offscreenFramecallbackTimer.stop();
4195     }
4196 }
4197 
4198 void Window::maybeSendFrameCallback()
4199 {
4200     if (m_surface && !m_windowItem->isVisible()) {
4201         const auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
4202         m_surface->traverseTree([this, &timestamp](SurfaceInterface *surface) {
4203             surface->frameRendered(timestamp);
4204             const auto feedback = surface->takePresentationFeedback(nullptr);
4205             if (feedback) {
4206                 feedback->presented(std::chrono::nanoseconds(1'000'000'000'000 / output()->refreshRate()), std::chrono::steady_clock::now().time_since_epoch(), PresentationMode::VSync);
4207             }
4208         });
4209         // update refresh rate, it might have changed
4210         m_offscreenFramecallbackTimer.start(1'000'000 / output()->refreshRate());
4211     }
4212 }
4213 
4214 WindowOffscreenRenderRef::WindowOffscreenRenderRef(Window *window)
4215     : m_window(window)
4216 {
4217     window->refOffscreenRendering();
4218 }
4219 
4220 WindowOffscreenRenderRef::~WindowOffscreenRenderRef()
4221 {
4222     if (m_window) {
4223         m_window->unrefOffscreenRendering();
4224     }
4225 }
4226 
4227 bool Window::isShown() const
4228 {
4229     return !isDeleted() && !isHidden() && !isHiddenByShowDesktop() && !isMinimized();
4230 }
4231 
4232 bool Window::isHidden() const
4233 {
4234     return m_hidden;
4235 }
4236 
4237 void Window::setHidden(bool hidden)
4238 {
4239     if (m_hidden == hidden) {
4240         return;
4241     }
4242     m_hidden = hidden;
4243     doSetHidden();
4244     if (hidden) {
4245         workspace()->windowHidden(this);
4246         Q_EMIT windowHidden(this);
4247     } else {
4248         Q_EMIT windowShown(this);
4249     }
4250 }
4251 
4252 bool Window::isHiddenByShowDesktop() const
4253 {
4254     return m_hiddenByShowDesktop;
4255 }
4256 
4257 void Window::setHiddenByShowDesktop(bool hidden)
4258 {
4259     if (m_hiddenByShowDesktop != hidden) {
4260         m_hiddenByShowDesktop = hidden;
4261         doSetHiddenByShowDesktop();
4262         Q_EMIT hiddenByShowDesktopChanged();
4263     }
4264 }
4265 
4266 bool Window::isSuspended() const
4267 {
4268     return m_suspended;
4269 }
4270 
4271 void Window::setSuspended(bool suspended)
4272 {
4273     if (isDeleted()) {
4274         return;
4275     }
4276     if (m_suspended != suspended) {
4277         m_suspended = suspended;
4278         doSetSuspended();
4279     }
4280 }
4281 
4282 void Window::doSetSuspended()
4283 {
4284 }
4285 
4286 } // namespace KWin
4287 
4288 #include "moc_window.cpp"