File indexing completed on 2024-04-28 16:49:08

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