File indexing completed on 2024-05-19 09:25:55

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         } else {
0723             Q_EMIT windowShown(this);
0724         }
0725     }
0726     workspace()->updateMinimizedOfTransients(this);
0727 }
0728 
0729 void XdgToplevelWindow::doInteractiveResizeSync(const QRectF &rect)
0730 {
0731     moveResize(rect);
0732 }
0733 
0734 void XdgToplevelWindow::doSetActive()
0735 {
0736     WaylandWindow::doSetActive();
0737 
0738     if (isActive()) {
0739         m_nextStates |= XdgToplevelInterface::State::Activated;
0740     } else {
0741         m_nextStates &= ~XdgToplevelInterface::State::Activated;
0742     }
0743 
0744     scheduleConfigure();
0745 }
0746 
0747 void XdgToplevelWindow::doSetFullScreen()
0748 {
0749     if (isRequestedFullScreen()) {
0750         m_nextStates |= XdgToplevelInterface::State::FullScreen;
0751     } else {
0752         m_nextStates &= ~XdgToplevelInterface::State::FullScreen;
0753     }
0754 
0755     scheduleConfigure();
0756 }
0757 
0758 void XdgToplevelWindow::doSetMaximized()
0759 {
0760     if (requestedMaximizeMode() & MaximizeHorizontal) {
0761         m_nextStates |= XdgToplevelInterface::State::MaximizedHorizontal;
0762     } else {
0763         m_nextStates &= ~XdgToplevelInterface::State::MaximizedHorizontal;
0764     }
0765 
0766     if (requestedMaximizeMode() & MaximizeVertical) {
0767         m_nextStates |= XdgToplevelInterface::State::MaximizedVertical;
0768     } else {
0769         m_nextStates &= ~XdgToplevelInterface::State::MaximizedVertical;
0770     }
0771 
0772     scheduleConfigure();
0773 }
0774 
0775 static Qt::Edges anchorsForQuickTileMode(QuickTileMode mode)
0776 {
0777     if (mode == QuickTileMode(QuickTileFlag::None)) {
0778         return Qt::Edges();
0779     }
0780 
0781     Qt::Edges anchors = Qt::LeftEdge | Qt::TopEdge | Qt::RightEdge | Qt::BottomEdge;
0782 
0783     if ((mode & QuickTileFlag::Left) && !(mode & QuickTileFlag::Right)) {
0784         anchors &= ~Qt::RightEdge;
0785     }
0786     if ((mode & QuickTileFlag::Right) && !(mode & QuickTileFlag::Left)) {
0787         anchors &= ~Qt::LeftEdge;
0788     }
0789 
0790     if ((mode & QuickTileFlag::Top) && !(mode & QuickTileFlag::Bottom)) {
0791         anchors &= ~Qt::BottomEdge;
0792     }
0793     if ((mode & QuickTileFlag::Bottom) && !(mode & QuickTileFlag::Top)) {
0794         anchors &= ~Qt::TopEdge;
0795     }
0796 
0797     return anchors;
0798 }
0799 
0800 void XdgToplevelWindow::doSetQuickTileMode()
0801 {
0802     const Qt::Edges anchors = anchorsForQuickTileMode(quickTileMode());
0803 
0804     if (anchors & Qt::LeftEdge) {
0805         m_nextStates |= XdgToplevelInterface::State::TiledLeft;
0806     } else {
0807         m_nextStates &= ~XdgToplevelInterface::State::TiledLeft;
0808     }
0809 
0810     if (anchors & Qt::RightEdge) {
0811         m_nextStates |= XdgToplevelInterface::State::TiledRight;
0812     } else {
0813         m_nextStates &= ~XdgToplevelInterface::State::TiledRight;
0814     }
0815 
0816     if (anchors & Qt::TopEdge) {
0817         m_nextStates |= XdgToplevelInterface::State::TiledTop;
0818     } else {
0819         m_nextStates &= ~XdgToplevelInterface::State::TiledTop;
0820     }
0821 
0822     if (anchors & Qt::BottomEdge) {
0823         m_nextStates |= XdgToplevelInterface::State::TiledBottom;
0824     } else {
0825         m_nextStates &= ~XdgToplevelInterface::State::TiledBottom;
0826     }
0827 
0828     scheduleConfigure();
0829 }
0830 
0831 bool XdgToplevelWindow::doStartInteractiveMoveResize()
0832 {
0833     if (interactiveMoveResizeGravity() != Gravity::None) {
0834         m_nextGravity = interactiveMoveResizeGravity();
0835         m_nextStates |= XdgToplevelInterface::State::Resizing;
0836         scheduleConfigure();
0837     }
0838     return true;
0839 }
0840 
0841 void XdgToplevelWindow::doFinishInteractiveMoveResize()
0842 {
0843     if (m_nextStates & XdgToplevelInterface::State::Resizing) {
0844         m_nextStates &= ~XdgToplevelInterface::State::Resizing;
0845         scheduleConfigure();
0846     }
0847 }
0848 
0849 void XdgToplevelWindow::doSetSuspended()
0850 {
0851     if (isSuspended()) {
0852         m_nextStates |= XdgToplevelInterface::State::Suspended;
0853     } else {
0854         m_nextStates &= ~XdgToplevelInterface::State::Suspended;
0855     }
0856 
0857     scheduleConfigure();
0858 }
0859 
0860 bool XdgToplevelWindow::takeFocus()
0861 {
0862     if (wantsInput()) {
0863         sendPing(PingReason::FocusWindow);
0864         setActive(true);
0865     }
0866     return true;
0867 }
0868 
0869 bool XdgToplevelWindow::wantsInput() const
0870 {
0871     return rules()->checkAcceptFocus(acceptsFocus());
0872 }
0873 
0874 bool XdgToplevelWindow::dockWantsInput() const
0875 {
0876     if (m_plasmaShellSurface) {
0877         if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Panel) {
0878             return m_plasmaShellSurface->panelTakesFocus();
0879         }
0880     }
0881     return false;
0882 }
0883 
0884 bool XdgToplevelWindow::acceptsFocus() const
0885 {
0886     if (m_plasmaShellSurface) {
0887         if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::OnScreenDisplay || m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::ToolTip) {
0888             return false;
0889         }
0890         switch (m_plasmaShellSurface->role()) {
0891         case PlasmaShellSurfaceInterface::Role::Notification:
0892         case PlasmaShellSurfaceInterface::Role::CriticalNotification:
0893             return m_plasmaShellSurface->panelTakesFocus();
0894         default:
0895             break;
0896         }
0897     }
0898     return !isDeleted() && readyForPainting();
0899 }
0900 
0901 void XdgToplevelWindow::handleWindowTitleChanged()
0902 {
0903     setCaption(m_shellSurface->windowTitle());
0904 }
0905 
0906 void XdgToplevelWindow::handleWindowClassChanged()
0907 {
0908     const QString applicationId = m_shellSurface->windowClass();
0909     setResourceClass(resourceName(), applicationId);
0910     if (shellSurface()->isConfigured()) {
0911         evaluateWindowRules();
0912     }
0913     setDesktopFileName(applicationId);
0914 }
0915 
0916 void XdgToplevelWindow::handleWindowMenuRequested(SeatInterface *seat, const QPoint &surfacePos,
0917                                                   quint32 serial)
0918 {
0919     performMouseCommand(Options::MouseOperationsMenu, mapFromLocal(surfacePos));
0920 }
0921 
0922 void XdgToplevelWindow::handleMoveRequested(SeatInterface *seat, quint32 serial)
0923 {
0924     if (!seat->hasImplicitPointerGrab(serial) && !seat->hasImplicitTouchGrab(serial)
0925         && !waylandServer()->tabletManagerV2()->seat(seat)->hasImplicitGrab(serial)) {
0926         return;
0927     }
0928     if (isMovable()) {
0929         QPointF cursorPos;
0930         if (seat->hasImplicitPointerGrab(serial)) {
0931             cursorPos = input()->pointer()->pos();
0932         } else if (seat->hasImplicitTouchGrab(serial)) {
0933             cursorPos = input()->touch()->position();
0934         } else {
0935             cursorPos = input()->tablet()->position();
0936         }
0937         performMouseCommand(Options::MouseMove, cursorPos);
0938     } else {
0939         qCDebug(KWIN_CORE) << this << "is immovable, ignoring the move request";
0940     }
0941 }
0942 
0943 void XdgToplevelWindow::handleResizeRequested(SeatInterface *seat, XdgToplevelInterface::ResizeAnchor anchor, quint32 serial)
0944 {
0945     if (!seat->hasImplicitPointerGrab(serial) && !seat->hasImplicitTouchGrab(serial)
0946         && !waylandServer()->tabletManagerV2()->seat(seat)->hasImplicitGrab(serial)) {
0947         return;
0948     }
0949     if (!isResizable() || isShade()) {
0950         return;
0951     }
0952     if (isInteractiveMoveResize()) {
0953         finishInteractiveMoveResize(false);
0954     }
0955     setInteractiveMoveResizePointerButtonDown(true);
0956     QPointF cursorPos;
0957     if (seat->hasImplicitPointerGrab(serial)) {
0958         cursorPos = input()->pointer()->pos();
0959     } else if (seat->hasImplicitTouchGrab(serial)) {
0960         cursorPos = input()->touch()->position();
0961     } else {
0962         cursorPos = input()->tablet()->position();
0963     }
0964     setInteractiveMoveOffset(cursorPos - pos()); // map from global
0965     setInvertedInteractiveMoveOffset(rect().bottomRight() - interactiveMoveOffset());
0966     setUnrestrictedInteractiveMoveResize(false);
0967     Gravity gravity;
0968     switch (anchor) {
0969     case XdgToplevelInterface::ResizeAnchor::TopLeft:
0970         gravity = Gravity::TopLeft;
0971         break;
0972     case XdgToplevelInterface::ResizeAnchor::Top:
0973         gravity = Gravity::Top;
0974         break;
0975     case XdgToplevelInterface::ResizeAnchor::TopRight:
0976         gravity = Gravity::TopRight;
0977         break;
0978     case XdgToplevelInterface::ResizeAnchor::Right:
0979         gravity = Gravity::Right;
0980         break;
0981     case XdgToplevelInterface::ResizeAnchor::BottomRight:
0982         gravity = Gravity::BottomRight;
0983         break;
0984     case XdgToplevelInterface::ResizeAnchor::Bottom:
0985         gravity = Gravity::Bottom;
0986         break;
0987     case XdgToplevelInterface::ResizeAnchor::BottomLeft:
0988         gravity = Gravity::BottomLeft;
0989         break;
0990     case XdgToplevelInterface::ResizeAnchor::Left:
0991         gravity = Gravity::Left;
0992         break;
0993     default:
0994         gravity = Gravity::None;
0995         break;
0996     }
0997     setInteractiveMoveResizeGravity(gravity);
0998     if (!startInteractiveMoveResize()) {
0999         setInteractiveMoveResizePointerButtonDown(false);
1000     }
1001     updateCursor();
1002 }
1003 
1004 void XdgToplevelWindow::handleStatesAcknowledged(const XdgToplevelInterface::States &states)
1005 {
1006     const XdgToplevelInterface::States delta = m_acknowledgedStates ^ states;
1007 
1008     if (delta & XdgToplevelInterface::State::Maximized) {
1009         MaximizeMode maximizeMode = MaximizeRestore;
1010         if (states & XdgToplevelInterface::State::MaximizedHorizontal) {
1011             maximizeMode = MaximizeMode(maximizeMode | MaximizeHorizontal);
1012         }
1013         if (states & XdgToplevelInterface::State::MaximizedVertical) {
1014             maximizeMode = MaximizeMode(maximizeMode | MaximizeVertical);
1015         }
1016         updateMaximizeMode(maximizeMode);
1017     }
1018     if (delta & XdgToplevelInterface::State::FullScreen) {
1019         updateFullScreenMode(states & XdgToplevelInterface::State::FullScreen);
1020     }
1021 
1022     m_acknowledgedStates = states;
1023 }
1024 
1025 void XdgToplevelWindow::handleMaximizeRequested()
1026 {
1027     if (m_isInitialized) {
1028         maximize(MaximizeFull);
1029         scheduleConfigure();
1030     } else {
1031         m_initialStates |= XdgToplevelInterface::State::Maximized;
1032     }
1033 }
1034 
1035 void XdgToplevelWindow::handleUnmaximizeRequested()
1036 {
1037     if (m_isInitialized) {
1038         maximize(MaximizeRestore);
1039         scheduleConfigure();
1040     } else {
1041         m_initialStates &= ~XdgToplevelInterface::State::Maximized;
1042     }
1043 }
1044 
1045 void XdgToplevelWindow::handleFullscreenRequested(OutputInterface *output)
1046 {
1047     m_fullScreenRequestedOutput = output ? output->handle() : nullptr;
1048     if (m_isInitialized) {
1049         setFullScreen(true);
1050         scheduleConfigure();
1051     } else {
1052         m_initialStates |= XdgToplevelInterface::State::FullScreen;
1053     }
1054 }
1055 
1056 void XdgToplevelWindow::handleUnfullscreenRequested()
1057 {
1058     m_fullScreenRequestedOutput.clear();
1059     if (m_isInitialized) {
1060         setFullScreen(false);
1061         scheduleConfigure();
1062     } else {
1063         m_initialStates &= ~XdgToplevelInterface::State::FullScreen;
1064     }
1065 }
1066 
1067 void XdgToplevelWindow::handleMinimizeRequested()
1068 {
1069     setMinimized(true);
1070 }
1071 
1072 void XdgToplevelWindow::handleTransientForChanged()
1073 {
1074     SurfaceInterface *transientForSurface = nullptr;
1075     if (XdgToplevelInterface *parentToplevel = m_shellSurface->parentXdgToplevel()) {
1076         transientForSurface = parentToplevel->surface();
1077     }
1078     if (!transientForSurface) {
1079         transientForSurface = waylandServer()->findForeignTransientForSurface(surface());
1080     }
1081     Window *transientForWindow = waylandServer()->findWindow(transientForSurface);
1082     if (transientForWindow != transientFor()) {
1083         if (transientFor()) {
1084             transientFor()->removeTransient(this);
1085         }
1086         if (transientForWindow) {
1087             transientForWindow->addTransient(this);
1088         }
1089         setTransientFor(transientForWindow);
1090     }
1091     m_isTransient = transientForWindow;
1092 }
1093 
1094 void XdgToplevelWindow::handleForeignTransientForChanged(SurfaceInterface *child)
1095 {
1096     if (surface() == child) {
1097         handleTransientForChanged();
1098     }
1099 }
1100 
1101 void XdgToplevelWindow::handlePingTimeout(quint32 serial)
1102 {
1103     auto pingIt = m_pings.find(serial);
1104     if (pingIt == m_pings.end()) {
1105         return;
1106     }
1107     if (pingIt.value() == PingReason::CloseWindow) {
1108         qCDebug(KWIN_CORE) << "Final ping timeout on a close attempt, asking to kill:" << caption();
1109 
1110         if (!m_killPrompt) {
1111             m_killPrompt = std::make_unique<KillPrompt>(this);
1112         }
1113         if (!m_killPrompt->isRunning()) {
1114             m_killPrompt->start();
1115         }
1116     }
1117     m_pings.erase(pingIt);
1118 }
1119 
1120 void XdgToplevelWindow::handlePingDelayed(quint32 serial)
1121 {
1122     auto it = m_pings.find(serial);
1123     if (it != m_pings.end()) {
1124         qCDebug(KWIN_CORE) << "First ping timeout:" << caption();
1125         setUnresponsive(true);
1126     }
1127 }
1128 
1129 void XdgToplevelWindow::handlePongReceived(quint32 serial)
1130 {
1131     if (m_pings.remove(serial)) {
1132         setUnresponsive(false);
1133         if (m_killPrompt) {
1134             m_killPrompt->quit();
1135         }
1136     }
1137 }
1138 
1139 void XdgToplevelWindow::handleMaximumSizeChanged()
1140 {
1141     updateCapabilities();
1142     Q_EMIT maximizeableChanged(isMaximizable());
1143 }
1144 
1145 void XdgToplevelWindow::handleMinimumSizeChanged()
1146 {
1147     updateCapabilities();
1148     Q_EMIT maximizeableChanged(isMaximizable());
1149 }
1150 
1151 void XdgToplevelWindow::sendPing(PingReason reason)
1152 {
1153     XdgShellInterface *shell = m_shellSurface->shell();
1154     XdgSurfaceInterface *surface = m_shellSurface->xdgSurface();
1155 
1156     const quint32 serial = shell->ping(surface);
1157     m_pings.insert(serial, reason);
1158 }
1159 
1160 MaximizeMode XdgToplevelWindow::initialMaximizeMode() const
1161 {
1162     MaximizeMode maximizeMode = MaximizeRestore;
1163     if (m_initialStates & XdgToplevelInterface::State::MaximizedHorizontal) {
1164         maximizeMode = MaximizeMode(maximizeMode | MaximizeHorizontal);
1165     }
1166     if (m_initialStates & XdgToplevelInterface::State::MaximizedVertical) {
1167         maximizeMode = MaximizeMode(maximizeMode | MaximizeVertical);
1168     }
1169     return maximizeMode;
1170 }
1171 
1172 bool XdgToplevelWindow::initialFullScreenMode() const
1173 {
1174     return m_initialStates & XdgToplevelInterface::State::FullScreen;
1175 }
1176 
1177 void XdgToplevelWindow::initialize()
1178 {
1179     bool needsPlacement = isPlaceable();
1180     setupWindowRules();
1181 
1182     // Move or resize the window only if enforced by a window rule.
1183     const QPointF forcedPosition = rules()->checkPositionSafe(invalidPoint, true);
1184     if (forcedPosition != invalidPoint) {
1185         move(forcedPosition);
1186     }
1187     const QSizeF forcedSize = rules()->checkSize(QSize(), true);
1188     if (forcedSize.isValid()) {
1189         resize(forcedSize);
1190     }
1191 
1192     maximize(rules()->checkMaximize(initialMaximizeMode(), true));
1193     setFullScreen(rules()->checkFullScreen(initialFullScreenMode(), true));
1194     setOnActivities(rules()->checkActivity(activities(), true));
1195     setDesktops(rules()->checkDesktops(desktops(), true));
1196     setDesktopFileName(rules()->checkDesktopFile(desktopFileName(), true));
1197     setMinimized(rules()->checkMinimize(isMinimized(), true));
1198     setSkipTaskbar(rules()->checkSkipTaskbar(skipTaskbar(), true));
1199     setSkipPager(rules()->checkSkipPager(skipPager(), true));
1200     setSkipSwitcher(rules()->checkSkipSwitcher(skipSwitcher(), true));
1201     setKeepAbove(rules()->checkKeepAbove(keepAbove(), true));
1202     setKeepBelow(rules()->checkKeepBelow(keepBelow(), true));
1203     setShortcut(rules()->checkShortcut(shortcut().toString(), true));
1204     setNoBorder(rules()->checkNoBorder(noBorder(), true));
1205 
1206     // Don't place the client if its position is set by a rule.
1207     if (rules()->checkPosition(invalidPoint, true) != invalidPoint) {
1208         needsPlacement = false;
1209     }
1210 
1211     // Don't place the client if the maximize state is set by a rule.
1212     if (requestedMaximizeMode() != MaximizeRestore) {
1213         needsPlacement = false;
1214     }
1215 
1216     workspace()->rulebook()->discardUsed(this, false); // Remove Apply Now rules.
1217     updateWindowRules(Rules::All);
1218 
1219     if (isRequestedFullScreen()) {
1220         needsPlacement = false;
1221     }
1222     if (needsPlacement) {
1223         const QRectF area = workspace()->clientArea(PlacementArea, this, workspace()->activeOutput());
1224         workspace()->placement()->place(this, area);
1225     }
1226 
1227     configureDecoration();
1228     scheduleConfigure();
1229     updateColorScheme();
1230     updateCapabilities();
1231     updateClientOutputs();
1232     setupWindowManagementInterface();
1233 
1234     m_isInitialized = true;
1235 }
1236 
1237 void XdgToplevelWindow::updateMaximizeMode(MaximizeMode maximizeMode)
1238 {
1239     if (m_maximizeMode == maximizeMode) {
1240         return;
1241     }
1242     m_maximizeMode = maximizeMode;
1243     updateWindowRules(Rules::MaximizeVert | Rules::MaximizeHoriz);
1244     Q_EMIT maximizedChanged();
1245 }
1246 
1247 void XdgToplevelWindow::updateFullScreenMode(bool set)
1248 {
1249     if (m_isFullScreen == set) {
1250         return;
1251     }
1252     StackingUpdatesBlocker blocker1(workspace());
1253     m_isFullScreen = set;
1254     updateLayer();
1255     updateWindowRules(Rules::Fullscreen);
1256     Q_EMIT fullScreenChanged();
1257 }
1258 
1259 void XdgToplevelWindow::updateCapabilities()
1260 {
1261     XdgToplevelInterface::Capabilities caps = XdgToplevelInterface::Capability::WindowMenu;
1262 
1263     if (isMaximizable()) {
1264         caps.setFlag(XdgToplevelInterface::Capability::Maximize);
1265     }
1266     if (isFullScreenable()) {
1267         caps.setFlag(XdgToplevelInterface::Capability::FullScreen);
1268     }
1269     if (isMinimizable()) {
1270         caps.setFlag(XdgToplevelInterface::Capability::Minimize);
1271     }
1272 
1273     if (m_capabilities != caps) {
1274         m_capabilities = caps;
1275         m_shellSurface->sendWmCapabilities(caps);
1276     }
1277 }
1278 
1279 QString XdgToplevelWindow::preferredColorScheme() const
1280 {
1281     if (m_paletteInterface) {
1282         return rules()->checkDecoColor(m_paletteInterface->palette());
1283     }
1284     return rules()->checkDecoColor(QString());
1285 }
1286 
1287 void XdgToplevelWindow::installAppMenu(AppMenuInterface *appMenu)
1288 {
1289     m_appMenuInterface = appMenu;
1290 
1291     auto updateMenu = [this](const AppMenuInterface::InterfaceAddress &address) {
1292         updateApplicationMenuServiceName(address.serviceName);
1293         updateApplicationMenuObjectPath(address.objectPath);
1294     };
1295     connect(m_appMenuInterface, &AppMenuInterface::addressChanged, this, updateMenu);
1296     updateMenu(appMenu->address());
1297 }
1298 
1299 XdgToplevelWindow::DecorationMode XdgToplevelWindow::preferredDecorationMode() const
1300 {
1301     if (!Decoration::DecorationBridge::hasPlugin()) {
1302         return DecorationMode::Client;
1303     } else if (m_userNoBorder || isRequestedFullScreen()) {
1304         return DecorationMode::None;
1305     }
1306 
1307     if (m_xdgDecoration) {
1308         switch (m_xdgDecoration->preferredMode()) {
1309         case XdgToplevelDecorationV1Interface::Mode::Undefined:
1310             return DecorationMode::Server;
1311         case XdgToplevelDecorationV1Interface::Mode::None:
1312             return DecorationMode::None;
1313         case XdgToplevelDecorationV1Interface::Mode::Client:
1314             return DecorationMode::Client;
1315         case XdgToplevelDecorationV1Interface::Mode::Server:
1316             return DecorationMode::Server;
1317         }
1318     }
1319 
1320     if (m_serverDecoration) {
1321         switch (m_serverDecoration->preferredMode()) {
1322         case ServerSideDecorationManagerInterface::Mode::None:
1323             return DecorationMode::None;
1324         case ServerSideDecorationManagerInterface::Mode::Client:
1325             return DecorationMode::Client;
1326         case ServerSideDecorationManagerInterface::Mode::Server:
1327             return DecorationMode::Server;
1328         }
1329     }
1330 
1331     return DecorationMode::Client;
1332 }
1333 
1334 void XdgToplevelWindow::clearDecoration()
1335 {
1336     m_nextDecoration = nullptr;
1337 }
1338 
1339 void XdgToplevelWindow::configureDecoration()
1340 {
1341     const DecorationMode decorationMode = preferredDecorationMode();
1342     switch (decorationMode) {
1343     case DecorationMode::None:
1344     case DecorationMode::Client:
1345         clearDecoration();
1346         break;
1347     case DecorationMode::Server:
1348         if (!m_nextDecoration) {
1349             m_nextDecoration.reset(Workspace::self()->decorationBridge()->createDecoration(this));
1350         }
1351         break;
1352     }
1353 
1354     // All decoration updates are synchronized to toplevel configure events.
1355     if (m_xdgDecoration) {
1356         configureXdgDecoration(decorationMode);
1357     } else if (m_serverDecoration) {
1358         configureServerDecoration(decorationMode);
1359     }
1360 }
1361 
1362 void XdgToplevelWindow::configureXdgDecoration(DecorationMode decorationMode)
1363 {
1364     switch (decorationMode) {
1365     case DecorationMode::None: // Faked as server side mode under the hood.
1366         m_xdgDecoration->sendConfigure(XdgToplevelDecorationV1Interface::Mode::None);
1367         break;
1368     case DecorationMode::Client:
1369         m_xdgDecoration->sendConfigure(XdgToplevelDecorationV1Interface::Mode::Client);
1370         break;
1371     case DecorationMode::Server:
1372         m_xdgDecoration->sendConfigure(XdgToplevelDecorationV1Interface::Mode::Server);
1373         break;
1374     }
1375     scheduleConfigure();
1376 }
1377 
1378 void XdgToplevelWindow::configureServerDecoration(DecorationMode decorationMode)
1379 {
1380     switch (decorationMode) {
1381     case DecorationMode::None:
1382         m_serverDecoration->setMode(ServerSideDecorationManagerInterface::Mode::None);
1383         break;
1384     case DecorationMode::Client:
1385         m_serverDecoration->setMode(ServerSideDecorationManagerInterface::Mode::Client);
1386         break;
1387     case DecorationMode::Server:
1388         m_serverDecoration->setMode(ServerSideDecorationManagerInterface::Mode::Server);
1389         break;
1390     }
1391     scheduleConfigure();
1392 }
1393 
1394 void XdgToplevelWindow::installXdgDecoration(XdgToplevelDecorationV1Interface *decoration)
1395 {
1396     m_xdgDecoration = decoration;
1397 
1398     connect(m_xdgDecoration, &XdgToplevelDecorationV1Interface::destroyed,
1399             this, &XdgToplevelWindow::clearDecoration);
1400     connect(m_xdgDecoration, &XdgToplevelDecorationV1Interface::preferredModeChanged, this, [this] {
1401         if (m_isInitialized) {
1402             configureDecoration();
1403         }
1404     });
1405 }
1406 
1407 void XdgToplevelWindow::installServerDecoration(ServerSideDecorationInterface *decoration)
1408 {
1409     m_serverDecoration = decoration;
1410     if (m_isInitialized) {
1411         configureDecoration();
1412     }
1413 
1414     connect(m_serverDecoration, &ServerSideDecorationInterface::destroyed,
1415             this, &XdgToplevelWindow::clearDecoration);
1416     connect(m_serverDecoration, &ServerSideDecorationInterface::preferredModeChanged, this, [this]() {
1417         if (m_isInitialized) {
1418             configureDecoration();
1419         }
1420     });
1421 }
1422 
1423 void XdgToplevelWindow::installPalette(ServerSideDecorationPaletteInterface *palette)
1424 {
1425     m_paletteInterface = palette;
1426 
1427     connect(m_paletteInterface, &ServerSideDecorationPaletteInterface::paletteChanged,
1428             this, &XdgToplevelWindow::updateColorScheme);
1429     connect(m_paletteInterface, &QObject::destroyed,
1430             this, &XdgToplevelWindow::updateColorScheme);
1431     updateColorScheme();
1432 }
1433 
1434 void XdgToplevelWindow::setFullScreen(bool set)
1435 {
1436     if (!isFullScreenable()) {
1437         return;
1438     }
1439 
1440     set = rules()->checkFullScreen(set);
1441     if (m_isRequestedFullScreen == set) {
1442         return;
1443     }
1444 
1445     m_isRequestedFullScreen = set;
1446     configureDecoration();
1447 
1448     if (set) {
1449         const Output *output = m_fullScreenRequestedOutput ? m_fullScreenRequestedOutput.data() : moveResizeOutput();
1450         setFullscreenGeometryRestore(moveResizeGeometry());
1451         moveResize(workspace()->clientArea(FullScreenArea, this, output));
1452     } else {
1453         m_fullScreenRequestedOutput.clear();
1454         if (fullscreenGeometryRestore().isValid()) {
1455             moveResize(QRectF(fullscreenGeometryRestore().topLeft(),
1456                               constrainFrameSize(fullscreenGeometryRestore().size())));
1457         } else {
1458             // this can happen when the window was first shown already fullscreen,
1459             // so let the client set the size by itself
1460             moveResize(QRectF(workspace()->clientArea(PlacementArea, this).topLeft(), QSize(0, 0)));
1461         }
1462     }
1463 
1464     doSetFullScreen();
1465 }
1466 
1467 static bool changeMaximizeRecursion = false;
1468 void XdgToplevelWindow::maximize(MaximizeMode mode)
1469 {
1470     if (changeMaximizeRecursion) {
1471         return;
1472     }
1473 
1474     if (!isResizable() || isAppletPopup()) {
1475         return;
1476     }
1477 
1478     const QRectF clientArea = isElectricBorderMaximizing() ? workspace()->clientArea(MaximizeArea, this, Cursors::self()->mouse()->pos()) : workspace()->clientArea(MaximizeArea, this, moveResizeOutput());
1479 
1480     const MaximizeMode oldMode = m_requestedMaximizeMode;
1481     const QRectF oldGeometry = moveResizeGeometry();
1482 
1483     mode = rules()->checkMaximize(mode);
1484     if (m_requestedMaximizeMode == mode) {
1485         return;
1486     }
1487 
1488     Q_EMIT maximizedAboutToChange(mode);
1489     m_requestedMaximizeMode = mode;
1490 
1491     // call into decoration update borders
1492     if (m_nextDecoration && !(options->borderlessMaximizedWindows() && m_requestedMaximizeMode == MaximizeFull)) {
1493         changeMaximizeRecursion = true;
1494         const auto c = m_nextDecoration->client();
1495         if ((m_requestedMaximizeMode & MaximizeVertical) != (oldMode & MaximizeVertical)) {
1496             Q_EMIT c->maximizedVerticallyChanged(m_requestedMaximizeMode & MaximizeVertical);
1497         }
1498         if ((m_requestedMaximizeMode & MaximizeHorizontal) != (oldMode & MaximizeHorizontal)) {
1499             Q_EMIT c->maximizedHorizontallyChanged(m_requestedMaximizeMode & MaximizeHorizontal);
1500         }
1501         if ((m_requestedMaximizeMode == MaximizeFull) != (oldMode == MaximizeFull)) {
1502             Q_EMIT c->maximizedChanged(m_requestedMaximizeMode == MaximizeFull);
1503         }
1504         changeMaximizeRecursion = false;
1505     }
1506 
1507     if (options->borderlessMaximizedWindows()) {
1508         setNoBorder(m_requestedMaximizeMode == MaximizeFull);
1509     }
1510 
1511     if (quickTileMode() == QuickTileMode(QuickTileFlag::None)) {
1512         QRectF savedGeometry = geometryRestore();
1513         if (!(oldMode & MaximizeVertical)) {
1514             savedGeometry.setTop(oldGeometry.top());
1515             savedGeometry.setBottom(oldGeometry.bottom());
1516         }
1517         if (!(oldMode & MaximizeHorizontal)) {
1518             savedGeometry.setLeft(oldGeometry.left());
1519             savedGeometry.setRight(oldGeometry.right());
1520         }
1521         setGeometryRestore(savedGeometry);
1522     }
1523 
1524     const MaximizeMode delta = m_requestedMaximizeMode ^ oldMode;
1525     QRectF geometry = oldGeometry;
1526 
1527     if (delta & MaximizeHorizontal) {
1528         if (m_requestedMaximizeMode & MaximizeHorizontal) {
1529             // Stretch the window vertically to fit the size of the maximize area.
1530             geometry.setX(clientArea.x());
1531             geometry.setWidth(clientArea.width());
1532         } else if (geometryRestore().isValid()) {
1533             // The window is no longer maximized horizontally and the saved geometry is valid.
1534             geometry.setX(geometryRestore().x());
1535             geometry.setWidth(geometryRestore().width());
1536         } else {
1537             // The window is no longer maximized horizontally and the saved geometry is
1538             // invalid. This would happen if the window had been mapped in the maximized state.
1539             // We ask the client to resize the window horizontally to its preferred size.
1540             geometry.setX(clientArea.x());
1541             geometry.setWidth(0);
1542         }
1543     }
1544 
1545     if (delta & MaximizeVertical) {
1546         if (m_requestedMaximizeMode & MaximizeVertical) {
1547             // Stretch the window horizontally to fit the size of the maximize area.
1548             geometry.setY(clientArea.y());
1549             geometry.setHeight(clientArea.height());
1550         } else if (geometryRestore().isValid()) {
1551             // The window is no longer maximized vertically and the saved geometry is valid.
1552             geometry.setY(geometryRestore().y());
1553             geometry.setHeight(geometryRestore().height());
1554         } else {
1555             // The window is no longer maximized vertically and the saved geometry is
1556             // invalid. This would happen if the window had been mapped in the maximized state.
1557             // We ask the client to resize the window vertically to its preferred size.
1558             geometry.setY(clientArea.y());
1559             geometry.setHeight(0);
1560         }
1561     }
1562 
1563     const auto oldQuickTileMode = quickTileMode();
1564     if (m_requestedMaximizeMode == MaximizeFull) {
1565         if (options->electricBorderMaximize()) {
1566             updateQuickTileMode(QuickTileFlag::Maximize);
1567         } else {
1568             updateQuickTileMode(QuickTileFlag::None);
1569         }
1570         setTile(nullptr);
1571     } else {
1572         updateQuickTileMode(QuickTileFlag::None);
1573     }
1574 
1575     moveResize(geometry);
1576 
1577     if (oldQuickTileMode != quickTileMode()) {
1578         doSetQuickTileMode();
1579         Q_EMIT quickTileModeChanged();
1580     }
1581 
1582     doSetMaximized();
1583 }
1584 
1585 XdgPopupWindow::XdgPopupWindow(XdgPopupInterface *shellSurface)
1586     : XdgSurfaceWindow(shellSurface->xdgSurface())
1587     , m_shellSurface(shellSurface)
1588 {
1589     m_windowType = NET::Unknown;
1590 
1591     connect(shellSurface, &XdgPopupInterface::grabRequested,
1592             this, &XdgPopupWindow::handleGrabRequested);
1593     connect(shellSurface, &XdgPopupInterface::initializeRequested,
1594             this, &XdgPopupWindow::initialize);
1595     connect(shellSurface, &XdgPopupInterface::repositionRequested,
1596             this, &XdgPopupWindow::handleRepositionRequested);
1597     connect(shellSurface, &XdgPopupInterface::aboutToBeDestroyed,
1598             this, &XdgPopupWindow::destroyWindow);
1599 }
1600 
1601 void XdgPopupWindow::handleRoleDestroyed()
1602 {
1603     disconnect(transientFor(), &Window::frameGeometryChanged,
1604                this, &XdgPopupWindow::relayout);
1605     m_shellSurface->disconnect(this);
1606 
1607     XdgSurfaceWindow::handleRoleDestroyed();
1608 }
1609 
1610 void XdgPopupWindow::handleRepositionRequested(quint32 token)
1611 {
1612     updateRelativePlacement();
1613     m_shellSurface->sendRepositioned(token);
1614     relayout();
1615 }
1616 
1617 void XdgPopupWindow::updateRelativePlacement()
1618 {
1619     const QPointF parentPosition = transientFor()->framePosToClientPos(transientFor()->pos());
1620     const QRectF bounds = workspace()->clientArea(transientFor()->isFullScreen() ? FullScreenArea : PlacementArea, transientFor()).translated(-parentPosition);
1621     const XdgPositioner positioner = m_shellSurface->positioner();
1622 
1623     if (m_plasmaShellSurface && m_plasmaShellSurface->isPositionSet()) {
1624         m_relativePlacement = QRectF(m_plasmaShellSurface->position(), positioner.size()).translated(-parentPosition);
1625     } else {
1626         m_relativePlacement = positioner.placement(bounds);
1627     }
1628 }
1629 
1630 void XdgPopupWindow::relayout()
1631 {
1632     if (m_shellSurface->positioner().isReactive()) {
1633         updateRelativePlacement();
1634     }
1635     workspace()->placement()->place(this, QRectF());
1636     scheduleConfigure();
1637 }
1638 
1639 XdgPopupWindow::~XdgPopupWindow()
1640 {
1641 }
1642 
1643 bool XdgPopupWindow::hasPopupGrab() const
1644 {
1645     return m_haveExplicitGrab;
1646 }
1647 
1648 void XdgPopupWindow::popupDone()
1649 {
1650     m_shellSurface->sendPopupDone();
1651 }
1652 
1653 bool XdgPopupWindow::isPopupWindow() const
1654 {
1655     return true;
1656 }
1657 
1658 bool XdgPopupWindow::isTransient() const
1659 {
1660     return true;
1661 }
1662 
1663 bool XdgPopupWindow::isResizable() const
1664 {
1665     return false;
1666 }
1667 
1668 bool XdgPopupWindow::isMovable() const
1669 {
1670     return false;
1671 }
1672 
1673 bool XdgPopupWindow::isMovableAcrossScreens() const
1674 {
1675     return false;
1676 }
1677 
1678 bool XdgPopupWindow::hasTransientPlacementHint() const
1679 {
1680     return true;
1681 }
1682 
1683 QRectF XdgPopupWindow::transientPlacement() const
1684 {
1685     const QPointF parentPosition = transientFor()->framePosToClientPos(transientFor()->pos());
1686     return m_relativePlacement.translated(parentPosition);
1687 }
1688 
1689 bool XdgPopupWindow::isCloseable() const
1690 {
1691     return false;
1692 }
1693 
1694 void XdgPopupWindow::closeWindow()
1695 {
1696 }
1697 
1698 bool XdgPopupWindow::wantsInput() const
1699 {
1700     return false;
1701 }
1702 
1703 bool XdgPopupWindow::takeFocus()
1704 {
1705     return false;
1706 }
1707 
1708 bool XdgPopupWindow::acceptsFocus() const
1709 {
1710     return false;
1711 }
1712 
1713 XdgSurfaceConfigure *XdgPopupWindow::sendRoleConfigure() const
1714 {
1715     const QPointF parentPosition = transientFor()->framePosToClientPos(transientFor()->pos());
1716     const QPointF popupPosition = moveResizeGeometry().topLeft() - parentPosition;
1717 
1718     const quint32 serial = m_shellSurface->sendConfigure(QRect(popupPosition.toPoint(), moveResizeGeometry().size().toSize()));
1719 
1720     XdgSurfaceConfigure *configureEvent = new XdgSurfaceConfigure();
1721     configureEvent->bounds = moveResizeGeometry();
1722     configureEvent->serial = serial;
1723 
1724     return configureEvent;
1725 }
1726 
1727 void XdgPopupWindow::handleGrabRequested(SeatInterface *seat, quint32 serial)
1728 {
1729     m_haveExplicitGrab = true;
1730 }
1731 
1732 void XdgPopupWindow::initialize()
1733 {
1734     Window *parent = waylandServer()->findWindow(m_shellSurface->parentSurface());
1735     parent->addTransient(this);
1736     setTransientFor(parent);
1737     setDesktops(parent->desktops());
1738 #if KWIN_BUILD_ACTIVITIES
1739     setOnActivities(parent->activities());
1740 #endif
1741 
1742     updateRelativePlacement();
1743     connect(parent, &Window::frameGeometryChanged, this, &XdgPopupWindow::relayout);
1744 
1745     workspace()->placement()->place(this, QRectF());
1746     scheduleConfigure();
1747 }
1748 
1749 } // namespace KWin
1750 
1751 #include "moc_xdgshellwindow.cpp"