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 | |
28 | constexpr const char *c_kdeXdgForeignExportedProperty("_kde_xdg_foreign_exported_v2" ); |
29 | constexpr const char *c_kdeXdgForeignImportedProperty("_kde_xdg_foreign_imported_v2" ); |
30 | constexpr const char *c_kdeXdgForeignPendingHandleProperty("_kde_xdg_foreign_pending_handle" ); |
31 | |
32 | class WindowManagement : public QWaylandClientExtensionTemplate<WindowManagement>, public QtWayland::org_kde_plasma_window_management |
33 | { |
34 | public: |
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 | |
49 | WindowSystem::WindowSystem() |
50 | : QObject() |
51 | , KWindowSystemPrivateV2() |
52 | , m_lastToken(qEnvironmentVariable(varName: "XDG_ACTIVATION_TOKEN" )) |
53 | { |
54 | m_windowManagement = new WindowManagement; |
55 | } |
56 | |
57 | WindowSystem::~WindowSystem() |
58 | { |
59 | delete m_windowManagement; |
60 | } |
61 | |
62 | void 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 | |
76 | void 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 | |
103 | void WindowSystem::setCurrentToken(const QString &token) |
104 | { |
105 | m_lastToken = token; |
106 | } |
107 | |
108 | quint32 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 | |
117 | void 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 | |
125 | bool WindowSystem::showingDesktop() |
126 | { |
127 | if (!m_windowManagement->isActive()) { |
128 | return false; |
129 | } |
130 | return m_windowManagement->showingDesktop; |
131 | } |
132 | |
133 | void 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 | |
185 | void 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 | |
197 | void 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 | |
235 | bool 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 | |
253 | void 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 | |