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

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