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 | |
37 | QT_BEGIN_NAMESPACE |
38 | |
39 | namespace QtWayland { |
40 | |
41 | WlShellIntegration::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 | |
60 | WlShellIntegration::~WlShellIntegration() |
61 | { |
62 | m_item->setSurface(nullptr); |
63 | } |
64 | |
65 | void WlShellIntegration::handleStartMove(QWaylandSeat *seat) |
66 | { |
67 | grabberState = GrabberState::Move; |
68 | moveState.seat = seat; |
69 | moveState.initialized = false; |
70 | } |
71 | |
72 | void 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 | |
81 | void 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 | |
99 | void 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 | |
109 | void 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 | |
134 | void 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 | |
146 | void 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 | |
174 | void 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 | |
185 | void 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 | |
224 | void 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 | |
232 | void WlShellIntegration::handlePopupRemoved() |
233 | { |
234 | if (!m_shellSurface || m_shellSurface->shell()->mappedPopups().isEmpty()) |
235 | QWaylandQuickShellEventFilter::cancelFilter(); |
236 | isPopup = false; |
237 | } |
238 | |
239 | qreal WlShellIntegration::devicePixelRatio() const |
240 | { |
241 | return m_item->window() ? m_item->window()->devicePixelRatio() : 1; |
242 | } |
243 | |
244 | void 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 | |
256 | void WlShellIntegration::handleSurfaceHasContentChanged() |
257 | { |
258 | if (m_shellSurface && m_shellSurface->surface()->destinationSize().isEmpty() |
259 | && m_shellSurface->windowType() == Qt::WindowType::Popup) { |
260 | handlePopupClosed(); |
261 | } |
262 | } |
263 | |
264 | void 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 | |
273 | void 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 | |
282 | bool 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 | |
294 | bool 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 | |
323 | bool 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 | |
335 | QT_END_NAMESPACE |
336 | |