1/*
2 SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
3 SPDX-FileCopyrightText: 2023 Kai Uwe Broulik <kde@broulik.de>
4
5 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6*/
7#include "windowsystem.h"
8#include "helpers.h"
9#include "logging.h"
10#include "waylandxdgactivationv1_p.h"
11#include "waylandxdgdialogv1_p.h"
12#include "waylandxdgforeignv2_p.h"
13#include "waylandxdgtopleveltagv1_p.h"
14
15#include <KWaylandExtras>
16#include <KWindowSystem>
17
18#include "qwayland-plasma-window-management.h"
19#include <QEvent>
20#include <QGuiApplication>
21#include <QLibraryInfo>
22#include <QPixmap>
23#include <QPoint>
24#include <QString>
25#include <QTimer>
26#include <QVersionNumber>
27#include <QWaylandClientExtensionTemplate>
28#include <QWindow>
29#include <qpa/qplatformnativeinterface.h>
30#include <qpa/qplatformwindow_p.h>
31
32constexpr const char *c_kdeXdgForeignExportedProperty("_kde_xdg_foreign_exported_v2");
33constexpr const char *c_kdeXdgForeignImportedProperty("_kde_xdg_foreign_imported_v2");
34constexpr const char *c_kdeXdgForeignPendingHandleProperty("_kde_xdg_foreign_pending_handle");
35constexpr const char *c_kdeXdgToplevelTagCookieName("_kde_xdg_toplevel_tag_cookie");
36constexpr const char *c_kdeXdgToplevelDescriptionCookieName("_kde_xdg_toplevel_description_cookie");
37constexpr const char *c_xdgActivationTokenEnv("XDG_ACTIVATION_TOKEN");
38
39class WindowManagement : public QWaylandClientExtensionTemplate<WindowManagement>, public QtWayland::org_kde_plasma_window_management
40{
41public:
42 WindowManagement()
43 : QWaylandClientExtensionTemplate<WindowManagement>(17)
44 {
45 }
46
47 void org_kde_plasma_window_management_show_desktop_changed(uint32_t state) override
48 {
49 showingDesktop = state == show_desktop_enabled;
50 KWindowSystem::self()->showingDesktopChanged(showing: showingDesktop);
51 }
52
53 bool showingDesktop = false;
54};
55
56WindowSystem::WindowSystem()
57{
58 m_windowManagement = new WindowManagement;
59}
60
61WindowSystem::~WindowSystem()
62{
63 delete m_windowManagement;
64}
65
66void WindowSystem::activateWindow(QWindow *win, long int time)
67{
68 Q_UNUSED(time);
69 auto s = surfaceForWindow(window: win);
70 if (!s) {
71 return;
72 }
73 WaylandXdgActivationV1 *activation = WaylandXdgActivationV1::self();
74 if (!activation->isActive()) {
75 return;
76 }
77 activation->activate(token: consumeCurrentActivationToken(), surface: s);
78}
79
80#if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(6, 19)
81void WindowSystem::requestToken(QWindow *window, uint32_t serial, const QString &app_id)
82{
83 if (window) {
84 window->create();
85 }
86 wl_surface *wlSurface = surfaceForWindow(window);
87
88 WaylandXdgActivationV1 *activation = WaylandXdgActivationV1::self();
89 if (!activation->isActive()) {
90 // Ensure that xdgActivationTokenArrived is always emitted asynchronously
91 QTimer::singleShot(interval: 0, slot: [serial] {
92 Q_EMIT KWaylandExtras::self()->xdgActivationTokenArrived(serial, token: {});
93 });
94 return;
95 }
96
97 auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
98 auto seat = waylandApp ? waylandApp->lastInputSeat() : nullptr;
99 auto tokenReq = activation->requestXdgActivationToken(seat, surface: wlSurface, serial, app_id);
100 connect(sender: tokenReq, signal: &WaylandXdgActivationTokenV1::done, context: KWindowSystem::self(), slot: [serial](const QString &token) {
101 Q_EMIT KWaylandExtras::self()->xdgActivationTokenArrived(serial, token);
102 });
103}
104#endif
105
106void WindowSystem::setCurrentToken(const QString &token)
107{
108 qputenv(varName: c_xdgActivationTokenEnv, value: token.toUtf8());
109}
110
111quint32 WindowSystem::lastInputSerial(QWindow *window)
112{
113 Q_UNUSED(window)
114 if (auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>()) {
115 return waylandApp->lastInputSerial();
116 }
117 return 0;
118}
119
120void WindowSystem::setShowingDesktop(bool showing)
121{
122 if (!m_windowManagement->isActive()) {
123 return;
124 }
125 m_windowManagement->show_desktop(state: showing ? WindowManagement::show_desktop_enabled : WindowManagement::show_desktop_disabled);
126}
127
128bool WindowSystem::showingDesktop()
129{
130 if (!m_windowManagement->isActive()) {
131 return false;
132 }
133 return m_windowManagement->showingDesktop;
134}
135
136void WindowSystem::exportWindow(QWindow *window)
137{
138 auto emitHandle = [window](const QString &handle) {
139 // Ensure that windowExported is always emitted asynchronously.
140 QMetaObject::invokeMethod(
141 object: window,
142 function: [window, handle] {
143 Q_EMIT KWaylandExtras::self()->windowExported(window, handle);
144 },
145 type: Qt::QueuedConnection);
146 };
147
148 if (!window) {
149 return;
150 }
151
152 window->create();
153
154 auto waylandWindow = window->nativeInterface<QNativeInterface::Private::QWaylandWindow>();
155 if (!waylandWindow) {
156 emitHandle({});
157 return;
158 }
159
160 auto &exporter = WaylandXdgForeignExporterV2::self();
161 if (!exporter.isActive()) {
162 emitHandle({});
163 return;
164 }
165
166 // We want to use QObject::property(char*) and use dynamic properties on the object rather than
167 // call QWaylandWindow::property(QString) and send it around.
168 WaylandXdgForeignExportedV2 *exported = waylandWindow->property(name: c_kdeXdgForeignExportedProperty).value<WaylandXdgForeignExportedV2 *>();
169 if (!exported) {
170 exported = exporter.exportToplevel(surface: surfaceForWindow(window));
171 exported->setParent(waylandWindow);
172
173 waylandWindow->setProperty(name: c_kdeXdgForeignExportedProperty, value: QVariant::fromValue(value: exported));
174 connect(sender: exported, signal: &QObject::destroyed, context: waylandWindow, slot: [waylandWindow] {
175 waylandWindow->setProperty(name: c_kdeXdgForeignExportedProperty, value: QVariant());
176 });
177
178 connect(sender: exported, signal: &WaylandXdgForeignExportedV2::handleReceived, context: window, slot: [window](const QString &handle) {
179 Q_EMIT KWaylandExtras::self()->windowExported(window, handle);
180 });
181 }
182
183 if (!exported->handle().isEmpty()) {
184 emitHandle(exported->handle());
185 }
186}
187
188void WindowSystem::unexportWindow(QWindow *window)
189{
190 auto waylandWindow = window ? window->nativeInterface<QNativeInterface::Private::QWaylandWindow>() : nullptr;
191 if (!waylandWindow) {
192 return;
193 }
194
195 WaylandXdgForeignExportedV2 *exported = waylandWindow->property(name: c_kdeXdgForeignExportedProperty).value<WaylandXdgForeignExportedV2 *>();
196 delete exported;
197 Q_ASSERT(!waylandWindow->property(c_kdeXdgForeignExportedProperty).isValid());
198}
199
200void WindowSystem::setMainWindow(QWindow *window, const QString &handle)
201{
202 if (!window) {
203 return;
204 }
205
206 window->create();
207 auto waylandWindow = window->nativeInterface<QNativeInterface::Private::QWaylandWindow>();
208 if (!waylandWindow) {
209 return;
210 }
211
212 // We want to use QObject::property(char*) and use dynamic properties on the object rather than
213 // call QWaylandWindow::property(QString) and send it around.
214 auto *imported = waylandWindow->property(name: c_kdeXdgForeignImportedProperty).value<WaylandXdgForeignImportedV2 *>();
215 // Window already parented with a different handle? Delete imported so we import the new one later.
216 if (imported && imported->handle() != handle) {
217 delete imported;
218 imported = nullptr;
219 Q_ASSERT(!waylandWindow->property(c_kdeXdgForeignImportedProperty).isValid());
220 }
221
222 // Don't bother.
223 if (handle.isEmpty()) {
224 return;
225 }
226
227 if (window->isExposed()) {
228 doSetMainWindow(window, handle);
229 } else {
230 // We can only import an XDG toplevel.
231 connect(sender: waylandWindow, signal: &QNativeInterface::Private::QWaylandWindow::surfaceRoleCreated, context: window, slot: [window, handle] {
232 doSetMainWindow(window, handle);
233 });
234 }
235}
236
237void WindowSystem::doSetMainWindow(QWindow *window, const QString &handle)
238{
239 Q_ASSERT(window);
240 Q_ASSERT(!handle.isEmpty());
241
242 auto waylandWindow = window->nativeInterface<QNativeInterface::Private::QWaylandWindow>();
243 if (!waylandWindow) {
244 return;
245 }
246
247 auto &importer = WaylandXdgForeignImporterV2::self();
248 if (!importer.isActive()) {
249 return;
250 }
251
252 Q_ASSERT(!waylandWindow->property(c_kdeXdgForeignImportedProperty).isValid());
253
254 WaylandXdgForeignImportedV2 *imported = importer.importToplevel(handle);
255 imported->set_parent_of(surfaceForWindow(window)); // foreign parent.
256 imported->setParent(waylandWindow); // memory owner.
257
258 waylandWindow->setProperty(name: c_kdeXdgForeignImportedProperty, value: QVariant::fromValue(value: imported));
259 connect(sender: imported, signal: &QObject::destroyed, context: waylandWindow, slot: [waylandWindow] {
260 waylandWindow->setProperty(name: c_kdeXdgForeignImportedProperty, value: QVariant());
261 });
262
263 // Before Qt 6.10, Qt sets XDG Dialog modal only when it has a transient parent.
264 if (QLibraryInfo::version() < QVersionNumber(6, 10, 0)) {
265 auto *oldDialog = waylandWindow->findChild<WaylandXdgDialogV1 *>();
266 if (window->modality() != Qt::NonModal && !oldDialog) {
267 auto &xdgDialog = WaylandXdgDialogWmV1::self();
268 if (xdgDialog.isActive()) {
269 if (auto *xdgToplevel = xdgToplevelForWindow(window)) {
270 auto *dialog = xdgDialog.getDialog(toplevel: xdgToplevel);
271 dialog->set_modal();
272 dialog->setParent(waylandWindow);
273 }
274 }
275 } else {
276 delete oldDialog;
277 }
278 }
279}
280
281QString WindowSystem::consumeCurrentActivationToken()
282{
283 const auto token = qEnvironmentVariable(varName: c_xdgActivationTokenEnv);
284 qunsetenv(varName: c_xdgActivationTokenEnv);
285 return token;
286}
287
288QFuture<QString> WindowSystem::xdgActivationToken(QWindow *window, uint32_t serial, const QString &appId)
289{
290 WaylandXdgActivationV1 *activation = WaylandXdgActivationV1::self();
291 if (!activation->isActive()) {
292 return QtFuture::makeReadyValueFuture(value: QString());
293 }
294
295 auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
296 if (!waylandApp) {
297 return QtFuture::makeReadyValueFuture(value: QString());
298 }
299
300 if (window) {
301 window->create();
302 }
303 wl_surface *wlSurface = surfaceForWindow(window);
304
305 auto token = activation->requestXdgActivationToken(seat: waylandApp->lastInputSeat(), surface: wlSurface, serial, app_id: appId);
306 return token->future();
307}
308
309void WindowSystem::setXdgToplevelTag(QWindow *window, const QString &tag)
310{
311 Q_ASSERT(window);
312
313 window->create();
314 auto waylandWindow = window->nativeInterface<QNativeInterface::Private::QWaylandWindow>();
315 if (!waylandWindow) {
316 return;
317 }
318
319 delete window->findChild<QObject *>(aName: c_kdeXdgToplevelTagCookieName, options: Qt::FindDirectChildrenOnly);
320
321 auto cookie = new QObject(window);
322 cookie->setObjectName(c_kdeXdgToplevelTagCookieName);
323
324 auto tryAssignTag = [waylandWindow, tag]() {
325 if (auto xdgToplevel = waylandWindow->surfaceRole<xdg_toplevel>()) {
326 WaylandXdgToplevelTagManagerV1::self()->set_toplevel_tag(toplevel: xdgToplevel, tag);
327 }
328 };
329
330 tryAssignTag();
331 connect(sender: waylandWindow, signal: &QNativeInterface::Private::QWaylandWindow::surfaceRoleCreated, context: cookie, slot&: tryAssignTag);
332}
333
334void WindowSystem::setXdgToplevelDescription(QWindow *window, const QString &description)
335{
336 Q_ASSERT(window);
337
338 window->create();
339 auto waylandWindow = window->nativeInterface<QNativeInterface::Private::QWaylandWindow>();
340 if (!waylandWindow) {
341 return;
342 }
343
344 delete window->findChild<QObject *>(aName: c_kdeXdgToplevelDescriptionCookieName, options: Qt::FindDirectChildrenOnly);
345
346 auto cookie = new QObject(window);
347 cookie->setObjectName(c_kdeXdgToplevelDescriptionCookieName);
348
349 auto tryAssignDescription = [waylandWindow, description]() {
350 if (auto xdgToplevel = waylandWindow->surfaceRole<xdg_toplevel>()) {
351 WaylandXdgToplevelTagManagerV1::self()->set_toplevel_description(toplevel: xdgToplevel, description);
352 }
353 };
354
355 tryAssignDescription();
356 connect(sender: waylandWindow, signal: &QNativeInterface::Private::QWaylandWindow::surfaceRoleCreated, context: cookie, slot&: tryAssignDescription);
357}
358
359#include "moc_windowsystem.cpp"
360

source code of kwindowsystem/src/platforms/wayland/windowsystem.cpp