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

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