File indexing completed on 2024-05-05 17:42:26

0001 /*  This file is part of the KDE libraries
0002     SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 #include "kwaylandintegration.h"
0007 
0008 #include <QExposeEvent>
0009 #include <QGuiApplication>
0010 #include <QWindow>
0011 #include <qpa/qplatformnativeinterface.h>
0012 #include <qtwaylandclientversion.h>
0013 
0014 #include "qdbusmenubar_p.h"
0015 #include "qwayland-appmenu.h"
0016 #include "qwayland-server-decoration-palette.h"
0017 
0018 #include <KWindowEffects>
0019 
0020 static const QByteArray s_schemePropertyName = QByteArrayLiteral("KDE_COLOR_SCHEME_PATH");
0021 static const QByteArray s_blurBehindPropertyName = QByteArrayLiteral("ENABLE_BLUR_BEHIND_HINT");
0022 
0023 class AppMenuManager : public QWaylandClientExtensionTemplate<AppMenuManager>, public QtWayland::org_kde_kwin_appmenu_manager
0024 {
0025     Q_OBJECT
0026 public:
0027     AppMenuManager()
0028         : QWaylandClientExtensionTemplate<AppMenuManager>(1)
0029     {
0030 #if QTWAYLANDCLIENT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
0031         initialize();
0032 #else
0033         // QWaylandClientExtensionTemplate invokes this with a QueuedConnection
0034         QMetaObject::invokeMethod(this, "addRegistryListener");
0035 #endif
0036     }
0037 };
0038 
0039 class ServerSideDecorationPaletteManager : public QWaylandClientExtensionTemplate<ServerSideDecorationPaletteManager>,
0040                                            public QtWayland::org_kde_kwin_server_decoration_palette_manager
0041 {
0042     Q_OBJECT
0043 public:
0044     ServerSideDecorationPaletteManager()
0045         : QWaylandClientExtensionTemplate<ServerSideDecorationPaletteManager>(1)
0046     {
0047 #if QTWAYLANDCLIENT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
0048         initialize();
0049 #else
0050         // QWaylandClientExtensionTemplate invokes this with a QueuedConnection
0051         QMetaObject::invokeMethod(this, "addRegistryListener");
0052 #endif
0053     }
0054 };
0055 
0056 using AppMenu = QtWayland::org_kde_kwin_appmenu;
0057 using ServerSideDecorationPalette = QtWayland::org_kde_kwin_server_decoration_palette;
0058 
0059 Q_DECLARE_METATYPE(AppMenu *);
0060 Q_DECLARE_METATYPE(ServerSideDecorationPalette *);
0061 
0062 KWaylandIntegration::KWaylandIntegration(KdePlatformTheme *platformTheme)
0063     : QObject()
0064     , m_appMenuManager(new AppMenuManager)
0065     , m_paletteManager(new ServerSideDecorationPaletteManager)
0066     , m_platformTheme(platformTheme)
0067 {
0068     QCoreApplication::instance()->installEventFilter(this);
0069 }
0070 
0071 KWaylandIntegration::~KWaylandIntegration() = default;
0072 
0073 bool KWaylandIntegration::isRelevantTopLevel(QWindow *w)
0074 {
0075     if (!w || w->parent()) {
0076         return false;
0077     }
0078 
0079     // ignore  windows that map to XdgPopup
0080     if (w->type() == Qt::ToolTip || w->type() == Qt::Popup) {
0081         return false;
0082     }
0083     return true;
0084 }
0085 
0086 bool KWaylandIntegration::eventFilter(QObject *watched, QEvent *event)
0087 {
0088     if (event->type() == QEvent::Expose) {
0089         auto ee = static_cast<QExposeEvent *>(event);
0090         if (ee->region().isNull()) {
0091             return false;
0092         }
0093         QWindow *w = qobject_cast<QWindow *>(watched);
0094         if (!isRelevantTopLevel(w)) {
0095             return false;
0096         }
0097         if (!w->isVisible()) {
0098             return false;
0099         }
0100 
0101         if (w->property("org.kde.plasma.integration.shellSurfaceCreated").isNull()) {
0102             shellSurfaceCreated(w);
0103         }
0104     } else if (event->type() == QEvent::Hide) {
0105         QWindow *w = qobject_cast<QWindow *>(watched);
0106         if (!isRelevantTopLevel(w)) {
0107             return false;
0108         }
0109         shellSurfaceDestroyed(w);
0110     } else if (event->type() == QEvent::ApplicationPaletteChange) {
0111         if (watched != QGuiApplication::instance()) {
0112             return false;
0113         }
0114         const auto topLevelWindows = QGuiApplication::topLevelWindows();
0115         for (QWindow *w : topLevelWindows) {
0116             if (isRelevantTopLevel(w)) {
0117                 installColorScheme(w);
0118             }
0119         }
0120     } else if (event->type() == QEvent::PlatformSurface) {
0121         if (QWindow *w = qobject_cast<QWindow *>(watched)) {
0122             QPlatformSurfaceEvent *pe = static_cast<QPlatformSurfaceEvent *>(event);
0123             if (!w->flags().testFlag(Qt::ForeignWindow)) {
0124                 if (pe->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated) {
0125                     m_platformTheme->windowCreated(w);
0126                 }
0127             }
0128         }
0129     }
0130 
0131     return false;
0132 }
0133 
0134 void KWaylandIntegration::shellSurfaceCreated(QWindow *w)
0135 {
0136     // set colorscheme hint
0137     if (qApp->property(s_schemePropertyName.constData()).isValid()) {
0138         installColorScheme(w);
0139     }
0140     const auto blurBehindProperty = w->property(s_blurBehindPropertyName.constData());
0141     if (blurBehindProperty.isValid()) {
0142         KWindowEffects::enableBlurBehind(w, blurBehindProperty.toBool());
0143     }
0144     // create deco
0145     wl_surface *s = surfaceFromWindow(w);
0146     if (!s) {
0147         return;
0148     }
0149 
0150     w->setProperty("org.kde.plasma.integration.shellSurfaceCreated", true);
0151 
0152 #ifndef KF6_TODO_DBUS_MENUBAR
0153     if (m_appMenuManager->isActive()) {
0154         auto menu = new AppMenu(m_appMenuManager->create(s));
0155         w->setProperty("org.kde.plasma.integration.appmenu", QVariant::fromValue(menu));
0156         auto menuBar = QDBusMenuBar::menuBarForWindow(w);
0157         if (!menuBar) {
0158             menuBar = QDBusMenuBar::globalMenuBar();
0159         }
0160         if (menuBar) {
0161             menu->set_address(QDBusConnection::sessionBus().baseService(), menuBar->objectPath());
0162         }
0163     }
0164 #endif
0165 }
0166 
0167 void KWaylandIntegration::shellSurfaceDestroyed(QWindow *w)
0168 {
0169     w->setProperty("org.kde.plasma.integration.shellSurfaceCreated", QVariant());
0170 
0171     auto appMenu = w->property("org.kde.plasma.integration.appmenu").value<AppMenu *>();
0172     if (appMenu) {
0173         appMenu->release();
0174         delete appMenu;
0175     }
0176     w->setProperty("org.kde.plasma.integration.appmenu", QVariant());
0177 
0178     auto decoPallete = w->property("org.kde.plasma.integration.palette").value<ServerSideDecorationPalette *>();
0179     if (decoPallete) {
0180         decoPallete->release();
0181         delete decoPallete;
0182     }
0183     w->setProperty("org.kde.plasma.integration.palette", QVariant());
0184 }
0185 
0186 void KWaylandIntegration::installColorScheme(QWindow *w)
0187 {
0188     if (!m_paletteManager->isActive()) {
0189         return;
0190     }
0191     auto palette = w->property("org.kde.plasma.integration.palette").value<ServerSideDecorationPalette *>();
0192     if (!palette) {
0193         auto s = surfaceFromWindow(w);
0194         if (!s) {
0195             return;
0196         }
0197         palette = new ServerSideDecorationPalette(m_paletteManager->create(s));
0198         w->setProperty("org.kde.plasma.integration.palette", QVariant::fromValue(palette));
0199     }
0200     if (palette) {
0201         palette->set_palette(qApp->property(s_schemePropertyName.constData()).toString());
0202     }
0203 }
0204 
0205 void KWaylandIntegration::setAppMenu(QWindow *window, const QString &serviceName, const QString &objectPath)
0206 {
0207     auto menu = window->property("org.kde.plasma.integration.appmenu").value<AppMenu *>();
0208     if (menu) {
0209         menu->set_address(serviceName, objectPath);
0210     }
0211 }
0212 
0213 wl_surface *KWaylandIntegration::surfaceFromWindow(QWindow *window)
0214 {
0215     QPlatformNativeInterface *nativeInterface = qGuiApp->platformNativeInterface();
0216     if (!nativeInterface) {
0217         return nullptr;
0218     }
0219     return static_cast<wl_surface *>(nativeInterface->nativeResourceForWindow("surface", window));
0220 }
0221 
0222 #include "kwaylandintegration.moc"