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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtwayland/src/compositor/extensions/qwaylandquickshellsurfaceitem.cpp