KWindowSystem

windowsystem.cpp
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;
47 }
48
49 bool showingDesktop = false;
50};
51
52WindowSystem::WindowSystem()
53 : QObject()
54 , KWindowSystemPrivateV2()
55 , m_lastToken(qEnvironmentVariable("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(win);
69 if (!s) {
70 return;
71 }
72 WaylandXdgActivationV1 *activation = WaylandXdgActivationV1::self();
73 if (!activation->isActive()) {
74 return;
75 }
76 activation->activate(m_lastToken, 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(0, [serial] {
90 Q_EMIT KWaylandExtras::self()->xdgActivationTokenArrived(serial, {});
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, wlSurface, serial, app_id);
98 connect(tokenReq, &WaylandXdgActivationTokenV1::failed, KWindowSystem::self(), [serial, app_id]() {
99 Q_EMIT KWaylandExtras::self()->xdgActivationTokenArrived(serial, {});
100 });
101 connect(tokenReq, &WaylandXdgActivationTokenV1::done, KWindowSystem::self(), [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(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.
141 window,
142 [window, handle] {
143 Q_EMIT KWaylandExtras::self()->windowExported(window, handle);
144 },
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(c_kdeXdgForeignExportedProperty).value<WaylandXdgForeignExportedV2 *>();
169 if (!exported) {
170 exported = exporter.exportToplevel(surfaceForWindow(window));
171 exported->setParent(waylandWindow);
172
173 waylandWindow->setProperty(c_kdeXdgForeignExportedProperty, QVariant::fromValue(exported));
174 connect(exported, &QObject::destroyed, waylandWindow, [waylandWindow] {
175 waylandWindow->setProperty(c_kdeXdgForeignExportedProperty, QVariant());
176 });
177
178 connect(exported, &WaylandXdgForeignExportedV2::handleReceived, window, [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(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(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);
237#else
238 connect(waylandWindow, &QNativeInterface::Private::QWaylandWindow::surfaceRoleCreated, window, [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
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(c_kdeXdgForeignImportedProperty, QVariant::fromValue(imported));
287 connect(imported, &QObject::destroyed, waylandWindow, [waylandWindow] {
288 waylandWindow->setProperty(c_kdeXdgForeignImportedProperty, 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(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"
void xdgActivationTokenArrived(int serial, const QString &token)
Activation token to pass to the client.
void windowExported(QWindow *window, const QString &handle)
Window handle to pass to the client.
void showingDesktopChanged(bool showing)
The state of showing the desktop has changed.
static KWindowSystem * self()
Access to the singleton instance.
QWidget * window(QObject *job)
QVersionNumber version()
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void destroyed(QObject *obj)
virtual bool event(QEvent *e)
virtual bool eventFilter(QObject *watched, QEvent *event)
void installEventFilter(QObject *filterObj)
QVariant property(const char *name) const const
void removeEventFilter(QObject *obj)
bool setProperty(const char *name, QVariant &&value)
bool isEmpty() const const
QueuedConnection
NonModal
QVariant fromValue(T &&value)
QString toString() const const
T value() const const
void create(WId window, bool initializeWindow, bool destroyOldWindow)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri May 2 2025 11:55:23 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.