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

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