File indexing completed on 2025-04-20 08:03:04
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"