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     SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006 */
0007 #include "x11integration.h"
0008 
0009 #include <NETWM>
0010 #include <QCoreApplication>
0011 #include <QGuiApplication>
0012 #include <QPlatformSurfaceEvent>
0013 #include <QWindow>
0014 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0015 #include <private/qtx11extras_p.h>
0016 #else
0017 #include <QX11Info>
0018 #endif
0019 
0020 #include <KWindowEffects>
0021 
0022 static const char s_schemePropertyName[] = "KDE_COLOR_SCHEME_PATH";
0023 static const QByteArray s_blurBehindPropertyName = QByteArrayLiteral("ENABLE_BLUR_BEHIND_HINT");
0024 
0025 X11Integration::X11Integration(KdePlatformTheme *platformTheme)
0026     : QObject()
0027     , m_platformTheme(platformTheme)
0028 {
0029 }
0030 
0031 X11Integration::~X11Integration() = default;
0032 
0033 void X11Integration::init()
0034 {
0035     QCoreApplication::instance()->installEventFilter(this);
0036 }
0037 
0038 bool X11Integration::eventFilter(QObject *watched, QEvent *event)
0039 {
0040     // the drag and drop window should NOT be a tooltip
0041     // https://bugreports.qt.io/browse/QTBUG-52560
0042     if (event->type() == QEvent::Show && watched->inherits("QShapedPixmapWindow")) {
0043         // static cast should be safe there
0044         QWindow *w = static_cast<QWindow *>(watched);
0045         NETWinInfo info(QX11Info::connection(), w->winId(), QX11Info::appRootWindow(), NET::WMWindowType, NET::Properties2());
0046         info.setWindowType(NET::DNDIcon);
0047         // TODO: does this flash the xcb connection?
0048     }
0049     if (event->type() == QEvent::PlatformSurface) {
0050         if (QWindow *w = qobject_cast<QWindow *>(watched)) {
0051             QPlatformSurfaceEvent *pe = static_cast<QPlatformSurfaceEvent *>(event);
0052             if (!w->flags().testFlag(Qt::ForeignWindow)) {
0053                 if (pe->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated) {
0054                     m_platformTheme->windowCreated(w);
0055                     auto flags = w->flags();
0056                     // A recent KWin change means it now follows WindowButtonHints on X11
0057                     // Some KDE applications use QDialogs for their main window,
0058                     // which means the KWin now surfaces those windows having the wrong hints.
0059                     // To avoid clients changing, we adjust flags here.
0060                     // This is documented as being only available on some platforms,
0061                     // so altering is relatively safe.
0062                     if (flags.testFlag(Qt::Dialog) && !flags.testFlag(Qt::CustomizeWindowHint)) {
0063                         if (!w->transientParent()) {
0064                             flags.setFlag(Qt::WindowCloseButtonHint);
0065                             flags.setFlag(Qt::WindowMinMaxButtonsHint);
0066                         }
0067                         w->setFlags(flags);
0068                     }
0069 
0070                     if (qApp->property(s_schemePropertyName).isValid()) {
0071                         installColorScheme(w);
0072                     }
0073                     const auto blurBehindProperty = w->property(s_blurBehindPropertyName.constData());
0074                     if (blurBehindProperty.isValid()) {
0075                         KWindowEffects::enableBlurBehind(w, blurBehindProperty.toBool());
0076                     }
0077                     installDesktopFileName(w);
0078                 }
0079             }
0080         }
0081     }
0082     if (event->type() == QEvent::ApplicationPaletteChange) {
0083         const auto topLevelWindows = QGuiApplication::topLevelWindows();
0084         for (QWindow *w : topLevelWindows) {
0085             installColorScheme(w);
0086         }
0087     }
0088     return false;
0089 }
0090 
0091 void X11Integration::installColorScheme(QWindow *w)
0092 {
0093     if (!w->isTopLevel() || !w->handle() /* e.g. WebEngine's QQuickWindow */) {
0094         return;
0095     }
0096     static xcb_atom_t atom = XCB_ATOM_NONE;
0097     xcb_connection_t *c = QX11Info::connection();
0098     if (atom == XCB_ATOM_NONE) {
0099         const QByteArray name = QByteArrayLiteral("_KDE_NET_WM_COLOR_SCHEME");
0100         const xcb_intern_atom_cookie_t cookie = xcb_intern_atom(c, false, name.length(), name.constData());
0101         QScopedPointer<xcb_intern_atom_reply_t, QScopedPointerPodDeleter> reply(xcb_intern_atom_reply(c, cookie, nullptr));
0102         if (!reply.isNull()) {
0103             atom = reply->atom;
0104         } else {
0105             // no point in continuing, we don't have the atom
0106             return;
0107         }
0108     }
0109     const QString path = qApp->property(s_schemePropertyName).toString();
0110     if (path.isEmpty()) {
0111         xcb_delete_property(c, w->winId(), atom);
0112     } else {
0113         xcb_change_property(c, XCB_PROP_MODE_REPLACE, w->winId(), atom, XCB_ATOM_STRING, 8, path.size(), qPrintable(path));
0114     }
0115 }
0116 
0117 void X11Integration::installDesktopFileName(QWindow *w)
0118 {
0119     if (!w->isTopLevel()) {
0120         return;
0121     }
0122 
0123     QString desktopFileName = QGuiApplication::desktopFileName();
0124     if (desktopFileName.isEmpty()) {
0125         return;
0126     }
0127     // handle apps which set the desktopFileName property with filename suffix,
0128     // due to unclear API dox (https://bugreports.qt.io/browse/QTBUG-75521)
0129     if (desktopFileName.endsWith(QLatin1String(".desktop"))) {
0130         desktopFileName.chop(8);
0131     }
0132     NETWinInfo info(QX11Info::connection(), w->winId(), QX11Info::appRootWindow(), NET::Properties(), NET::Properties2());
0133     info.setDesktopFileName(desktopFileName.toUtf8().constData());
0134 }
0135 
0136 void X11Integration::setWindowProperty(QWindow *window, const QByteArray &name, const QByteArray &value)
0137 {
0138     auto *c = QX11Info::connection();
0139 
0140     xcb_atom_t atom;
0141     auto it = m_atoms.find(name);
0142     if (it == m_atoms.end()) {
0143         const xcb_intern_atom_cookie_t cookie = xcb_intern_atom(c, false, name.length(), name.constData());
0144         QScopedPointer<xcb_intern_atom_reply_t, QScopedPointerPodDeleter> reply(xcb_intern_atom_reply(c, cookie, nullptr));
0145         if (!reply.isNull()) {
0146             atom = reply->atom;
0147             m_atoms[name] = atom;
0148         } else {
0149             return;
0150         }
0151     } else {
0152         atom = *it;
0153     }
0154 
0155     if (value.isEmpty()) {
0156         xcb_delete_property(c, window->winId(), atom);
0157     } else {
0158         xcb_change_property(c, XCB_PROP_MODE_REPLACE, window->winId(), atom, XCB_ATOM_STRING, 8, value.length(), value.constData());
0159     }
0160 }