| 1 | // Copyright (C) 2017 The Qt Company Ltd. | 
| 2 | // Copyright (C) 2017 Eurogiciel, author: <philippe.coval@eurogiciel.fr> | 
| 3 | // Copyright (C) 2023 David Edmundson <davidedmundson@kde.org> | 
| 4 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only | 
| 5 |  | 
| 6 | #include "qwaylandxdgshell_p.h" | 
| 7 |  | 
| 8 | #include "qwaylandxdgexporterv2_p.h" | 
| 9 | #include "qwaylandxdgdialogv1_p.h" | 
| 10 |  | 
| 11 | #include <QtWaylandClient/private/qwaylanddisplay_p.h> | 
| 12 | #include <QtWaylandClient/private/qwaylandwindow_p.h> | 
| 13 | #include <QtWaylandClient/private/qwaylandinputdevice_p.h> | 
| 14 | #include <QtWaylandClient/private/qwaylandscreen_p.h> | 
| 15 | #include <QtWaylandClient/private/qwaylandcursor_p.h> | 
| 16 | #include <QtWaylandClient/private/qwaylandabstractdecoration_p.h> | 
| 17 |  | 
| 18 | #include <QtGui/QGuiApplication> | 
| 19 | #include <QtGui/private/qwindow_p.h> | 
| 20 |  | 
| 21 | QT_BEGIN_NAMESPACE | 
| 22 |  | 
| 23 | namespace QtWaylandClient { | 
| 24 |  | 
| 25 | QWaylandXdgSurface::Toplevel::Toplevel(QWaylandXdgSurface *xdgSurface) | 
| 26 |     : QtWayland::xdg_toplevel(xdgSurface->get_toplevel()) | 
| 27 |     , m_xdgSurface(xdgSurface) | 
| 28 | { | 
| 29 |     QWindow *window = xdgSurface->window()->window(); | 
| 30 |     if (auto *decorationManager = m_xdgSurface->m_shell->decorationManager()) { | 
| 31 |         if (!(window->flags() & Qt::FramelessWindowHint)) | 
| 32 |             m_decoration = decorationManager->createToplevelDecoration(object()); | 
| 33 |     } | 
| 34 |     requestWindowStates(states: window->windowStates()); | 
| 35 |     requestWindowFlags(flags: window->flags()); | 
| 36 |     if (auto transientParent = xdgSurface->window()->transientParent()) { | 
| 37 |         auto parentSurface = qobject_cast<QWaylandXdgSurface *>(object: transientParent->shellSurface()); | 
| 38 |         if (parentSurface && parentSurface->m_toplevel) { | 
| 39 |             set_parent(parentSurface->m_toplevel->object()); | 
| 40 |             if (window->modality() != Qt::NonModal && m_xdgSurface->m_shell->m_xdgDialogWm) { | 
| 41 |                 m_xdgDialog.reset(other: m_xdgSurface->m_shell->m_xdgDialogWm->getDialog(object())); | 
| 42 |                 m_xdgDialog->set_modal(); | 
| 43 |             } | 
| 44 |         } | 
| 45 |     } | 
| 46 | } | 
| 47 |  | 
| 48 | QWaylandXdgSurface::Toplevel::~Toplevel() | 
| 49 | { | 
| 50 |     // The protocol spec requires that the decoration object is deleted before xdg_toplevel. | 
| 51 |     delete m_decoration; | 
| 52 |     m_decoration = nullptr; | 
| 53 |  | 
| 54 |     if (isInitialized()) | 
| 55 |         destroy(); | 
| 56 | } | 
| 57 |  | 
| 58 | void QWaylandXdgSurface::Toplevel::applyConfigure() | 
| 59 | { | 
| 60 |     if (!(m_applied.states & (Qt::WindowMaximized|Qt::WindowFullScreen))) | 
| 61 |         m_normalSize = m_xdgSurface->m_window->windowContentGeometry().size(); | 
| 62 |  | 
| 63 |     if ((m_pending.states & Qt::WindowActive) && !(m_applied.states & Qt::WindowActive) | 
| 64 |         && !m_xdgSurface->m_window->display()->isKeyboardAvailable()) | 
| 65 |         m_xdgSurface->m_window->display()->handleWindowActivated(window: m_xdgSurface->m_window); | 
| 66 |  | 
| 67 |     if (!(m_pending.states & Qt::WindowActive) && (m_applied.states & Qt::WindowActive) | 
| 68 |         && !m_xdgSurface->m_window->display()->isKeyboardAvailable()) | 
| 69 |         m_xdgSurface->m_window->display()->handleWindowDeactivated(window: m_xdgSurface->m_window); | 
| 70 |  | 
| 71 |     m_xdgSurface->m_window->handleToplevelWindowTilingStatesChanged(states: m_toplevelStates); | 
| 72 |     m_xdgSurface->m_window->handleWindowStatesChanged(states: m_pending.states); | 
| 73 |  | 
| 74 |     // If the width or height is zero, the client should decide the size on its own. | 
| 75 |     QSize surfaceSize; | 
| 76 |  | 
| 77 |     if (m_pending.size.width() > 0) { | 
| 78 |         surfaceSize.setWidth(m_pending.size.width()); | 
| 79 |     } else { | 
| 80 |         if (Q_UNLIKELY(m_pending.states & (Qt::WindowMaximized | Qt::WindowFullScreen))) { | 
| 81 |             qCWarning(lcQpaWayland) << "Configure event with maximized or fullscreen state contains invalid width:"  << m_pending.size.width(); | 
| 82 |         } else { | 
| 83 |             int width = m_normalSize.width(); | 
| 84 |             if (!m_pending.bounds.isEmpty()) | 
| 85 |                 width = std::min(a: width, b: m_pending.bounds.width()); | 
| 86 |             surfaceSize.setWidth(width); | 
| 87 |         } | 
| 88 |     } | 
| 89 |  | 
| 90 |     if (m_pending.size.height() > 0) { | 
| 91 |         surfaceSize.setHeight(m_pending.size.height()); | 
| 92 |     } else { | 
| 93 |         if (Q_UNLIKELY(m_pending.states & (Qt::WindowMaximized | Qt::WindowFullScreen))) { | 
| 94 |             qCWarning(lcQpaWayland) << "Configure event with maximized or fullscreen state contains invalid height:"  << m_pending.size.height(); | 
| 95 |         } else { | 
| 96 |             int height = m_normalSize.height(); | 
| 97 |             if (!m_pending.bounds.isEmpty()) | 
| 98 |                 height = std::min(a: height, b: m_pending.bounds.height()); | 
| 99 |             surfaceSize.setHeight(height); | 
| 100 |         } | 
| 101 |     } | 
| 102 |  | 
| 103 |     m_applied = m_pending; | 
| 104 |  | 
| 105 |     if (!surfaceSize.isEmpty()) | 
| 106 |         m_xdgSurface->m_window->resizeFromApplyConfigure(sizeWithMargins: surfaceSize.grownBy(m: m_xdgSurface->m_window->windowContentMargins())); | 
| 107 |  | 
| 108 |     qCDebug(lcQpaWayland) << "Applied pending xdg_toplevel configure event:"  << m_applied.size | 
| 109 |                           << "and"  << m_applied.states | 
| 110 |                           << ", suspended "  << m_applied.suspended; | 
| 111 | } | 
| 112 |  | 
| 113 | bool QWaylandXdgSurface::Toplevel::wantsDecorations() | 
| 114 | { | 
| 115 |     if (m_decoration && (m_decoration->pending() == QWaylandXdgToplevelDecorationV1::mode_server_side | 
| 116 |                          || !m_decoration->isConfigured())) | 
| 117 |         return false; | 
| 118 |  | 
| 119 |     return !(m_pending.states & Qt::WindowFullScreen); | 
| 120 | } | 
| 121 |  | 
| 122 | void QWaylandXdgSurface::Toplevel::xdg_toplevel_configure_bounds(int32_t width, int32_t height) | 
| 123 | { | 
| 124 |     m_pending.bounds = QSize(width, height); | 
| 125 | } | 
| 126 |  | 
| 127 | void QWaylandXdgSurface::Toplevel::xdg_toplevel_configure(int32_t width, int32_t height, wl_array *states) | 
| 128 | { | 
| 129 |     m_pending.size = QSize(width, height); | 
| 130 |  | 
| 131 |     auto *xdgStates = static_cast<uint32_t *>(states->data); | 
| 132 |     size_t numStates = states->size / sizeof(uint32_t); | 
| 133 |  | 
| 134 |     m_pending.suspended = false; | 
| 135 |     m_pending.states = Qt::WindowNoState; | 
| 136 |     m_toplevelStates = QWaylandWindow::WindowNoState; | 
| 137 |  | 
| 138 |     for (size_t i = 0; i < numStates; i++) { | 
| 139 |         switch (xdgStates[i]) { | 
| 140 |         case XDG_TOPLEVEL_STATE_ACTIVATED: | 
| 141 |             m_pending.states |= Qt::WindowActive; | 
| 142 |             break; | 
| 143 |         case XDG_TOPLEVEL_STATE_MAXIMIZED: | 
| 144 |             m_pending.states |= Qt::WindowMaximized; | 
| 145 |             break; | 
| 146 |         case XDG_TOPLEVEL_STATE_FULLSCREEN: | 
| 147 |             m_pending.states |= Qt::WindowFullScreen; | 
| 148 |             break; | 
| 149 |         case XDG_TOPLEVEL_STATE_TILED_LEFT: | 
| 150 |             m_toplevelStates |= QWaylandWindow::WindowTiledLeft; | 
| 151 |             break; | 
| 152 |         case XDG_TOPLEVEL_STATE_TILED_RIGHT: | 
| 153 |             m_toplevelStates |= QWaylandWindow::WindowTiledRight; | 
| 154 |             break; | 
| 155 |         case XDG_TOPLEVEL_STATE_TILED_TOP: | 
| 156 |             m_toplevelStates |= QWaylandWindow::WindowTiledTop; | 
| 157 |             break; | 
| 158 |         case XDG_TOPLEVEL_STATE_TILED_BOTTOM: | 
| 159 |             m_toplevelStates |= QWaylandWindow::WindowTiledBottom; | 
| 160 |             break; | 
| 161 |         case XDG_TOPLEVEL_STATE_SUSPENDED: | 
| 162 |             m_pending.suspended = true; | 
| 163 |             break; | 
| 164 |         default: | 
| 165 |             break; | 
| 166 |         } | 
| 167 |     } | 
| 168 |     qCDebug(lcQpaWayland) << "Received xdg_toplevel.configure with"  << m_pending.size | 
| 169 |                           << "and"  << m_pending.states | 
| 170 |                           << ", suspended "  << m_pending.suspended; | 
| 171 | } | 
| 172 |  | 
| 173 | void QWaylandXdgSurface::Toplevel::xdg_toplevel_close() | 
| 174 | { | 
| 175 |     QWindowSystemInterface::handleCloseEvent(m_xdgSurface->m_window->window()); | 
| 176 | } | 
| 177 |  | 
| 178 | void QWaylandXdgSurface::Toplevel::requestWindowFlags(Qt::WindowFlags flags) | 
| 179 | { | 
| 180 |     if (m_decoration) { | 
| 181 |         if (flags & Qt::FramelessWindowHint) { | 
| 182 |             delete m_decoration; | 
| 183 |             m_decoration = nullptr; | 
| 184 |         } else { | 
| 185 |             m_decoration->unsetMode(); | 
| 186 |         } | 
| 187 |     } | 
| 188 | } | 
| 189 |  | 
| 190 | void QWaylandXdgSurface::Toplevel::requestWindowStates(Qt::WindowStates states) | 
| 191 | { | 
| 192 |     // Re-send what's different from the applied state | 
| 193 |     Qt::WindowStates changedStates = m_applied.states ^ states; | 
| 194 |  | 
| 195 |     // Minimized state is not reported by the protocol, so always send it | 
| 196 |     if (states & Qt::WindowMinimized) { | 
| 197 |         set_minimized(); | 
| 198 |         m_xdgSurface->window()->handleWindowStatesChanged(states: states & ~Qt::WindowMinimized); | 
| 199 |         // The internal window state whilst minimized is not maximised or fullscreen, but we don't want to | 
| 200 |         // update the compositors cached version of this state | 
| 201 |         return; | 
| 202 |     } | 
| 203 |  | 
| 204 |     if (changedStates & Qt::WindowMaximized) { | 
| 205 |         if (states & Qt::WindowMaximized) | 
| 206 |             set_maximized(); | 
| 207 |         else | 
| 208 |             unset_maximized(); | 
| 209 |     } | 
| 210 |  | 
| 211 |     if (changedStates & Qt::WindowFullScreen) { | 
| 212 |         if (states & Qt::WindowFullScreen) { | 
| 213 |             auto screen = m_xdgSurface->window()->waylandScreen(); | 
| 214 |             if (screen) { | 
| 215 |                 set_fullscreen(screen->output()); | 
| 216 |             } | 
| 217 |         } else | 
| 218 |             unset_fullscreen(); | 
| 219 |     } | 
| 220 |  | 
| 221 |  | 
| 222 | } | 
| 223 |  | 
| 224 | QtWayland::xdg_toplevel::resize_edge QWaylandXdgSurface::Toplevel::convertToResizeEdges(Qt::Edges edges) | 
| 225 | { | 
| 226 |     return static_cast<enum resize_edge>( | 
| 227 |                 ((edges & Qt::TopEdge) ? resize_edge_top : 0) | 
| 228 |                 | ((edges & Qt::BottomEdge) ? resize_edge_bottom : 0) | 
| 229 |                 | ((edges & Qt::LeftEdge) ? resize_edge_left : 0) | 
| 230 |                 | ((edges & Qt::RightEdge) ? resize_edge_right : 0)); | 
| 231 | } | 
| 232 |  | 
| 233 | QWaylandXdgSurface::Popup::Popup(QWaylandXdgSurface *xdgSurface, QWaylandWindow *parent, | 
| 234 |                                  Positioner *positioner) | 
| 235 |     : m_xdgSurface(xdgSurface) | 
| 236 |     , m_parentXdgSurface(qobject_cast<QWaylandXdgSurface *>(object: parent->shellSurface())) | 
| 237 |     , m_parent(parent) | 
| 238 | { | 
| 239 |  | 
| 240 |     init(xdgSurface->get_popup(m_parentXdgSurface ? m_parentXdgSurface->object() : nullptr, | 
| 241 |                                positioner->object())); | 
| 242 | } | 
| 243 |  | 
| 244 | QWaylandXdgSurface::Popup::~Popup() | 
| 245 | { | 
| 246 |     if (isInitialized()) | 
| 247 |         destroy(); | 
| 248 |  | 
| 249 |     if (m_grabbing) { | 
| 250 |         m_grabbing = false; | 
| 251 |  | 
| 252 |         // Synthesize Qt enter/leave events for popup | 
| 253 |         QWindow *leave = nullptr; | 
| 254 |         if (m_xdgSurface && m_xdgSurface->window()) | 
| 255 |             leave = m_xdgSurface->window()->window(); | 
| 256 |         QWindowSystemInterface::handleLeaveEvent(window: leave); | 
| 257 |  | 
| 258 |         QWindow *enter = nullptr; | 
| 259 |         if (m_parentXdgSurface && m_parentXdgSurface->window()) { | 
| 260 |             enter = m_parentXdgSurface->window()->window(); | 
| 261 |             const auto pos = m_xdgSurface->window()->display()->waylandCursor()->pos(); | 
| 262 |             QWindowSystemInterface::handleEnterEvent(window: enter, local: enter->handle()->mapFromGlobal(pos), global: pos); | 
| 263 |         } | 
| 264 |     } | 
| 265 | } | 
| 266 |  | 
| 267 | void QWaylandXdgSurface::Popup::applyConfigure() | 
| 268 | { | 
| 269 |     if (m_pendingGeometry.isValid()) { | 
| 270 |         QRect geometryWithMargins = m_pendingGeometry.marginsAdded(margins: m_xdgSurface->m_window->windowContentMargins()); | 
| 271 |         QMargins parentMargins = m_parent->windowContentMargins() - m_parent->clientSideMargins(); | 
| 272 |         QRect globalGeometry = geometryWithMargins.translated(m_parent->geometry().topLeft() + QPoint(parentMargins.left(), parentMargins.top())); | 
| 273 |         m_xdgSurface->setGeometryFromApplyConfigure(globalPosition: globalGeometry.topLeft(), sizeWithMargins: globalGeometry.size()); | 
| 274 |     } | 
| 275 |     resetConfiguration(); | 
| 276 | } | 
| 277 |  | 
| 278 | void QWaylandXdgSurface::Popup::resetConfiguration() | 
| 279 | { | 
| 280 |     m_pendingGeometry = QRect(); | 
| 281 | } | 
| 282 |  | 
| 283 | void QWaylandXdgSurface::Popup::grab(QWaylandInputDevice *seat, uint serial) | 
| 284 | { | 
| 285 |     xdg_popup::grab(seat->wl_seat(), serial); | 
| 286 |     m_grabbing = true; | 
| 287 | } | 
| 288 |  | 
| 289 | void QWaylandXdgSurface::Popup::xdg_popup_configure(int32_t x, int32_t y, int32_t width, int32_t height) | 
| 290 | { | 
| 291 |     m_pendingGeometry = QRect(x, y, width, height); | 
| 292 | } | 
| 293 |  | 
| 294 | void QWaylandXdgSurface::Popup::xdg_popup_popup_done() | 
| 295 | { | 
| 296 |     QWindowSystemInterface::handleCloseEvent(m_xdgSurface->m_window->window()); | 
| 297 | } | 
| 298 |  | 
| 299 | void QWaylandXdgSurface::Popup::xdg_popup_repositioned(uint32_t token) | 
| 300 | { | 
| 301 |     if (token == m_waitingForRepositionSerial) | 
| 302 |         m_waitingForReposition = false; | 
| 303 | } | 
| 304 |  | 
| 305 | QWaylandXdgSurface::QWaylandXdgSurface(QWaylandXdgShell *shell, ::xdg_surface *surface, QWaylandWindow *window) | 
| 306 |     : QWaylandShellSurface(window) | 
| 307 |     , xdg_surface(surface) | 
| 308 |     , m_shell(shell) | 
| 309 |     , m_window(window) | 
| 310 | { | 
| 311 |     QWaylandDisplay *display = window->display(); | 
| 312 |     Qt::WindowType type = window->window()->type(); | 
| 313 |     auto *transientParent = window->transientParent(); | 
| 314 |  | 
| 315 |     if (type == Qt::ToolTip && transientParent) { | 
| 316 |         setPopup(transientParent); | 
| 317 |     } else if (type == Qt::Popup && transientParent && display->lastInputDevice()) { | 
| 318 |         setGrabPopup(parent: transientParent, device: display->lastInputDevice(), serial: display->lastInputSerial()); | 
| 319 |     } else { | 
| 320 |         setToplevel(); | 
| 321 |     } | 
| 322 |     setSizeHints(); | 
| 323 | } | 
| 324 |  | 
| 325 | QWaylandXdgSurface::~QWaylandXdgSurface() | 
| 326 | { | 
| 327 |     if (m_toplevel) { | 
| 328 |         delete m_toplevel; | 
| 329 |         m_toplevel = nullptr; | 
| 330 |     } | 
| 331 |     if (m_popup) { | 
| 332 |         delete m_popup; | 
| 333 |         m_popup = nullptr; | 
| 334 |     } | 
| 335 |     destroy(); | 
| 336 | } | 
| 337 |  | 
| 338 | bool QWaylandXdgSurface::resize(QWaylandInputDevice *inputDevice, Qt::Edges edges) | 
| 339 | { | 
| 340 |     if (!m_toplevel || !m_toplevel->isInitialized()) | 
| 341 |         return false; | 
| 342 |  | 
| 343 |     auto resizeEdges = Toplevel::convertToResizeEdges(edges); | 
| 344 |     m_toplevel->resize(inputDevice->wl_seat(), inputDevice->serial(), resizeEdges); | 
| 345 |     return true; | 
| 346 | } | 
| 347 |  | 
| 348 | bool QWaylandXdgSurface::move(QWaylandInputDevice *inputDevice) | 
| 349 | { | 
| 350 |     if (m_toplevel && m_toplevel->isInitialized()) { | 
| 351 |         m_toplevel->move(inputDevice->wl_seat(), inputDevice->serial()); | 
| 352 |         return true; | 
| 353 |     } | 
| 354 |     return false; | 
| 355 | } | 
| 356 |  | 
| 357 | bool QWaylandXdgSurface::showWindowMenu(QWaylandInputDevice *seat) | 
| 358 | { | 
| 359 |     if (m_toplevel && m_toplevel->isInitialized()) { | 
| 360 |         QPoint position = seat->pointerSurfacePosition().toPoint(); | 
| 361 |         m_toplevel->show_window_menu(seat->wl_seat(), seat->serial(), position.x(), position.y()); | 
| 362 |         return true; | 
| 363 |     } | 
| 364 |     return false; | 
| 365 | } | 
| 366 |  | 
| 367 | void QWaylandXdgSurface::setTitle(const QString &title) | 
| 368 | { | 
| 369 |     if (m_toplevel) | 
| 370 |         m_toplevel->set_title(title); | 
| 371 | } | 
| 372 |  | 
| 373 | void QWaylandXdgSurface::setAppId(const QString &appId) | 
| 374 | { | 
| 375 |     if (m_toplevel) | 
| 376 |         m_toplevel->set_app_id(appId); | 
| 377 |  | 
| 378 |     m_appId = appId; | 
| 379 | } | 
| 380 |  | 
| 381 | void QWaylandXdgSurface::setWindowFlags(Qt::WindowFlags flags) | 
| 382 | { | 
| 383 |     if (m_toplevel) | 
| 384 |         m_toplevel->requestWindowFlags(flags); | 
| 385 | } | 
| 386 |  | 
| 387 | bool QWaylandXdgSurface::isExposed() const | 
| 388 | { | 
| 389 |     if (m_toplevel && m_toplevel->m_applied.suspended) | 
| 390 |         return false; | 
| 391 |  | 
| 392 |     // the popup repositioning specification is async | 
| 393 |     // we need to defer commits between our resize request | 
| 394 |     // and our new popup position being set | 
| 395 |     if (m_popup && m_popup->m_waitingForReposition) | 
| 396 |         return false; | 
| 397 |  | 
| 398 |     return m_configured; | 
| 399 | } | 
| 400 |  | 
| 401 | bool QWaylandXdgSurface::handleExpose(const QRegion ®ion) | 
| 402 | { | 
| 403 |     if (!isExposed() && !region.isEmpty()) { | 
| 404 |         return true; | 
| 405 |     } | 
| 406 |     setWindowGeometry(window()->windowContentGeometry()); | 
| 407 |     return false; | 
| 408 | } | 
| 409 |  | 
| 410 | void QWaylandXdgSurface::applyConfigure() | 
| 411 | { | 
| 412 |     bool wasExposed = isExposed(); | 
| 413 |  | 
| 414 |     // It is a redundant ack_configure, so skipped. | 
| 415 |     if (m_pendingConfigureSerial == m_appliedConfigureSerial) | 
| 416 |         return; | 
| 417 |  | 
| 418 |     if (m_toplevel) | 
| 419 |         m_toplevel->applyConfigure(); | 
| 420 |     if (m_popup) | 
| 421 |         m_popup->applyConfigure(); | 
| 422 |     m_appliedConfigureSerial = m_pendingConfigureSerial; | 
| 423 |  | 
| 424 |     m_configured = true; | 
| 425 |     ack_configure(m_appliedConfigureSerial); | 
| 426 |  | 
| 427 |     if (!wasExposed && isExposed()) | 
| 428 |         m_window->sendRecursiveExposeEvent(); | 
| 429 | } | 
| 430 |  | 
| 431 | bool QWaylandXdgSurface::wantsDecorations() const | 
| 432 | { | 
| 433 |     return m_toplevel && m_toplevel->wantsDecorations(); | 
| 434 | } | 
| 435 |  | 
| 436 | void QWaylandXdgSurface::propagateSizeHints() | 
| 437 | { | 
| 438 |     setSizeHints(); | 
| 439 | } | 
| 440 |  | 
| 441 | void QWaylandXdgSurface::setWindowGeometry(const QRect &rect) | 
| 442 | { | 
| 443 |     if (window()->isExposed()) | 
| 444 |         set_window_geometry(rect.x(), rect.y(), rect.width(), rect.height()); | 
| 445 | } | 
| 446 |  | 
| 447 | void QWaylandXdgSurface::setSizeHints() | 
| 448 | { | 
| 449 |     if (m_toplevel && m_window) { | 
| 450 |         const QMargins margins = m_window->windowContentMargins() - m_window->clientSideMargins(); | 
| 451 |         const QSize minSize = m_window->windowMinimumSize().shrunkBy(margins); | 
| 452 |         const QSize maxSize = m_window->windowMaximumSize().shrunkBy(margins); | 
| 453 |         const int minWidth = qMax(a: 0, b: minSize.width()); | 
| 454 |         const int minHeight = qMax(a: 0, b: minSize.height()); | 
| 455 |         int maxWidth = qMax(a: 0, b: maxSize.width()); | 
| 456 |         int maxHeight = qMax(a: 0, b: maxSize.height()); | 
| 457 |  | 
| 458 |         // It will not change min/max sizes if invalid. | 
| 459 |         if (minWidth > maxWidth || minHeight > maxHeight) | 
| 460 |             return; | 
| 461 |  | 
| 462 |         if (maxWidth == QWINDOWSIZE_MAX) | 
| 463 |             maxWidth = 0; | 
| 464 |         if (maxHeight == QWINDOWSIZE_MAX) | 
| 465 |             maxHeight = 0; | 
| 466 |  | 
| 467 |         m_toplevel->set_min_size(minWidth, minHeight); | 
| 468 |         m_toplevel->set_max_size(maxWidth, maxHeight); | 
| 469 |     } | 
| 470 | } | 
| 471 |  | 
| 472 | void *QWaylandXdgSurface::nativeResource(const QByteArray &resource) | 
| 473 | { | 
| 474 |     QByteArray lowerCaseResource = resource.toLower(); | 
| 475 |     if (lowerCaseResource == "xdg_surface" ) | 
| 476 |         return object(); | 
| 477 |     else if (lowerCaseResource == "xdg_toplevel"  && m_toplevel) | 
| 478 |         return m_toplevel->object(); | 
| 479 |     else if (lowerCaseResource == "xdg_popup"  && m_popup) | 
| 480 |         return m_popup->object(); | 
| 481 |     return nullptr; | 
| 482 | } | 
| 483 |  | 
| 484 | std::any QWaylandXdgSurface::surfaceRole() const | 
| 485 | { | 
| 486 |     if (m_toplevel) | 
| 487 |         return m_toplevel->object(); | 
| 488 |     if (m_popup) | 
| 489 |         return m_popup->object(); | 
| 490 |     return {}; | 
| 491 | } | 
| 492 |  | 
| 493 | void QWaylandXdgSurface::requestWindowStates(Qt::WindowStates states) | 
| 494 | { | 
| 495 |     if (m_toplevel) | 
| 496 |         m_toplevel->requestWindowStates(states); | 
| 497 |     else | 
| 498 |         qCDebug(lcQpaWayland) << "Ignoring window states requested by non-toplevel zxdg_surface_v6." ; | 
| 499 | } | 
| 500 |  | 
| 501 | void QWaylandXdgSurface::setToplevel() | 
| 502 | { | 
| 503 |     Q_ASSERT(!m_toplevel && !m_popup); | 
| 504 |     m_toplevel = new Toplevel(this); | 
| 505 | } | 
| 506 |  | 
| 507 | void QWaylandXdgSurface::setPopup(QWaylandWindow *parent) | 
| 508 | { | 
| 509 |     Q_ASSERT(!m_toplevel && !m_popup); | 
| 510 |  | 
| 511 |     std::unique_ptr<Positioner> positioner = createPositioner(parent); | 
| 512 |     m_popup = new Popup(this, parent, positioner.get()); | 
| 513 | } | 
| 514 |  | 
| 515 | void QWaylandXdgSurface::setGrabPopup(QWaylandWindow *parent, QWaylandInputDevice *device, int serial) | 
| 516 | { | 
| 517 |     setPopup(parent); | 
| 518 |     m_popup->grab(seat: device, serial); | 
| 519 |  | 
| 520 |     // Synthesize Qt enter/leave events for popup | 
| 521 |     if (!parent) | 
| 522 |         return; | 
| 523 |     QWindow *leave = parent->window(); | 
| 524 |     QWindowSystemInterface::handleLeaveEvent(window: leave); | 
| 525 |  | 
| 526 |     QWindow *enter = nullptr; | 
| 527 |     if (m_popup && m_popup->m_xdgSurface && m_popup->m_xdgSurface->window()) | 
| 528 |         enter = m_popup->m_xdgSurface->window()->window(); | 
| 529 |  | 
| 530 |     if (enter) { | 
| 531 |         const auto pos = m_popup->m_xdgSurface->window()->display()->waylandCursor()->pos(); | 
| 532 |         QWindowSystemInterface::handleEnterEvent(window: enter, local: enter->handle()->mapFromGlobal(pos), global: pos); | 
| 533 |     } | 
| 534 | } | 
| 535 |  | 
| 536 | void QWaylandXdgSurface::xdg_surface_configure(uint32_t serial) | 
| 537 | { | 
| 538 |     m_pendingConfigureSerial = serial; | 
| 539 |     if (!m_configured) { | 
| 540 |         // We have to do the initial applyConfigure() immediately, since that is the expose. | 
| 541 |         applyConfigure(); | 
| 542 |     } else { | 
| 543 |         // Later configures are probably resizes, so we have to queue them up for a time when we | 
| 544 |         // are not painting to the window. | 
| 545 |         m_window->applyConfigureWhenPossible(); | 
| 546 |     } | 
| 547 | } | 
| 548 |  | 
| 549 | bool QWaylandXdgSurface::requestActivate() | 
| 550 | { | 
| 551 |     if (auto *activation = m_shell->activation()) { | 
| 552 |         if (!m_activationToken.isEmpty()) { | 
| 553 |             activation->activate(m_activationToken, window()->wlSurface()); | 
| 554 |             m_activationToken = {}; | 
| 555 |             return true; | 
| 556 |         } else if (const auto token = qEnvironmentVariable(varName: "XDG_ACTIVATION_TOKEN" ); !token.isEmpty()) { | 
| 557 |             activation->activate(token, window()->wlSurface()); | 
| 558 |             qunsetenv(varName: "XDG_ACTIVATION_TOKEN" ); | 
| 559 |             return true; | 
| 560 |         } else { | 
| 561 |             const auto focusWindow = QGuiApplication::focusWindow(); | 
| 562 |             // At least GNOME requires to request the token in order to get the | 
| 563 |             // focus stealing prevention indication, so requestXdgActivationToken call | 
| 564 |             // is still necessary in that case. | 
| 565 |             const auto wlWindow = focusWindow ? static_cast<QWaylandWindow*>(focusWindow->handle()) : m_window; | 
| 566 |  | 
| 567 |             QString appId; | 
| 568 |             if (const auto xdgSurface = qobject_cast<QWaylandXdgSurface *>(object: wlWindow->shellSurface())) | 
| 569 |                 appId = xdgSurface->m_appId; | 
| 570 |  | 
| 571 |             if (const auto seat = wlWindow->display()->lastInputDevice()) { | 
| 572 |                 const auto tokenProvider = activation->requestXdgActivationToken( | 
| 573 |                         wlWindow->display(), wlWindow->wlSurface(), seat->serial(), appId); | 
| 574 |                 connect(tokenProvider, &QWaylandXdgActivationTokenV1::done, this, | 
| 575 |                         [this](const QString &token) { | 
| 576 |                             m_shell->activation()->activate(token, window()->wlSurface()); | 
| 577 |                         }); | 
| 578 |                 connect(tokenProvider, &QWaylandXdgActivationTokenV1::done, tokenProvider, &QObject::deleteLater); | 
| 579 |                 return true; | 
| 580 |             } | 
| 581 |         } | 
| 582 |     } | 
| 583 |     return false; | 
| 584 | } | 
| 585 |  | 
| 586 | bool QWaylandXdgSurface::requestActivateOnShow() | 
| 587 | { | 
| 588 |     const Qt::WindowType type = m_window->window()->type(); | 
| 589 |     if (type == Qt::ToolTip || type == Qt::Popup || type == Qt::SplashScreen) | 
| 590 |         return false; | 
| 591 |  | 
| 592 |     if (m_window->window()->property("_q_showWithoutActivating" ).toBool()) | 
| 593 |         return false; | 
| 594 |  | 
| 595 |     return requestActivate(); | 
| 596 | } | 
| 597 |  | 
| 598 | void QWaylandXdgSurface::requestXdgActivationToken(quint32 serial) | 
| 599 | { | 
| 600 |     if (auto *activation = m_shell->activation()) { | 
| 601 |         auto tokenProvider = activation->requestXdgActivationToken( | 
| 602 |                 m_shell->m_display, m_window->wlSurface(), serial, m_appId); | 
| 603 |         connect(tokenProvider, &QWaylandXdgActivationTokenV1::done, m_window, &QWaylandWindow::xdgActivationTokenCreated); | 
| 604 |         connect(tokenProvider, &QWaylandXdgActivationTokenV1::done, tokenProvider, &QObject::deleteLater); | 
| 605 |     } else { | 
| 606 |         QWaylandShellSurface::requestXdgActivationToken(serial); | 
| 607 |     } | 
| 608 | } | 
| 609 |  | 
| 610 | void QWaylandXdgSurface::setXdgActivationToken(const QString &token) | 
| 611 | { | 
| 612 |     if (m_shell->activation()) { | 
| 613 |         m_activationToken = token; | 
| 614 |     } else { | 
| 615 |         qCWarning(lcQpaWayland) << "zxdg_activation_v1 not available" ; | 
| 616 |     } | 
| 617 | } | 
| 618 |  | 
| 619 | void QWaylandXdgSurface::setAlertState(bool enabled) | 
| 620 | { | 
| 621 |     if (m_alertState == enabled) | 
| 622 |         return; | 
| 623 |  | 
| 624 |     m_alertState = enabled; | 
| 625 |  | 
| 626 |     if (!m_alertState) | 
| 627 |         return; | 
| 628 |  | 
| 629 |     auto *activation = m_shell->activation(); | 
| 630 |     if (!activation) | 
| 631 |         return; | 
| 632 |  | 
| 633 |     const auto tokenProvider = activation->requestXdgActivationToken( | 
| 634 |             m_shell->m_display, m_window->wlSurface(), std::nullopt, m_appId); | 
| 635 |     connect(tokenProvider, &QWaylandXdgActivationTokenV1::done, this, | 
| 636 |             [this](const QString &token) { | 
| 637 |                 m_shell->activation()->activate(token, m_window->wlSurface()); | 
| 638 |             }); | 
| 639 |     connect(tokenProvider, &QWaylandXdgActivationTokenV1::done, tokenProvider, &QObject::deleteLater); | 
| 640 | } | 
| 641 |  | 
| 642 | QString QWaylandXdgSurface::externWindowHandle() | 
| 643 | { | 
| 644 |     if (!m_toplevel || !m_shell->exporter()) { | 
| 645 |         return QString(); | 
| 646 |     } | 
| 647 |     if (!m_toplevel->m_exported) { | 
| 648 |         auto *exporterWrapper = static_cast<zxdg_exporter_v2 *>( | 
| 649 |                 wl_proxy_create_wrapper(m_shell->exporter()->object())); | 
| 650 |         auto exportQueue = wl_display_create_queue(m_shell->display()->wl_display()); | 
| 651 |         wl_proxy_set_queue(reinterpret_cast<wl_proxy *>(exporterWrapper), exportQueue); | 
| 652 |         m_toplevel->m_exported.reset(other: new QWaylandXdgExportedV2( | 
| 653 |                 zxdg_exporter_v2_export_toplevel(exporterWrapper, m_window->wlSurface()))); | 
| 654 |         // handle events is sent immediately | 
| 655 |         wl_display_roundtrip_queue(m_shell->display()->wl_display(), exportQueue); | 
| 656 |  | 
| 657 |         wl_proxy_set_queue(reinterpret_cast<wl_proxy *>(m_toplevel->m_exported->object()), nullptr); | 
| 658 |         wl_proxy_wrapper_destroy(exporterWrapper); | 
| 659 |         wl_event_queue_destroy(exportQueue); | 
| 660 |     } | 
| 661 |     return m_toplevel->m_exported->handle(); | 
| 662 | } | 
| 663 |  | 
| 664 | void QWaylandXdgSurface::setWindowPosition(const QPoint &position) | 
| 665 | { | 
| 666 |     Q_UNUSED(position); | 
| 667 |  | 
| 668 |     if (!m_popup) | 
| 669 |         return; | 
| 670 |  | 
| 671 |     if (m_popup->version() < XDG_POPUP_REPOSITIONED_SINCE_VERSION) | 
| 672 |         return; | 
| 673 |  | 
| 674 |     std::unique_ptr<Positioner> positioner = createPositioner(parent: m_window->transientParent()); | 
| 675 |     m_popup->m_waitingForRepositionSerial++; | 
| 676 |     m_popup->reposition(positioner->object(), m_popup->m_waitingForRepositionSerial); | 
| 677 |     m_popup->m_waitingForReposition = true; | 
| 678 | } | 
| 679 |  | 
| 680 | std::unique_ptr<QWaylandXdgSurface::Positioner> QWaylandXdgSurface::createPositioner(QWaylandWindow *parent) | 
| 681 | { | 
| 682 |     std::unique_ptr<Positioner> positioner(new Positioner(m_shell)); | 
| 683 |     // set_popup expects a position relative to the parent | 
| 684 |     QRect windowGeometry = m_window->windowContentGeometry(); | 
| 685 |     QMargins windowMargins = m_window->windowContentMargins() - m_window->clientSideMargins(); | 
| 686 |     QMargins parentMargins = parent->windowContentMargins() - parent->clientSideMargins(); | 
| 687 |  | 
| 688 |     // These property overrides may be removed when public API becomes available | 
| 689 |     QRect placementAnchor = m_window->window()->property("_q_waylandPopupAnchorRect" ).toRect(); | 
| 690 |     if (!placementAnchor.isValid()) { | 
| 691 |         placementAnchor = QRect(m_window->geometry().topLeft() - parent->geometry().topLeft(), QSize(1,1)); | 
| 692 |     } | 
| 693 |     placementAnchor.translate(dx: windowMargins.left(), dy: windowMargins.top()); | 
| 694 |     placementAnchor.translate(dx: -parentMargins.left(), dy: -parentMargins.top()); | 
| 695 |  | 
| 696 |     uint32_t anchor = QtWayland::xdg_positioner::anchor_top_left; | 
| 697 |     const QVariant anchorVariant = m_window->window()->property("_q_waylandPopupAnchor" ); | 
| 698 |     if (anchorVariant.isValid()) { | 
| 699 |         switch (anchorVariant.value<Qt::Edges>()) { | 
| 700 |         case Qt::Edges(): | 
| 701 |             anchor = QtWayland::xdg_positioner::anchor_none; | 
| 702 |             break; | 
| 703 |         case Qt::TopEdge: | 
| 704 |             anchor = QtWayland::xdg_positioner::anchor_top; | 
| 705 |             break; | 
| 706 |         case Qt::TopEdge | Qt::RightEdge: | 
| 707 |             anchor = QtWayland::xdg_positioner::anchor_top_right; | 
| 708 |             break; | 
| 709 |         case Qt::RightEdge: | 
| 710 |             anchor = QtWayland::xdg_positioner::anchor_right; | 
| 711 |             break; | 
| 712 |         case Qt::BottomEdge | Qt::RightEdge: | 
| 713 |             anchor = QtWayland::xdg_positioner::anchor_bottom_right; | 
| 714 |             break; | 
| 715 |         case Qt::BottomEdge: | 
| 716 |             anchor = QtWayland::xdg_positioner::anchor_bottom; | 
| 717 |             break; | 
| 718 |         case Qt::BottomEdge | Qt::LeftEdge: | 
| 719 |             anchor = QtWayland::xdg_positioner::anchor_bottom_left; | 
| 720 |             break; | 
| 721 |         case Qt::LeftEdge: | 
| 722 |             anchor = QtWayland::xdg_positioner::anchor_left; | 
| 723 |             break; | 
| 724 |         case Qt::TopEdge | Qt::LeftEdge: | 
| 725 |             anchor = QtWayland::xdg_positioner::anchor_top_left; | 
| 726 |             break; | 
| 727 |         } | 
| 728 |     } | 
| 729 |  | 
| 730 |     uint32_t gravity = QtWayland::xdg_positioner::gravity_bottom_right; | 
| 731 |     const QVariant  = m_window->window()->property("_q_waylandPopupGravity" ); | 
| 732 |     if (popupGravityVariant.isValid()) { | 
| 733 |         switch (popupGravityVariant.value<Qt::Edges>()) { | 
| 734 |         case Qt::Edges(): | 
| 735 |             gravity = QtWayland::xdg_positioner::gravity_none; | 
| 736 |             break; | 
| 737 |         case Qt::TopEdge: | 
| 738 |             gravity = QtWayland::xdg_positioner::gravity_top; | 
| 739 |             break; | 
| 740 |         case Qt::TopEdge | Qt::RightEdge: | 
| 741 |             gravity = QtWayland::xdg_positioner::gravity_top_right; | 
| 742 |             break; | 
| 743 |         case Qt::RightEdge: | 
| 744 |             gravity = QtWayland::xdg_positioner::gravity_right; | 
| 745 |             break; | 
| 746 |         case Qt::BottomEdge | Qt::RightEdge: | 
| 747 |             gravity = QtWayland::xdg_positioner::gravity_bottom_right; | 
| 748 |             break; | 
| 749 |         case Qt::BottomEdge: | 
| 750 |             gravity = QtWayland::xdg_positioner::gravity_bottom; | 
| 751 |             break; | 
| 752 |         case Qt::BottomEdge | Qt::LeftEdge: | 
| 753 |             gravity = QtWayland::xdg_positioner::gravity_bottom_left; | 
| 754 |             break; | 
| 755 |         case Qt::LeftEdge: | 
| 756 |             gravity = QtWayland::xdg_positioner::gravity_left; | 
| 757 |             break; | 
| 758 |         case Qt::TopEdge | Qt::LeftEdge: | 
| 759 |             gravity = QtWayland::xdg_positioner::gravity_top_left; | 
| 760 |             break; | 
| 761 |         } | 
| 762 |     } | 
| 763 |  | 
| 764 |     uint32_t constraintAdjustment = QtWayland::xdg_positioner::constraint_adjustment_slide_x | QtWayland::xdg_positioner::constraint_adjustment_slide_y; | 
| 765 |     const QVariant constraintAdjustmentVariant = m_window->window()->property("_q_waylandPopupConstraintAdjustment" ); | 
| 766 |     if (constraintAdjustmentVariant.isValid()) { | 
| 767 |         constraintAdjustment = constraintAdjustmentVariant.toUInt(); | 
| 768 |     } | 
| 769 |  | 
| 770 |     positioner->set_anchor_rect(placementAnchor.x(), | 
| 771 |                                 placementAnchor.y(), | 
| 772 |                                 placementAnchor.width(), | 
| 773 |                                 placementAnchor.height()); | 
| 774 |     positioner->set_anchor(anchor); | 
| 775 |     positioner->set_gravity(gravity); | 
| 776 |     positioner->set_size(windowGeometry.width(), windowGeometry.height()); | 
| 777 |     positioner->set_constraint_adjustment(constraintAdjustment); | 
| 778 |     return positioner; | 
| 779 | } | 
| 780 |  | 
| 781 |  | 
| 782 | QWaylandXdgShell::QWaylandXdgShell(QWaylandDisplay *display, QtWayland::xdg_wm_base *xdgWmBase) | 
| 783 |     : m_display(display), m_xdgWmBase(xdgWmBase) | 
| 784 | { | 
| 785 |     display->addRegistryListener(listener: &QWaylandXdgShell::handleRegistryGlobal, data: this); | 
| 786 | } | 
| 787 |  | 
| 788 | QWaylandXdgShell::~QWaylandXdgShell() | 
| 789 | { | 
| 790 |     m_display->removeListener(listener: &QWaylandXdgShell::handleRegistryGlobal, data: this); | 
| 791 | } | 
| 792 |  | 
| 793 | void QWaylandXdgShell::handleRegistryGlobal(void *data, wl_registry *registry, uint id, | 
| 794 |                                             const QString &interface, uint version) | 
| 795 | { | 
| 796 |     QWaylandXdgShell *xdgShell = static_cast<QWaylandXdgShell *>(data); | 
| 797 |     if (interface == QLatin1String(QWaylandXdgDecorationManagerV1::interface()->name)) | 
| 798 |         xdgShell->m_xdgDecorationManager.reset(new QWaylandXdgDecorationManagerV1(registry, id, version)); | 
| 799 |  | 
| 800 |     if (interface == QLatin1String(QWaylandXdgActivationV1::interface()->name)) { | 
| 801 |         xdgShell->m_xdgActivation.reset(new QWaylandXdgActivationV1(registry, id, version)); | 
| 802 |     } | 
| 803 |  | 
| 804 |     if (interface == QLatin1String(QWaylandXdgExporterV2::interface()->name)) { | 
| 805 |         xdgShell->m_xdgExporter.reset(other: new QWaylandXdgExporterV2(registry, id, version)); | 
| 806 |     } | 
| 807 |  | 
| 808 |     if (interface == QLatin1String(QWaylandXdgDialogWmV1::interface()->name)) { | 
| 809 |         xdgShell->m_xdgDialogWm.reset(other: new QWaylandXdgDialogWmV1(registry, id, version)); | 
| 810 |     } | 
| 811 | } | 
| 812 |  | 
| 813 | QWaylandXdgSurface::Positioner::Positioner(QWaylandXdgShell *xdgShell) | 
| 814 |     : QtWayland::xdg_positioner(xdgShell->m_xdgWmBase->create_positioner()) | 
| 815 | { | 
| 816 | } | 
| 817 |  | 
| 818 | QWaylandXdgSurface::Positioner::~Positioner() | 
| 819 | { | 
| 820 |     destroy(); | 
| 821 | } | 
| 822 |  | 
| 823 | } | 
| 824 |  | 
| 825 | QT_END_NAMESPACE | 
| 826 |  | 
| 827 | #include "moc_qwaylandxdgshell_p.cpp" | 
| 828 |  |