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

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