1 /****************************************************************************
2 **
3 ** Copyright (C) 2017 The Qt Company Ltd.
4 ** Copyright (C) 2017 Eurogiciel, author: <philippe.coval@eurogiciel.fr>
5 ** Contact: https://www.qt.io/licensing/
6 **
7 ** This file is part of the config.tests of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** Commercial License Usage
11 ** Licensees holding valid commercial Qt licenses may use this file in
12 ** accordance with the commercial license agreement provided with the
13 ** Software or, alternatively, in accordance with the terms contained in
14 ** a written agreement between you and The Qt Company. For licensing terms
15 ** and conditions see https://www.qt.io/terms-conditions. For further
16 ** information use the contact form at https://www.qt.io/contact-us.
17 **
18 ** GNU Lesser General Public License Usage
19 ** Alternatively, this file may be used under the terms of the GNU Lesser
20 ** General Public License version 3 as published by the Free Software
21 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
22 ** packaging of this file. Please review the following information to
23 ** ensure the GNU Lesser General Public License version 3 requirements
24 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
25 **
26 ** GNU General Public License Usage
27 ** Alternatively, this file may be used under the terms of the GNU
28 ** General Public License version 2.0 or (at your option) the GNU General
29 ** Public license version 3 or any later version approved by the KDE Free
30 ** Qt Foundation. The licenses are as published by the Free Software
31 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
32 ** included in the packaging of this file. Please review the following
33 ** information to ensure the GNU General Public License requirements will
34 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
35 ** https://www.gnu.org/licenses/gpl-3.0.html.
36 **
37 ** $QT_END_LICENSE$
38 **
39 ****************************************************************************/
40
41 #include "qwaylandxdgshell_p.h"
42
43 #include <QtWaylandClient/private/qwaylanddisplay_p.h>
44 #include <QtWaylandClient/private/qwaylandwindow_p.h>
45 #include <QtWaylandClient/private/qwaylandinputdevice_p.h>
46 #include <QtWaylandClient/private/qwaylandscreen_p.h>
47 #include <QtWaylandClient/private/qwaylandabstractdecoration_p.h>
48
49 #include <QtGui/private/qwindow_p.h>
50
51 QT_BEGIN_NAMESPACE
52
53 namespace QtWaylandClient {
54
Toplevel(QWaylandXdgSurface * xdgSurface)55 QWaylandXdgSurface::Toplevel::Toplevel(QWaylandXdgSurface *xdgSurface)
56 : QtWayland::xdg_toplevel(xdgSurface->get_toplevel())
57 , m_xdgSurface(xdgSurface)
58 {
59 QWindow *window = xdgSurface->window()->window();
60 if (auto *decorationManager = m_xdgSurface->m_shell->decorationManager()) {
61 if (!(window->flags() & Qt::FramelessWindowHint))
62 m_decoration = decorationManager->createToplevelDecoration(object());
63 }
64 requestWindowStates(window->windowStates());
65 requestWindowFlags(window->flags());
66 }
67
~Toplevel()68 QWaylandXdgSurface::Toplevel::~Toplevel()
69 {
70 // The protocol spec requires that the decoration object is deleted before xdg_toplevel.
71 delete m_decoration;
72 m_decoration = nullptr;
73
74 if (isInitialized())
75 destroy();
76 }
77
applyConfigure()78 void QWaylandXdgSurface::Toplevel::applyConfigure()
79 {
80 if (!(m_applied.states & (Qt::WindowMaximized|Qt::WindowFullScreen)))
81 m_normalSize = m_xdgSurface->m_window->windowFrameGeometry().size();
82
83 if ((m_pending.states & Qt::WindowActive) && !(m_applied.states & Qt::WindowActive)
84 && !m_xdgSurface->m_window->display()->isKeyboardAvailable())
85 m_xdgSurface->m_window->display()->handleWindowActivated(m_xdgSurface->m_window);
86
87 if (!(m_pending.states & Qt::WindowActive) && (m_applied.states & Qt::WindowActive)
88 && !m_xdgSurface->m_window->display()->isKeyboardAvailable())
89 m_xdgSurface->m_window->display()->handleWindowDeactivated(m_xdgSurface->m_window);
90
91 m_xdgSurface->m_window->handleWindowStatesChanged(m_pending.states);
92
93 if (m_pending.size.isEmpty()) {
94 // An empty size in the configure means it's up to the client to choose the size
95 bool normalPending = !(m_pending.states & (Qt::WindowMaximized|Qt::WindowFullScreen));
96 if (normalPending && !m_normalSize.isEmpty())
97 m_xdgSurface->m_window->resizeFromApplyConfigure(m_normalSize);
98 } else {
99 m_xdgSurface->m_window->resizeFromApplyConfigure(m_pending.size);
100 }
101
102 m_applied = m_pending;
103 qCDebug(lcQpaWayland) << "Applied pending xdg_toplevel configure event:" << m_applied.size << m_applied.states;
104 }
105
wantsDecorations()106 bool QWaylandXdgSurface::Toplevel::wantsDecorations()
107 {
108 if (m_decoration && (m_decoration->pending() == QWaylandXdgToplevelDecorationV1::mode_server_side
109 || !m_decoration->isConfigured()))
110 return false;
111
112 return !(m_pending.states & Qt::WindowFullScreen);
113 }
114
xdg_toplevel_configure(int32_t width,int32_t height,wl_array * states)115 void QWaylandXdgSurface::Toplevel::xdg_toplevel_configure(int32_t width, int32_t height, wl_array *states)
116 {
117 m_pending.size = QSize(width, height);
118
119 auto *xdgStates = static_cast<uint32_t *>(states->data);
120 size_t numStates = states->size / sizeof(uint32_t);
121
122 m_pending.states = Qt::WindowNoState;
123
124 for (size_t i = 0; i < numStates; i++) {
125 switch (xdgStates[i]) {
126 case XDG_TOPLEVEL_STATE_ACTIVATED:
127 m_pending.states |= Qt::WindowActive;
128 break;
129 case XDG_TOPLEVEL_STATE_MAXIMIZED:
130 m_pending.states |= Qt::WindowMaximized;
131 break;
132 case XDG_TOPLEVEL_STATE_FULLSCREEN:
133 m_pending.states |= Qt::WindowFullScreen;
134 break;
135 default:
136 break;
137 }
138 }
139 qCDebug(lcQpaWayland) << "Received xdg_toplevel.configure with" << m_pending.size
140 << "and" << m_pending.states;
141 }
142
xdg_toplevel_close()143 void QWaylandXdgSurface::Toplevel::xdg_toplevel_close()
144 {
145 m_xdgSurface->m_window->window()->close();
146 }
147
requestWindowFlags(Qt::WindowFlags flags)148 void QWaylandXdgSurface::Toplevel::requestWindowFlags(Qt::WindowFlags flags)
149 {
150 if (m_decoration) {
151 if (flags & Qt::FramelessWindowHint) {
152 delete m_decoration;
153 m_decoration = nullptr;
154 } else {
155 m_decoration->unsetMode();
156 }
157 }
158 }
159
requestWindowStates(Qt::WindowStates states)160 void QWaylandXdgSurface::Toplevel::requestWindowStates(Qt::WindowStates states)
161 {
162 // Re-send what's different from the applied state
163 Qt::WindowStates changedStates = m_applied.states ^ states;
164
165 if (changedStates & Qt::WindowMaximized) {
166 if (states & Qt::WindowMaximized)
167 set_maximized();
168 else
169 unset_maximized();
170 }
171
172 if (changedStates & Qt::WindowFullScreen) {
173 if (states & Qt::WindowFullScreen) {
174 auto screen = m_xdgSurface->window()->waylandScreen();
175 if (screen) {
176 set_fullscreen(screen->output());
177 }
178 } else
179 unset_fullscreen();
180 }
181
182 // Minimized state is not reported by the protocol, so always send it
183 if (states & Qt::WindowMinimized) {
184 set_minimized();
185 m_xdgSurface->window()->handleWindowStatesChanged(states & ~Qt::WindowMinimized);
186 }
187 }
188
convertToResizeEdges(Qt::Edges edges)189 QtWayland::xdg_toplevel::resize_edge QWaylandXdgSurface::Toplevel::convertToResizeEdges(Qt::Edges edges)
190 {
191 return static_cast<enum resize_edge>(
192 ((edges & Qt::TopEdge) ? resize_edge_top : 0)
193 | ((edges & Qt::BottomEdge) ? resize_edge_bottom : 0)
194 | ((edges & Qt::LeftEdge) ? resize_edge_left : 0)
195 | ((edges & Qt::RightEdge) ? resize_edge_right : 0));
196 }
197
Popup(QWaylandXdgSurface * xdgSurface,QWaylandXdgSurface * parent,QtWayland::xdg_positioner * positioner)198 QWaylandXdgSurface::Popup::Popup(QWaylandXdgSurface *xdgSurface, QWaylandXdgSurface *parent,
199 QtWayland::xdg_positioner *positioner)
200 : xdg_popup(xdgSurface->get_popup(parent->object(), positioner->object()))
201 , m_xdgSurface(xdgSurface)
202 , m_parent(parent)
203 {
204 }
205
~Popup()206 QWaylandXdgSurface::Popup::~Popup()
207 {
208 if (isInitialized())
209 destroy();
210
211 if (m_grabbing) {
212 auto *shell = m_xdgSurface->m_shell;
213 Q_ASSERT(shell->m_topmostGrabbingPopup == this);
214 shell->m_topmostGrabbingPopup = m_parent->m_popup;
215 }
216 }
217
grab(QWaylandInputDevice * seat,uint serial)218 void QWaylandXdgSurface::Popup::grab(QWaylandInputDevice *seat, uint serial)
219 {
220 m_xdgSurface->m_shell->m_topmostGrabbingPopup = this;
221 xdg_popup::grab(seat->wl_seat(), serial);
222 m_grabbing = true;
223 }
224
xdg_popup_popup_done()225 void QWaylandXdgSurface::Popup::xdg_popup_popup_done()
226 {
227 m_xdgSurface->m_window->window()->close();
228 }
229
QWaylandXdgSurface(QWaylandXdgShell * shell,::xdg_surface * surface,QWaylandWindow * window)230 QWaylandXdgSurface::QWaylandXdgSurface(QWaylandXdgShell *shell, ::xdg_surface *surface, QWaylandWindow *window)
231 : QWaylandShellSurface(window)
232 , xdg_surface(surface)
233 , m_shell(shell)
234 , m_window(window)
235 {
236 QWaylandDisplay *display = window->display();
237 Qt::WindowType type = window->window()->type();
238 auto *transientParent = window->transientParent();
239
240 if (type == Qt::ToolTip && transientParent) {
241 setPopup(transientParent);
242 } else if (type == Qt::Popup && transientParent && display->lastInputDevice()) {
243 setGrabPopup(transientParent, display->lastInputDevice(), display->lastInputSerial());
244 } else {
245 setToplevel();
246 if (transientParent) {
247 auto parentXdgSurface = static_cast<QWaylandXdgSurface *>(transientParent->shellSurface());
248 if (parentXdgSurface)
249 m_toplevel->set_parent(parentXdgSurface->m_toplevel->object());
250 }
251 }
252 setSizeHints();
253 }
254
~QWaylandXdgSurface()255 QWaylandXdgSurface::~QWaylandXdgSurface()
256 {
257 if (m_toplevel) {
258 delete m_toplevel;
259 m_toplevel = nullptr;
260 }
261 if (m_popup) {
262 delete m_popup;
263 m_popup = nullptr;
264 }
265 destroy();
266 }
267
resize(QWaylandInputDevice * inputDevice,Qt::Edges edges)268 bool QWaylandXdgSurface::resize(QWaylandInputDevice *inputDevice, Qt::Edges edges)
269 {
270 if (!m_toplevel || !m_toplevel->isInitialized())
271 return false;
272
273 auto resizeEdges = Toplevel::convertToResizeEdges(edges);
274 m_toplevel->resize(inputDevice->wl_seat(), inputDevice->serial(), resizeEdges);
275 return true;
276 }
277
move(QWaylandInputDevice * inputDevice)278 bool QWaylandXdgSurface::move(QWaylandInputDevice *inputDevice)
279 {
280 if (m_toplevel && m_toplevel->isInitialized()) {
281 m_toplevel->move(inputDevice->wl_seat(), inputDevice->serial());
282 return true;
283 }
284 return false;
285 }
286
showWindowMenu(QWaylandInputDevice * seat)287 bool QWaylandXdgSurface::showWindowMenu(QWaylandInputDevice *seat)
288 {
289 if (m_toplevel && m_toplevel->isInitialized()) {
290 QPoint position = seat->pointerSurfacePosition().toPoint();
291 m_toplevel->show_window_menu(seat->wl_seat(), seat->serial(), position.x(), position.y());
292 return true;
293 }
294 return false;
295 }
296
setTitle(const QString & title)297 void QWaylandXdgSurface::setTitle(const QString &title)
298 {
299 if (m_toplevel)
300 m_toplevel->set_title(title);
301 }
302
setAppId(const QString & appId)303 void QWaylandXdgSurface::setAppId(const QString &appId)
304 {
305 if (m_toplevel)
306 m_toplevel->set_app_id(appId);
307 }
308
setWindowFlags(Qt::WindowFlags flags)309 void QWaylandXdgSurface::setWindowFlags(Qt::WindowFlags flags)
310 {
311 if (m_toplevel)
312 m_toplevel->requestWindowFlags(flags);
313 }
314
isExposed() const315 bool QWaylandXdgSurface::isExposed() const
316 {
317 return m_configured || m_pendingConfigureSerial;
318 }
319
handleExpose(const QRegion & region)320 bool QWaylandXdgSurface::handleExpose(const QRegion ®ion)
321 {
322 if (!isExposed() && !region.isEmpty()) {
323 m_exposeRegion = region;
324 return true;
325 }
326 return false;
327 }
328
applyConfigure()329 void QWaylandXdgSurface::applyConfigure()
330 {
331 Q_ASSERT(m_pendingConfigureSerial != 0);
332
333 if (m_toplevel)
334 m_toplevel->applyConfigure();
335
336 m_configured = true;
337 ack_configure(m_pendingConfigureSerial);
338
339 m_pendingConfigureSerial = 0;
340 }
341
wantsDecorations() const342 bool QWaylandXdgSurface::wantsDecorations() const
343 {
344 return m_toplevel && m_toplevel->wantsDecorations();
345 }
346
propagateSizeHints()347 void QWaylandXdgSurface::propagateSizeHints()
348 {
349 setSizeHints();
350
351 if (m_toplevel && m_window)
352 m_window->commit();
353 }
354
setWindowGeometry(const QRect & rect)355 void QWaylandXdgSurface::setWindowGeometry(const QRect &rect)
356 {
357 set_window_geometry(rect.x(), rect.y(), rect.width(), rect.height());
358 }
359
setSizeHints()360 void QWaylandXdgSurface::setSizeHints()
361 {
362 if (m_toplevel && m_window) {
363 const int minWidth = qMax(0, m_window->windowMinimumSize().width());
364 const int minHeight = qMax(0, m_window->windowMinimumSize().height());
365 m_toplevel->set_min_size(minWidth, minHeight);
366
367 int maxWidth = qMax(0, m_window->windowMaximumSize().width());
368 if (maxWidth == QWINDOWSIZE_MAX)
369 maxWidth = 0;
370 int maxHeight = qMax(0, m_window->windowMaximumSize().height());
371 if (maxHeight == QWINDOWSIZE_MAX)
372 maxHeight = 0;
373 m_toplevel->set_max_size(maxWidth, maxHeight);
374 }
375 }
376
requestWindowStates(Qt::WindowStates states)377 void QWaylandXdgSurface::requestWindowStates(Qt::WindowStates states)
378 {
379 if (m_toplevel)
380 m_toplevel->requestWindowStates(states);
381 else
382 qCDebug(lcQpaWayland) << "Ignoring window states requested by non-toplevel zxdg_surface_v6.";
383 }
384
setToplevel()385 void QWaylandXdgSurface::setToplevel()
386 {
387 Q_ASSERT(!m_toplevel && !m_popup);
388 m_toplevel = new Toplevel(this);
389 }
390
setPopup(QWaylandWindow * parent)391 void QWaylandXdgSurface::setPopup(QWaylandWindow *parent)
392 {
393 Q_ASSERT(!m_toplevel && !m_popup);
394
395 auto parentXdgSurface = static_cast<QWaylandXdgSurface *>(parent->shellSurface());
396
397 auto positioner = new QtWayland::xdg_positioner(m_shell->create_positioner());
398 // set_popup expects a position relative to the parent
399 QPoint transientPos = m_window->geometry().topLeft(); // this is absolute
400 transientPos -= parent->geometry().topLeft();
401 if (parent->decoration()) {
402 transientPos.setX(transientPos.x() + parent->decoration()->margins().left());
403 transientPos.setY(transientPos.y() + parent->decoration()->margins().top());
404 }
405 positioner->set_anchor_rect(transientPos.x(), transientPos.y(), 1, 1);
406 positioner->set_anchor(QtWayland::xdg_positioner::anchor_top_left);
407 positioner->set_gravity(QtWayland::xdg_positioner::gravity_bottom_right);
408 positioner->set_size(m_window->geometry().width(), m_window->geometry().height());
409 m_popup = new Popup(this, parentXdgSurface, positioner);
410 positioner->destroy();
411 delete positioner;
412 }
413
setGrabPopup(QWaylandWindow * parent,QWaylandInputDevice * device,int serial)414 void QWaylandXdgSurface::setGrabPopup(QWaylandWindow *parent, QWaylandInputDevice *device, int serial)
415 {
416 auto parentXdgSurface = static_cast<QWaylandXdgSurface *>(parent->shellSurface());
417 auto *top = m_shell->m_topmostGrabbingPopup;
418
419 if (top && top->m_xdgSurface != parentXdgSurface) {
420 qCWarning(lcQpaWayland) << "setGrabPopup called with a parent," << parentXdgSurface
421 << "which does not match the current topmost grabbing popup,"
422 << top->m_xdgSurface << "According to the xdg-shell protocol, this"
423 << "is not allowed. The wayland QPA plugin is currently handling"
424 << "it by setting the parent to the topmost grabbing popup."
425 << "Note, however, that this may cause positioning errors and"
426 << "popups closing unxpectedly because xdg-shell mandate that child"
427 << "popups close before parents";
428 parent = top->m_xdgSurface->m_window;
429 }
430 setPopup(parent);
431 m_popup->grab(device, serial);
432 }
433
xdg_surface_configure(uint32_t serial)434 void QWaylandXdgSurface::xdg_surface_configure(uint32_t serial)
435 {
436 m_pendingConfigureSerial = serial;
437 if (!m_configured) {
438 // We have to do the initial applyConfigure() immediately, since that is the expose.
439 applyConfigure();
440 m_exposeRegion = QRegion(QRect(QPoint(), m_window->geometry().size()));
441 } else {
442 // Later configures are probably resizes, so we have to queue them up for a time when we
443 // are not painting to the window.
444 m_window->applyConfigureWhenPossible();
445 }
446
447 if (!m_exposeRegion.isEmpty()) {
448 m_window->handleExpose(m_exposeRegion);
449 m_exposeRegion = QRegion();
450 }
451 }
452
QWaylandXdgShell(QWaylandDisplay * display,uint32_t id,uint32_t availableVersion)453 QWaylandXdgShell::QWaylandXdgShell(QWaylandDisplay *display, uint32_t id, uint32_t availableVersion)
454 : QtWayland::xdg_wm_base(display->wl_registry(), id, qMin(availableVersion, 1u))
455 , m_display(display)
456 {
457 display->addRegistryListener(&QWaylandXdgShell::handleRegistryGlobal, this);
458 }
459
~QWaylandXdgShell()460 QWaylandXdgShell::~QWaylandXdgShell()
461 {
462 m_display->removeListener(&QWaylandXdgShell::handleRegistryGlobal, this);
463 destroy();
464 }
465
getXdgSurface(QWaylandWindow * window)466 QWaylandXdgSurface *QWaylandXdgShell::getXdgSurface(QWaylandWindow *window)
467 {
468 return new QWaylandXdgSurface(this, get_xdg_surface(window->wlSurface()), window);
469 }
470
xdg_wm_base_ping(uint32_t serial)471 void QWaylandXdgShell::xdg_wm_base_ping(uint32_t serial)
472 {
473 pong(serial);
474 }
475
handleRegistryGlobal(void * data,wl_registry * registry,uint id,const QString & interface,uint version)476 void QWaylandXdgShell::handleRegistryGlobal(void *data, wl_registry *registry, uint id,
477 const QString &interface, uint version)
478 {
479 QWaylandXdgShell *xdgShell = static_cast<QWaylandXdgShell *>(data);
480 if (interface == QLatin1String(QWaylandXdgDecorationManagerV1::interface()->name))
481 xdgShell->m_xdgDecorationManager.reset(new QWaylandXdgDecorationManagerV1(registry, id, version));
482 }
483
484 }
485
486 QT_END_NAMESPACE
487