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
21QT_BEGIN_NAMESPACE
22
23namespace QtWaylandClient {
24
25QWaylandXdgSurface::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
48QWaylandXdgSurface::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
58void 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
113bool 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
122void QWaylandXdgSurface::Toplevel::xdg_toplevel_configure_bounds(int32_t width, int32_t height)
123{
124 m_pending.bounds = QSize(width, height);
125}
126
127void 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
173void QWaylandXdgSurface::Toplevel::xdg_toplevel_close()
174{
175 QWindowSystemInterface::handleCloseEvent(m_xdgSurface->m_window->window());
176}
177
178void 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
190void 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
224QtWayland::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
233QWaylandXdgSurface::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
244QWaylandXdgSurface::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
267void 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
278void QWaylandXdgSurface::Popup::resetConfiguration()
279{
280 m_pendingGeometry = QRect();
281}
282
283void QWaylandXdgSurface::Popup::grab(QWaylandInputDevice *seat, uint serial)
284{
285 xdg_popup::grab(seat->wl_seat(), serial);
286 m_grabbing = true;
287}
288
289void 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
294void QWaylandXdgSurface::Popup::xdg_popup_popup_done()
295{
296 QWindowSystemInterface::handleCloseEvent(m_xdgSurface->m_window->window());
297}
298
299void QWaylandXdgSurface::Popup::xdg_popup_repositioned(uint32_t token)
300{
301 if (token == m_waitingForRepositionSerial)
302 m_waitingForReposition = false;
303}
304
305QWaylandXdgSurface::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
325QWaylandXdgSurface::~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
338bool 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
348bool 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
357bool 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
367void QWaylandXdgSurface::setTitle(const QString &title)
368{
369 if (m_toplevel)
370 m_toplevel->set_title(title);
371}
372
373void QWaylandXdgSurface::setAppId(const QString &appId)
374{
375 if (m_toplevel)
376 m_toplevel->set_app_id(appId);
377
378 m_appId = appId;
379}
380
381void QWaylandXdgSurface::setWindowFlags(Qt::WindowFlags flags)
382{
383 if (m_toplevel)
384 m_toplevel->requestWindowFlags(flags);
385}
386
387bool 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
401bool QWaylandXdgSurface::handleExpose(const QRegion &region)
402{
403 if (!isExposed() && !region.isEmpty()) {
404 return true;
405 }
406 setWindowGeometry(window()->windowContentGeometry());
407 return false;
408}
409
410void 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
431bool QWaylandXdgSurface::wantsDecorations() const
432{
433 return m_toplevel && m_toplevel->wantsDecorations();
434}
435
436void QWaylandXdgSurface::propagateSizeHints()
437{
438 setSizeHints();
439}
440
441void QWaylandXdgSurface::setWindowGeometry(const QRect &rect)
442{
443 if (window()->isExposed())
444 set_window_geometry(rect.x(), rect.y(), rect.width(), rect.height());
445}
446
447void 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
472void *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
484std::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
493void 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
501void QWaylandXdgSurface::setToplevel()
502{
503 Q_ASSERT(!m_toplevel && !m_popup);
504 m_toplevel = new Toplevel(this);
505}
506
507void 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
515void 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
536void 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
549bool 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
586bool 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
598void 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
613void 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
622void 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
645QString 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
667void 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
683std::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
785QWaylandXdgShell::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
791QWaylandXdgShell::~QWaylandXdgShell()
792{
793 m_display->removeListener(listener: &QWaylandXdgShell::handleRegistryGlobal, data: this);
794}
795
796void 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
816QWaylandXdgSurface::Positioner::Positioner(QWaylandXdgShell *xdgShell)
817 : QtWayland::xdg_positioner(xdgShell->m_xdgWmBase->create_positioner())
818{
819}
820
821QWaylandXdgSurface::Positioner::~Positioner()
822{
823 destroy();
824}
825
826}
827
828QT_END_NAMESPACE
829
830#include "moc_qwaylandxdgshell_p.cpp"
831

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtwayland/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp