1 | /* |
2 | SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org> |
3 | |
4 | SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL |
5 | */ |
6 | #include "shell.h" |
7 | #include "event_queue.h" |
8 | #include "output.h" |
9 | #include "seat.h" |
10 | #include "surface.h" |
11 | #include "wayland_pointer_p.h" |
12 | // Qt |
13 | #include <QGuiApplication> |
14 | #include <QList> |
15 | #include <qpa/qplatformnativeinterface.h> |
16 | // Wayland |
17 | #include <wayland-client-protocol.h> |
18 | |
19 | namespace KWayland |
20 | { |
21 | namespace Client |
22 | { |
23 | class Q_DECL_HIDDEN Shell::Private |
24 | { |
25 | public: |
26 | WaylandPointer<wl_shell, wl_shell_destroy> shell; |
27 | EventQueue *queue = nullptr; |
28 | }; |
29 | |
30 | Shell::Shell(QObject *parent) |
31 | : QObject(parent) |
32 | , d(new Private) |
33 | { |
34 | } |
35 | |
36 | Shell::~Shell() |
37 | { |
38 | release(); |
39 | } |
40 | |
41 | void Shell::destroy() |
42 | { |
43 | if (!d->shell) { |
44 | return; |
45 | } |
46 | Q_EMIT interfaceAboutToBeDestroyed(); |
47 | d->shell.destroy(); |
48 | } |
49 | |
50 | void Shell::release() |
51 | { |
52 | if (!d->shell) { |
53 | return; |
54 | } |
55 | Q_EMIT interfaceAboutToBeReleased(); |
56 | d->shell.release(); |
57 | } |
58 | |
59 | void Shell::setup(wl_shell *shell) |
60 | { |
61 | Q_ASSERT(!d->shell); |
62 | Q_ASSERT(shell); |
63 | d->shell.setup(pointer: shell); |
64 | } |
65 | |
66 | void Shell::setEventQueue(EventQueue *queue) |
67 | { |
68 | d->queue = queue; |
69 | } |
70 | |
71 | EventQueue *Shell::eventQueue() |
72 | { |
73 | return d->queue; |
74 | } |
75 | |
76 | ShellSurface *Shell::createSurface(wl_surface *surface, QObject *parent) |
77 | { |
78 | Q_ASSERT(isValid()); |
79 | ShellSurface *s = new ShellSurface(parent); |
80 | connect(sender: this, signal: &Shell::interfaceAboutToBeReleased, context: s, slot: &ShellSurface::release); |
81 | connect(sender: this, signal: &Shell::interfaceAboutToBeDestroyed, context: s, slot: &ShellSurface::destroy); |
82 | auto w = wl_shell_get_shell_surface(wl_shell: d->shell, surface); |
83 | if (d->queue) { |
84 | d->queue->addProxy(proxy: w); |
85 | } |
86 | s->setup(w); |
87 | return s; |
88 | } |
89 | |
90 | ShellSurface *Shell::createSurface(Surface *surface, QObject *parent) |
91 | { |
92 | Q_ASSERT(surface); |
93 | return createSurface(surface: *surface, parent); |
94 | } |
95 | |
96 | bool Shell::isValid() const |
97 | { |
98 | return d->shell.isValid(); |
99 | } |
100 | |
101 | Shell::operator wl_shell *() |
102 | { |
103 | return d->shell; |
104 | } |
105 | |
106 | Shell::operator wl_shell *() const |
107 | { |
108 | return d->shell; |
109 | } |
110 | |
111 | class Q_DECL_HIDDEN ShellSurface::Private |
112 | { |
113 | public: |
114 | Private(ShellSurface *q); |
115 | void setup(wl_shell_surface *surface); |
116 | |
117 | WaylandPointer<wl_shell_surface, wl_shell_surface_destroy> surface; |
118 | QSize size; |
119 | static QList<ShellSurface *> s_surfaces; |
120 | |
121 | private: |
122 | void ping(uint32_t serial); |
123 | static void pingCallback(void *data, struct wl_shell_surface *shellSurface, uint32_t serial); |
124 | static void configureCallback(void *data, struct wl_shell_surface *shellSurface, uint32_t edges, int32_t width, int32_t height); |
125 | static void popupDoneCallback(void *data, struct wl_shell_surface *shellSurface); |
126 | |
127 | ShellSurface *q; |
128 | static const struct wl_shell_surface_listener s_listener; |
129 | }; |
130 | |
131 | QList<ShellSurface *> ShellSurface::Private::s_surfaces = QList<ShellSurface *>(); |
132 | |
133 | ShellSurface::Private::Private(ShellSurface *q) |
134 | : q(q) |
135 | { |
136 | } |
137 | |
138 | void ShellSurface::Private::setup(wl_shell_surface *s) |
139 | { |
140 | Q_ASSERT(s); |
141 | Q_ASSERT(!surface); |
142 | surface.setup(pointer: s); |
143 | wl_shell_surface_add_listener(wl_shell_surface: surface, listener: &s_listener, data: this); |
144 | } |
145 | |
146 | ShellSurface *ShellSurface::fromWindow(QWindow *window) |
147 | { |
148 | if (!window) { |
149 | return nullptr; |
150 | } |
151 | QPlatformNativeInterface *native = qApp->platformNativeInterface(); |
152 | if (!native) { |
153 | return nullptr; |
154 | } |
155 | window->create(); |
156 | wl_shell_surface *s = reinterpret_cast<wl_shell_surface *>(native->nativeResourceForWindow(QByteArrayLiteral("wl_shell_surface" ), window)); |
157 | if (!s) { |
158 | return nullptr; |
159 | } |
160 | if (auto surface = get(native: s)) { |
161 | return surface; |
162 | } |
163 | ShellSurface *surface = new ShellSurface(window); |
164 | surface->d->surface.setup(pointer: s, foreign: true); |
165 | return surface; |
166 | } |
167 | |
168 | ShellSurface *ShellSurface::fromQtWinId(WId wid) |
169 | { |
170 | QWindow *window = nullptr; |
171 | |
172 | for (auto win : qApp->allWindows()) { |
173 | if (win->winId() == wid) { |
174 | window = win; |
175 | break; |
176 | } |
177 | } |
178 | |
179 | if (!window) { |
180 | return nullptr; |
181 | } |
182 | return fromWindow(window); |
183 | } |
184 | |
185 | ShellSurface *ShellSurface::get(wl_shell_surface *native) |
186 | { |
187 | auto it = std::find_if(first: Private::s_surfaces.constBegin(), last: Private::s_surfaces.constEnd(), pred: [native](ShellSurface *s) { |
188 | return s->d->surface == native; |
189 | }); |
190 | if (it != Private::s_surfaces.constEnd()) { |
191 | return *(it); |
192 | } |
193 | return nullptr; |
194 | } |
195 | |
196 | ShellSurface::ShellSurface(QObject *parent) |
197 | : QObject(parent) |
198 | , d(new Private(this)) |
199 | { |
200 | Private::s_surfaces << this; |
201 | } |
202 | |
203 | ShellSurface::~ShellSurface() |
204 | { |
205 | Private::s_surfaces.removeOne(t: this); |
206 | release(); |
207 | } |
208 | |
209 | void ShellSurface::release() |
210 | { |
211 | d->surface.release(); |
212 | } |
213 | |
214 | void ShellSurface::destroy() |
215 | { |
216 | d->surface.destroy(); |
217 | } |
218 | |
219 | #ifndef K_DOXYGEN |
220 | const struct wl_shell_surface_listener ShellSurface::Private::s_listener = {.ping: pingCallback, .configure: configureCallback, .popup_done: popupDoneCallback}; |
221 | #endif |
222 | |
223 | void ShellSurface::Private::configureCallback(void *data, wl_shell_surface *shellSurface, uint32_t edges, int32_t width, int32_t height) |
224 | { |
225 | Q_UNUSED(edges) |
226 | auto s = reinterpret_cast<ShellSurface::Private *>(data); |
227 | Q_ASSERT(s->surface == shellSurface); |
228 | s->q->setSize(QSize(width, height)); |
229 | } |
230 | |
231 | void ShellSurface::Private::pingCallback(void *data, wl_shell_surface *shellSurface, uint32_t serial) |
232 | { |
233 | auto s = reinterpret_cast<ShellSurface::Private *>(data); |
234 | Q_ASSERT(s->surface == shellSurface); |
235 | s->ping(serial); |
236 | } |
237 | |
238 | void ShellSurface::Private::popupDoneCallback(void *data, wl_shell_surface *shellSurface) |
239 | { |
240 | auto s = reinterpret_cast<ShellSurface::Private *>(data); |
241 | Q_ASSERT(s->surface == shellSurface); |
242 | Q_EMIT s->q->popupDone(); |
243 | } |
244 | |
245 | void ShellSurface::setup(wl_shell_surface *surface) |
246 | { |
247 | d->setup(surface); |
248 | } |
249 | |
250 | void ShellSurface::Private::ping(uint32_t serial) |
251 | { |
252 | wl_shell_surface_pong(wl_shell_surface: surface, serial); |
253 | Q_EMIT q->pinged(); |
254 | } |
255 | |
256 | void ShellSurface::setSize(const QSize &size) |
257 | { |
258 | if (d->size == size) { |
259 | return; |
260 | } |
261 | d->size = size; |
262 | Q_EMIT sizeChanged(size); |
263 | } |
264 | |
265 | void ShellSurface::setFullscreen(Output *output) |
266 | { |
267 | Q_ASSERT(isValid()); |
268 | wl_shell_surface_set_fullscreen(wl_shell_surface: d->surface, method: WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT, framerate: 0, output: output ? output->output() : nullptr); |
269 | } |
270 | |
271 | void ShellSurface::setMaximized(Output *output) |
272 | { |
273 | Q_ASSERT(isValid()); |
274 | wl_shell_surface_set_maximized(wl_shell_surface: d->surface, output: output ? output->output() : nullptr); |
275 | } |
276 | |
277 | void ShellSurface::setToplevel() |
278 | { |
279 | Q_ASSERT(isValid()); |
280 | wl_shell_surface_set_toplevel(wl_shell_surface: d->surface); |
281 | } |
282 | |
283 | void ShellSurface::setTransient(Surface *parent, const QPoint &offset, TransientFlags flags) |
284 | { |
285 | Q_ASSERT(isValid()); |
286 | uint32_t wlFlags = 0; |
287 | if (flags.testFlag(flag: TransientFlag::NoFocus)) { |
288 | wlFlags |= WL_SHELL_SURFACE_TRANSIENT_INACTIVE; |
289 | } |
290 | wl_shell_surface_set_transient(wl_shell_surface: d->surface, parent: *parent, x: offset.x(), y: offset.y(), flags: wlFlags); |
291 | } |
292 | |
293 | void ShellSurface::setTransientPopup(Surface *parent, Seat *grabbedSeat, quint32 grabSerial, const QPoint &offset, TransientFlags flags) |
294 | { |
295 | Q_ASSERT(isValid()); |
296 | Q_ASSERT(parent); |
297 | Q_ASSERT(grabbedSeat); |
298 | uint32_t wlFlags = 0; |
299 | if (flags.testFlag(flag: TransientFlag::NoFocus)) { |
300 | wlFlags |= WL_SHELL_SURFACE_TRANSIENT_INACTIVE; |
301 | } |
302 | wl_shell_surface_set_popup(wl_shell_surface: d->surface, seat: *grabbedSeat, serial: grabSerial, parent: *parent, x: offset.x(), y: offset.y(), flags: wlFlags); |
303 | } |
304 | |
305 | void ShellSurface::requestMove(Seat *seat, quint32 serial) |
306 | { |
307 | Q_ASSERT(isValid()); |
308 | Q_ASSERT(seat); |
309 | |
310 | wl_shell_surface_move(wl_shell_surface: d->surface, seat: *seat, serial); |
311 | } |
312 | |
313 | void ShellSurface::requestResize(Seat *seat, quint32 serial, Qt::Edges edges) |
314 | { |
315 | Q_ASSERT(isValid()); |
316 | Q_ASSERT(seat); |
317 | |
318 | uint wlEdge = WL_SHELL_SURFACE_RESIZE_NONE; |
319 | if (edges.testFlag(flag: Qt::TopEdge)) { |
320 | if (edges.testFlag(flag: Qt::LeftEdge) && ((edges & ~Qt::LeftEdge) == Qt::TopEdge)) { |
321 | wlEdge = WL_SHELL_SURFACE_RESIZE_TOP_LEFT; |
322 | } else if (edges.testFlag(flag: Qt::RightEdge) && ((edges & ~Qt::RightEdge) == Qt::TopEdge)) { |
323 | wlEdge = WL_SHELL_SURFACE_RESIZE_TOP_RIGHT; |
324 | } else if ((edges & ~Qt::TopEdge) == Qt::Edges()) { |
325 | wlEdge = WL_SHELL_SURFACE_RESIZE_TOP; |
326 | } |
327 | } else if (edges.testFlag(flag: Qt::BottomEdge)) { |
328 | if (edges.testFlag(flag: Qt::LeftEdge) && ((edges & ~Qt::LeftEdge) == Qt::BottomEdge)) { |
329 | wlEdge = WL_SHELL_SURFACE_RESIZE_BOTTOM_LEFT; |
330 | } else if (edges.testFlag(flag: Qt::RightEdge) && ((edges & ~Qt::RightEdge) == Qt::BottomEdge)) { |
331 | wlEdge = WL_SHELL_SURFACE_RESIZE_BOTTOM_RIGHT; |
332 | } else if ((edges & ~Qt::BottomEdge) == Qt::Edges()) { |
333 | wlEdge = WL_SHELL_SURFACE_RESIZE_BOTTOM; |
334 | } |
335 | } else if (edges.testFlag(flag: Qt::RightEdge) && ((edges & ~Qt::RightEdge) == Qt::Edges())) { |
336 | wlEdge = WL_SHELL_SURFACE_RESIZE_RIGHT; |
337 | } else if (edges.testFlag(flag: Qt::LeftEdge) && ((edges & ~Qt::LeftEdge) == Qt::Edges())) { |
338 | wlEdge = WL_SHELL_SURFACE_RESIZE_LEFT; |
339 | } |
340 | |
341 | wl_shell_surface_resize(wl_shell_surface: d->surface, seat: *seat, serial, edges: wlEdge); |
342 | } |
343 | |
344 | void ShellSurface::setTitle(const QString &title) |
345 | { |
346 | wl_shell_surface_set_title(wl_shell_surface: d->surface, title: title.toUtf8().constData()); |
347 | } |
348 | |
349 | void ShellSurface::setWindowClass(const QByteArray &windowClass) |
350 | { |
351 | wl_shell_surface_set_class(wl_shell_surface: d->surface, class_: windowClass.constData()); |
352 | } |
353 | |
354 | QSize ShellSurface::size() const |
355 | { |
356 | return d->size; |
357 | } |
358 | |
359 | bool ShellSurface::isValid() const |
360 | { |
361 | return d->surface.isValid(); |
362 | } |
363 | |
364 | ShellSurface::operator wl_shell_surface *() |
365 | { |
366 | return d->surface; |
367 | } |
368 | |
369 | ShellSurface::operator wl_shell_surface *() const |
370 | { |
371 | return d->surface; |
372 | } |
373 | |
374 | } |
375 | } |
376 | |
377 | #include "moc_shell.cpp" |
378 | |