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