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