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