File indexing completed on 2024-11-17 05:01:38

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         // QWaylandClientExtensionTemplate invokes this with a QueuedConnection
0031         QMetaObject::invokeMethod(this, "addRegistryListener");
0032     }
0033 };
0034 
0035 class AppMenu : public QtWayland::org_kde_kwin_appmenu
0036 {
0037 public:
0038     using org_kde_kwin_appmenu::org_kde_kwin_appmenu;
0039     ~AppMenu()
0040     {
0041         release();
0042     }
0043 };
0044 
0045 class ServerSideDecorationPaletteManager : public QWaylandClientExtensionTemplate<ServerSideDecorationPaletteManager>,
0046                                            public QtWayland::org_kde_kwin_server_decoration_palette_manager
0047 {
0048     Q_OBJECT
0049 public:
0050     ServerSideDecorationPaletteManager()
0051         : QWaylandClientExtensionTemplate<ServerSideDecorationPaletteManager>(1)
0052     {
0053         QMetaObject::invokeMethod(this, "addRegistryListener");
0054     }
0055     ~ServerSideDecorationPaletteManager() override
0056     {
0057         if (isActive()) {
0058             org_kde_kwin_server_decoration_palette_manager_destroy(object());
0059         }
0060     }
0061 };
0062 
0063 class ServerSideDecorationPalette : public QtWayland::org_kde_kwin_server_decoration_palette
0064 {
0065 public:
0066     using org_kde_kwin_server_decoration_palette::org_kde_kwin_server_decoration_palette;
0067     ~ServerSideDecorationPalette() override
0068     {
0069         release();
0070     }
0071 };
0072 
0073 Q_DECLARE_METATYPE(AppMenu *);
0074 Q_DECLARE_METATYPE(ServerSideDecorationPalette *);
0075 
0076 KWaylandIntegration::KWaylandIntegration(KdePlatformTheme *platformTheme)
0077     : QObject()
0078     , m_platformTheme(platformTheme)
0079 {
0080     QCoreApplication::instance()->installEventFilter(this);
0081 }
0082 
0083 KWaylandIntegration::~KWaylandIntegration() = default;
0084 
0085 bool KWaylandIntegration::isRelevantTopLevel(QWindow *w)
0086 {
0087     if (!w || w->parent()) {
0088         return false;
0089     }
0090 
0091     // ignore  windows that map to XdgPopup
0092     if (w->type() == Qt::ToolTip || w->type() == Qt::Popup) {
0093         return false;
0094     }
0095     return true;
0096 }
0097 
0098 bool KWaylandIntegration::eventFilter(QObject *watched, QEvent *event)
0099 {
0100     if (event->type() == QEvent::Expose) {
0101         auto ee = static_cast<QExposeEvent *>(event);
0102         if (ee->region().isNull()) {
0103             return false;
0104         }
0105         QWindow *w = qobject_cast<QWindow *>(watched);
0106         if (!isRelevantTopLevel(w)) {
0107             return false;
0108         }
0109         if (!w->isVisible()) {
0110             return false;
0111         }
0112 
0113         if (w->property("org.kde.plasma.integration.shellSurfaceCreated").isNull()) {
0114             shellSurfaceCreated(w);
0115         }
0116     } else if (event->type() == QEvent::Hide) {
0117         QWindow *w = qobject_cast<QWindow *>(watched);
0118         if (!isRelevantTopLevel(w)) {
0119             return false;
0120         }
0121         shellSurfaceDestroyed(w);
0122     } else if (event->type() == QEvent::ApplicationPaletteChange) {
0123         if (watched != QGuiApplication::instance()) {
0124             return false;
0125         }
0126         const auto topLevelWindows = QGuiApplication::topLevelWindows();
0127         for (QWindow *w : topLevelWindows) {
0128             if (isRelevantTopLevel(w)) {
0129                 installColorScheme(w);
0130             }
0131         }
0132     } else if (event->type() == QEvent::PlatformSurface) {
0133         QWindow *w = qobject_cast<QWindow *>(watched);
0134         QPlatformSurfaceEvent *pe = static_cast<QPlatformSurfaceEvent *>(event);
0135         if (w && !w->flags().testFlag(Qt::ForeignWindow) && pe->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated) {
0136             m_platformTheme->windowCreated(w);
0137         }
0138     }
0139 
0140     return false;
0141 }
0142 
0143 void KWaylandIntegration::shellSurfaceCreated(QWindow *w)
0144 {
0145     // set colorscheme hint
0146     if (qApp->property(s_schemePropertyName.constData()).isValid()) {
0147         installColorScheme(w);
0148     }
0149     const auto blurBehindProperty = w->property(s_blurBehindPropertyName.constData());
0150     if (blurBehindProperty.isValid()) {
0151         KWindowEffects::enableBlurBehind(w, blurBehindProperty.toBool());
0152     }
0153     // create deco
0154     wl_surface *s = surfaceFromWindow(w);
0155     if (!s) {
0156         return;
0157     }
0158 
0159     w->setProperty("org.kde.plasma.integration.shellSurfaceCreated", true);
0160 
0161 #ifndef KF6_TODO_DBUS_MENUBAR
0162     if (!m_appMenuManager) {
0163         m_appMenuManager.reset(new AppMenuManager());
0164     }
0165     if (m_appMenuManager->isActive()) {
0166         auto menu = new AppMenu(m_appMenuManager->create(s));
0167         w->setProperty("org.kde.plasma.integration.appmenu", QVariant::fromValue(menu));
0168         auto menuBar = QDBusMenuBar::menuBarForWindow(w);
0169         if (!menuBar) {
0170             menuBar = QDBusMenuBar::globalMenuBar();
0171         }
0172         if (menuBar) {
0173             menu->set_address(QDBusConnection::sessionBus().baseService(), menuBar->objectPath());
0174         }
0175     }
0176 #endif
0177 }
0178 
0179 void KWaylandIntegration::shellSurfaceDestroyed(QWindow *w)
0180 {
0181     w->setProperty("org.kde.plasma.integration.shellSurfaceCreated", QVariant());
0182 
0183     auto appMenu = w->property("org.kde.plasma.integration.appmenu").value<AppMenu *>();
0184     if (appMenu) {
0185         delete appMenu;
0186     }
0187     w->setProperty("org.kde.plasma.integration.appmenu", QVariant());
0188 
0189     auto decoPallete = w->property("org.kde.plasma.integration.palette").value<ServerSideDecorationPalette *>();
0190     if (decoPallete) {
0191         delete decoPallete;
0192     }
0193     w->setProperty("org.kde.plasma.integration.palette", QVariant());
0194 }
0195 
0196 void KWaylandIntegration::installColorScheme(QWindow *w)
0197 {
0198     if (!m_paletteManager) {
0199         m_paletteManager.reset(new ServerSideDecorationPaletteManager());
0200     }
0201     if (!m_paletteManager->isActive()) {
0202         return;
0203     }
0204     auto palette = w->property("org.kde.plasma.integration.palette").value<ServerSideDecorationPalette *>();
0205     if (!palette) {
0206         auto s = surfaceFromWindow(w);
0207         if (!s) {
0208             return;
0209         }
0210         palette = new ServerSideDecorationPalette(m_paletteManager->create(s));
0211         w->setProperty("org.kde.plasma.integration.palette", QVariant::fromValue(palette));
0212     }
0213     if (palette) {
0214         palette->set_palette(qApp->property(s_schemePropertyName.constData()).toString());
0215     }
0216 }
0217 
0218 void KWaylandIntegration::setAppMenu(QWindow *window, const QString &serviceName, const QString &objectPath)
0219 {
0220     auto menu = window->property("org.kde.plasma.integration.appmenu").value<AppMenu *>();
0221     if (menu) {
0222         menu->set_address(serviceName, objectPath);
0223     }
0224 }
0225 
0226 wl_surface *KWaylandIntegration::surfaceFromWindow(QWindow *window)
0227 {
0228     QPlatformNativeInterface *nativeInterface = qGuiApp->platformNativeInterface();
0229     if (!nativeInterface) {
0230         return nullptr;
0231     }
0232     return static_cast<wl_surface *>(nativeInterface->nativeResourceForWindow("surface", window));
0233 }
0234 
0235 #include "kwaylandintegration.moc"