Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwaylandquickshellsurfaceitem.cpp
Go to the documentation of this file.
1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
6
7#include <QtWaylandCompositor/QWaylandShellSurface>
8#include <QGuiApplication>
9
11
12QWaylandQuickShellSurfaceItem *QWaylandQuickShellSurfaceItemPrivate::maybeCreateAutoPopup(QWaylandShellSurface* shellSurface)
13{
14 if (!m_autoCreatePopupItems)
15 return nullptr;
16
17 Q_Q(QWaylandQuickShellSurfaceItem);
18 auto *popupItem = new QWaylandQuickShellSurfaceItem(q);
19 popupItem->setShellSurface(shellSurface);
20 popupItem->setAutoCreatePopupItems(true);
21 QObject::connect(popupItem, &QWaylandQuickShellSurfaceItem::surfaceDestroyed,
22 popupItem, &QObject::deleteLater);
23 return popupItem;
24}
25
26/*!
27 * \qmltype ShellSurfaceItem
28 * \nativetype QWaylandQuickShellSurfaceItem
29 * \inherits WaylandQuickItem
30 * \inqmlmodule QtWayland.Compositor
31 * \since 5.8
32 * \brief A Qt Quick item type for displaying and interacting with a ShellSurface.
33 *
34 * This type is used to render \c wl_shell, \c xdg_shell or \c ivi_application surfaces as part of
35 * a Qt Quick scene. It handles moving and resizing triggered by clicking on the window decorations.
36 *
37 * \sa WaylandQuickItem, WlShellSurface, IviSurface
38 */
39
40/*!
41 * \class QWaylandQuickShellSurfaceItem
42 * \inmodule QtWaylandCompositor
43 * \since 5.8
44 * \brief The QWaylandQuickShellSurfaceItem class provides a Qt Quick item that represents a QWaylandShellSurface.
45 *
46 * This class is used to render \c wl_shell, \c xdg_shell or \c ivi_application surfaces as part of
47 * a Qt Quick scene. It handles moving and resizing triggered by clicking on the window decorations.
48 *
49 * \sa QWaylandQuickItem, QWaylandWlShellSurface, QWaylandIviSurface
50 */
51
52/*!
53 * Constructs a QWaylandQuickWlShellSurfaceItem with the given \a parent.
54 */
55QWaylandQuickShellSurfaceItem::QWaylandQuickShellSurfaceItem(QQuickItem *parent)
56 : QWaylandQuickItem(*new QWaylandQuickShellSurfaceItemPrivate(), parent)
57{
58}
59
60QWaylandQuickShellSurfaceItem::~QWaylandQuickShellSurfaceItem()
61{
62 Q_D(QWaylandQuickShellSurfaceItem);
63
64 if (d->m_shellSurface)
65 disconnect(d->m_shellSurface, &QWaylandShellSurface::modalChanged, this, nullptr);
66
67 if (d->m_shellIntegration) {
68 removeEventFilter(d->m_shellIntegration);
69 delete d->m_shellIntegration;
70 }
71}
72
73/*!
74 * \internal
75 */
76QWaylandQuickShellSurfaceItem::QWaylandQuickShellSurfaceItem(QWaylandQuickShellSurfaceItemPrivate &dd, QQuickItem *parent)
77 : QWaylandQuickItem(dd, parent)
78{
79}
80
81/*!
82 * \qmlproperty ShellSurface QtWayland.Compositor::ShellSurfaceItem::shellSurface
83 *
84 * This property holds the ShellSurface rendered by this ShellSurfaceItem.
85 * It may either be an XdgSurfaceV5, WlShellSurface or IviSurface depending on which shell protocol
86 * is in use.
87 */
88
89/*!
90 * \property QWaylandQuickShellSurfaceItem::shellSurface
91 *
92 * This property holds the QWaylandShellSurface rendered by this QWaylandQuickShellSurfaceItem.
93 * It may either be a QWaylandXdgSurfaceV5, QWaylandWlShellSurface or QWaylandIviSurface depending
94 * on which shell protocol is in use.
95 */
96QWaylandShellSurface *QWaylandQuickShellSurfaceItem::shellSurface() const
97{
98 Q_D(const QWaylandQuickShellSurfaceItem);
99 return d->m_shellSurface;
100}
101
102void QWaylandQuickShellSurfaceItem::setShellSurface(QWaylandShellSurface *shellSurface)
103{
104 Q_D(QWaylandQuickShellSurfaceItem);
105 if (d->m_shellSurface == shellSurface)
106 return;
107
108 if (Q_UNLIKELY(d->m_shellSurface))
109 disconnect(d->m_shellSurface, &QWaylandShellSurface::modalChanged, this, nullptr);
110
111 if (d->m_shellIntegration) {
112 removeEventFilter(d->m_shellIntegration);
113 delete d->m_shellIntegration;
114 d->m_shellIntegration = nullptr;
115 }
116
117 d->m_shellSurface = shellSurface;
118
119 if (shellSurface) {
120 d->m_shellIntegration = shellSurface->createIntegration(this);
121 installEventFilter(d->m_shellIntegration);
122
123 connect(shellSurface, &QWaylandShellSurface::modalChanged, this,
124 [d](){ if (d->m_shellSurface->isModal()) d->raise(); });
125 }
126
127 emit shellSurfaceChanged();
128}
129
130/*!
131 * \qmlproperty Item QtWayland.Compositor::ShellSurfaceItem::moveItem
132 *
133 * This property holds the move item for this ShellSurfaceItem. This is the item that will be moved
134 * when the clients request the ShellSurface to be moved, maximized, resized etc. This property is
135 * useful when implementing server-side decorations.
136 */
137
138/*!
139 * \property QWaylandQuickShellSurfaceItem::moveItem
140 *
141 * This property holds the move item for this QWaylandQuickShellSurfaceItem. This is the item that
142 * will be moved when the clients request the QWaylandShellSurface to be moved, maximized, resized
143 * etc. This property is useful when implementing server-side decorations.
144 */
145QQuickItem *QWaylandQuickShellSurfaceItem::moveItem() const
146{
147 Q_D(const QWaylandQuickShellSurfaceItem);
148 return d->m_moveItem ? d->m_moveItem : const_cast<QWaylandQuickShellSurfaceItem *>(this);
149}
150
151void QWaylandQuickShellSurfaceItem::setMoveItem(QQuickItem *moveItem)
152{
153 Q_D(QWaylandQuickShellSurfaceItem);
154 moveItem = moveItem ? moveItem : this;
155 if (this->moveItem() == moveItem)
156 return;
157 d->m_moveItem = moveItem;
158 moveItemChanged();
159}
160
161/*!
162 * \qmlproperty bool QtWayland.Compositor::ShellSurfaceItem::autoCreatePopupItems
163 *
164 * This property holds whether ShellSurfaceItems for popups parented to the shell
165 * surface managed by this item should automatically be created.
166 */
167
168/*!
169 * \property QWaylandQuickShellSurfaceItem::autoCreatePopupItems
170 *
171 * This property holds whether QWaylandQuickShellSurfaceItems for popups
172 * parented to the shell surface managed by this item should automatically be created.
173 */
174bool QWaylandQuickShellSurfaceItem::autoCreatePopupItems()
175{
176 Q_D(const QWaylandQuickShellSurfaceItem);
177 return d->m_autoCreatePopupItems;
178}
179
180void QWaylandQuickShellSurfaceItem::setAutoCreatePopupItems(bool enabled)
181{
182 Q_D(QWaylandQuickShellSurfaceItem);
183
184 if (enabled == d->m_autoCreatePopupItems)
185 return;
186
187 d->m_autoCreatePopupItems = enabled;
188 emit autoCreatePopupItemsChanged();
189}
190
191/*!
192\class QWaylandQuickShellEventFilter
193\brief QWaylandQuickShellEventFilter implements a Wayland popup grab
194\internal
195*/
196
197void QWaylandQuickShellEventFilter::startFilter(QWaylandClient *client, CallbackFunction closePopups)
198{
199 if (!self)
200 self = new QWaylandQuickShellEventFilter(qGuiApp);
201 if (!self->eventFilterInstalled) {
202 qGuiApp->installEventFilter(self);
203 self->eventFilterInstalled = true;
204 self->client = client;
205 self->closePopups = closePopups;
206 }
207}
208
209void QWaylandQuickShellEventFilter::cancelFilter()
210{
211 if (!self)
212 return;
213 if (self->eventFilterInstalled && !self->waitForRelease)
214 self->stopFilter();
215}
216
217void QWaylandQuickShellEventFilter::stopFilter()
218{
219 if (eventFilterInstalled) {
220 qGuiApp->removeEventFilter(this);
221 eventFilterInstalled = false;
222 }
223}
224QWaylandQuickShellEventFilter *QWaylandQuickShellEventFilter::self = nullptr;
225
226QWaylandQuickShellEventFilter::QWaylandQuickShellEventFilter(QObject *parent)
227 : QObject(parent)
228{
229}
230
231bool QWaylandQuickShellEventFilter::eventFilter(QObject *receiver, QEvent *e)
232{
233 if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonRelease) {
234 bool press = e->type() == QEvent::MouseButtonPress;
235 if (press && !waitForRelease) {
236 // The user clicked something: we need to close popups unless this press is caught later
237 if (!mousePressTimeout.isActive())
238 mousePressTimeout.start(0, this);
239 }
240
241 QQuickItem *item = qobject_cast<QQuickItem*>(receiver);
242 if (!item)
243 return false;
244
245 QMouseEvent *event = static_cast<QMouseEvent*>(e);
246 QWaylandQuickShellSurfaceItem *shellSurfaceItem = qobject_cast<QWaylandQuickShellSurfaceItem*>(item);
247 bool finalRelease = (event->type() == QEvent::MouseButtonRelease) && (event->buttons() == Qt::NoButton);
248 bool popupClient = shellSurfaceItem && shellSurfaceItem->surface() && shellSurfaceItem->surface()->client() == client;
249
250 if (waitForRelease) {
251 // We are eating events until all mouse buttons are released
252 if (finalRelease) {
253 waitForRelease = false;
254 stopFilter();
255 }
256 return true;
257 }
258
259 if (finalRelease && mousePressTimeout.isActive()) {
260 // the user somehow managed to press and release the mouse button in 0 milliseconds
261 qWarning("Badly written autotest detected");
262 mousePressTimeout.stop();
263 stopFilter();
264 }
265
266 if (press && !shellSurfaceItem && !QQmlProperty(item, QStringLiteral("qtwayland_blocking_overlay")).isValid()) {
267 // the user clicked on something that's not blocking mouse events
268 e->ignore(); //propagate the event to items below
269 return true; // don't give the event to the item
270 }
271
272 mousePressTimeout.stop(); // we've got this
273
274 if (press && !popupClient) {
275 // The user clicked outside the active popup's client. The popups should
276 // be closed, but the event filter will stay to catch the release-
277 // event before removing itself.
278 waitForRelease = true;
279 closePopups();
280 return true;
281 }
282 }
283
284 return false;
285}
286
287void QWaylandQuickShellEventFilter::timerEvent(QTimerEvent *event)
288{
289 if (event->timerId() == mousePressTimeout.timerId()) {
290 mousePressTimeout.stop();
291 closePopups();
292 stopFilter();
293 // Don't wait for release: Since the press wasn't accepted,
294 // the release won't be delivered.
295 }
296}
297
298static QWaylandQuickShellSurfaceItem *findSurfaceItemFromMoveItem(QQuickItem *moveItem)
299{
300 if (Q_UNLIKELY(!moveItem))
301 return nullptr;
302 if (auto *surf = qobject_cast<QWaylandQuickShellSurfaceItem *>(moveItem))
303 return surf;
304 for (auto *item : moveItem->childItems()) {
305 if (auto *surf = findSurfaceItemFromMoveItem(item))
306 return surf;
307 }
308 return nullptr;
309}
310
311static inline bool onTop(QWaylandQuickShellSurfaceItem *surf)
312{
313 return surf->staysOnTop() || (surf->shellSurface() && surf->shellSurface()->isModal());
314}
315
316static inline bool onBottom(QWaylandQuickShellSurfaceItem *surf)
317{
318 return surf->staysOnBottom() && !(surf->shellSurface() && surf->shellSurface()->isModal());
319}
320
321/*
322 To raise a surface, find the topmost suitable surface and place above that.
323 We start from the top and:
324 If we don't have staysOnTop, skip all surfaces with staysOnTop
325 If we have staysOnBottom, skip all surfaces that don't have staysOnBottom
326 A modal dialog is handled as if it had staysOnTop
327 */
328void QWaylandQuickShellSurfaceItemPrivate::raise()
329{
330 Q_Q(QWaylandQuickShellSurfaceItem);
331 auto *moveItem = q->moveItem();
332 QQuickItem *parent = moveItem->parentItem();
333 if (!parent)
334 return;
335 const bool putOnTop = staysOnTop || m_shellSurface->isModal();
336 const bool putOnBottom = staysOnBottom && !m_shellSurface->isModal();
337
338 auto it = parent->childItems().crbegin();
339 auto skip = [=](QQuickItem *item) {
340 if (auto *surf = findSurfaceItemFromMoveItem(item))
341 return (!putOnTop && onTop(surf)) || (putOnBottom && !onBottom(surf));
342 return true; // ignore any other Quick items that may be there
343 };
344 auto end = parent->childItems().crend();
345 while (it != end && skip(*it))
346 ++it;
347 if (it != end) {
348 QQuickItem *top = *it;
349 if (moveItem != top)
350 moveItem->stackAfter(top);
351 }
352}
353
354/*
355 To lower a surface, find the lowest suitable surface and place below that.
356 We start from the bottom and:
357 If we don't have staysOnBottom, skip all surfaces with staysOnBottom
358 If we have staysOnTop, skip all surfaces that don't have staysOnTop
359 A modal dialog is handled as if it had staysOnTop
360 */
361void QWaylandQuickShellSurfaceItemPrivate::lower()
362{
363 Q_Q(QWaylandQuickShellSurfaceItem);
364 auto *moveItem = q->moveItem();
365 QQuickItem *parent = moveItem->parentItem();
366 if (!parent)
367 return;
368 const bool putOnTop = staysOnTop || m_shellSurface->isModal();
369 const bool putOnBottom = staysOnBottom && !m_shellSurface->isModal();
370
371 auto it = parent->childItems().cbegin();
372 auto skip = [=](QQuickItem *item) {
373 if (auto *surf = findSurfaceItemFromMoveItem(item))
374 return (!putOnBottom && onBottom(surf)) || (putOnTop && !onTop(surf));
375 return true; // ignore any other Quick items that may be there
376 };
377 while (skip(*it))
378 ++it;
379
380 QQuickItem *bottom = *it;
381 if (moveItem != bottom)
382 moveItem->stackBefore(bottom);
383}
384
385/*!
386 * \property QWaylandQuickShellSurfaceItem::staysOnTop
387 *
388 * Keep this item above other Wayland surfaces
389 */
390bool QWaylandQuickShellSurfaceItem::staysOnTop() const
391{
392 Q_D(const QWaylandQuickShellSurfaceItem);
393 return d->staysOnTop;
394}
395
396void QWaylandQuickShellSurfaceItem::setStaysOnTop(bool onTop)
397{
398 Q_D(QWaylandQuickShellSurfaceItem);
399 if (d->staysOnTop == onTop)
400 return;
401 d->staysOnTop = onTop;
402 if (d->staysOnBottom) {
403 d->staysOnBottom = false;
404 emit staysOnBottomChanged();
405 }
406 // We need to call raise() even if onTop is false, since we need to stack under any other
407 // staysOnTop surfaces in that case
408 raise();
409 emit staysOnTopChanged();
410 Q_ASSERT(!(d->staysOnTop && d->staysOnBottom));
411}
412
413/*!
414 * \property QWaylandQuickShellSurfaceItem::staysOnBottom
415 *
416 * Keep this item above other Wayland surfaces
417 */
418bool QWaylandQuickShellSurfaceItem::staysOnBottom() const
419{
420 Q_D(const QWaylandQuickShellSurfaceItem);
421 return d->staysOnBottom;
422}
423
424void QWaylandQuickShellSurfaceItem::setStaysOnBottom(bool onBottom)
425{
426 Q_D(QWaylandQuickShellSurfaceItem);
427 if (d->staysOnBottom == onBottom)
428 return;
429 d->staysOnBottom = onBottom;
430 if (d->staysOnTop) {
431 d->staysOnTop = false;
432 emit staysOnTopChanged();
433 }
434 // We need to call lower() even if onBottom is false, since we need to stack over any other
435 // staysOnBottom surfaces in that case
436 lower();
437 emit staysOnBottomChanged();
438 Q_ASSERT(!(d->staysOnTop && d->staysOnBottom));
439}
440
441QT_END_NAMESPACE
442
443#include "moc_qwaylandquickshellsurfaceitem_p.cpp"
444
445#include "moc_qwaylandquickshellsurfaceitem.cpp"
static bool onBottom(QWaylandQuickShellSurfaceItem *surf)
static QWaylandQuickShellSurfaceItem * findSurfaceItemFromMoveItem(QQuickItem *moveItem)
static bool onTop(QWaylandQuickShellSurfaceItem *surf)