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