1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qwaylandwlshellintegration_p.h"
5
6#include <QtWaylandCompositor/QWaylandCompositor>
7#include <QtWaylandCompositor/QWaylandWlShellSurface>
8#include <QtWaylandCompositor/QWaylandQuickShellSurfaceItem>
9#include <QtWaylandCompositor/QWaylandSeat>
10
11QT_BEGIN_NAMESPACE
12
13namespace QtWayland {
14
15WlShellIntegration::WlShellIntegration(QWaylandQuickShellSurfaceItem *item)
16 : QWaylandQuickShellIntegration(item)
17 , m_item(item)
18 , m_shellSurface(qobject_cast<QWaylandWlShellSurface *>(object: item->shellSurface()))
19{
20 m_item->setSurface(m_shellSurface->surface());
21 connect(sender: m_shellSurface.data(), signal: &QWaylandWlShellSurface::startMove, context: this, slot: &WlShellIntegration::handleStartMove);
22 connect(sender: m_shellSurface.data(), signal: &QWaylandWlShellSurface::startResize, context: this, slot: &WlShellIntegration::handleStartResize);
23 connect(sender: m_shellSurface->surface(), signal: &QWaylandSurface::redraw, context: this, slot: &WlShellIntegration::handleRedraw);
24 connect(sender: m_shellSurface->surface(), signal: &QWaylandSurface::offsetForNextFrame, context: this, slot: &WlShellIntegration::adjustOffsetForNextFrame);
25 connect(sender: m_shellSurface->surface(), signal: &QWaylandSurface::hasContentChanged, context: this, slot: &WlShellIntegration::handleSurfaceHasContentChanged);
26 connect(sender: m_shellSurface.data(), signal: &QWaylandWlShellSurface::setDefaultToplevel, context: this, slot: &WlShellIntegration::handleSetDefaultTopLevel);
27 connect(sender: m_shellSurface.data(), signal: &QWaylandWlShellSurface::setTransient, context: this, slot: &WlShellIntegration::handleSetTransient);
28 connect(sender: m_shellSurface.data(), signal: &QWaylandWlShellSurface::setMaximized, context: this, slot: &WlShellIntegration::handleSetMaximized);
29 connect(sender: m_shellSurface.data(), signal: &QWaylandWlShellSurface::setFullScreen, context: this, slot: &WlShellIntegration::handleSetFullScreen);
30 connect(sender: m_shellSurface.data(), signal: &QWaylandWlShellSurface::setPopup, context: this, slot: &WlShellIntegration::handleSetPopup);
31 connect(sender: m_shellSurface.data(), signal: &QWaylandWlShellSurface::destroyed, context: this, slot: &WlShellIntegration::handleShellSurfaceDestroyed);
32}
33
34WlShellIntegration::~WlShellIntegration()
35{
36 m_item->setSurface(nullptr);
37}
38
39void WlShellIntegration::handleStartMove(QWaylandSeat *seat)
40{
41 grabberState = GrabberState::Move;
42 moveState.seat = seat;
43 moveState.initialized = false;
44}
45
46void WlShellIntegration::handleStartResize(QWaylandSeat *seat, QWaylandWlShellSurface::ResizeEdge edges)
47{
48 grabberState = GrabberState::Resize;
49 resizeState.seat = seat;
50 resizeState.resizeEdges = edges;
51 resizeState.initialSize = m_shellSurface->surface()->destinationSize();
52 resizeState.initialized = false;
53}
54
55void WlShellIntegration::handleSetDefaultTopLevel()
56{
57 // Take focus if the policy allows
58 if (m_shellSurface->shell()->focusPolicy() == QWaylandShell::AutomaticFocus)
59 m_item->takeFocus();
60
61 // In order to restore the window state, the client calls setDefaultToplevel()
62 // so we need to unset the flags here but we save the previous state and move
63 // to the initial position when redrawing
64 nextState = State::Windowed;
65
66 // Any handlers for making maximized or fullscreen state track the size of
67 // the designated output, are unneeded now that we're going to windowed
68 // state.
69 nonwindowedState.output = nullptr;
70 disconnect(nonwindowedState.sizeChangedConnection);
71}
72
73void WlShellIntegration::handleSetTransient(QWaylandSurface *parentSurface, const QPoint &relativeToParent, bool inactive)
74{
75 Q_UNUSED(parentSurface);
76 Q_UNUSED(relativeToParent);
77
78 // Take focus if the policy allows and it's not inactive
79 if (m_shellSurface->shell()->focusPolicy() == QWaylandShell::AutomaticFocus && !inactive)
80 m_item->takeFocus();
81}
82
83void WlShellIntegration::handleSetMaximized(QWaylandOutput *output)
84{
85 if (!m_item->view()->isPrimary())
86 return;
87
88 if (currentState == State::Maximized)
89 return;
90
91 QWaylandOutput *designatedOutput = output ? output : m_item->view()->output();
92 if (!designatedOutput)
93 return;
94
95 if (currentState == State::Windowed)
96 normalPosition = m_item->moveItem()->position();
97
98 nextState = State::Maximized;
99 finalPosition = designatedOutput->position() + designatedOutput->availableGeometry().topLeft();
100
101 // Any prior output-resize handlers are irrelevant at this point
102 disconnect(nonwindowedState.sizeChangedConnection);
103 nonwindowedState.output = designatedOutput;
104 nonwindowedState.sizeChangedConnection = connect(sender: designatedOutput, signal: &QWaylandOutput::availableGeometryChanged, context: this, slot: &WlShellIntegration::handleMaximizedSizeChanged);
105 handleMaximizedSizeChanged();
106}
107
108void WlShellIntegration::handleMaximizedSizeChanged()
109{
110 if (!m_shellSurface)
111 return;
112
113 if (nextState == State::Maximized) {
114 QWaylandOutput *designatedOutput = nonwindowedState.output;
115 auto scaleFactor = designatedOutput->scaleFactor();
116 m_shellSurface->sendConfigure(size: designatedOutput->availableGeometry().size() / scaleFactor, edges: QWaylandWlShellSurface::NoneEdge);
117 }
118}
119
120void WlShellIntegration::handleSetFullScreen(QWaylandWlShellSurface::FullScreenMethod method, uint framerate, QWaylandOutput *output)
121{
122 Q_UNUSED(method);
123 Q_UNUSED(framerate);
124
125 if (!m_item->view()->isPrimary())
126 return;
127
128 if (currentState == State::FullScreen)
129 return;
130
131 QWaylandOutput *designatedOutput = output ? output : m_item->view()->output();
132 if (!designatedOutput)
133 return;
134
135 if (currentState == State::Windowed)
136 normalPosition = m_item->moveItem()->position();
137
138 nextState = State::FullScreen;
139 finalPosition = designatedOutput->position();
140
141 // Any prior output-resize handlers are irrelevant at this point
142 disconnect(nonwindowedState.sizeChangedConnection);
143 nonwindowedState.output = designatedOutput;
144 nonwindowedState.sizeChangedConnection = connect(sender: designatedOutput, signal: &QWaylandOutput::geometryChanged, context: this, slot: &WlShellIntegration::handleFullScreenSizeChanged);
145 handleFullScreenSizeChanged();
146}
147
148void WlShellIntegration::handleFullScreenSizeChanged()
149{
150 if (!m_shellSurface)
151 return;
152
153 if (nextState == State::FullScreen) {
154 QWaylandOutput *designatedOutput = nonwindowedState.output;
155 m_shellSurface->sendConfigure(size: designatedOutput->geometry().size(), edges: QWaylandWlShellSurface::NoneEdge);
156 }
157}
158
159void WlShellIntegration::handleSetPopup(QWaylandSeat *seat, QWaylandSurface *parent, const QPoint &relativeToParent)
160{
161 Q_UNUSED(seat);
162
163 // Find the parent item on the same output
164 QWaylandQuickShellSurfaceItem *parentItem = nullptr;
165 const auto views = parent->views();
166 for (QWaylandView *view : views) {
167 if (view->output() == m_item->view()->output()) {
168 QWaylandQuickShellSurfaceItem *item = qobject_cast<QWaylandQuickShellSurfaceItem*>(object: view->renderObject());
169 if (item) {
170 parentItem = item;
171 break;
172 }
173 }
174 }
175
176 if (parentItem) {
177 // Clear all the transforms for this ShellSurfaceItem. They are not
178 // applicable when the item becomes a child to a surface that has its
179 // own transforms. Otherwise the transforms would be applied twice.
180 QQmlListProperty<QQuickTransform> t = m_item->transform();
181 t.clear(&t);
182 m_item->setRotation(0);
183 m_item->setScale(1.0);
184 m_item->setPosition(m_item->mapFromSurface(point: relativeToParent));
185 m_item->setParentItem(parentItem);
186 }
187
188 isPopup = true;
189 auto shell = m_shellSurface->shell();
190 QWaylandQuickShellEventFilter::startFilter(client: m_shellSurface->surface()->client(), closePopupCallback: [shell]() {
191 shell->closeAllPopups();
192 });
193
194 QObject::connect(sender: m_shellSurface->surface(), signal: &QWaylandSurface::hasContentChanged,
195 context: this, slot: &WlShellIntegration::handleSurfaceHasContentChanged);
196}
197
198void WlShellIntegration::handlePopupClosed()
199{
200 handlePopupRemoved();
201 if (m_shellSurface)
202 QObject::disconnect(sender: m_shellSurface->surface(), signal: &QWaylandSurface::hasContentChanged,
203 receiver: this, slot: &WlShellIntegration::handleSurfaceHasContentChanged);
204}
205
206void WlShellIntegration::handlePopupRemoved()
207{
208 if (!m_shellSurface || m_shellSurface->shell()->mappedPopups().isEmpty())
209 QWaylandQuickShellEventFilter::cancelFilter();
210 isPopup = false;
211}
212
213qreal WlShellIntegration::devicePixelRatio() const
214{
215 return m_item->window() ? m_item->window()->devicePixelRatio() : 1;
216}
217
218void WlShellIntegration::handleShellSurfaceDestroyed()
219{
220 if (isPopup)
221 handlePopupRemoved();
222
223 // Disarm any handlers that might fire and attempt to use the now-stale pointer
224 nonwindowedState.output = nullptr;
225 disconnect(nonwindowedState.sizeChangedConnection);
226
227 m_shellSurface = nullptr;
228}
229
230void WlShellIntegration::handleSurfaceHasContentChanged()
231{
232 if (m_shellSurface && m_shellSurface->surface()->destinationSize().isEmpty()
233 && m_shellSurface->windowType() == Qt::WindowType::Popup) {
234 handlePopupClosed();
235 }
236}
237
238void WlShellIntegration::handleRedraw()
239{
240 if (currentState == nextState)
241 return;
242
243 m_item->moveItem()->setPosition(nextState == State::Windowed ? normalPosition : finalPosition);
244 currentState = nextState;
245}
246
247void WlShellIntegration::adjustOffsetForNextFrame(const QPointF &offset)
248{
249 if (!m_item->view()->isPrimary())
250 return;
251
252 QQuickItem *moveItem = m_item->moveItem();
253 moveItem->setPosition(moveItem->position() + m_item->mapFromSurface(point: offset));
254}
255
256bool WlShellIntegration::eventFilter(QObject *object, QEvent *event)
257{
258 if (event->type() == QEvent::MouseMove) {
259 QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
260 return filterMouseMoveEvent(event: mouseEvent);
261 } else if (event->type() == QEvent::MouseButtonRelease) {
262 QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
263 return filterMouseReleaseEvent(event: mouseEvent);
264 }
265 return QWaylandQuickShellIntegration::eventFilter(watched: object, event);
266}
267
268bool WlShellIntegration::filterMouseMoveEvent(QMouseEvent *event)
269{
270 if (grabberState == GrabberState::Resize) {
271 Q_ASSERT(resizeState.seat == m_item->compositor()->seatFor(event));
272 if (!resizeState.initialized) {
273 resizeState.initialMousePos = event->scenePosition();
274 resizeState.initialized = true;
275 return true;
276 }
277 float scaleFactor = m_item->view()->output()->scaleFactor();
278 QPointF delta = (event->scenePosition() - resizeState.initialMousePos) / scaleFactor * devicePixelRatio();
279 QSize newSize = m_shellSurface->sizeForResize(size: resizeState.initialSize, delta, edges: resizeState.resizeEdges);
280 m_shellSurface->sendConfigure(size: newSize, edges: resizeState.resizeEdges);
281 } else if (grabberState == GrabberState::Move) {
282 Q_ASSERT(moveState.seat == m_item->compositor()->seatFor(event));
283 QQuickItem *moveItem = m_item->moveItem();
284 if (!moveState.initialized) {
285 moveState.initialOffset = moveItem->mapFromItem(item: nullptr, point: event->scenePosition());
286 moveState.initialized = true;
287 return true;
288 }
289 if (!moveItem->parentItem())
290 return true;
291 QPointF parentPos = moveItem->parentItem()->mapFromItem(item: nullptr, point: event->scenePosition());
292 moveItem->setPosition(parentPos - moveState.initialOffset);
293 }
294 return false;
295}
296
297bool WlShellIntegration::filterMouseReleaseEvent(QMouseEvent *event)
298{
299 Q_UNUSED(event);
300 if (grabberState != GrabberState::Default) {
301 grabberState = GrabberState::Default;
302 return true;
303 }
304 return false;
305}
306
307}
308
309QT_END_NAMESPACE
310
311#include "moc_qwaylandwlshellintegration_p.cpp"
312

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