File indexing completed on 2024-05-12 04:00:25

0001 /*
0002     SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
0003     SPDX-FileCopyrightText: 2023 Kai Uwe Broulik <kde@broulik.de>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006 */
0007 #include "windowsystem.h"
0008 #include "logging.h"
0009 #include "surfacehelper.h"
0010 #include "waylandxdgactivationv1_p.h"
0011 #include "waylandxdgforeignv2_p.h"
0012 
0013 #include <KWaylandExtras>
0014 #include <KWindowSystem>
0015 
0016 #include "qwayland-plasma-window-management.h"
0017 #include <QEvent>
0018 #include <QGuiApplication>
0019 #include <QPixmap>
0020 #include <QPoint>
0021 #include <QString>
0022 #include <QWaylandClientExtensionTemplate>
0023 #include <QWindow>
0024 #include <private/qwaylanddisplay_p.h>
0025 #include <private/qwaylandinputdevice_p.h>
0026 #include <private/qwaylandwindow_p.h>
0027 #include <qpa/qplatformnativeinterface.h>
0028 #include <qwaylandclientextension.h>
0029 
0030 constexpr const char *c_kdeXdgForeignExportedProperty("_kde_xdg_foreign_exported_v2");
0031 constexpr const char *c_kdeXdgForeignImportedProperty("_kde_xdg_foreign_imported_v2");
0032 constexpr const char *c_kdeXdgForeignPendingHandleProperty("_kde_xdg_foreign_pending_handle");
0033 
0034 class WindowManagement : public QWaylandClientExtensionTemplate<WindowManagement>, public QtWayland::org_kde_plasma_window_management
0035 {
0036 public:
0037     WindowManagement()
0038         : QWaylandClientExtensionTemplate<WindowManagement>(16)
0039     {
0040     }
0041 
0042     void org_kde_plasma_window_management_show_desktop_changed(uint32_t state) override
0043     {
0044         showingDesktop = state == show_desktop_enabled;
0045         KWindowSystem::self()->showingDesktopChanged(showingDesktop);
0046     }
0047 
0048     bool showingDesktop = false;
0049 };
0050 
0051 WindowSystem::WindowSystem()
0052     : QObject()
0053     , KWindowSystemPrivateV2()
0054     , m_lastToken(qEnvironmentVariable("XDG_ACTIVATION_TOKEN"))
0055 {
0056     m_windowManagement = new WindowManagement;
0057 }
0058 
0059 WindowSystem::~WindowSystem()
0060 {
0061     delete m_windowManagement;
0062 }
0063 
0064 void WindowSystem::activateWindow(QWindow *win, long int time)
0065 {
0066     Q_UNUSED(time);
0067     auto s = surfaceForWindow(win);
0068     if (!s) {
0069         return;
0070     }
0071     WaylandXdgActivationV1 *activation = WaylandXdgActivationV1::self();
0072     if (!activation->isActive()) {
0073         return;
0074     }
0075     activation->activate(m_lastToken, s);
0076 }
0077 
0078 void WindowSystem::requestToken(QWindow *window, uint32_t serial, const QString &app_id)
0079 {
0080     if (window) {
0081         window->create();
0082     }
0083     wl_surface *wlSurface = surfaceForWindow(window);
0084 
0085     WaylandXdgActivationV1 *activation = WaylandXdgActivationV1::self();
0086     if (!activation->isActive()) {
0087         // Ensure that xdgActivationTokenArrived is always emitted asynchronously
0088         QTimer::singleShot(0, [serial] {
0089             Q_EMIT KWaylandExtras::self()->xdgActivationTokenArrived(serial, {});
0090         });
0091         return;
0092     }
0093 
0094     auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
0095     auto seat = waylandApp ? waylandApp->lastInputSeat() : nullptr;
0096     auto tokenReq = activation->requestXdgActivationToken(seat, wlSurface, serial, app_id);
0097     connect(tokenReq, &WaylandXdgActivationTokenV1::failed, KWindowSystem::self(), [serial, app_id]() {
0098         Q_EMIT KWaylandExtras::self()->xdgActivationTokenArrived(serial, {});
0099     });
0100     connect(tokenReq, &WaylandXdgActivationTokenV1::done, KWindowSystem::self(), [serial](const QString &token) {
0101         Q_EMIT KWaylandExtras::self()->xdgActivationTokenArrived(serial, token);
0102     });
0103 }
0104 
0105 void WindowSystem::setCurrentToken(const QString &token)
0106 {
0107     m_lastToken = token;
0108 }
0109 
0110 quint32 WindowSystem::lastInputSerial(QWindow *window)
0111 {
0112     Q_UNUSED(window)
0113     if (auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>()) {
0114         return waylandApp->lastInputSerial();
0115     }
0116     return 0;
0117 }
0118 
0119 void WindowSystem::setShowingDesktop(bool showing)
0120 {
0121     if (!m_windowManagement->isActive()) {
0122         return;
0123     }
0124     m_windowManagement->show_desktop(showing ? WindowManagement::show_desktop_enabled : WindowManagement::show_desktop_disabled);
0125 }
0126 
0127 bool WindowSystem::showingDesktop()
0128 {
0129     if (!m_windowManagement->isActive()) {
0130         return false;
0131     }
0132     return m_windowManagement->showingDesktop;
0133 }
0134 
0135 void WindowSystem::exportWindow(QWindow *window)
0136 {
0137     auto emitHandle = [window](const QString &handle) {
0138         // Ensure that windowExported is always emitted asynchronously.
0139         QMetaObject::invokeMethod(
0140             window,
0141             [window, handle] {
0142                 Q_EMIT KWaylandExtras::self()->windowExported(window, handle);
0143             },
0144             Qt::QueuedConnection);
0145     };
0146 
0147     if (!window) {
0148         return;
0149     }
0150 
0151     window->create();
0152 
0153     auto waylandWindow = dynamic_cast<QtWaylandClient::QWaylandWindow *>(window->handle());
0154     if (!waylandWindow) {
0155         emitHandle({});
0156         return;
0157     }
0158 
0159     auto &exporter = WaylandXdgForeignExporterV2::self();
0160     if (!exporter.isActive()) {
0161         emitHandle({});
0162         return;
0163     }
0164 
0165     // We want to use QObject::property(char*) and use dynamic properties on the object rather than
0166     // call QWaylandWindow::property(QString) and send it around.
0167     auto *waylandWindowQObject = static_cast<QObject *>(waylandWindow);
0168     WaylandXdgForeignExportedV2 *exported = waylandWindowQObject->property(c_kdeXdgForeignExportedProperty).value<WaylandXdgForeignExportedV2 *>();
0169     if (!exported) {
0170         exported = exporter.exportToplevel(surfaceForWindow(window));
0171         exported->setParent(waylandWindow);
0172 
0173         waylandWindowQObject->setProperty(c_kdeXdgForeignExportedProperty, QVariant::fromValue(exported));
0174         connect(exported, &QObject::destroyed, waylandWindow, [waylandWindowQObject] {
0175             waylandWindowQObject->setProperty(c_kdeXdgForeignExportedProperty, QVariant());
0176         });
0177 
0178         connect(exported, &WaylandXdgForeignExportedV2::handleReceived, window, [window](const QString &handle) {
0179             Q_EMIT KWaylandExtras::self()->windowExported(window, handle);
0180         });
0181     }
0182 
0183     if (!exported->handle().isEmpty()) {
0184         emitHandle(exported->handle());
0185     }
0186 }
0187 
0188 void WindowSystem::unexportWindow(QWindow *window)
0189 {
0190     auto waylandWindow = window ? dynamic_cast<QtWaylandClient::QWaylandWindow *>(window->handle()) : nullptr;
0191     if (!waylandWindow) {
0192         return;
0193     }
0194 
0195     auto *waylandWindowQObject = static_cast<QObject *>(waylandWindow);
0196     WaylandXdgForeignExportedV2 *exported = waylandWindowQObject->property(c_kdeXdgForeignExportedProperty).value<WaylandXdgForeignExportedV2 *>();
0197     delete exported;
0198     Q_ASSERT(!waylandWindowQObject->property(c_kdeXdgForeignExportedProperty).isValid());
0199 }
0200 
0201 void WindowSystem::setMainWindow(QWindow *window, const QString &handle)
0202 {
0203     if (!window) {
0204         return;
0205     }
0206 
0207     window->create();
0208     auto waylandWindow = dynamic_cast<QtWaylandClient::QWaylandWindow *>(window->handle());
0209     if (!waylandWindow) {
0210         return;
0211     }
0212 
0213     // We want to use QObject::property(char*) and use dynamic properties on the object rather than
0214     // call QWaylandWindow::property(QString) and send it around.
0215     auto *waylandWindowQObject = static_cast<QObject *>(waylandWindow);
0216     auto *imported = waylandWindowQObject->property(c_kdeXdgForeignImportedProperty).value<WaylandXdgForeignImportedV2 *>();
0217     // Window already parented with a different handle? Delete imported so we import the new one later.
0218     if (imported && imported->handle() != handle) {
0219         delete imported;
0220         imported = nullptr;
0221         Q_ASSERT(!waylandWindowQObject->property(c_kdeXdgForeignImportedProperty).isValid());
0222     }
0223 
0224     // Don't bother.
0225     if (handle.isEmpty()) {
0226         return;
0227     }
0228 
0229     if (window->isExposed()) {
0230         doSetMainWindow(window, handle);
0231     } else {
0232         // We can only import an XDG toplevel. QtWayland currently has no proper signal
0233         // for shell surface creation. wlSurfaceCreated() is too early.
0234         // Instead, we wait for it being exposed and then set its parent.
0235         window->setProperty(c_kdeXdgForeignPendingHandleProperty, handle);
0236         window->installEventFilter(this);
0237     }
0238 }
0239 
0240 bool WindowSystem::eventFilter(QObject *watched, QEvent *event)
0241 {
0242     if (event->type() == QEvent::Expose) {
0243         auto *window = static_cast<QWindow *>(watched);
0244         if (window->isExposed()) {
0245             const QString handle = window->property(c_kdeXdgForeignPendingHandleProperty).toString();
0246             if (!handle.isEmpty()) {
0247                 doSetMainWindow(window, handle);
0248                 window->setProperty(c_kdeXdgForeignPendingHandleProperty, QVariant());
0249             }
0250 
0251             window->removeEventFilter(this);
0252         }
0253     }
0254 
0255     return QObject::eventFilter(watched, event);
0256 }
0257 
0258 void WindowSystem::doSetMainWindow(QWindow *window, const QString &handle)
0259 {
0260     Q_ASSERT(window);
0261     Q_ASSERT(!handle.isEmpty());
0262 
0263     auto waylandWindow = dynamic_cast<QtWaylandClient::QWaylandWindow *>(window->handle());
0264     if (!waylandWindow) {
0265         return;
0266     }
0267 
0268     auto &importer = WaylandXdgForeignImporterV2::self();
0269     if (!importer.isActive()) {
0270         return;
0271     }
0272 
0273     auto *waylandWindowQObject = static_cast<QObject *>(waylandWindow);
0274 
0275     Q_ASSERT(!waylandWindowQObject->property(c_kdeXdgForeignImportedProperty).isValid());
0276 
0277     WaylandXdgForeignImportedV2 *imported = importer.importToplevel(handle);
0278     imported->set_parent_of(surfaceForWindow(window)); // foreign parent.
0279     imported->setParent(waylandWindow); // memory owner.
0280 
0281     waylandWindowQObject->setProperty(c_kdeXdgForeignImportedProperty, QVariant::fromValue(imported));
0282     connect(imported, &QObject::destroyed, waylandWindow, [waylandWindowQObject] {
0283         waylandWindowQObject->setProperty(c_kdeXdgForeignImportedProperty, QVariant());
0284     });
0285 }