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 "logging.h"
9#include "surfacehelper.h"
10#include "waylandxdgactivationv1_p.h"
11#include "waylandxdgdialogv1_p.h"
12#include "waylandxdgforeignv2_p.h"
13
14#include <KWaylandExtras>
15#include <KWindowSystem>
16
17#include "qwayland-plasma-window-management.h"
18#include <QEvent>
19#include <QGuiApplication>
20#include <QLibraryInfo>
21#include <QPixmap>
22#include <QPoint>
23#include <QString>
24#include <QTimer>
25#include <QVersionNumber>
26#include <QWaylandClientExtensionTemplate>
27#include <QWindow>
28#include <qpa/qplatformnativeinterface.h>
29#include <qpa/qplatformwindow_p.h>
30
31constexpr const char *c_kdeXdgForeignExportedProperty("_kde_xdg_foreign_exported_v2");
32constexpr const char *c_kdeXdgForeignImportedProperty("_kde_xdg_foreign_imported_v2");
33constexpr const char *c_kdeXdgForeignPendingHandleProperty("_kde_xdg_foreign_pending_handle");
34
35class WindowManagement : public QWaylandClientExtensionTemplate<WindowManagement>, public QtWayland::org_kde_plasma_window_management
36{
37public:
38 WindowManagement()
39 : QWaylandClientExtensionTemplate<WindowManagement>(17)
40 {
41 }
42
43 void org_kde_plasma_window_management_show_desktop_changed(uint32_t state) override
44 {
45 showingDesktop = state == show_desktop_enabled;
46 KWindowSystem::self()->showingDesktopChanged(showing: showingDesktop);
47 }
48
49 bool showingDesktop = false;
50};
51
52WindowSystem::WindowSystem()
53 : QObject()
54 , KWindowSystemPrivateV2()
55 , m_lastToken(qEnvironmentVariable(varName: "XDG_ACTIVATION_TOKEN"))
56{
57 m_windowManagement = new WindowManagement;
58}
59
60WindowSystem::~WindowSystem()
61{
62 delete m_windowManagement;
63}
64
65void WindowSystem::activateWindow(QWindow *win, long int time)
66{
67 Q_UNUSED(time);
68 auto s = surfaceForWindow(window: win);
69 if (!s) {
70 return;
71 }
72 WaylandXdgActivationV1 *activation = WaylandXdgActivationV1::self();
73 if (!activation->isActive()) {
74 return;
75 }
76 activation->activate(token: m_lastToken, surface: s);
77}
78
79void WindowSystem::requestToken(QWindow *window, uint32_t serial, const QString &app_id)
80{
81 if (window) {
82 window->create();
83 }
84 wl_surface *wlSurface = surfaceForWindow(window);
85
86 WaylandXdgActivationV1 *activation = WaylandXdgActivationV1::self();
87 if (!activation->isActive()) {
88 // Ensure that xdgActivationTokenArrived is always emitted asynchronously
89 QTimer::singleShot(interval: 0, slot: [serial] {
90 Q_EMIT KWaylandExtras::self()->xdgActivationTokenArrived(serial, token: {});
91 });
92 return;
93 }
94
95 auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
96 auto seat = waylandApp ? waylandApp->lastInputSeat() : nullptr;
97 auto tokenReq = activation->requestXdgActivationToken(seat, surface: wlSurface, serial, app_id);
98 connect(sender: tokenReq, signal: &WaylandXdgActivationTokenV1::failed, context: KWindowSystem::self(), slot: [serial, app_id]() {
99 Q_EMIT KWaylandExtras::self()->xdgActivationTokenArrived(serial, token: {});
100 });
101 connect(sender: tokenReq, signal: &WaylandXdgActivationTokenV1::done, context: KWindowSystem::self(), slot: [serial](const QString &token) {
102 Q_EMIT KWaylandExtras::self()->xdgActivationTokenArrived(serial, token);
103 });
104}
105
106void WindowSystem::setCurrentToken(const QString &token)
107{
108 m_lastToken = token;
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 // QWaylandWindow::surfaceRoleCreated is only in Qt 6.8,
232 // in earlier versions wait for the window be exposed,
233 // since QWaylandWindow::wlSurfaceCreated is too early.
234#if QT_VERSION < QT_VERSION_CHECK(6, 8, 0)
235 window->setProperty(c_kdeXdgForeignPendingHandleProperty, handle);
236 window->installEventFilter(this);
237#else
238 connect(sender: waylandWindow, signal: &QNativeInterface::Private::QWaylandWindow::surfaceRoleCreated, context: window, slot: [window, handle] {
239 doSetMainWindow(window, handle);
240 });
241#endif
242 }
243}
244
245bool WindowSystem::eventFilter(QObject *watched, QEvent *event)
246{
247#if QT_VERSION < QT_VERSION_CHECK(6, 8, 0)
248 if (event->type() == QEvent::Expose) {
249 auto *window = static_cast<QWindow *>(watched);
250 if (window->isExposed()) {
251 const QString handle = window->property(c_kdeXdgForeignPendingHandleProperty).toString();
252 if (!handle.isEmpty()) {
253 doSetMainWindow(window, handle);
254 window->setProperty(c_kdeXdgForeignPendingHandleProperty, QVariant());
255 }
256
257 window->removeEventFilter(this);
258 }
259 }
260#endif
261
262 return QObject::eventFilter(watched, event);
263}
264
265void WindowSystem::doSetMainWindow(QWindow *window, const QString &handle)
266{
267 Q_ASSERT(window);
268 Q_ASSERT(!handle.isEmpty());
269
270 auto waylandWindow = window->nativeInterface<QNativeInterface::Private::QWaylandWindow>();
271 if (!waylandWindow) {
272 return;
273 }
274
275 auto &importer = WaylandXdgForeignImporterV2::self();
276 if (!importer.isActive()) {
277 return;
278 }
279
280 Q_ASSERT(!waylandWindow->property(c_kdeXdgForeignImportedProperty).isValid());
281
282 WaylandXdgForeignImportedV2 *imported = importer.importToplevel(handle);
283 imported->set_parent_of(surfaceForWindow(window)); // foreign parent.
284 imported->setParent(waylandWindow); // memory owner.
285
286 waylandWindow->setProperty(name: c_kdeXdgForeignImportedProperty, value: QVariant::fromValue(value: imported));
287 connect(sender: imported, signal: &QObject::destroyed, context: waylandWindow, slot: [waylandWindow] {
288 waylandWindow->setProperty(name: c_kdeXdgForeignImportedProperty, value: QVariant());
289 });
290
291 // Before Qt 6.10, Qt sets XDG Dialog modal only when it has a transient parent.
292 if (QLibraryInfo::version() < QVersionNumber(6, 10, 0)) {
293 auto *oldDialog = waylandWindow->findChild<WaylandXdgDialogV1 *>();
294 if (window->modality() != Qt::NonModal && !oldDialog) {
295 auto &xdgDialog = WaylandXdgDialogWmV1::self();
296 if (xdgDialog.isActive()) {
297 if (auto *xdgToplevel = xdgToplevelForWindow(window)) {
298 auto *dialog = xdgDialog.getDialog(toplevel: xdgToplevel);
299 dialog->set_modal();
300 dialog->setParent(waylandWindow);
301 }
302 }
303 } else {
304 delete oldDialog;
305 }
306 }
307}
308
309#include "moc_windowsystem.cpp"
310

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