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, tokenProvider](const QString &token) { |
576 | m_shell->activation()->activate(token, window()->wlSurface()); |
577 | tokenProvider->deleteLater(); |
578 | }); |
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, this, |
604 | [this, tokenProvider](const QString &token) { |
605 | Q_EMIT m_window->xdgActivationTokenCreated(token); |
606 | tokenProvider->deleteLater(); |
607 | }); |
608 | } else { |
609 | QWaylandShellSurface::requestXdgActivationToken(serial); |
610 | } |
611 | } |
612 | |
613 | void QWaylandXdgSurface::setXdgActivationToken(const QString &token) |
614 | { |
615 | if (m_shell->activation()) { |
616 | m_activationToken = token; |
617 | } else { |
618 | qCWarning(lcQpaWayland) << "zxdg_activation_v1 not available"; |
619 | } |
620 | } |
621 | |
622 | void QWaylandXdgSurface::setAlertState(bool enabled) |
623 | { |
624 | if (m_alertState == enabled) |
625 | return; |
626 | |
627 | m_alertState = enabled; |
628 | |
629 | if (!m_alertState) |
630 | return; |
631 | |
632 | auto *activation = m_shell->activation(); |
633 | if (!activation) |
634 | return; |
635 | |
636 | const auto tokenProvider = activation->requestXdgActivationToken( |
637 | m_shell->m_display, m_window->wlSurface(), std::nullopt, m_appId); |
638 | connect(tokenProvider, &QWaylandXdgActivationTokenV1::done, this, |
639 | [this, tokenProvider](const QString &token) { |
640 | m_shell->activation()->activate(token, m_window->wlSurface()); |
641 | tokenProvider->deleteLater(); |
642 | }); |
643 | } |
644 | |
645 | QString QWaylandXdgSurface::externWindowHandle() |
646 | { |
647 | if (!m_toplevel || !m_shell->exporter()) { |
648 | return QString(); |
649 | } |
650 | if (!m_toplevel->m_exported) { |
651 | auto *exporterWrapper = static_cast<zxdg_exporter_v2 *>( |
652 | wl_proxy_create_wrapper(m_shell->exporter()->object())); |
653 | auto exportQueue = wl_display_create_queue(m_shell->display()->wl_display()); |
654 | wl_proxy_set_queue(reinterpret_cast<wl_proxy *>(exporterWrapper), exportQueue); |
655 | m_toplevel->m_exported.reset(other: new QWaylandXdgExportedV2( |
656 | zxdg_exporter_v2_export_toplevel(exporterWrapper, m_window->wlSurface()))); |
657 | // handle events is sent immediately |
658 | wl_display_roundtrip_queue(m_shell->display()->wl_display(), exportQueue); |
659 | |
660 | wl_proxy_set_queue(reinterpret_cast<wl_proxy *>(m_toplevel->m_exported->object()), nullptr); |
661 | wl_proxy_wrapper_destroy(exporterWrapper); |
662 | wl_event_queue_destroy(exportQueue); |
663 | } |
664 | return m_toplevel->m_exported->handle(); |
665 | } |
666 | |
667 | void QWaylandXdgSurface::setWindowPosition(const QPoint &position) |
668 | { |
669 | Q_UNUSED(position); |
670 | |
671 | if (!m_popup) |
672 | return; |
673 | |
674 | if (m_popup->version() < XDG_POPUP_REPOSITIONED_SINCE_VERSION) |
675 | return; |
676 | |
677 | std::unique_ptr<Positioner> positioner = createPositioner(parent: m_window->transientParent()); |
678 | m_popup->m_waitingForRepositionSerial++; |
679 | m_popup->reposition(positioner->object(), m_popup->m_waitingForRepositionSerial); |
680 | m_popup->m_waitingForReposition = true; |
681 | } |
682 | |
683 | std::unique_ptr<QWaylandXdgSurface::Positioner> QWaylandXdgSurface::createPositioner(QWaylandWindow *parent) |
684 | { |
685 | std::unique_ptr<Positioner> positioner(new Positioner(m_shell)); |
686 | // set_popup expects a position relative to the parent |
687 | QRect windowGeometry = m_window->windowContentGeometry(); |
688 | QMargins windowMargins = m_window->windowContentMargins() - m_window->clientSideMargins(); |
689 | QMargins parentMargins = parent->windowContentMargins() - parent->clientSideMargins(); |
690 | |
691 | // These property overrides may be removed when public API becomes available |
692 | QRect placementAnchor = m_window->window()->property("_q_waylandPopupAnchorRect").toRect(); |
693 | if (!placementAnchor.isValid()) { |
694 | placementAnchor = QRect(m_window->geometry().topLeft() - parent->geometry().topLeft(), QSize(1,1)); |
695 | } |
696 | placementAnchor.translate(dx: windowMargins.left(), dy: windowMargins.top()); |
697 | placementAnchor.translate(dx: -parentMargins.left(), dy: -parentMargins.top()); |
698 | |
699 | uint32_t anchor = QtWayland::xdg_positioner::anchor_top_left; |
700 | const QVariant anchorVariant = m_window->window()->property("_q_waylandPopupAnchor"); |
701 | if (anchorVariant.isValid()) { |
702 | switch (anchorVariant.value<Qt::Edges>()) { |
703 | case Qt::Edges(): |
704 | anchor = QtWayland::xdg_positioner::anchor_none; |
705 | break; |
706 | case Qt::TopEdge: |
707 | anchor = QtWayland::xdg_positioner::anchor_top; |
708 | break; |
709 | case Qt::TopEdge | Qt::RightEdge: |
710 | anchor = QtWayland::xdg_positioner::anchor_top_right; |
711 | break; |
712 | case Qt::RightEdge: |
713 | anchor = QtWayland::xdg_positioner::anchor_right; |
714 | break; |
715 | case Qt::BottomEdge | Qt::RightEdge: |
716 | anchor = QtWayland::xdg_positioner::anchor_bottom_right; |
717 | break; |
718 | case Qt::BottomEdge: |
719 | anchor = QtWayland::xdg_positioner::anchor_bottom; |
720 | break; |
721 | case Qt::BottomEdge | Qt::LeftEdge: |
722 | anchor = QtWayland::xdg_positioner::anchor_bottom_left; |
723 | break; |
724 | case Qt::LeftEdge: |
725 | anchor = QtWayland::xdg_positioner::anchor_left; |
726 | break; |
727 | case Qt::TopEdge | Qt::LeftEdge: |
728 | anchor = QtWayland::xdg_positioner::anchor_top_left; |
729 | break; |
730 | } |
731 | } |
732 | |
733 | uint32_t gravity = QtWayland::xdg_positioner::gravity_bottom_right; |
734 | const QVariant popupGravityVariant = m_window->window()->property("_q_waylandPopupGravity"); |
735 | if (popupGravityVariant.isValid()) { |
736 | switch (popupGravityVariant.value<Qt::Edges>()) { |
737 | case Qt::Edges(): |
738 | gravity = QtWayland::xdg_positioner::gravity_none; |
739 | break; |
740 | case Qt::TopEdge: |
741 | gravity = QtWayland::xdg_positioner::gravity_top; |
742 | break; |
743 | case Qt::TopEdge | Qt::RightEdge: |
744 | gravity = QtWayland::xdg_positioner::gravity_top_right; |
745 | break; |
746 | case Qt::RightEdge: |
747 | gravity = QtWayland::xdg_positioner::gravity_right; |
748 | break; |
749 | case Qt::BottomEdge | Qt::RightEdge: |
750 | gravity = QtWayland::xdg_positioner::gravity_bottom_right; |
751 | break; |
752 | case Qt::BottomEdge: |
753 | gravity = QtWayland::xdg_positioner::gravity_bottom; |
754 | break; |
755 | case Qt::BottomEdge | Qt::LeftEdge: |
756 | gravity = QtWayland::xdg_positioner::gravity_bottom_left; |
757 | break; |
758 | case Qt::LeftEdge: |
759 | gravity = QtWayland::xdg_positioner::gravity_left; |
760 | break; |
761 | case Qt::TopEdge | Qt::LeftEdge: |
762 | gravity = QtWayland::xdg_positioner::gravity_top_left; |
763 | break; |
764 | } |
765 | } |
766 | |
767 | uint32_t constraintAdjustment = QtWayland::xdg_positioner::constraint_adjustment_slide_x | QtWayland::xdg_positioner::constraint_adjustment_slide_y; |
768 | const QVariant constraintAdjustmentVariant = m_window->window()->property("_q_waylandPopupConstraintAdjustment"); |
769 | if (constraintAdjustmentVariant.isValid()) { |
770 | constraintAdjustment = constraintAdjustmentVariant.toUInt(); |
771 | } |
772 | |
773 | positioner->set_anchor_rect(placementAnchor.x(), |
774 | placementAnchor.y(), |
775 | placementAnchor.width(), |
776 | placementAnchor.height()); |
777 | positioner->set_anchor(anchor); |
778 | positioner->set_gravity(gravity); |
779 | positioner->set_size(windowGeometry.width(), windowGeometry.height()); |
780 | positioner->set_constraint_adjustment(constraintAdjustment); |
781 | return positioner; |
782 | } |
783 | |
784 | |
785 | QWaylandXdgShell::QWaylandXdgShell(QWaylandDisplay *display, QtWayland::xdg_wm_base *xdgWmBase) |
786 | : m_display(display), m_xdgWmBase(xdgWmBase) |
787 | { |
788 | display->addRegistryListener(listener: &QWaylandXdgShell::handleRegistryGlobal, data: this); |
789 | } |
790 | |
791 | QWaylandXdgShell::~QWaylandXdgShell() |
792 | { |
793 | m_display->removeListener(listener: &QWaylandXdgShell::handleRegistryGlobal, data: this); |
794 | } |
795 | |
796 | void QWaylandXdgShell::handleRegistryGlobal(void *data, wl_registry *registry, uint id, |
797 | const QString &interface, uint version) |
798 | { |
799 | QWaylandXdgShell *xdgShell = static_cast<QWaylandXdgShell *>(data); |
800 | if (interface == QLatin1String(QWaylandXdgDecorationManagerV1::interface()->name)) |
801 | xdgShell->m_xdgDecorationManager.reset(new QWaylandXdgDecorationManagerV1(registry, id, version)); |
802 | |
803 | if (interface == QLatin1String(QWaylandXdgActivationV1::interface()->name)) { |
804 | xdgShell->m_xdgActivation.reset(new QWaylandXdgActivationV1(registry, id, version)); |
805 | } |
806 | |
807 | if (interface == QLatin1String(QWaylandXdgExporterV2::interface()->name)) { |
808 | xdgShell->m_xdgExporter.reset(other: new QWaylandXdgExporterV2(registry, id, version)); |
809 | } |
810 | |
811 | if (interface == QLatin1String(QWaylandXdgDialogWmV1::interface()->name)) { |
812 | xdgShell->m_xdgDialogWm.reset(other: new QWaylandXdgDialogWmV1(registry, id, version)); |
813 | } |
814 | } |
815 | |
816 | QWaylandXdgSurface::Positioner::Positioner(QWaylandXdgShell *xdgShell) |
817 | : QtWayland::xdg_positioner(xdgShell->m_xdgWmBase->create_positioner()) |
818 | { |
819 | } |
820 | |
821 | QWaylandXdgSurface::Positioner::~Positioner() |
822 | { |
823 | destroy(); |
824 | } |
825 | |
826 | } |
827 | |
828 | QT_END_NAMESPACE |
829 | |
830 | #include "moc_qwaylandxdgshell_p.cpp" |
831 |
Definitions
- Toplevel
- ~Toplevel
- applyConfigure
- wantsDecorations
- xdg_toplevel_configure_bounds
- xdg_toplevel_configure
- xdg_toplevel_close
- requestWindowFlags
- requestWindowStates
- convertToResizeEdges
- Popup
- ~Popup
- applyConfigure
- resetConfiguration
- grab
- xdg_popup_configure
- xdg_popup_popup_done
- xdg_popup_repositioned
- QWaylandXdgSurface
- ~QWaylandXdgSurface
- resize
- move
- showWindowMenu
- setTitle
- setAppId
- setWindowFlags
- isExposed
- handleExpose
- applyConfigure
- wantsDecorations
- propagateSizeHints
- setWindowGeometry
- setSizeHints
- nativeResource
- surfaceRole
- requestWindowStates
- setToplevel
- setPopup
- setGrabPopup
- xdg_surface_configure
- requestActivate
- requestActivateOnShow
- requestXdgActivationToken
- setXdgActivationToken
- setAlertState
- externWindowHandle
- setWindowPosition
- createPositioner
- QWaylandXdgShell
- ~QWaylandXdgShell
- handleRegistryGlobal
- Positioner
Learn to use CMake with our Intro Training
Find out more