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

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: 2018 David Edmundson <davidedmundson@kde.org>
0007     SPDX-FileCopyrightText: 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 #include "xdgshellwindow.h"
0012 #include "core/output.h"
0013 #if KWIN_BUILD_ACTIVITIES
0014 #include "activities.h"
0015 #endif
0016 #include "decorations/decorationbridge.h"
0017 #include "deleted.h"
0018 #include "placement.h"
0019 #include "pointer_input.h"
0020 #include "screenedge.h"
0021 #include "touch_input.h"
0022 #include "utils/subsurfacemonitor.h"
0023 #include "virtualdesktops.h"
0024 #include "wayland/appmenu_interface.h"
0025 #include "wayland/output_interface.h"
0026 #include "wayland/plasmashell_interface.h"
0027 #include "wayland/seat_interface.h"
0028 #include "wayland/server_decoration_interface.h"
0029 #include "wayland/server_decoration_palette_interface.h"
0030 #include "wayland/surface_interface.h"
0031 #include "wayland/xdgdecoration_v1_interface.h"
0032 #include "wayland_server.h"
0033 #include "workspace.h"
0034 
0035 #include <KDecoration2/DecoratedClient>
0036 #include <KDecoration2/Decoration>
0037 
0038 using namespace KWaylandServer;
0039 
0040 namespace KWin
0041 {
0042 
0043 XdgSurfaceWindow::XdgSurfaceWindow(XdgSurfaceInterface *shellSurface)
0044     : WaylandWindow(shellSurface->surface())
0045     , m_shellSurface(shellSurface)
0046     , m_configureTimer(new QTimer(this))
0047 {
0048     setupPlasmaShellIntegration();
0049     connect(shellSurface, &XdgSurfaceInterface::configureAcknowledged,
0050             this, &XdgSurfaceWindow::handleConfigureAcknowledged);
0051     connect(shellSurface, &XdgSurfaceInterface::resetOccurred,
0052             this, &XdgSurfaceWindow::destroyWindow);
0053     connect(shellSurface->surface(), &SurfaceInterface::committed,
0054             this, &XdgSurfaceWindow::handleCommit);
0055 #if 0 // TODO: Refactor kwin core in order to uncomment this code.
0056     connect(shellSurface->surface(), &SurfaceInterface::mapped,
0057             this, &XdgSurfaceWindow::setReadyForPainting);
0058 #endif
0059     connect(shellSurface, &XdgSurfaceInterface::aboutToBeDestroyed,
0060             this, &XdgSurfaceWindow::destroyWindow);
0061     connect(shellSurface->surface(), &SurfaceInterface::aboutToBeDestroyed,
0062             this, &XdgSurfaceWindow::destroyWindow);
0063 
0064     // The effective window geometry is determined by two things: (a) the rectangle that bounds
0065     // the main surface and all of its sub-surfaces, (b) the client-specified window geometry, if
0066     // any. If the client hasn't provided the window geometry, we fallback to the bounding sub-
0067     // surface rectangle. If the client has provided the window geometry, we intersect it with
0068     // the bounding rectangle and that will be the effective window geometry. It's worth to point
0069     // out that geometry updates do not occur that frequently, so we don't need to recompute the
0070     // bounding geometry every time the client commits the surface.
0071 
0072     SubSurfaceMonitor *treeMonitor = new SubSurfaceMonitor(surface(), this);
0073 
0074     connect(treeMonitor, &SubSurfaceMonitor::subSurfaceAdded,
0075             this, &XdgSurfaceWindow::setHaveNextWindowGeometry);
0076     connect(treeMonitor, &SubSurfaceMonitor::subSurfaceRemoved,
0077             this, &XdgSurfaceWindow::setHaveNextWindowGeometry);
0078     connect(treeMonitor, &SubSurfaceMonitor::subSurfaceMoved,
0079             this, &XdgSurfaceWindow::setHaveNextWindowGeometry);
0080     connect(treeMonitor, &SubSurfaceMonitor::subSurfaceResized,
0081             this, &XdgSurfaceWindow::setHaveNextWindowGeometry);
0082     connect(shellSurface, &XdgSurfaceInterface::windowGeometryChanged,
0083             this, &XdgSurfaceWindow::setHaveNextWindowGeometry);
0084     connect(surface(), &SurfaceInterface::sizeChanged,
0085             this, &XdgSurfaceWindow::setHaveNextWindowGeometry);
0086 
0087     // Configure events are not sent immediately, but rather scheduled to be sent when the event
0088     // loop is about to be idle. By doing this, we can avoid sending configure events that do
0089     // nothing, and implementation-wise, it's simpler.
0090 
0091     m_configureTimer->setSingleShot(true);
0092     connect(m_configureTimer, &QTimer::timeout, this, &XdgSurfaceWindow::sendConfigure);
0093 }
0094 
0095 XdgSurfaceWindow::~XdgSurfaceWindow()
0096 {
0097     qDeleteAll(m_configureEvents);
0098 }
0099 
0100 NET::WindowType XdgSurfaceWindow::windowType(bool direct, int supported_types) const
0101 {
0102     return m_windowType;
0103 }
0104 
0105 QRectF XdgSurfaceWindow::inputGeometry() const
0106 {
0107     return isDecorated() ? Window::inputGeometry() : bufferGeometry();
0108 }
0109 
0110 QMatrix4x4 XdgSurfaceWindow::inputTransformation() const
0111 {
0112     QMatrix4x4 transformation;
0113     transformation.translate(-bufferGeometry().x(), -bufferGeometry().y());
0114     return transformation;
0115 }
0116 
0117 XdgSurfaceConfigure *XdgSurfaceWindow::lastAcknowledgedConfigure() const
0118 {
0119     return m_lastAcknowledgedConfigure.get();
0120 }
0121 
0122 void XdgSurfaceWindow::scheduleConfigure()
0123 {
0124     if (!isZombie()) {
0125         m_configureTimer->start();
0126     }
0127 }
0128 
0129 void XdgSurfaceWindow::sendConfigure()
0130 {
0131     XdgSurfaceConfigure *configureEvent = sendRoleConfigure();
0132 
0133     // The configure event inherits configure flags from the previous event.
0134     if (!m_configureEvents.isEmpty()) {
0135         const XdgSurfaceConfigure *previousEvent = m_configureEvents.constLast();
0136         configureEvent->flags = previousEvent->flags;
0137     }
0138 
0139     configureEvent->gravity = m_nextGravity;
0140     configureEvent->flags |= m_configureFlags;
0141     m_configureFlags = {};
0142 
0143     m_configureEvents.append(configureEvent);
0144 }
0145 
0146 void XdgSurfaceWindow::handleConfigureAcknowledged(quint32 serial)
0147 {
0148     m_lastAcknowledgedConfigureSerial = serial;
0149 }
0150 
0151 void XdgSurfaceWindow::handleCommit()
0152 {
0153     if (!surface()->buffer()) {
0154         return;
0155     }
0156 
0157     if (m_lastAcknowledgedConfigureSerial.has_value()) {
0158         const quint32 serial = m_lastAcknowledgedConfigureSerial.value();
0159         while (!m_configureEvents.isEmpty()) {
0160             if (serial < m_configureEvents.constFirst()->serial) {
0161                 break;
0162             }
0163             m_lastAcknowledgedConfigure.reset(m_configureEvents.takeFirst());
0164         }
0165     }
0166 
0167     handleRolePrecommit();
0168     if (haveNextWindowGeometry()) {
0169         handleNextWindowGeometry();
0170         resetHaveNextWindowGeometry();
0171     }
0172 
0173     handleRoleCommit();
0174     m_lastAcknowledgedConfigure.reset();
0175     m_lastAcknowledgedConfigureSerial.reset();
0176 
0177     setReadyForPainting();
0178     updateDepth();
0179 }
0180 
0181 void XdgSurfaceWindow::handleRolePrecommit()
0182 {
0183 }
0184 
0185 void XdgSurfaceWindow::handleRoleCommit()
0186 {
0187 }
0188 
0189 void XdgSurfaceWindow::maybeUpdateMoveResizeGeometry(const QRectF &rect)
0190 {
0191     // We are about to send a configure event, ignore the committed window geometry.
0192     if (m_configureTimer->isActive()) {
0193         return;
0194     }
0195 
0196     // If there are unacknowledged configure events that change the geometry, don't sync
0197     // the move resize geometry in order to avoid rolling back to old state. When the last
0198     // configure event is acknowledged, the move resize geometry will be synchronized.
0199     for (int i = m_configureEvents.count() - 1; i >= 0; --i) {
0200         if (m_configureEvents[i]->flags & XdgSurfaceConfigure::ConfigurePosition) {
0201             return;
0202         }
0203     }
0204 
0205     setMoveResizeGeometry(rect);
0206 }
0207 
0208 void XdgSurfaceWindow::handleNextWindowGeometry()
0209 {
0210     const QRectF boundingGeometry = surface()->boundingRect();
0211 
0212     // The effective window geometry is defined as the intersection of the window geometry
0213     // and the rectangle that bounds the main surface and all of its sub-surfaces. If the
0214     // client hasn't specified the window geometry, we must fallback to the bounding geometry.
0215     // Note that the xdg-shell spec is not clear about when exactly we have to clamp the
0216     // window geometry.
0217 
0218     m_windowGeometry = m_shellSurface->windowGeometry();
0219     if (m_windowGeometry.isValid()) {
0220         m_windowGeometry &= boundingGeometry;
0221     } else {
0222         m_windowGeometry = boundingGeometry;
0223     }
0224 
0225     if (m_windowGeometry.isEmpty()) {
0226         qCWarning(KWIN_CORE) << "Committed empty window geometry, dealing with a buggy client!";
0227     }
0228 
0229     QRectF frameGeometry(pos(), clientSizeToFrameSize(m_windowGeometry.size()));
0230     if (const XdgSurfaceConfigure *configureEvent = lastAcknowledgedConfigure()) {
0231         if (configureEvent->flags & XdgSurfaceConfigure::ConfigurePosition) {
0232             frameGeometry = gravitateGeometry(frameGeometry, configureEvent->bounds, configureEvent->gravity);
0233         }
0234     }
0235 
0236     if (!isInteractiveMoveResize()) {
0237         // Both the compositor and the client can change the window geometry. If the client
0238         // sets a new window geometry, the compositor's move-resize geometry will be invalid.
0239         maybeUpdateMoveResizeGeometry(frameGeometry);
0240     }
0241 
0242     updateGeometry(frameGeometry);
0243 }
0244 
0245 bool XdgSurfaceWindow::haveNextWindowGeometry() const
0246 {
0247     return m_haveNextWindowGeometry || m_lastAcknowledgedConfigure;
0248 }
0249 
0250 void XdgSurfaceWindow::setHaveNextWindowGeometry()
0251 {
0252     m_haveNextWindowGeometry = true;
0253 }
0254 
0255 void XdgSurfaceWindow::resetHaveNextWindowGeometry()
0256 {
0257     m_haveNextWindowGeometry = false;
0258 }
0259 
0260 void XdgSurfaceWindow::moveResizeInternal(const QRectF &rect, MoveResizeMode mode)
0261 {
0262     if (areGeometryUpdatesBlocked()) {
0263         setPendingMoveResizeMode(mode);
0264         return;
0265     }
0266 
0267     Q_EMIT frameGeometryAboutToChange(this);
0268 
0269     if (mode != MoveResizeMode::Move) {
0270         const QSizeF requestedClientSize = frameSizeToClientSize(rect.size());
0271         if (requestedClientSize == clientSize()) {
0272             updateGeometry(rect);
0273         } else {
0274             m_configureFlags |= XdgSurfaceConfigure::ConfigurePosition;
0275             scheduleConfigure();
0276         }
0277     } else {
0278         // If the window is moved, cancel any queued window position updates.
0279         for (XdgSurfaceConfigure *configureEvent : std::as_const(m_configureEvents)) {
0280             configureEvent->flags.setFlag(XdgSurfaceConfigure::ConfigurePosition, false);
0281         }
0282         m_configureFlags.setFlag(XdgSurfaceConfigure::ConfigurePosition, false);
0283         updateGeometry(QRectF(rect.topLeft(), size()));
0284     }
0285 }
0286 
0287 QRectF XdgSurfaceWindow::frameRectToBufferRect(const QRectF &rect) const
0288 {
0289     const qreal left = rect.left() + borderLeft() - m_windowGeometry.left();
0290     const qreal top = rect.top() + borderTop() - m_windowGeometry.top();
0291     return QRectF(QPoint(left, top), surface()->size());
0292 }
0293 
0294 void XdgSurfaceWindow::destroyWindow()
0295 {
0296     markAsZombie();
0297     if (isInteractiveMoveResize()) {
0298         leaveInteractiveMoveResize();
0299         Q_EMIT clientFinishUserMovedResized(this);
0300     }
0301     m_configureTimer->stop();
0302     cleanTabBox();
0303     Deleted *deleted = Deleted::create(this);
0304     Q_EMIT windowClosed(this, deleted);
0305     StackingUpdatesBlocker blocker(workspace());
0306     workspace()->rulebook()->discardUsed(this, true);
0307     setDecoration(nullptr);
0308     cleanGrouping();
0309     waylandServer()->removeWindow(this);
0310     deleted->unrefWindow();
0311     delete this;
0312 }
0313 
0314 void XdgSurfaceWindow::updateClientArea()
0315 {
0316     if (hasStrut()) {
0317         workspace()->updateClientArea();
0318     }
0319 }
0320 
0321 void XdgSurfaceWindow::updateShowOnScreenEdge()
0322 {
0323     if (!workspace()->screenEdges()) {
0324         return;
0325     }
0326     if (!readyForPainting() || !m_plasmaShellSurface || m_plasmaShellSurface->role() != PlasmaShellSurfaceInterface::Role::Panel) {
0327         workspace()->screenEdges()->reserve(this, ElectricNone);
0328         return;
0329     }
0330     const PlasmaShellSurfaceInterface::PanelBehavior panelBehavior = m_plasmaShellSurface->panelBehavior();
0331     if ((panelBehavior == PlasmaShellSurfaceInterface::PanelBehavior::AutoHide && isHidden()) || panelBehavior == PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover) {
0332         // Screen edge API requires an edge, thus we need to figure out which edge the window borders.
0333         const QRect clientGeometry = frameGeometry().toRect(); // converted here to match output checks
0334         Qt::Edges edges;
0335 
0336         const auto outputs = workspace()->outputs();
0337         for (const Output *output : outputs) {
0338             const QRect screenGeometry = output->geometry();
0339             if (screenGeometry.left() == clientGeometry.left()) {
0340                 edges |= Qt::LeftEdge;
0341             }
0342             if (screenGeometry.right() == clientGeometry.right()) {
0343                 edges |= Qt::RightEdge;
0344             }
0345             if (screenGeometry.top() == clientGeometry.top()) {
0346                 edges |= Qt::TopEdge;
0347             }
0348             if (screenGeometry.bottom() == clientGeometry.bottom()) {
0349                 edges |= Qt::BottomEdge;
0350             }
0351         }
0352 
0353         // A panel might border multiple screen edges. E.g. a horizontal panel at the bottom will
0354         // also border the left and right edge. Let's remove such cases.
0355         if (edges & Qt::LeftEdge && edges & Qt::RightEdge) {
0356             edges = edges & (~(Qt::LeftEdge | Qt::RightEdge));
0357         }
0358         if (edges & Qt::TopEdge && edges & Qt::BottomEdge) {
0359             edges = edges & (~(Qt::TopEdge | Qt::BottomEdge));
0360         }
0361 
0362         // It's still possible that a panel borders two edges, e.g. bottom and left
0363         // in that case the one which is sharing more with the edge wins.
0364         auto check = [clientGeometry](Qt::Edges edges, Qt::Edge horizontal, Qt::Edge vertical) {
0365             if (edges & horizontal && edges & vertical) {
0366                 if (clientGeometry.width() >= clientGeometry.height()) {
0367                     return edges & ~horizontal;
0368                 } else {
0369                     return edges & ~vertical;
0370                 }
0371             }
0372             return edges;
0373         };
0374         edges = check(edges, Qt::LeftEdge, Qt::TopEdge);
0375         edges = check(edges, Qt::LeftEdge, Qt::BottomEdge);
0376         edges = check(edges, Qt::RightEdge, Qt::TopEdge);
0377         edges = check(edges, Qt::RightEdge, Qt::BottomEdge);
0378 
0379         ElectricBorder border = ElectricNone;
0380         if (edges & Qt::LeftEdge) {
0381             border = ElectricLeft;
0382         }
0383         if (edges & Qt::RightEdge) {
0384             border = ElectricRight;
0385         }
0386         if (edges & Qt::TopEdge) {
0387             border = ElectricTop;
0388         }
0389         if (edges & Qt::BottomEdge) {
0390             border = ElectricBottom;
0391         }
0392         workspace()->screenEdges()->reserve(this, border);
0393     } else {
0394         workspace()->screenEdges()->reserve(this, ElectricNone);
0395     }
0396 }
0397 
0398 /**
0399  * \todo This whole plasma shell surface thing doesn't seem right. It turns xdg-toplevel into
0400  * something completely different! Perhaps plasmashell surfaces need to be implemented via a
0401  * proprietary protocol that doesn't piggyback on existing shell surface protocols. It'll lead
0402  * to cleaner code and will be technically correct, but I'm not sure whether this is do-able.
0403  */
0404 void XdgSurfaceWindow::installPlasmaShellSurface(PlasmaShellSurfaceInterface *shellSurface)
0405 {
0406     m_plasmaShellSurface = shellSurface;
0407 
0408     auto updatePosition = [this, shellSurface] {
0409         move(shellSurface->position());
0410     };
0411     auto moveUnderCursor = [this] {
0412         // Wait for the first commit
0413         auto connection = new QMetaObject::Connection;
0414         *connection = connect(this, &Window::windowShown,  [this, connection] () {
0415             disconnect(*connection);
0416             if (input()->hasPointer()) {
0417                 move(input()->globalPointer());
0418                 keepInArea(workspace()->clientArea(PlacementArea, this));
0419             }
0420         });
0421     };
0422     auto updateRole = [this, shellSurface] {
0423         NET::WindowType type = NET::Unknown;
0424         switch (shellSurface->role()) {
0425         case PlasmaShellSurfaceInterface::Role::Desktop:
0426             type = NET::Desktop;
0427             break;
0428         case PlasmaShellSurfaceInterface::Role::Panel:
0429             type = NET::Dock;
0430             break;
0431         case PlasmaShellSurfaceInterface::Role::OnScreenDisplay:
0432             type = NET::OnScreenDisplay;
0433             break;
0434         case PlasmaShellSurfaceInterface::Role::Notification:
0435             type = NET::Notification;
0436             break;
0437         case PlasmaShellSurfaceInterface::Role::ToolTip:
0438             type = NET::Tooltip;
0439             break;
0440         case PlasmaShellSurfaceInterface::Role::CriticalNotification:
0441             type = NET::CriticalNotification;
0442             break;
0443         case PlasmaShellSurfaceInterface::Role::AppletPopup:
0444             type = NET::AppletPopup;
0445             break;
0446         case PlasmaShellSurfaceInterface::Role::Normal:
0447         default:
0448             type = NET::Normal;
0449             break;
0450         }
0451         if (m_windowType == type) {
0452             return;
0453         }
0454         m_windowType = type;
0455         switch (m_windowType) {
0456         case NET::Desktop:
0457         case NET::Dock:
0458         case NET::OnScreenDisplay:
0459         case NET::Notification:
0460         case NET::CriticalNotification:
0461         case NET::Tooltip:
0462         case NET::AppletPopup:
0463             setOnAllDesktops(true);
0464 #if KWIN_BUILD_ACTIVITIES
0465             setOnAllActivities(true);
0466 #endif
0467             break;
0468         default:
0469             break;
0470         }
0471         workspace()->updateClientArea();
0472     };
0473     connect(shellSurface, &PlasmaShellSurfaceInterface::positionChanged, this, updatePosition);
0474     connect(shellSurface, &PlasmaShellSurfaceInterface::openUnderCursorRequested, this, moveUnderCursor);
0475     connect(shellSurface, &PlasmaShellSurfaceInterface::roleChanged, this, updateRole);
0476     connect(shellSurface, &PlasmaShellSurfaceInterface::panelBehaviorChanged, this, [this] {
0477         updateShowOnScreenEdge();
0478         workspace()->updateClientArea();
0479     });
0480     connect(shellSurface, &PlasmaShellSurfaceInterface::panelAutoHideHideRequested, this, [this] {
0481         if (m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AutoHide) {
0482             hideClient();
0483             m_plasmaShellSurface->hideAutoHidingPanel();
0484         }
0485         updateShowOnScreenEdge();
0486     });
0487     connect(shellSurface, &PlasmaShellSurfaceInterface::panelAutoHideShowRequested, this, [this] {
0488         showClient();
0489         workspace()->screenEdges()->reserve(this, ElectricNone);
0490         m_plasmaShellSurface->showAutoHidingPanel();
0491     });
0492     connect(shellSurface, &PlasmaShellSurfaceInterface::panelTakesFocusChanged, this, [this] {
0493         if (m_plasmaShellSurface->panelTakesFocus()) {
0494             workspace()->activateWindow(this);
0495         }
0496     });
0497     if (shellSurface->isPositionSet()) {
0498         updatePosition();
0499     }
0500     if (shellSurface->wantsOpenUnderCursor()) {
0501         moveUnderCursor();
0502     }
0503     updateRole();
0504     updateShowOnScreenEdge();
0505     connect(this, &XdgSurfaceWindow::frameGeometryChanged,
0506             this, &XdgSurfaceWindow::updateShowOnScreenEdge);
0507     connect(this, &XdgSurfaceWindow::windowShown,
0508             this, &XdgSurfaceWindow::updateShowOnScreenEdge);
0509 
0510     setSkipTaskbar(shellSurface->skipTaskbar());
0511     connect(shellSurface, &PlasmaShellSurfaceInterface::skipTaskbarChanged, this, [this] {
0512         setSkipTaskbar(m_plasmaShellSurface->skipTaskbar());
0513     });
0514 
0515     setSkipSwitcher(shellSurface->skipSwitcher());
0516     connect(shellSurface, &PlasmaShellSurfaceInterface::skipSwitcherChanged, this, [this] {
0517         setSkipSwitcher(m_plasmaShellSurface->skipSwitcher());
0518     });
0519 }
0520 
0521 void XdgSurfaceWindow::setupPlasmaShellIntegration()
0522 {
0523     connect(surface(), &SurfaceInterface::mapped,
0524             this, &XdgSurfaceWindow::updateShowOnScreenEdge);
0525     connect(this, &XdgSurfaceWindow::frameGeometryChanged,
0526             this, &XdgSurfaceWindow::updateClientArea);
0527 }
0528 
0529 XdgToplevelWindow::XdgToplevelWindow(XdgToplevelInterface *shellSurface)
0530     : XdgSurfaceWindow(shellSurface->xdgSurface())
0531     , m_shellSurface(shellSurface)
0532 {
0533     setDesktops({VirtualDesktopManager::self()->currentDesktop()});
0534 #if KWIN_BUILD_ACTIVITIES
0535     if (auto a = Workspace::self()->activities()) {
0536         setOnActivities({a->current()});
0537     }
0538 #endif
0539     move(workspace()->activeOutput()->geometry().center());
0540 
0541     connect(shellSurface, &XdgToplevelInterface::windowTitleChanged,
0542             this, &XdgToplevelWindow::handleWindowTitleChanged);
0543     connect(shellSurface, &XdgToplevelInterface::windowClassChanged,
0544             this, &XdgToplevelWindow::handleWindowClassChanged);
0545     connect(shellSurface, &XdgToplevelInterface::windowMenuRequested,
0546             this, &XdgToplevelWindow::handleWindowMenuRequested);
0547     connect(shellSurface, &XdgToplevelInterface::moveRequested,
0548             this, &XdgToplevelWindow::handleMoveRequested);
0549     connect(shellSurface, &XdgToplevelInterface::resizeRequested,
0550             this, &XdgToplevelWindow::handleResizeRequested);
0551     connect(shellSurface, &XdgToplevelInterface::maximizeRequested,
0552             this, &XdgToplevelWindow::handleMaximizeRequested);
0553     connect(shellSurface, &XdgToplevelInterface::unmaximizeRequested,
0554             this, &XdgToplevelWindow::handleUnmaximizeRequested);
0555     connect(shellSurface, &XdgToplevelInterface::fullscreenRequested,
0556             this, &XdgToplevelWindow::handleFullscreenRequested);
0557     connect(shellSurface, &XdgToplevelInterface::unfullscreenRequested,
0558             this, &XdgToplevelWindow::handleUnfullscreenRequested);
0559     connect(shellSurface, &XdgToplevelInterface::minimizeRequested,
0560             this, &XdgToplevelWindow::handleMinimizeRequested);
0561     connect(shellSurface, &XdgToplevelInterface::parentXdgToplevelChanged,
0562             this, &XdgToplevelWindow::handleTransientForChanged);
0563     connect(shellSurface, &XdgToplevelInterface::initializeRequested,
0564             this, &XdgToplevelWindow::initialize);
0565     connect(shellSurface, &XdgToplevelInterface::aboutToBeDestroyed,
0566             this, &XdgToplevelWindow::destroyWindow);
0567     connect(shellSurface, &XdgToplevelInterface::maximumSizeChanged,
0568             this, &XdgToplevelWindow::handleMaximumSizeChanged);
0569     connect(shellSurface, &XdgToplevelInterface::minimumSizeChanged,
0570             this, &XdgToplevelWindow::handleMinimumSizeChanged);
0571     connect(shellSurface->shell(), &XdgShellInterface::pingTimeout,
0572             this, &XdgToplevelWindow::handlePingTimeout);
0573     connect(shellSurface->shell(), &XdgShellInterface::pingDelayed,
0574             this, &XdgToplevelWindow::handlePingDelayed);
0575     connect(shellSurface->shell(), &XdgShellInterface::pongReceived,
0576             this, &XdgToplevelWindow::handlePongReceived);
0577 
0578     connect(waylandServer(), &WaylandServer::foreignTransientChanged,
0579             this, &XdgToplevelWindow::handleForeignTransientForChanged);
0580 }
0581 
0582 XdgToplevelWindow::~XdgToplevelWindow()
0583 {
0584 }
0585 
0586 XdgToplevelInterface *XdgToplevelWindow::shellSurface() const
0587 {
0588     return m_shellSurface;
0589 }
0590 
0591 MaximizeMode XdgToplevelWindow::maximizeMode() const
0592 {
0593     return m_maximizeMode;
0594 }
0595 
0596 MaximizeMode XdgToplevelWindow::requestedMaximizeMode() const
0597 {
0598     return m_requestedMaximizeMode;
0599 }
0600 
0601 QSizeF XdgToplevelWindow::minSize() const
0602 {
0603     const int enforcedMinimum = m_nextDecoration ? 150 : 20;
0604     return rules()->checkMinSize(m_shellSurface->minimumSize()).expandedTo(QSizeF(enforcedMinimum, enforcedMinimum));
0605 }
0606 
0607 QSizeF XdgToplevelWindow::maxSize() const
0608 {
0609     // enforce the same minimum as for minSize, so that maxSize is always bigger than minSize
0610     const int enforcedMinimum = m_nextDecoration ? 150 : 20;
0611     return rules()->checkMaxSize(m_shellSurface->maximumSize()).expandedTo(QSizeF(enforcedMinimum, enforcedMinimum));
0612 }
0613 
0614 bool XdgToplevelWindow::isFullScreen() const
0615 {
0616     return m_isFullScreen;
0617 }
0618 
0619 bool XdgToplevelWindow::isRequestedFullScreen() const
0620 {
0621     return m_isRequestedFullScreen;
0622 }
0623 
0624 bool XdgToplevelWindow::isMovable() const
0625 {
0626     if (isRequestedFullScreen()) {
0627         return false;
0628     }
0629     if ((isSpecialWindow() && !isSplash() && !isToolbar()) || isAppletPopup()) {
0630         return false;
0631     }
0632     if (rules()->checkPosition(invalidPoint) != invalidPoint) {
0633         return false;
0634     }
0635     return true;
0636 }
0637 
0638 bool XdgToplevelWindow::isMovableAcrossScreens() const
0639 {
0640     if ((isSpecialWindow() && !isSplash() && !isToolbar()) || isAppletPopup()) {
0641         return false;
0642     }
0643     if (rules()->checkPosition(invalidPoint) != invalidPoint) {
0644         return false;
0645     }
0646     return true;
0647 }
0648 
0649 bool XdgToplevelWindow::isResizable() const
0650 {
0651     if (isRequestedFullScreen()) {
0652         return false;
0653     }
0654     if (isSpecialWindow() || isSplash() || isToolbar()) {
0655         return false;
0656     }
0657     if (rules()->checkSize(QSize()).isValid()) {
0658         return false;
0659     }
0660     const QSizeF min = minSize();
0661     const QSizeF max = maxSize();
0662     return min.width() < max.width() || min.height() < max.height();
0663 }
0664 
0665 bool XdgToplevelWindow::isCloseable() const
0666 {
0667     return !isDesktop() && !isDock();
0668 }
0669 
0670 bool XdgToplevelWindow::isFullScreenable() const
0671 {
0672     if (!rules()->checkFullScreen(true)) {
0673         return false;
0674     }
0675     return !isSpecialWindow();
0676 }
0677 
0678 bool XdgToplevelWindow::isMaximizable() const
0679 {
0680     if (!isResizable() || isAppletPopup()) {
0681         return false;
0682     }
0683     if (rules()->checkMaximize(MaximizeRestore) != MaximizeRestore || rules()->checkMaximize(MaximizeFull) != MaximizeFull) {
0684         return false;
0685     }
0686     return true;
0687 }
0688 
0689 bool XdgToplevelWindow::isMinimizable() const
0690 {
0691     if ((isSpecialWindow() && !isTransient()) || isAppletPopup()) {
0692         return false;
0693     }
0694     if (!rules()->checkMinimize(true)) {
0695         return false;
0696     }
0697     return true;
0698 }
0699 
0700 bool XdgToplevelWindow::isPlaceable() const
0701 {
0702     if (m_plasmaShellSurface) {
0703         return !m_plasmaShellSurface->isPositionSet() && !m_plasmaShellSurface->wantsOpenUnderCursor();
0704     }
0705     return true;
0706 }
0707 
0708 bool XdgToplevelWindow::isTransient() const
0709 {
0710     return m_isTransient;
0711 }
0712 
0713 bool XdgToplevelWindow::userCanSetFullScreen() const
0714 {
0715     return true;
0716 }
0717 
0718 bool XdgToplevelWindow::userCanSetNoBorder() const
0719 {
0720     return (m_serverDecoration || m_xdgDecoration) && !isFullScreen() && !isShade();
0721 }
0722 
0723 bool XdgToplevelWindow::noBorder() const
0724 {
0725     return m_userNoBorder;
0726 }
0727 
0728 void XdgToplevelWindow::setNoBorder(bool set)
0729 {
0730     set = rules()->checkNoBorder(set);
0731     if (m_userNoBorder == set) {
0732         return;
0733     }
0734     m_userNoBorder = set;
0735     configureDecoration();
0736     updateWindowRules(Rules::NoBorder);
0737 }
0738 
0739 void XdgToplevelWindow::invalidateDecoration()
0740 {
0741     clearDecoration();
0742     configureDecoration();
0743 }
0744 
0745 bool XdgToplevelWindow::supportsWindowRules() const
0746 {
0747     return true;
0748 }
0749 
0750 StrutRect XdgToplevelWindow::strutRect(StrutArea area) const
0751 {
0752     if (!hasStrut()) {
0753         return StrutRect();
0754     }
0755 
0756     const QRect windowRect = frameGeometry().toRect();
0757     const QRect outputRect = output()->geometry();
0758 
0759     const bool left = windowRect.left() == outputRect.left();
0760     const bool right = windowRect.right() == outputRect.right();
0761     const bool top = windowRect.top() == outputRect.top();
0762     const bool bottom = windowRect.bottom() == outputRect.bottom();
0763     const bool horizontal = width() >= height();
0764 
0765     switch (area) {
0766     case StrutAreaTop:
0767         if (top && horizontal) {
0768             return StrutRect(windowRect, StrutAreaTop);
0769         }
0770         return StrutRect();
0771     case StrutAreaRight:
0772         if (right && !horizontal) {
0773             return StrutRect(windowRect, StrutAreaRight);
0774         }
0775         return StrutRect();
0776     case StrutAreaBottom:
0777         if (bottom && horizontal) {
0778             return StrutRect(windowRect, StrutAreaBottom);
0779         }
0780         return StrutRect();
0781     case StrutAreaLeft:
0782         if (left && !horizontal) {
0783             return StrutRect(windowRect, StrutAreaLeft);
0784         }
0785         return StrutRect();
0786     default:
0787         return StrutRect();
0788     }
0789 }
0790 
0791 bool XdgToplevelWindow::hasStrut() const
0792 {
0793     if (!isShown()) {
0794         return false;
0795     }
0796     if (!m_plasmaShellSurface) {
0797         return false;
0798     }
0799     if (m_plasmaShellSurface->role() != PlasmaShellSurfaceInterface::Role::Panel) {
0800         return false;
0801     }
0802     return m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible;
0803 }
0804 
0805 void XdgToplevelWindow::showOnScreenEdge()
0806 {
0807     // ShowOnScreenEdge can be called by an Edge, and hideClient could destroy the Edge
0808     // Use the singleshot to avoid use-after-free
0809     QTimer::singleShot(0, this, [this]() {
0810         showClient();
0811         workspace()->raiseWindow(this);
0812         if (m_plasmaShellSurface && m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AutoHide) {
0813             m_plasmaShellSurface->showAutoHidingPanel();
0814         }
0815     });
0816 }
0817 
0818 void XdgToplevelWindow::closeWindow()
0819 {
0820     if (isCloseable()) {
0821         sendPing(PingReason::CloseWindow);
0822         m_shellSurface->sendClose();
0823     }
0824 }
0825 
0826 XdgSurfaceConfigure *XdgToplevelWindow::sendRoleConfigure() const
0827 {
0828     QSize framePadding(0, 0);
0829     if (m_nextDecoration) {
0830         framePadding.setWidth(m_nextDecoration->borderLeft() + m_nextDecoration->borderRight());
0831         framePadding.setHeight(m_nextDecoration->borderTop() + m_nextDecoration->borderBottom());
0832     }
0833 
0834     QSizeF nextClientSize = moveResizeGeometry().size();
0835     if (!nextClientSize.isEmpty()) {
0836         nextClientSize.rwidth() -= framePadding.width();
0837         nextClientSize.rheight() -= framePadding.height();
0838     }
0839 
0840     if (nextClientSize.isEmpty()) {
0841         QSizeF bounds = workspace()->clientArea(PlacementArea, this, moveResizeOutput()).size();
0842         bounds.rwidth() -= framePadding.width();
0843         bounds.rheight() -= framePadding.height();
0844         m_shellSurface->sendConfigureBounds(bounds.toSize());
0845     }
0846 
0847     const quint32 serial = m_shellSurface->sendConfigure(nextClientSize.toSize(), m_nextStates);
0848 
0849     XdgToplevelConfigure *configureEvent = new XdgToplevelConfigure();
0850     configureEvent->bounds = moveResizeGeometry();
0851     configureEvent->states = m_nextStates;
0852     configureEvent->decoration = m_nextDecoration;
0853     configureEvent->serial = serial;
0854 
0855     return configureEvent;
0856 }
0857 
0858 void XdgToplevelWindow::handleRolePrecommit()
0859 {
0860     auto configureEvent = static_cast<XdgToplevelConfigure *>(lastAcknowledgedConfigure());
0861     if (configureEvent && decoration() != configureEvent->decoration.get()) {
0862         setDecoration(configureEvent->decoration);
0863         updateShadow();
0864     }
0865 }
0866 
0867 void XdgToplevelWindow::handleRoleCommit()
0868 {
0869     auto configureEvent = static_cast<XdgToplevelConfigure *>(lastAcknowledgedConfigure());
0870     if (configureEvent) {
0871         handleStatesAcknowledged(configureEvent->states);
0872     }
0873 }
0874 
0875 void XdgToplevelWindow::doMinimize()
0876 {
0877     if (isMinimized()) {
0878         workspace()->windowHidden(this);
0879     } else {
0880         Q_EMIT windowShown(this);
0881     }
0882     workspace()->updateMinimizedOfTransients(this);
0883 }
0884 
0885 void XdgToplevelWindow::doInteractiveResizeSync(const QRectF &rect)
0886 {
0887     moveResize(rect);
0888 }
0889 
0890 void XdgToplevelWindow::doSetActive()
0891 {
0892     WaylandWindow::doSetActive();
0893 
0894     if (isActive()) {
0895         m_nextStates |= XdgToplevelInterface::State::Activated;
0896     } else {
0897         m_nextStates &= ~XdgToplevelInterface::State::Activated;
0898     }
0899 
0900     scheduleConfigure();
0901 }
0902 
0903 void XdgToplevelWindow::doSetFullScreen()
0904 {
0905     if (isRequestedFullScreen()) {
0906         m_nextStates |= XdgToplevelInterface::State::FullScreen;
0907     } else {
0908         m_nextStates &= ~XdgToplevelInterface::State::FullScreen;
0909     }
0910 
0911     scheduleConfigure();
0912 }
0913 
0914 void XdgToplevelWindow::doSetMaximized()
0915 {
0916     if (requestedMaximizeMode() & MaximizeHorizontal) {
0917         m_nextStates |= XdgToplevelInterface::State::MaximizedHorizontal;
0918     } else {
0919         m_nextStates &= ~XdgToplevelInterface::State::MaximizedHorizontal;
0920     }
0921 
0922     if (requestedMaximizeMode() & MaximizeVertical) {
0923         m_nextStates |= XdgToplevelInterface::State::MaximizedVertical;
0924     } else {
0925         m_nextStates &= ~XdgToplevelInterface::State::MaximizedVertical;
0926     }
0927 
0928     scheduleConfigure();
0929 }
0930 
0931 static Qt::Edges anchorsForQuickTileMode(QuickTileMode mode)
0932 {
0933     if (mode == QuickTileMode(QuickTileFlag::None)) {
0934         return Qt::Edges();
0935     }
0936 
0937     Qt::Edges anchors = Qt::LeftEdge | Qt::TopEdge | Qt::RightEdge | Qt::BottomEdge;
0938 
0939     if ((mode & QuickTileFlag::Left) && !(mode & QuickTileFlag::Right)) {
0940         anchors &= ~Qt::RightEdge;
0941     }
0942     if ((mode & QuickTileFlag::Right) && !(mode & QuickTileFlag::Left)) {
0943         anchors &= ~Qt::LeftEdge;
0944     }
0945 
0946     if ((mode & QuickTileFlag::Top) && !(mode & QuickTileFlag::Bottom)) {
0947         anchors &= ~Qt::BottomEdge;
0948     }
0949     if ((mode & QuickTileFlag::Bottom) && !(mode & QuickTileFlag::Top)) {
0950         anchors &= ~Qt::TopEdge;
0951     }
0952 
0953     return anchors;
0954 }
0955 
0956 void XdgToplevelWindow::doSetQuickTileMode()
0957 {
0958     const Qt::Edges anchors = anchorsForQuickTileMode(quickTileMode());
0959 
0960     if (anchors & Qt::LeftEdge) {
0961         m_nextStates |= XdgToplevelInterface::State::TiledLeft;
0962     } else {
0963         m_nextStates &= ~XdgToplevelInterface::State::TiledLeft;
0964     }
0965 
0966     if (anchors & Qt::RightEdge) {
0967         m_nextStates |= XdgToplevelInterface::State::TiledRight;
0968     } else {
0969         m_nextStates &= ~XdgToplevelInterface::State::TiledRight;
0970     }
0971 
0972     if (anchors & Qt::TopEdge) {
0973         m_nextStates |= XdgToplevelInterface::State::TiledTop;
0974     } else {
0975         m_nextStates &= ~XdgToplevelInterface::State::TiledTop;
0976     }
0977 
0978     if (anchors & Qt::BottomEdge) {
0979         m_nextStates |= XdgToplevelInterface::State::TiledBottom;
0980     } else {
0981         m_nextStates &= ~XdgToplevelInterface::State::TiledBottom;
0982     }
0983 
0984     scheduleConfigure();
0985 }
0986 
0987 bool XdgToplevelWindow::doStartInteractiveMoveResize()
0988 {
0989     if (interactiveMoveResizeGravity() != Gravity::None) {
0990         m_nextGravity = interactiveMoveResizeGravity();
0991         m_nextStates |= XdgToplevelInterface::State::Resizing;
0992         scheduleConfigure();
0993     }
0994     return true;
0995 }
0996 
0997 void XdgToplevelWindow::doFinishInteractiveMoveResize()
0998 {
0999     if (m_nextStates & XdgToplevelInterface::State::Resizing) {
1000         m_nextStates &= ~XdgToplevelInterface::State::Resizing;
1001         scheduleConfigure();
1002     }
1003 }
1004 
1005 bool XdgToplevelWindow::takeFocus()
1006 {
1007     if (wantsInput()) {
1008         sendPing(PingReason::FocusWindow);
1009         setActive(true);
1010     }
1011     if (!keepAbove() && !isOnScreenDisplay() && !belongsToDesktop()) {
1012         workspace()->setShowingDesktop(false);
1013     }
1014     return true;
1015 }
1016 
1017 bool XdgToplevelWindow::wantsInput() const
1018 {
1019     return rules()->checkAcceptFocus(acceptsFocus());
1020 }
1021 
1022 bool XdgToplevelWindow::dockWantsInput() const
1023 {
1024     if (m_plasmaShellSurface) {
1025         if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Panel) {
1026             return m_plasmaShellSurface->panelTakesFocus();
1027         }
1028     }
1029     return false;
1030 }
1031 
1032 bool XdgToplevelWindow::acceptsFocus() const
1033 {
1034     if (m_plasmaShellSurface) {
1035         if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::OnScreenDisplay || m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::ToolTip) {
1036             return false;
1037         }
1038         switch (m_plasmaShellSurface->role()) {
1039         case PlasmaShellSurfaceInterface::Role::Notification:
1040         case PlasmaShellSurfaceInterface::Role::CriticalNotification:
1041             return m_plasmaShellSurface->panelTakesFocus();
1042         default:
1043             break;
1044         }
1045     }
1046     return !isZombie() && readyForPainting();
1047 }
1048 
1049 Layer XdgToplevelWindow::layerForDock() const
1050 {
1051     if (m_plasmaShellSurface) {
1052         switch (m_plasmaShellSurface->panelBehavior()) {
1053         case PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover:
1054             return NormalLayer;
1055         case PlasmaShellSurfaceInterface::PanelBehavior::AutoHide:
1056         case PlasmaShellSurfaceInterface::PanelBehavior::WindowsGoBelow:
1057             return AboveLayer;
1058         case PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible:
1059             return DockLayer;
1060         default:
1061             Q_UNREACHABLE();
1062             break;
1063         }
1064     }
1065     return Window::layerForDock();
1066 }
1067 
1068 void XdgToplevelWindow::handleWindowTitleChanged()
1069 {
1070     setCaption(m_shellSurface->windowTitle());
1071 }
1072 
1073 void XdgToplevelWindow::handleWindowClassChanged()
1074 {
1075     const QString applicationId = m_shellSurface->windowClass();
1076     setResourceClass(resourceName(), applicationId);
1077     if (shellSurface()->isConfigured()) {
1078         evaluateWindowRules();
1079     }
1080     setDesktopFileName(applicationId);
1081 }
1082 
1083 void XdgToplevelWindow::handleWindowMenuRequested(SeatInterface *seat, const QPoint &surfacePos,
1084                                                   quint32 serial)
1085 {
1086     performMouseCommand(Options::MouseOperationsMenu, pos() + surfacePos);
1087 }
1088 
1089 void XdgToplevelWindow::handleMoveRequested(SeatInterface *seat, quint32 serial)
1090 {
1091     if (!seat->hasImplicitPointerGrab(serial) && !seat->hasImplicitTouchGrab(serial)) {
1092         return;
1093     }
1094     if (isMovable()) {
1095         QPointF cursorPos;
1096         if (seat->hasImplicitPointerGrab(serial)) {
1097             cursorPos = input()->pointer()->pos();
1098         } else {
1099             cursorPos = input()->touch()->position();
1100         }
1101         performMouseCommand(Options::MouseMove, cursorPos);
1102     } else {
1103         qCDebug(KWIN_CORE) << this << "is immovable, ignoring the move request";
1104     }
1105 }
1106 
1107 void XdgToplevelWindow::handleResizeRequested(SeatInterface *seat, XdgToplevelInterface::ResizeAnchor anchor, quint32 serial)
1108 {
1109     if (!seat->hasImplicitPointerGrab(serial) && !seat->hasImplicitTouchGrab(serial)) {
1110         return;
1111     }
1112     if (!isResizable() || isShade()) {
1113         return;
1114     }
1115     if (isInteractiveMoveResize()) {
1116         finishInteractiveMoveResize(false);
1117     }
1118     setInteractiveMoveResizePointerButtonDown(true);
1119     QPointF cursorPos;
1120     if (seat->hasImplicitPointerGrab(serial)) {
1121         cursorPos = input()->pointer()->pos();
1122     } else {
1123         cursorPos = input()->touch()->position();
1124     }
1125     setInteractiveMoveOffset(cursorPos - pos()); // map from global
1126     setInvertedInteractiveMoveOffset(rect().bottomRight() - interactiveMoveOffset());
1127     setUnrestrictedInteractiveMoveResize(false);
1128     Gravity gravity;
1129     switch (anchor) {
1130     case XdgToplevelInterface::ResizeAnchor::TopLeft:
1131         gravity = Gravity::TopLeft;
1132         break;
1133     case XdgToplevelInterface::ResizeAnchor::Top:
1134         gravity = Gravity::Top;
1135         break;
1136     case XdgToplevelInterface::ResizeAnchor::TopRight:
1137         gravity = Gravity::TopRight;
1138         break;
1139     case XdgToplevelInterface::ResizeAnchor::Right:
1140         gravity = Gravity::Right;
1141         break;
1142     case XdgToplevelInterface::ResizeAnchor::BottomRight:
1143         gravity = Gravity::BottomRight;
1144         break;
1145     case XdgToplevelInterface::ResizeAnchor::Bottom:
1146         gravity = Gravity::Bottom;
1147         break;
1148     case XdgToplevelInterface::ResizeAnchor::BottomLeft:
1149         gravity = Gravity::BottomLeft;
1150         break;
1151     case XdgToplevelInterface::ResizeAnchor::Left:
1152         gravity = Gravity::Left;
1153         break;
1154     default:
1155         gravity = Gravity::None;
1156         break;
1157     }
1158     setInteractiveMoveResizeGravity(gravity);
1159     if (!startInteractiveMoveResize()) {
1160         setInteractiveMoveResizePointerButtonDown(false);
1161     }
1162     updateCursor();
1163 }
1164 
1165 void XdgToplevelWindow::handleStatesAcknowledged(const XdgToplevelInterface::States &states)
1166 {
1167     const XdgToplevelInterface::States delta = m_acknowledgedStates ^ states;
1168 
1169     if (delta & XdgToplevelInterface::State::Maximized) {
1170         MaximizeMode maximizeMode = MaximizeRestore;
1171         if (states & XdgToplevelInterface::State::MaximizedHorizontal) {
1172             maximizeMode = MaximizeMode(maximizeMode | MaximizeHorizontal);
1173         }
1174         if (states & XdgToplevelInterface::State::MaximizedVertical) {
1175             maximizeMode = MaximizeMode(maximizeMode | MaximizeVertical);
1176         }
1177         updateMaximizeMode(maximizeMode);
1178     }
1179     if (delta & XdgToplevelInterface::State::FullScreen) {
1180         updateFullScreenMode(states & XdgToplevelInterface::State::FullScreen);
1181     }
1182 
1183     m_acknowledgedStates = states;
1184 }
1185 
1186 void XdgToplevelWindow::handleMaximizeRequested()
1187 {
1188     if (m_isInitialized) {
1189         maximize(MaximizeFull);
1190         scheduleConfigure();
1191     } else {
1192         m_initialStates |= XdgToplevelInterface::State::Maximized;
1193     }
1194 }
1195 
1196 void XdgToplevelWindow::handleUnmaximizeRequested()
1197 {
1198     if (m_isInitialized) {
1199         maximize(MaximizeRestore);
1200         scheduleConfigure();
1201     } else {
1202         m_initialStates &= ~XdgToplevelInterface::State::Maximized;
1203     }
1204 }
1205 
1206 void XdgToplevelWindow::handleFullscreenRequested(OutputInterface *output)
1207 {
1208     m_fullScreenRequestedOutput = output ? output->handle() : nullptr;
1209     if (m_isInitialized) {
1210         setFullScreen(/* set */ true, /* user */ false);
1211         scheduleConfigure();
1212     } else {
1213         m_initialStates |= XdgToplevelInterface::State::FullScreen;
1214     }
1215 }
1216 
1217 void XdgToplevelWindow::handleUnfullscreenRequested()
1218 {
1219     m_fullScreenRequestedOutput.clear();
1220     if (m_isInitialized) {
1221         setFullScreen(/* set */ false, /* user */ false);
1222         scheduleConfigure();
1223     } else {
1224         m_initialStates &= ~XdgToplevelInterface::State::FullScreen;
1225     }
1226 }
1227 
1228 void XdgToplevelWindow::handleMinimizeRequested()
1229 {
1230     minimize();
1231 }
1232 
1233 void XdgToplevelWindow::handleTransientForChanged()
1234 {
1235     SurfaceInterface *transientForSurface = nullptr;
1236     if (XdgToplevelInterface *parentToplevel = m_shellSurface->parentXdgToplevel()) {
1237         transientForSurface = parentToplevel->surface();
1238     }
1239     if (!transientForSurface) {
1240         transientForSurface = waylandServer()->findForeignTransientForSurface(surface());
1241     }
1242     Window *transientForWindow = waylandServer()->findWindow(transientForSurface);
1243     if (transientForWindow != transientFor()) {
1244         if (transientFor()) {
1245             transientFor()->removeTransient(this);
1246         }
1247         if (transientForWindow) {
1248             transientForWindow->addTransient(this);
1249         }
1250         setTransientFor(transientForWindow);
1251     }
1252     m_isTransient = transientForWindow;
1253 }
1254 
1255 void XdgToplevelWindow::handleForeignTransientForChanged(SurfaceInterface *child)
1256 {
1257     if (surface() == child) {
1258         handleTransientForChanged();
1259     }
1260 }
1261 
1262 void XdgToplevelWindow::handlePingTimeout(quint32 serial)
1263 {
1264     auto pingIt = m_pings.find(serial);
1265     if (pingIt == m_pings.end()) {
1266         return;
1267     }
1268     if (pingIt.value() == PingReason::CloseWindow) {
1269         qCDebug(KWIN_CORE) << "Final ping timeout on a close attempt, asking to kill:" << caption();
1270 
1271         // for internal windows, killing the window will delete this
1272         QPointer<QObject> guard(this);
1273         killWindow();
1274         if (!guard) {
1275             return;
1276         }
1277     }
1278     m_pings.erase(pingIt);
1279 }
1280 
1281 void XdgToplevelWindow::handlePingDelayed(quint32 serial)
1282 {
1283     auto it = m_pings.find(serial);
1284     if (it != m_pings.end()) {
1285         qCDebug(KWIN_CORE) << "First ping timeout:" << caption();
1286         setUnresponsive(true);
1287     }
1288 }
1289 
1290 void XdgToplevelWindow::handlePongReceived(quint32 serial)
1291 {
1292     if (m_pings.remove(serial)) {
1293         setUnresponsive(false);
1294     }
1295 }
1296 
1297 void XdgToplevelWindow::handleMaximumSizeChanged()
1298 {
1299     Q_EMIT maximizeableChanged(isMaximizable());
1300 }
1301 
1302 void XdgToplevelWindow::handleMinimumSizeChanged()
1303 {
1304     Q_EMIT maximizeableChanged(isMaximizable());
1305 }
1306 
1307 void XdgToplevelWindow::sendPing(PingReason reason)
1308 {
1309     XdgShellInterface *shell = m_shellSurface->shell();
1310     XdgSurfaceInterface *surface = m_shellSurface->xdgSurface();
1311 
1312     const quint32 serial = shell->ping(surface);
1313     m_pings.insert(serial, reason);
1314 }
1315 
1316 MaximizeMode XdgToplevelWindow::initialMaximizeMode() const
1317 {
1318     MaximizeMode maximizeMode = MaximizeRestore;
1319     if (m_initialStates & XdgToplevelInterface::State::MaximizedHorizontal) {
1320         maximizeMode = MaximizeMode(maximizeMode | MaximizeHorizontal);
1321     }
1322     if (m_initialStates & XdgToplevelInterface::State::MaximizedVertical) {
1323         maximizeMode = MaximizeMode(maximizeMode | MaximizeVertical);
1324     }
1325     return maximizeMode;
1326 }
1327 
1328 bool XdgToplevelWindow::initialFullScreenMode() const
1329 {
1330     return m_initialStates & XdgToplevelInterface::State::FullScreen;
1331 }
1332 
1333 void XdgToplevelWindow::initialize()
1334 {
1335     bool needsPlacement = isPlaceable();
1336     setupWindowRules(false);
1337 
1338     // Move or resize the window only if enforced by a window rule.
1339     const QPointF forcedPosition = rules()->checkPositionSafe(invalidPoint, true);
1340     if (forcedPosition != invalidPoint) {
1341         move(forcedPosition);
1342     }
1343     const QSizeF forcedSize = rules()->checkSize(QSize(), true);
1344     if (forcedSize.isValid()) {
1345         resize(forcedSize);
1346     }
1347 
1348     maximize(rules()->checkMaximize(initialMaximizeMode(), true));
1349     setFullScreen(rules()->checkFullScreen(initialFullScreenMode(), true), false);
1350     setOnActivities(rules()->checkActivity(activities(), true));
1351     setDesktops(rules()->checkDesktops(desktops(), true));
1352     setDesktopFileName(rules()->checkDesktopFile(desktopFileName(), true));
1353     if (rules()->checkMinimize(isMinimized(), true)) {
1354         minimize(true); // No animation.
1355     }
1356     setSkipTaskbar(rules()->checkSkipTaskbar(skipTaskbar(), true));
1357     setSkipPager(rules()->checkSkipPager(skipPager(), true));
1358     setSkipSwitcher(rules()->checkSkipSwitcher(skipSwitcher(), true));
1359     setKeepAbove(rules()->checkKeepAbove(keepAbove(), true));
1360     setKeepBelow(rules()->checkKeepBelow(keepBelow(), true));
1361     setShortcut(rules()->checkShortcut(shortcut().toString(), true));
1362     setNoBorder(rules()->checkNoBorder(noBorder(), true));
1363 
1364     // Don't place the client if its position is set by a rule.
1365     if (rules()->checkPosition(invalidPoint, true) != invalidPoint) {
1366         needsPlacement = false;
1367     }
1368 
1369     // Don't place the client if the maximize state is set by a rule.
1370     if (requestedMaximizeMode() != MaximizeRestore) {
1371         needsPlacement = false;
1372     }
1373 
1374     discardTemporaryRules();
1375     workspace()->rulebook()->discardUsed(this, false); // Remove Apply Now rules.
1376     updateWindowRules(Rules::All);
1377 
1378     if (isRequestedFullScreen()) {
1379         needsPlacement = false;
1380     }
1381     if (needsPlacement) {
1382         const QRectF area = workspace()->clientArea(PlacementArea, this, workspace()->activeOutput());
1383         workspace()->placement()->place(this, area);
1384     }
1385 
1386     configureDecoration();
1387     scheduleConfigure();
1388     updateColorScheme();
1389     setupWindowManagementInterface();
1390 
1391     m_isInitialized = true;
1392 }
1393 
1394 void XdgToplevelWindow::updateMaximizeMode(MaximizeMode maximizeMode)
1395 {
1396     if (m_maximizeMode == maximizeMode) {
1397         return;
1398     }
1399     m_maximizeMode = maximizeMode;
1400     updateWindowRules(Rules::MaximizeVert | Rules::MaximizeHoriz);
1401     Q_EMIT clientMaximizedStateChanged(this, maximizeMode);
1402     Q_EMIT clientMaximizedStateChanged(this, maximizeMode & MaximizeHorizontal, maximizeMode & MaximizeVertical);
1403 }
1404 
1405 void XdgToplevelWindow::updateFullScreenMode(bool set)
1406 {
1407     if (m_isFullScreen == set) {
1408         return;
1409     }
1410     StackingUpdatesBlocker blocker1(workspace());
1411     m_isFullScreen = set;
1412     updateLayer();
1413     updateWindowRules(Rules::Fullscreen);
1414     Q_EMIT fullScreenChanged();
1415 }
1416 
1417 QString XdgToplevelWindow::preferredColorScheme() const
1418 {
1419     if (m_paletteInterface) {
1420         return rules()->checkDecoColor(m_paletteInterface->palette());
1421     }
1422     return rules()->checkDecoColor(QString());
1423 }
1424 
1425 void XdgToplevelWindow::installAppMenu(AppMenuInterface *appMenu)
1426 {
1427     m_appMenuInterface = appMenu;
1428 
1429     auto updateMenu = [this](const AppMenuInterface::InterfaceAddress &address) {
1430         updateApplicationMenuServiceName(address.serviceName);
1431         updateApplicationMenuObjectPath(address.objectPath);
1432     };
1433     connect(m_appMenuInterface, &AppMenuInterface::addressChanged, this, updateMenu);
1434     updateMenu(appMenu->address());
1435 }
1436 
1437 XdgToplevelWindow::DecorationMode XdgToplevelWindow::preferredDecorationMode() const
1438 {
1439     if (!Decoration::DecorationBridge::hasPlugin()) {
1440         return DecorationMode::Client;
1441     } else if (m_userNoBorder || isRequestedFullScreen()) {
1442         return DecorationMode::None;
1443     }
1444 
1445     if (m_xdgDecoration) {
1446         switch (m_xdgDecoration->preferredMode()) {
1447         case XdgToplevelDecorationV1Interface::Mode::Undefined:
1448             return DecorationMode::Server;
1449         case XdgToplevelDecorationV1Interface::Mode::None:
1450             return DecorationMode::None;
1451         case XdgToplevelDecorationV1Interface::Mode::Client:
1452             return DecorationMode::Client;
1453         case XdgToplevelDecorationV1Interface::Mode::Server:
1454             return DecorationMode::Server;
1455         }
1456     }
1457 
1458     if (m_serverDecoration) {
1459         switch (m_serverDecoration->preferredMode()) {
1460         case ServerSideDecorationManagerInterface::Mode::None:
1461             return DecorationMode::None;
1462         case ServerSideDecorationManagerInterface::Mode::Client:
1463             return DecorationMode::Client;
1464         case ServerSideDecorationManagerInterface::Mode::Server:
1465             return DecorationMode::Server;
1466         }
1467     }
1468 
1469     return DecorationMode::Client;
1470 }
1471 
1472 void XdgToplevelWindow::clearDecoration()
1473 {
1474     m_nextDecoration = nullptr;
1475 }
1476 
1477 void XdgToplevelWindow::configureDecoration()
1478 {
1479     const DecorationMode decorationMode = preferredDecorationMode();
1480     switch (decorationMode) {
1481     case DecorationMode::None:
1482     case DecorationMode::Client:
1483         clearDecoration();
1484         break;
1485     case DecorationMode::Server:
1486         if (!m_nextDecoration) {
1487             m_nextDecoration.reset(Workspace::self()->decorationBridge()->createDecoration(this));
1488         }
1489         break;
1490     }
1491 
1492     // All decoration updates are synchronized to toplevel configure events.
1493     if (m_xdgDecoration) {
1494         configureXdgDecoration(decorationMode);
1495     } else if (m_serverDecoration) {
1496         configureServerDecoration(decorationMode);
1497     }
1498 }
1499 
1500 void XdgToplevelWindow::configureXdgDecoration(DecorationMode decorationMode)
1501 {
1502     switch (decorationMode) {
1503     case DecorationMode::None: // Faked as server side mode under the hood.
1504         m_xdgDecoration->sendConfigure(XdgToplevelDecorationV1Interface::Mode::None);
1505         break;
1506     case DecorationMode::Client:
1507         m_xdgDecoration->sendConfigure(XdgToplevelDecorationV1Interface::Mode::Client);
1508         break;
1509     case DecorationMode::Server:
1510         m_xdgDecoration->sendConfigure(XdgToplevelDecorationV1Interface::Mode::Server);
1511         break;
1512     }
1513     scheduleConfigure();
1514 }
1515 
1516 void XdgToplevelWindow::configureServerDecoration(DecorationMode decorationMode)
1517 {
1518     switch (decorationMode) {
1519     case DecorationMode::None:
1520         m_serverDecoration->setMode(ServerSideDecorationManagerInterface::Mode::None);
1521         break;
1522     case DecorationMode::Client:
1523         m_serverDecoration->setMode(ServerSideDecorationManagerInterface::Mode::Client);
1524         break;
1525     case DecorationMode::Server:
1526         m_serverDecoration->setMode(ServerSideDecorationManagerInterface::Mode::Server);
1527         break;
1528     }
1529     scheduleConfigure();
1530 }
1531 
1532 void XdgToplevelWindow::installXdgDecoration(XdgToplevelDecorationV1Interface *decoration)
1533 {
1534     m_xdgDecoration = decoration;
1535 
1536     connect(m_xdgDecoration, &XdgToplevelDecorationV1Interface::destroyed,
1537             this, &XdgToplevelWindow::clearDecoration);
1538     connect(m_xdgDecoration, &XdgToplevelDecorationV1Interface::preferredModeChanged, this, [this] {
1539         if (m_isInitialized) {
1540             configureDecoration();
1541         }
1542     });
1543 }
1544 
1545 void XdgToplevelWindow::installServerDecoration(ServerSideDecorationInterface *decoration)
1546 {
1547     m_serverDecoration = decoration;
1548     if (m_isInitialized) {
1549         configureDecoration();
1550     }
1551 
1552     connect(m_serverDecoration, &ServerSideDecorationInterface::destroyed,
1553             this, &XdgToplevelWindow::clearDecoration);
1554     connect(m_serverDecoration, &ServerSideDecorationInterface::preferredModeChanged, this, [this]() {
1555         if (m_isInitialized) {
1556             configureDecoration();
1557         }
1558     });
1559 }
1560 
1561 void XdgToplevelWindow::installPalette(ServerSideDecorationPaletteInterface *palette)
1562 {
1563     m_paletteInterface = palette;
1564 
1565     connect(m_paletteInterface, &ServerSideDecorationPaletteInterface::paletteChanged,
1566             this, &XdgToplevelWindow::updateColorScheme);
1567     connect(m_paletteInterface, &QObject::destroyed,
1568             this, &XdgToplevelWindow::updateColorScheme);
1569     updateColorScheme();
1570 }
1571 
1572 void XdgToplevelWindow::setFullScreen(bool set, bool user)
1573 {
1574     set = rules()->checkFullScreen(set);
1575     if (m_isRequestedFullScreen == set) {
1576         return;
1577     }
1578     if (isSpecialWindow()) {
1579         return;
1580     }
1581     if (user && !userCanSetFullScreen()) {
1582         return;
1583     }
1584 
1585     m_isRequestedFullScreen = set;
1586     configureDecoration();
1587 
1588     if (set) {
1589         const Output *output = m_fullScreenRequestedOutput ? m_fullScreenRequestedOutput.data() : moveResizeOutput();
1590         setFullscreenGeometryRestore(moveResizeGeometry());
1591         moveResize(workspace()->clientArea(FullScreenArea, this, output));
1592     } else {
1593         m_fullScreenRequestedOutput.clear();
1594         if (fullscreenGeometryRestore().isValid()) {
1595             moveResize(QRectF(fullscreenGeometryRestore().topLeft(),
1596                               constrainFrameSize(fullscreenGeometryRestore().size())));
1597         } else {
1598             // this can happen when the window was first shown already fullscreen,
1599             // so let the client set the size by itself
1600             moveResize(QRectF(workspace()->clientArea(PlacementArea, this).topLeft(), QSize(0, 0)));
1601         }
1602     }
1603 
1604     doSetFullScreen();
1605 }
1606 
1607 static bool changeMaximizeRecursion = false;
1608 void XdgToplevelWindow::maximize(MaximizeMode mode)
1609 {
1610     if (changeMaximizeRecursion) {
1611         return;
1612     }
1613 
1614     if (!isResizable() || isAppletPopup()) {
1615         return;
1616     }
1617 
1618     const QRectF clientArea = isElectricBorderMaximizing() ? workspace()->clientArea(MaximizeArea, this, Cursors::self()->mouse()->pos()) : workspace()->clientArea(MaximizeArea, this, moveResizeOutput());
1619 
1620     const MaximizeMode oldMode = m_requestedMaximizeMode;
1621     const QRectF oldGeometry = moveResizeGeometry();
1622 
1623     mode = rules()->checkMaximize(mode);
1624     if (m_requestedMaximizeMode == mode) {
1625         return;
1626     }
1627 
1628     Q_EMIT clientMaximizedStateAboutToChange(this, mode);
1629     m_requestedMaximizeMode = mode;
1630 
1631     // call into decoration update borders
1632     if (m_nextDecoration && !(options->borderlessMaximizedWindows() && m_requestedMaximizeMode == KWin::MaximizeFull)) {
1633         changeMaximizeRecursion = true;
1634         const auto c = m_nextDecoration->client().toStrongRef();
1635         if ((m_requestedMaximizeMode & MaximizeVertical) != (oldMode & MaximizeVertical)) {
1636             Q_EMIT c->maximizedVerticallyChanged(m_requestedMaximizeMode & MaximizeVertical);
1637         }
1638         if ((m_requestedMaximizeMode & MaximizeHorizontal) != (oldMode & MaximizeHorizontal)) {
1639             Q_EMIT c->maximizedHorizontallyChanged(m_requestedMaximizeMode & MaximizeHorizontal);
1640         }
1641         if ((m_requestedMaximizeMode == MaximizeFull) != (oldMode == MaximizeFull)) {
1642             Q_EMIT c->maximizedChanged(m_requestedMaximizeMode == MaximizeFull);
1643         }
1644         changeMaximizeRecursion = false;
1645     }
1646 
1647     if (options->borderlessMaximizedWindows()) {
1648         setNoBorder(m_requestedMaximizeMode == MaximizeFull);
1649     }
1650 
1651     if (quickTileMode() == QuickTileMode(QuickTileFlag::None)) {
1652         QRectF savedGeometry = geometryRestore();
1653         if (!(oldMode & MaximizeVertical)) {
1654             savedGeometry.setTop(oldGeometry.top());
1655             savedGeometry.setBottom(oldGeometry.bottom());
1656         }
1657         if (!(oldMode & MaximizeHorizontal)) {
1658             savedGeometry.setLeft(oldGeometry.left());
1659             savedGeometry.setRight(oldGeometry.right());
1660         }
1661         setGeometryRestore(savedGeometry);
1662     }
1663 
1664     const MaximizeMode delta = m_requestedMaximizeMode ^ oldMode;
1665     QRectF geometry = oldGeometry;
1666 
1667     if (delta & MaximizeHorizontal) {
1668         if (m_requestedMaximizeMode & MaximizeHorizontal) {
1669             // Stretch the window vertically to fit the size of the maximize area.
1670             geometry.setX(clientArea.x());
1671             geometry.setWidth(clientArea.width());
1672         } else if (geometryRestore().isValid()) {
1673             // The window is no longer maximized horizontally and the saved geometry is valid.
1674             geometry.setX(geometryRestore().x());
1675             geometry.setWidth(geometryRestore().width());
1676         } else {
1677             // The window is no longer maximized horizontally and the saved geometry is
1678             // invalid. This would happen if the window had been mapped in the maximized state.
1679             // We ask the client to resize the window horizontally to its preferred size.
1680             geometry.setX(clientArea.x());
1681             geometry.setWidth(0);
1682         }
1683     }
1684 
1685     if (delta & MaximizeVertical) {
1686         if (m_requestedMaximizeMode & MaximizeVertical) {
1687             // Stretch the window horizontally to fit the size of the maximize area.
1688             geometry.setY(clientArea.y());
1689             geometry.setHeight(clientArea.height());
1690         } else if (geometryRestore().isValid()) {
1691             // The window is no longer maximized vertically and the saved geometry is valid.
1692             geometry.setY(geometryRestore().y());
1693             geometry.setHeight(geometryRestore().height());
1694         } else {
1695             // The window is no longer maximized vertically and the saved geometry is
1696             // invalid. This would happen if the window had been mapped in the maximized state.
1697             // We ask the client to resize the window vertically to its preferred size.
1698             geometry.setY(clientArea.y());
1699             geometry.setHeight(0);
1700         }
1701     }
1702 
1703     const auto oldQuickTileMode = quickTileMode();
1704     if (m_requestedMaximizeMode == MaximizeFull) {
1705         if (options->electricBorderMaximize()) {
1706             updateQuickTileMode(QuickTileFlag::Maximize);
1707         } else {
1708             updateQuickTileMode(QuickTileFlag::None);
1709         }
1710     } else {
1711         updateQuickTileMode(QuickTileFlag::None);
1712     }
1713 
1714     moveResize(geometry);
1715 
1716     if (oldQuickTileMode != quickTileMode()) {
1717         doSetQuickTileMode();
1718         Q_EMIT quickTileModeChanged();
1719     }
1720 
1721     doSetMaximized();
1722 }
1723 
1724 XdgPopupWindow::XdgPopupWindow(XdgPopupInterface *shellSurface)
1725     : XdgSurfaceWindow(shellSurface->xdgSurface())
1726     , m_shellSurface(shellSurface)
1727 {
1728     m_windowType = NET::Unknown;
1729     setDesktops({VirtualDesktopManager::self()->currentDesktop()});
1730 #if KWIN_BUILD_ACTIVITIES
1731     if (auto a = Workspace::self()->activities()) {
1732         setOnActivities({a->current()});
1733     }
1734 #endif
1735 
1736     connect(shellSurface, &XdgPopupInterface::grabRequested,
1737             this, &XdgPopupWindow::handleGrabRequested);
1738     connect(shellSurface, &XdgPopupInterface::initializeRequested,
1739             this, &XdgPopupWindow::initialize);
1740     connect(shellSurface, &XdgPopupInterface::repositionRequested,
1741             this, &XdgPopupWindow::handleRepositionRequested);
1742     connect(shellSurface, &XdgPopupInterface::aboutToBeDestroyed,
1743             this, &XdgPopupWindow::destroyWindow);
1744 }
1745 
1746 void XdgPopupWindow::updateReactive()
1747 {
1748     if (m_shellSurface->positioner().isReactive()) {
1749         connect(transientFor(), &Window::frameGeometryChanged,
1750                 this, &XdgPopupWindow::relayout, Qt::UniqueConnection);
1751     } else {
1752         disconnect(transientFor(), &Window::frameGeometryChanged,
1753                    this, &XdgPopupWindow::relayout);
1754     }
1755 }
1756 
1757 void XdgPopupWindow::handleRepositionRequested(quint32 token)
1758 {
1759     updateReactive();
1760     m_shellSurface->sendRepositioned(token);
1761     relayout();
1762 }
1763 
1764 void XdgPopupWindow::relayout()
1765 {
1766     workspace()->placement()->place(this, QRect());
1767     scheduleConfigure();
1768 }
1769 
1770 XdgPopupWindow::~XdgPopupWindow()
1771 {
1772 }
1773 
1774 bool XdgPopupWindow::hasPopupGrab() const
1775 {
1776     return m_haveExplicitGrab;
1777 }
1778 
1779 void XdgPopupWindow::popupDone()
1780 {
1781     m_shellSurface->sendPopupDone();
1782 }
1783 
1784 bool XdgPopupWindow::isPopupWindow() const
1785 {
1786     return true;
1787 }
1788 
1789 bool XdgPopupWindow::isTransient() const
1790 {
1791     return true;
1792 }
1793 
1794 bool XdgPopupWindow::isResizable() const
1795 {
1796     return false;
1797 }
1798 
1799 bool XdgPopupWindow::isMovable() const
1800 {
1801     return false;
1802 }
1803 
1804 bool XdgPopupWindow::isMovableAcrossScreens() const
1805 {
1806     return false;
1807 }
1808 
1809 bool XdgPopupWindow::hasTransientPlacementHint() const
1810 {
1811     return true;
1812 }
1813 
1814 QRectF XdgPopupWindow::transientPlacement(const QRectF &bounds) const
1815 {
1816     const XdgPositioner positioner = m_shellSurface->positioner();
1817     const QSize desiredSize = positioner.size();
1818 
1819     if (m_plasmaShellSurface && m_plasmaShellSurface->isPositionSet()) {
1820         return QRectF(m_plasmaShellSurface->position(), desiredSize);
1821     }
1822 
1823     const QPointF parentPosition = transientFor()->framePosToClientPos(transientFor()->pos());
1824 
1825     // returns if a target is within the supplied bounds, optional edges argument states which side to check
1826     auto inBounds = [bounds](const QRectF &target, Qt::Edges edges = Qt::LeftEdge | Qt::RightEdge | Qt::TopEdge | Qt::BottomEdge) -> bool {
1827         if (edges & Qt::LeftEdge && target.left() < bounds.left()) {
1828             return false;
1829         }
1830         if (edges & Qt::TopEdge && target.top() < bounds.top()) {
1831             return false;
1832         }
1833         if (edges & Qt::RightEdge && target.right() > bounds.right()) {
1834             // normal QRect::right issue cancels out
1835             return false;
1836         }
1837         if (edges & Qt::BottomEdge && target.bottom() > bounds.bottom()) {
1838             return false;
1839         }
1840         return true;
1841     };
1842 
1843     QRectF popupRect(popupOffset(positioner.anchorRect(), positioner.anchorEdges(), positioner.gravityEdges(), desiredSize) + positioner.offset() + parentPosition, desiredSize);
1844 
1845     // if that fits, we don't need to do anything
1846     if (inBounds(popupRect)) {
1847         return popupRect;
1848     }
1849     // otherwise apply constraint adjustment per axis in order XDG Shell Popup states
1850 
1851     if (positioner.flipConstraintAdjustments() & Qt::Horizontal) {
1852         if (!inBounds(popupRect, Qt::LeftEdge | Qt::RightEdge)) {
1853             // flip both edges (if either bit is set, XOR both)
1854             auto flippedAnchorEdge = positioner.anchorEdges();
1855             if (flippedAnchorEdge & (Qt::LeftEdge | Qt::RightEdge)) {
1856                 flippedAnchorEdge ^= (Qt::LeftEdge | Qt::RightEdge);
1857             }
1858             auto flippedGravity = positioner.gravityEdges();
1859             if (flippedGravity & (Qt::LeftEdge | Qt::RightEdge)) {
1860                 flippedGravity ^= (Qt::LeftEdge | Qt::RightEdge);
1861             }
1862             auto flippedPopupRect = QRectF(popupOffset(positioner.anchorRect(), flippedAnchorEdge, flippedGravity, desiredSize) + positioner.offset() + parentPosition, desiredSize);
1863 
1864             // if it still doesn't fit we should continue with the unflipped version
1865             if (inBounds(flippedPopupRect, Qt::LeftEdge | Qt::RightEdge)) {
1866                 popupRect.moveLeft(flippedPopupRect.left());
1867             }
1868         }
1869     }
1870     if (positioner.slideConstraintAdjustments() & Qt::Horizontal) {
1871         if (!inBounds(popupRect, Qt::LeftEdge)) {
1872             popupRect.moveLeft(bounds.left());
1873         }
1874         if (!inBounds(popupRect, Qt::RightEdge)) {
1875             popupRect.moveRight(bounds.right());
1876         }
1877     }
1878     if (positioner.resizeConstraintAdjustments() & Qt::Horizontal) {
1879         QRectF unconstrainedRect = popupRect;
1880 
1881         if (!inBounds(unconstrainedRect, Qt::LeftEdge)) {
1882             unconstrainedRect.setLeft(bounds.left());
1883         }
1884         if (!inBounds(unconstrainedRect, Qt::RightEdge)) {
1885             unconstrainedRect.setRight(bounds.right());
1886         }
1887 
1888         if (unconstrainedRect.isValid()) {
1889             popupRect = unconstrainedRect;
1890         }
1891     }
1892 
1893     if (positioner.flipConstraintAdjustments() & Qt::Vertical) {
1894         if (!inBounds(popupRect, Qt::TopEdge | Qt::BottomEdge)) {
1895             // flip both edges (if either bit is set, XOR both)
1896             auto flippedAnchorEdge = positioner.anchorEdges();
1897             if (flippedAnchorEdge & (Qt::TopEdge | Qt::BottomEdge)) {
1898                 flippedAnchorEdge ^= (Qt::TopEdge | Qt::BottomEdge);
1899             }
1900             auto flippedGravity = positioner.gravityEdges();
1901             if (flippedGravity & (Qt::TopEdge | Qt::BottomEdge)) {
1902                 flippedGravity ^= (Qt::TopEdge | Qt::BottomEdge);
1903             }
1904             auto flippedPopupRect = QRectF(popupOffset(positioner.anchorRect(), flippedAnchorEdge, flippedGravity, desiredSize) + positioner.offset() + parentPosition, desiredSize);
1905 
1906             // if it still doesn't fit we should continue with the unflipped version
1907             if (inBounds(flippedPopupRect, Qt::TopEdge | Qt::BottomEdge)) {
1908                 popupRect.moveTop(flippedPopupRect.top());
1909             }
1910         }
1911     }
1912     if (positioner.slideConstraintAdjustments() & Qt::Vertical) {
1913         if (!inBounds(popupRect, Qt::TopEdge)) {
1914             popupRect.moveTop(bounds.top());
1915         }
1916         if (!inBounds(popupRect, Qt::BottomEdge)) {
1917             popupRect.moveBottom(bounds.bottom());
1918         }
1919     }
1920     if (positioner.resizeConstraintAdjustments() & Qt::Vertical) {
1921         QRectF unconstrainedRect = popupRect;
1922 
1923         if (!inBounds(unconstrainedRect, Qt::TopEdge)) {
1924             unconstrainedRect.setTop(bounds.top());
1925         }
1926         if (!inBounds(unconstrainedRect, Qt::BottomEdge)) {
1927             unconstrainedRect.setBottom(bounds.bottom());
1928         }
1929 
1930         if (unconstrainedRect.isValid()) {
1931             popupRect = unconstrainedRect;
1932         }
1933     }
1934 
1935     return popupRect;
1936 }
1937 
1938 bool XdgPopupWindow::isCloseable() const
1939 {
1940     return false;
1941 }
1942 
1943 void XdgPopupWindow::closeWindow()
1944 {
1945 }
1946 
1947 bool XdgPopupWindow::wantsInput() const
1948 {
1949     return false;
1950 }
1951 
1952 bool XdgPopupWindow::takeFocus()
1953 {
1954     return false;
1955 }
1956 
1957 bool XdgPopupWindow::acceptsFocus() const
1958 {
1959     return false;
1960 }
1961 
1962 XdgSurfaceConfigure *XdgPopupWindow::sendRoleConfigure() const
1963 {
1964     const QPointF parentPosition = transientFor()->framePosToClientPos(transientFor()->pos());
1965     const QPointF popupPosition = moveResizeGeometry().topLeft() - parentPosition;
1966 
1967     const quint32 serial = m_shellSurface->sendConfigure(QRect(popupPosition.toPoint(), moveResizeGeometry().size().toSize()));
1968 
1969     XdgSurfaceConfigure *configureEvent = new XdgSurfaceConfigure();
1970     configureEvent->bounds = moveResizeGeometry();
1971     configureEvent->serial = serial;
1972 
1973     return configureEvent;
1974 }
1975 
1976 void XdgPopupWindow::handleGrabRequested(SeatInterface *seat, quint32 serial)
1977 {
1978     m_haveExplicitGrab = true;
1979 }
1980 
1981 void XdgPopupWindow::initialize()
1982 {
1983     Window *parent = waylandServer()->findWindow(m_shellSurface->parentSurface());
1984     parent->addTransient(this);
1985     setTransientFor(parent);
1986 
1987     updateReactive();
1988 
1989     const QRectF area = workspace()->clientArea(PlacementArea, this, workspace()->activeOutput());
1990     workspace()->placement()->place(this, area);
1991     scheduleConfigure();
1992 }
1993 
1994 } // namespace KWin