1/****************************************************************************
2**
3** Copyright (C) 2018 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 "qwaylandxdgshellintegration_p.h"
31
32#include <QtWaylandCompositor/QWaylandXdgSurface>
33#include <QtWaylandCompositor/QWaylandCompositor>
34#include <QtWaylandCompositor/QWaylandSeat>
35
36QT_BEGIN_NAMESPACE
37
38namespace QtWayland {
39
40static void handlePopupCreated(QWaylandQuickShellSurfaceItem *parentItem, QWaylandXdgPopup *popup)
41{
42 if (parentItem->shellSurface() == popup->parentXdgSurface())
43 QWaylandQuickShellSurfaceItemPrivate::get(item: parentItem)->maybeCreateAutoPopup(shellSurface: popup->xdgSurface());
44}
45
46XdgToplevelIntegration::XdgToplevelIntegration(QWaylandQuickShellSurfaceItem *item)
47 : QWaylandQuickShellIntegration(item)
48 , m_item(item)
49 , m_xdgSurface(qobject_cast<QWaylandXdgSurface *>(object: item->shellSurface()))
50 , m_toplevel(m_xdgSurface->toplevel())
51 , grabberState(GrabberState::Default)
52{
53 Q_ASSERT(m_toplevel);
54
55 m_item->setSurface(m_xdgSurface->surface());
56
57 connect(sender: m_toplevel, signal: &QWaylandXdgToplevel::startMove, receiver: this, slot: &XdgToplevelIntegration::handleStartMove);
58 connect(sender: m_toplevel, signal: &QWaylandXdgToplevel::startResize, receiver: this, slot: &XdgToplevelIntegration::handleStartResize);
59 connect(sender: m_toplevel, signal: &QWaylandXdgToplevel::setMaximized, receiver: this, slot: &XdgToplevelIntegration::handleSetMaximized);
60 connect(sender: m_toplevel, signal: &QWaylandXdgToplevel::unsetMaximized, receiver: this, slot: &XdgToplevelIntegration::handleUnsetMaximized);
61 connect(sender: m_toplevel, signal: &QWaylandXdgToplevel::maximizedChanged, receiver: this, slot: &XdgToplevelIntegration::handleMaximizedChanged);
62 connect(sender: m_toplevel, signal: &QWaylandXdgToplevel::setFullscreen, receiver: this, slot: &XdgToplevelIntegration::handleSetFullscreen);
63 connect(sender: m_toplevel, signal: &QWaylandXdgToplevel::unsetFullscreen, receiver: this, slot: &XdgToplevelIntegration::handleUnsetFullscreen);
64 connect(sender: m_toplevel, signal: &QWaylandXdgToplevel::fullscreenChanged, receiver: this, slot: &XdgToplevelIntegration::handleFullscreenChanged);
65 connect(sender: m_toplevel, signal: &QWaylandXdgToplevel::activatedChanged, receiver: this, slot: &XdgToplevelIntegration::handleActivatedChanged);
66 connect(sender: m_xdgSurface->shell(), signal: &QWaylandXdgShell::popupCreated, context: this, slot: [item](QWaylandXdgPopup *popup, QWaylandXdgSurface *){
67 handlePopupCreated(parentItem: item, popup);
68 });
69 connect(sender: m_xdgSurface->surface(), signal: &QWaylandSurface::destinationSizeChanged, receiver: this, slot: &XdgToplevelIntegration::handleSurfaceSizeChanged);
70 connect(sender: m_toplevel, signal: &QObject::destroyed, receiver: this, slot: &XdgToplevelIntegration::handleToplevelDestroyed);
71}
72
73bool XdgToplevelIntegration::eventFilter(QObject *object, QEvent *event)
74{
75 if (event->type() == QEvent::MouseMove) {
76 QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
77 return filterMouseMoveEvent(event: mouseEvent);
78 } else if (event->type() == QEvent::MouseButtonRelease) {
79 QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
80 return filterMouseReleaseEvent(event: mouseEvent);
81 }
82 return QWaylandQuickShellIntegration::eventFilter(watched: object, event);
83}
84
85bool XdgToplevelIntegration::filterMouseMoveEvent(QMouseEvent *event)
86{
87 if (grabberState == GrabberState::Resize) {
88 Q_ASSERT(resizeState.seat == m_item->compositor()->seatFor(event));
89 if (!resizeState.initialized) {
90 resizeState.initialMousePos = event->windowPos();
91 resizeState.initialized = true;
92 return true;
93 }
94 QPointF delta = m_item->mapToSurface(point: event->windowPos() - resizeState.initialMousePos);
95 QSize newSize = m_toplevel->sizeForResize(size: resizeState.initialWindowSize, delta, edges: resizeState.resizeEdges);
96 m_toplevel->sendResizing(maxSize: newSize);
97 } else if (grabberState == GrabberState::Move) {
98 Q_ASSERT(moveState.seat == m_item->compositor()->seatFor(event));
99 QQuickItem *moveItem = m_item->moveItem();
100 if (!moveState.initialized) {
101 moveState.initialOffset = moveItem->mapFromItem(item: nullptr, point: event->windowPos());
102 moveState.initialized = true;
103 return true;
104 }
105 if (!moveItem->parentItem())
106 return true;
107 QPointF parentPos = moveItem->parentItem()->mapFromItem(item: nullptr, point: event->windowPos());
108 moveItem->setPosition(parentPos - moveState.initialOffset);
109 }
110 return false;
111}
112
113bool XdgToplevelIntegration::filterMouseReleaseEvent(QMouseEvent *event)
114{
115 Q_UNUSED(event);
116
117 if (grabberState != GrabberState::Default) {
118 grabberState = GrabberState::Default;
119 return true;
120 }
121 return false;
122}
123
124void XdgToplevelIntegration::handleStartMove(QWaylandSeat *seat)
125{
126 grabberState = GrabberState::Move;
127 moveState.seat = seat;
128 moveState.initialized = false;
129}
130
131void XdgToplevelIntegration::handleStartResize(QWaylandSeat *seat, Qt::Edges edges)
132{
133 grabberState = GrabberState::Resize;
134 resizeState.seat = seat;
135 resizeState.resizeEdges = edges;
136 resizeState.initialWindowSize = m_xdgSurface->windowGeometry().size();
137 resizeState.initialPosition = m_item->moveItem()->position();
138 resizeState.initialSurfaceSize = m_item->surface()->destinationSize();
139 resizeState.initialized = false;
140}
141
142void XdgToplevelIntegration::handleSetMaximized()
143{
144 if (!m_item->view()->isPrimary())
145 return;
146
147 QVector<QWaylandXdgToplevel::State> states = m_toplevel->states();
148
149 if (!states.contains(t: QWaylandXdgToplevel::State::FullscreenState) && !states.contains(t: QWaylandXdgToplevel::State::MaximizedState)) {
150 windowedGeometry.initialWindowSize = m_xdgSurface->windowGeometry().size();
151 windowedGeometry.initialPosition = m_item->moveItem()->position();
152 }
153
154 // Any prior output-resize handlers are irrelevant at this point.
155 disconnect(nonwindowedState.sizeChangedConnection);
156 nonwindowedState.output = m_item->view()->output();
157 nonwindowedState.sizeChangedConnection = connect(sender: nonwindowedState.output, signal: &QWaylandOutput::availableGeometryChanged, receiver: this, slot: &XdgToplevelIntegration::handleMaximizedSizeChanged);
158 handleMaximizedSizeChanged();
159}
160
161void XdgToplevelIntegration::handleMaximizedSizeChanged()
162{
163 // Insurance against handleToplevelDestroyed() not managing to disconnect this
164 // handler in time.
165 if (m_toplevel == nullptr)
166 return;
167
168 m_toplevel->sendMaximized(size: nonwindowedState.output->availableGeometry().size() / nonwindowedState.output->scaleFactor());
169}
170
171void XdgToplevelIntegration::handleUnsetMaximized()
172{
173 if (!m_item->view()->isPrimary())
174 return;
175
176 // If no prior windowed size was recorded, send a 0x0 configure event
177 // to allow the client to choose its preferred size.
178 if (windowedGeometry.initialWindowSize.isValid())
179 m_toplevel->sendUnmaximized(size: windowedGeometry.initialWindowSize);
180 else
181 m_toplevel->sendUnmaximized();
182}
183
184void XdgToplevelIntegration::handleMaximizedChanged()
185{
186 if (m_toplevel->maximized()) {
187 if (auto *output = m_item->view()->output()) {
188 m_item->moveItem()->setPosition(output->position() + output->availableGeometry().topLeft());
189 } else {
190 qCWarning(qLcWaylandCompositor) << "The view does not have a corresponding output,"
191 << "ignoring maximized state";
192 }
193 } else {
194 m_item->moveItem()->setPosition(windowedGeometry.initialPosition);
195 }
196}
197
198void XdgToplevelIntegration::handleSetFullscreen()
199{
200 if (!m_item->view()->isPrimary())
201 return;
202
203 QVector<QWaylandXdgToplevel::State> states = m_toplevel->states();
204
205 if (!states.contains(t: QWaylandXdgToplevel::State::FullscreenState) && !states.contains(t: QWaylandXdgToplevel::State::MaximizedState)) {
206 windowedGeometry.initialWindowSize = m_xdgSurface->windowGeometry().size();
207 windowedGeometry.initialPosition = m_item->moveItem()->position();
208 }
209
210 // Any prior output-resize handlers are irrelevant at this point.
211 disconnect(nonwindowedState.sizeChangedConnection);
212 nonwindowedState.output = m_item->view()->output();
213 nonwindowedState.sizeChangedConnection = connect(sender: nonwindowedState.output, signal: &QWaylandOutput::geometryChanged, receiver: this, slot: &XdgToplevelIntegration::handleFullscreenSizeChanged);
214 handleFullscreenSizeChanged();
215}
216
217void XdgToplevelIntegration::handleFullscreenSizeChanged()
218{
219 // Insurance against handleToplevelDestroyed() not managing to disconnect this
220 // handler in time.
221 if (m_toplevel == nullptr)
222 return;
223
224 m_toplevel->sendFullscreen(size: nonwindowedState.output->geometry().size() / nonwindowedState.output->scaleFactor());
225}
226
227void XdgToplevelIntegration::handleUnsetFullscreen()
228{
229 if (!m_item->view()->isPrimary())
230 return;
231
232 // If no prior windowed size was recorded, send a 0x0 configure event
233 // to allow the client to choose its preferred size.
234 if (windowedGeometry.initialWindowSize.isValid())
235 m_toplevel->sendUnmaximized(size: windowedGeometry.initialWindowSize);
236 else
237 m_toplevel->sendUnmaximized();
238}
239
240void XdgToplevelIntegration::handleFullscreenChanged()
241{
242 if (m_toplevel->fullscreen()) {
243 if (auto *output = m_item->view()->output()) {
244 m_item->moveItem()->setPosition(output->position() + output->geometry().topLeft());
245 } else {
246 qCWarning(qLcWaylandCompositor) << "The view does not have a corresponding output,"
247 << "ignoring fullscreen state";
248 }
249 } else {
250 m_item->moveItem()->setPosition(windowedGeometry.initialPosition);
251 }
252}
253
254void XdgToplevelIntegration::handleActivatedChanged()
255{
256 if (m_toplevel->activated())
257 m_item->raise();
258}
259
260void XdgToplevelIntegration::handleSurfaceSizeChanged()
261{
262 if (grabberState == GrabberState::Resize) {
263 qreal dx = 0;
264 qreal dy = 0;
265 if (resizeState.resizeEdges & Qt::TopEdge)
266 dy = resizeState.initialSurfaceSize.height() - m_item->surface()->destinationSize().height();
267 if (resizeState.resizeEdges & Qt::LeftEdge)
268 dx = resizeState.initialSurfaceSize.width() - m_item->surface()->destinationSize().width();
269 QPointF offset = m_item->mapFromSurface(point: {dx, dy});
270 m_item->moveItem()->setPosition(resizeState.initialPosition + offset);
271 }
272}
273
274void XdgToplevelIntegration::handleToplevelDestroyed()
275{
276 // Disarm any handlers that might fire on the now-stale toplevel pointer
277 nonwindowedState.output = nullptr;
278 disconnect(nonwindowedState.sizeChangedConnection);
279}
280
281XdgPopupIntegration::XdgPopupIntegration(QWaylandQuickShellSurfaceItem *item)
282 : m_item(item)
283 , m_xdgSurface(qobject_cast<QWaylandXdgSurface *>(object: item->shellSurface()))
284 , m_popup(m_xdgSurface->popup())
285{
286 Q_ASSERT(m_popup);
287
288 m_item->setSurface(m_xdgSurface->surface());
289 handleGeometryChanged();
290
291 connect(sender: m_popup, signal: &QWaylandXdgPopup::configuredGeometryChanged, receiver: this, slot: &XdgPopupIntegration::handleGeometryChanged);
292 connect(sender: m_xdgSurface->shell(), signal: &QWaylandXdgShell::popupCreated, context: this, slot: [item](QWaylandXdgPopup *popup, QWaylandXdgSurface *){
293 handlePopupCreated(parentItem: item, popup);
294 });
295}
296
297void XdgPopupIntegration::handleGeometryChanged()
298{
299 if (m_item->view()->output()) {
300 const QPoint windowOffset = m_popup->parentXdgSurface()->windowGeometry().topLeft();
301 const QPoint surfacePosition = m_popup->unconstrainedPosition() + windowOffset;
302 const QPoint itemPosition = m_item->mapFromSurface(point: surfacePosition).toPoint();
303 //TODO: positioner size or other size...?
304 //TODO check positioner constraints etc... sliding, flipping
305 m_item->moveItem()->setPosition(itemPosition);
306 } else {
307 qWarning() << "XdgPopupIntegration popup item without output" << m_item;
308 }
309}
310
311}
312
313QT_END_NAMESPACE
314

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