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

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