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 &region)
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