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 "waylandxdgforeignv2_p.h"
12
13#include <KWaylandExtras>
14#include <KWindowSystem>
15
16#include "qwayland-plasma-window-management.h"
17#include <QEvent>
18#include <QGuiApplication>
19#include <QPixmap>
20#include <QPoint>
21#include <QString>
22#include <QTimer>
23#include <QWaylandClientExtensionTemplate>
24#include <QWindow>
25#include <qpa/qplatformnativeinterface.h>
26#include <qpa/qplatformwindow_p.h>
27
28constexpr const char *c_kdeXdgForeignExportedProperty("_kde_xdg_foreign_exported_v2");
29constexpr const char *c_kdeXdgForeignImportedProperty("_kde_xdg_foreign_imported_v2");
30constexpr const char *c_kdeXdgForeignPendingHandleProperty("_kde_xdg_foreign_pending_handle");
31
32class WindowManagement : public QWaylandClientExtensionTemplate<WindowManagement>, public QtWayland::org_kde_plasma_window_management
33{
34public:
35 WindowManagement()
36 : QWaylandClientExtensionTemplate<WindowManagement>(16)
37 {
38 }
39
40 void org_kde_plasma_window_management_show_desktop_changed(uint32_t state) override
41 {
42 showingDesktop = state == show_desktop_enabled;
43 KWindowSystem::self()->showingDesktopChanged(showing: showingDesktop);
44 }
45
46 bool showingDesktop = false;
47};
48
49WindowSystem::WindowSystem()
50 : QObject()
51 , KWindowSystemPrivateV2()
52 , m_lastToken(qEnvironmentVariable(varName: "XDG_ACTIVATION_TOKEN"))
53{
54 m_windowManagement = new WindowManagement;
55}
56
57WindowSystem::~WindowSystem()
58{
59 delete m_windowManagement;
60}
61
62void WindowSystem::activateWindow(QWindow *win, long int time)
63{
64 Q_UNUSED(time);
65 auto s = surfaceForWindow(window: win);
66 if (!s) {
67 return;
68 }
69 WaylandXdgActivationV1 *activation = WaylandXdgActivationV1::self();
70 if (!activation->isActive()) {
71 return;
72 }
73 activation->activate(token: m_lastToken, surface: s);
74}
75
76void WindowSystem::requestToken(QWindow *window, uint32_t serial, const QString &app_id)
77{
78 if (window) {
79 window->create();
80 }
81 wl_surface *wlSurface = surfaceForWindow(window);
82
83 WaylandXdgActivationV1 *activation = WaylandXdgActivationV1::self();
84 if (!activation->isActive()) {
85 // Ensure that xdgActivationTokenArrived is always emitted asynchronously
86 QTimer::singleShot(interval: 0, slot: [serial] {
87 Q_EMIT KWaylandExtras::self()->xdgActivationTokenArrived(serial, token: {});
88 });
89 return;
90 }
91
92 auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
93 auto seat = waylandApp ? waylandApp->lastInputSeat() : nullptr;
94 auto tokenReq = activation->requestXdgActivationToken(seat, surface: wlSurface, serial, app_id);
95 connect(sender: tokenReq, signal: &WaylandXdgActivationTokenV1::failed, context: KWindowSystem::self(), slot: [serial, app_id]() {
96 Q_EMIT KWaylandExtras::self()->xdgActivationTokenArrived(serial, token: {});
97 });
98 connect(sender: tokenReq, signal: &WaylandXdgActivationTokenV1::done, context: KWindowSystem::self(), slot: [serial](const QString &token) {
99 Q_EMIT KWaylandExtras::self()->xdgActivationTokenArrived(serial, token);
100 });
101}
102
103void WindowSystem::setCurrentToken(const QString &token)
104{
105 m_lastToken = token;
106}
107
108quint32 WindowSystem::lastInputSerial(QWindow *window)
109{
110 Q_UNUSED(window)
111 if (auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>()) {
112 return waylandApp->lastInputSerial();
113 }
114 return 0;
115}
116
117void WindowSystem::setShowingDesktop(bool showing)
118{
119 if (!m_windowManagement->isActive()) {
120 return;
121 }
122 m_windowManagement->show_desktop(state: showing ? WindowManagement::show_desktop_enabled : WindowManagement::show_desktop_disabled);
123}
124
125bool WindowSystem::showingDesktop()
126{
127 if (!m_windowManagement->isActive()) {
128 return false;
129 }
130 return m_windowManagement->showingDesktop;
131}
132
133void WindowSystem::exportWindow(QWindow *window)
134{
135 auto emitHandle = [window](const QString &handle) {
136 // Ensure that windowExported is always emitted asynchronously.
137 QMetaObject::invokeMethod(
138 object: window,
139 function: [window, handle] {
140 Q_EMIT KWaylandExtras::self()->windowExported(window, handle);
141 },
142 type: Qt::QueuedConnection);
143 };
144
145 if (!window) {
146 return;
147 }
148
149 window->create();
150
151 auto waylandWindow = window->nativeInterface<QNativeInterface::Private::QWaylandWindow>();
152 if (!waylandWindow) {
153 emitHandle({});
154 return;
155 }
156
157 auto &exporter = WaylandXdgForeignExporterV2::self();
158 if (!exporter.isActive()) {
159 emitHandle({});
160 return;
161 }
162
163 // We want to use QObject::property(char*) and use dynamic properties on the object rather than
164 // call QWaylandWindow::property(QString) and send it around.
165 WaylandXdgForeignExportedV2 *exported = waylandWindow->property(name: c_kdeXdgForeignExportedProperty).value<WaylandXdgForeignExportedV2 *>();
166 if (!exported) {
167 exported = exporter.exportToplevel(surface: surfaceForWindow(window));
168 exported->setParent(waylandWindow);
169
170 waylandWindow->setProperty(name: c_kdeXdgForeignExportedProperty, value: QVariant::fromValue(value: exported));
171 connect(sender: exported, signal: &QObject::destroyed, context: waylandWindow, slot: [waylandWindow] {
172 waylandWindow->setProperty(name: c_kdeXdgForeignExportedProperty, value: QVariant());
173 });
174
175 connect(sender: exported, signal: &WaylandXdgForeignExportedV2::handleReceived, context: window, slot: [window](const QString &handle) {
176 Q_EMIT KWaylandExtras::self()->windowExported(window, handle);
177 });
178 }
179
180 if (!exported->handle().isEmpty()) {
181 emitHandle(exported->handle());
182 }
183}
184
185void WindowSystem::unexportWindow(QWindow *window)
186{
187 auto waylandWindow = window ? window->nativeInterface<QNativeInterface::Private::QWaylandWindow>() : nullptr;
188 if (!waylandWindow) {
189 return;
190 }
191
192 WaylandXdgForeignExportedV2 *exported = waylandWindow->property(name: c_kdeXdgForeignExportedProperty).value<WaylandXdgForeignExportedV2 *>();
193 delete exported;
194 Q_ASSERT(!waylandWindow->property(c_kdeXdgForeignExportedProperty).isValid());
195}
196
197void WindowSystem::setMainWindow(QWindow *window, const QString &handle)
198{
199 if (!window) {
200 return;
201 }
202
203 window->create();
204 auto waylandWindow = window->nativeInterface<QNativeInterface::Private::QWaylandWindow>();
205 if (!waylandWindow) {
206 return;
207 }
208
209 // We want to use QObject::property(char*) and use dynamic properties on the object rather than
210 // call QWaylandWindow::property(QString) and send it around.
211 auto *imported = waylandWindow->property(name: c_kdeXdgForeignImportedProperty).value<WaylandXdgForeignImportedV2 *>();
212 // Window already parented with a different handle? Delete imported so we import the new one later.
213 if (imported && imported->handle() != handle) {
214 delete imported;
215 imported = nullptr;
216 Q_ASSERT(!waylandWindow->property(c_kdeXdgForeignImportedProperty).isValid());
217 }
218
219 // Don't bother.
220 if (handle.isEmpty()) {
221 return;
222 }
223
224 if (window->isExposed()) {
225 doSetMainWindow(window, handle);
226 } else {
227 // We can only import an XDG toplevel. QtWayland currently has no proper signal
228 // for shell surface creation. wlSurfaceCreated() is too early.
229 // Instead, we wait for it being exposed and then set its parent.
230 window->setProperty(name: c_kdeXdgForeignPendingHandleProperty, value: handle);
231 window->installEventFilter(filterObj: this);
232 }
233}
234
235bool WindowSystem::eventFilter(QObject *watched, QEvent *event)
236{
237 if (event->type() == QEvent::Expose) {
238 auto *window = static_cast<QWindow *>(watched);
239 if (window->isExposed()) {
240 const QString handle = window->property(name: c_kdeXdgForeignPendingHandleProperty).toString();
241 if (!handle.isEmpty()) {
242 doSetMainWindow(window, handle);
243 window->setProperty(name: c_kdeXdgForeignPendingHandleProperty, value: QVariant());
244 }
245
246 window->removeEventFilter(obj: this);
247 }
248 }
249
250 return QObject::eventFilter(watched, event);
251}
252
253void WindowSystem::doSetMainWindow(QWindow *window, const QString &handle)
254{
255 Q_ASSERT(window);
256 Q_ASSERT(!handle.isEmpty());
257
258 auto waylandWindow = window->nativeInterface<QNativeInterface::Private::QWaylandWindow>();
259 if (!waylandWindow) {
260 return;
261 }
262
263 auto &importer = WaylandXdgForeignImporterV2::self();
264 if (!importer.isActive()) {
265 return;
266 }
267
268 Q_ASSERT(!waylandWindow->property(c_kdeXdgForeignImportedProperty).isValid());
269
270 WaylandXdgForeignImportedV2 *imported = importer.importToplevel(handle);
271 imported->set_parent_of(surfaceForWindow(window)); // foreign parent.
272 imported->setParent(waylandWindow); // memory owner.
273
274 waylandWindow->setProperty(name: c_kdeXdgForeignImportedProperty, value: QVariant::fromValue(value: imported));
275 connect(sender: imported, signal: &QObject::destroyed, context: waylandWindow, slot: [waylandWindow] {
276 waylandWindow->setProperty(name: c_kdeXdgForeignImportedProperty, value: QVariant());
277 });
278}
279
280#include "moc_windowsystem.cpp"
281

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