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 | |
10 | QT_BEGIN_NAMESPACE |
11 | |
12 | QWaylandQuickShellSurfaceItem *QWaylandQuickShellSurfaceItemPrivate::maybeCreateAutoPopup(QWaylandShellSurface* shellSurface) |
13 | { |
14 | if (!m_autoCreatePopupItems) |
15 | return nullptr; |
16 | |
17 | Q_Q(QWaylandQuickShellSurfaceItem); |
18 | auto * = 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 | */ |
56 | QWaylandQuickShellSurfaceItem::QWaylandQuickShellSurfaceItem(QQuickItem *parent) |
57 | : QWaylandQuickItem(*new QWaylandQuickShellSurfaceItemPrivate(), parent) |
58 | { |
59 | } |
60 | |
61 | QWaylandQuickShellSurfaceItem::~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 | */ |
74 | QWaylandQuickShellSurfaceItem::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 | */ |
94 | QWaylandShellSurface *QWaylandQuickShellSurfaceItem::shellSurface() const |
95 | { |
96 | Q_D(const QWaylandQuickShellSurfaceItem); |
97 | return d->m_shellSurface; |
98 | } |
99 | |
100 | void 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 | */ |
137 | QQuickItem *QWaylandQuickShellSurfaceItem::moveItem() const |
138 | { |
139 | Q_D(const QWaylandQuickShellSurfaceItem); |
140 | return d->m_moveItem ? d->m_moveItem : const_cast<QWaylandQuickShellSurfaceItem *>(this); |
141 | } |
142 | |
143 | void 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 | */ |
166 | bool QWaylandQuickShellSurfaceItem::autoCreatePopupItems() |
167 | { |
168 | Q_D(const QWaylandQuickShellSurfaceItem); |
169 | return d->m_autoCreatePopupItems; |
170 | } |
171 | |
172 | void 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 | |
189 | void QWaylandQuickShellEventFilter::startFilter(QWaylandClient *client, CallbackFunction ) |
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 | |
201 | void QWaylandQuickShellEventFilter::cancelFilter() |
202 | { |
203 | if (!self) |
204 | return; |
205 | if (self->eventFilterInstalled && !self->waitForRelease) |
206 | self->stopFilter(); |
207 | } |
208 | |
209 | void QWaylandQuickShellEventFilter::stopFilter() |
210 | { |
211 | if (eventFilterInstalled) { |
212 | qGuiApp->removeEventFilter(obj: this); |
213 | eventFilterInstalled = false; |
214 | } |
215 | } |
216 | QWaylandQuickShellEventFilter *QWaylandQuickShellEventFilter::self = nullptr; |
217 | |
218 | QWaylandQuickShellEventFilter::QWaylandQuickShellEventFilter(QObject *parent) |
219 | : QObject(parent) |
220 | { |
221 | } |
222 | |
223 | bool 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 = 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 | |
279 | void 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 | |
290 | static 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 | */ |
309 | void 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 | */ |
338 | void 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 | */ |
365 | bool QWaylandQuickShellSurfaceItem::staysOnTop() const |
366 | { |
367 | Q_D(const QWaylandQuickShellSurfaceItem); |
368 | return d->staysOnTop; |
369 | } |
370 | |
371 | void 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 | */ |
393 | bool QWaylandQuickShellSurfaceItem::staysOnBottom() const |
394 | { |
395 | Q_D(const QWaylandQuickShellSurfaceItem); |
396 | return d->staysOnBottom; |
397 | } |
398 | |
399 | void 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 | |
416 | QT_END_NAMESPACE |
417 | |
418 | #include "moc_qwaylandquickshellsurfaceitem_p.cpp" |
419 | |
420 | #include "moc_qwaylandquickshellsurfaceitem.cpp" |
421 | |