1 /****************************************************************************
2 **
3 ** Copyright (C) 2017 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtWaylandCompositor module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 or (at your option) any later version
20 ** approved by the KDE Free Qt Foundation. The licenses are as published by
21 ** the Free Software Foundation and appearing in the file LICENSE.GPL3
22 ** included in the packaging of this file. Please review the following
23 ** information to ensure the GNU General Public License requirements will
24 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25 **
26 ** $QT_END_LICENSE$
27 **
28 ****************************************************************************/
29 
30 #include "qwaylandquickshellsurfaceitem.h"
31 #include "qwaylandquickshellsurfaceitem_p.h"
32 
33 #include <QtWaylandCompositor/QWaylandShellSurface>
34 #include <QGuiApplication>
35 
36 QT_BEGIN_NAMESPACE
37 
maybeCreateAutoPopup(QWaylandShellSurface * shellSurface)38 QWaylandQuickShellSurfaceItem *QWaylandQuickShellSurfaceItemPrivate::maybeCreateAutoPopup(QWaylandShellSurface* shellSurface)
39 {
40     if (!m_autoCreatePopupItems)
41         return nullptr;
42 
43     Q_Q(QWaylandQuickShellSurfaceItem);
44     auto *popupItem = new QWaylandQuickShellSurfaceItem(q);
45     popupItem->setShellSurface(shellSurface);
46     popupItem->setAutoCreatePopupItems(true);
47     QObject::connect(popupItem, &QWaylandQuickShellSurfaceItem::surfaceDestroyed, [popupItem](){
48         popupItem->deleteLater();
49     });
50     return popupItem;
51 }
52 
53 /*!
54  * \qmltype ShellSurfaceItem
55  * \inherits WaylandQuickItem
56  * \inqmlmodule QtWayland.Compositor
57  * \since 5.8
58  * \brief A Qt Quick item type for displaying and interacting with a ShellSurface.
59  *
60  * This type is used to render \c wl_shell, \c xdg_shell or \c ivi_application surfaces as part of
61  * a Qt Quick scene. It handles moving and resizing triggered by clicking on the window decorations.
62  *
63  * \sa WaylandQuickItem, WlShellSurface, XdgSurfaceV5, IviSurface
64  */
65 
66 /*!
67  * \class QWaylandQuickShellSurfaceItem
68  * \inmodule QtWaylandCompositor
69  * \since 5.8
70  * \brief The QWaylandQuickShellSurfaceItem class provides a Qt Quick item that represents a QWaylandShellSurface.
71  *
72  * This class is used to render \c wl_shell, \c xdg_shell or \c ivi_application surfaces as part of
73  * a Qt Quick scene. It handles moving and resizing triggered by clicking on the window decorations.
74  *
75  * \sa QWaylandQuickItem, QWaylandWlShellSurface, QWaylandXdgSurfaceV5, QWaylandIviSurface
76  */
77 
78 /*!
79  * Constructs a QWaylandQuickWlShellSurfaceItem with the given \a parent.
80  */
QWaylandQuickShellSurfaceItem(QQuickItem * parent)81 QWaylandQuickShellSurfaceItem::QWaylandQuickShellSurfaceItem(QQuickItem *parent)
82     : QWaylandQuickItem(*new QWaylandQuickShellSurfaceItemPrivate(), parent)
83 {
84 }
85 
~QWaylandQuickShellSurfaceItem()86 QWaylandQuickShellSurfaceItem::~QWaylandQuickShellSurfaceItem()
87 {
88     Q_D(QWaylandQuickShellSurfaceItem);
89 
90     if (d->m_shellIntegration) {
91         removeEventFilter(d->m_shellIntegration);
92         delete d->m_shellIntegration;
93     }
94 }
95 
96 /*!
97  * \internal
98  */
QWaylandQuickShellSurfaceItem(QWaylandQuickShellSurfaceItemPrivate & dd,QQuickItem * parent)99 QWaylandQuickShellSurfaceItem::QWaylandQuickShellSurfaceItem(QWaylandQuickShellSurfaceItemPrivate &dd, QQuickItem *parent)
100     : QWaylandQuickItem(dd, parent)
101 {
102 }
103 
104 /*!
105  * \qmlproperty ShellSurface QtWaylandCompositor::ShellSurfaceItem::shellSurface
106  *
107  * This property holds the ShellSurface rendered by this ShellSurfaceItem.
108  * It may either be an XdgSurfaceV5, WlShellSurface or IviSurface depending on which shell protocol
109  * is in use.
110  */
111 
112 /*!
113  * \property QWaylandQuickShellSurfaceItem::shellSurface
114  *
115  * This property holds the QWaylandShellSurface rendered by this QWaylandQuickShellSurfaceItem.
116  * It may either be a QWaylandXdgSurfaceV5, QWaylandWlShellSurface or QWaylandIviSurface depending
117  * on which shell protocol is in use.
118  */
shellSurface() const119 QWaylandShellSurface *QWaylandQuickShellSurfaceItem::shellSurface() const
120 {
121     Q_D(const QWaylandQuickShellSurfaceItem);
122     return d->m_shellSurface;
123 }
124 
setShellSurface(QWaylandShellSurface * shellSurface)125 void QWaylandQuickShellSurfaceItem::setShellSurface(QWaylandShellSurface *shellSurface)
126 {
127     Q_D(QWaylandQuickShellSurfaceItem);
128     if (d->m_shellSurface == shellSurface)
129         return;
130 
131     d->m_shellSurface = shellSurface;
132 
133     if (d->m_shellIntegration) {
134         removeEventFilter(d->m_shellIntegration);
135         delete d->m_shellIntegration;
136         d->m_shellIntegration = nullptr;
137     }
138 
139     if (shellSurface) {
140         d->m_shellIntegration = shellSurface->createIntegration(this);
141         installEventFilter(d->m_shellIntegration);
142     }
143 
144     emit shellSurfaceChanged();
145 }
146 
147 /*!
148  * \qmlproperty Item QtWaylandCompositor::ShellSurfaceItem::moveItem
149  *
150  * This property holds the move item for this ShellSurfaceItem. This is the item that will be moved
151  * when the clients request the ShellSurface to be moved, maximized, resized etc. This property is
152  * useful when implementing server-side decorations.
153  */
154 
155 /*!
156  * \property QWaylandQuickShellSurfaceItem::moveItem
157  *
158  * This property holds the move item for this QWaylandQuickShellSurfaceItem. This is the item that
159  * will be moved when the clients request the QWaylandShellSurface to be moved, maximized, resized
160  * etc. This property is useful when implementing server-side decorations.
161  */
moveItem() const162 QQuickItem *QWaylandQuickShellSurfaceItem::moveItem() const
163 {
164     Q_D(const QWaylandQuickShellSurfaceItem);
165     return d->m_moveItem ? d->m_moveItem : const_cast<QWaylandQuickShellSurfaceItem *>(this);
166 }
167 
setMoveItem(QQuickItem * moveItem)168 void QWaylandQuickShellSurfaceItem::setMoveItem(QQuickItem *moveItem)
169 {
170     Q_D(QWaylandQuickShellSurfaceItem);
171     moveItem = moveItem ? moveItem : this;
172     if (this->moveItem() == moveItem)
173         return;
174     d->m_moveItem = moveItem;
175     moveItemChanged();
176 }
177 
178 /*!
179  * \qmlproperty bool QtWaylandCompositor::ShellSurfaceItem::autoCreatePopupItems
180  *
181  * This property holds whether ShellSurfaceItems for popups parented to the shell
182  * surface managed by this item should automatically be created.
183  */
184 
185 /*!
186  * \property QWaylandQuickShellSurfaceItem::autoCreatePopupItems
187  *
188  * This property holds whether QWaylandQuickShellSurfaceItems for popups
189  * parented to the shell surface managed by this item should automatically be created.
190  */
autoCreatePopupItems()191 bool QWaylandQuickShellSurfaceItem::autoCreatePopupItems()
192 {
193     Q_D(const QWaylandQuickShellSurfaceItem);
194     return d->m_autoCreatePopupItems;
195 }
196 
setAutoCreatePopupItems(bool enabled)197 void QWaylandQuickShellSurfaceItem::setAutoCreatePopupItems(bool enabled)
198 {
199     Q_D(QWaylandQuickShellSurfaceItem);
200 
201     if (enabled == d->m_autoCreatePopupItems)
202         return;
203 
204     d->m_autoCreatePopupItems = enabled;
205     emit autoCreatePopupItemsChanged();
206 }
207 
208 /*!
209 \class QWaylandQuickShellEventFilter
210 \brief QWaylandQuickShellEventFilter implements a Wayland popup grab
211 \internal
212 */
213 
startFilter(QWaylandClient * client,CallbackFunction closePopups)214 void QWaylandQuickShellEventFilter::startFilter(QWaylandClient *client, CallbackFunction closePopups)
215 {
216     if (!self)
217         self = new QWaylandQuickShellEventFilter(qGuiApp);
218     if (!self->eventFilterInstalled) {
219         qGuiApp->installEventFilter(self);
220         self->eventFilterInstalled = true;
221         self->client = client;
222         self->closePopups = closePopups;
223     }
224 }
225 
cancelFilter()226 void QWaylandQuickShellEventFilter::cancelFilter()
227 {
228     if (!self)
229         return;
230     if (self->eventFilterInstalled && !self->waitForRelease)
231         self->stopFilter();
232 }
233 
stopFilter()234 void QWaylandQuickShellEventFilter::stopFilter()
235 {
236     if (eventFilterInstalled) {
237         qGuiApp->removeEventFilter(this);
238         eventFilterInstalled = false;
239     }
240 }
241 QWaylandQuickShellEventFilter *QWaylandQuickShellEventFilter::self = nullptr;
242 
QWaylandQuickShellEventFilter(QObject * parent)243 QWaylandQuickShellEventFilter::QWaylandQuickShellEventFilter(QObject *parent)
244     : QObject(parent)
245 {
246 }
247 
eventFilter(QObject * receiver,QEvent * e)248 bool QWaylandQuickShellEventFilter::eventFilter(QObject *receiver, QEvent *e)
249 {
250     if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonRelease) {
251         bool press = e->type() == QEvent::MouseButtonPress;
252         if (press && !waitForRelease) {
253             // The user clicked something: we need to close popups unless this press is caught later
254             if (!mousePressTimeout.isActive())
255                 mousePressTimeout.start(0, this);
256         }
257 
258         QQuickItem *item = qobject_cast<QQuickItem*>(receiver);
259         if (!item)
260             return false;
261 
262         QMouseEvent *event = static_cast<QMouseEvent*>(e);
263         QWaylandQuickShellSurfaceItem *shellSurfaceItem = qobject_cast<QWaylandQuickShellSurfaceItem*>(item);
264         bool finalRelease = (event->type() == QEvent::MouseButtonRelease) && (event->buttons() == Qt::NoButton);
265         bool popupClient = shellSurfaceItem && shellSurfaceItem->surface() && shellSurfaceItem->surface()->client() == client;
266 
267         if (waitForRelease) {
268             // We are eating events until all mouse buttons are released
269             if (finalRelease) {
270                 waitForRelease = false;
271                 stopFilter();
272             }
273             return true;
274         }
275 
276         if (finalRelease && mousePressTimeout.isActive()) {
277             // the user somehow managed to press and release the mouse button in 0 milliseconds
278             qWarning("Badly written autotest detected");
279             mousePressTimeout.stop();
280             stopFilter();
281         }
282 
283         if (press && !shellSurfaceItem && !QQmlProperty(item, QStringLiteral("qtwayland_blocking_overlay")).isValid()) {
284             // the user clicked on something that's not blocking mouse events
285             e->ignore(); //propagate the event to items below
286             return true; // don't give the event to the item
287         }
288 
289         mousePressTimeout.stop(); // we've got this
290 
291         if (press && !popupClient) {
292             // The user clicked outside the active popup's client. The popups should
293             // be closed, but the event filter will stay to catch the release-
294             // event before removing itself.
295             waitForRelease = true;
296             closePopups();
297             return true;
298         }
299     }
300 
301     return false;
302 }
303 
timerEvent(QTimerEvent * event)304 void QWaylandQuickShellEventFilter::timerEvent(QTimerEvent *event)
305 {
306     if (event->timerId() == mousePressTimeout.timerId()) {
307         mousePressTimeout.stop();
308         closePopups();
309         stopFilter();
310         // Don't wait for release: Since the press wasn't accepted,
311         // the release won't be delivered.
312     }
313 }
314 
315 QT_END_NAMESPACE
316