From d0b4e8a601bc2f81ddb23c124acc99defa26c02f Mon Sep 17 00:00:00 2001 From: Jan Grulich Date: Fri, 22 Mar 2024 12:12:51 +0100 Subject: [PATCH] QGtk3Theme: Add support for xdg-desktop-portal to get color scheme Use xdg-desktop-portal to get color scheme used by GNOME. Recent GNOME versions use this as primary way to switch between light and dark theme. Make this a preferred way to get color scheme and fallback to previous methods in case xdg-desktop-portal cannot be used. Also update app theme on runtime when color scheme changes, not only when theme is changed. [ChangeLog][Platform Specific Changes][Linux] Add support for xdg-desktop-portal to get color scheme in QGtk3Theme. Fixes: QTBUG-116197 Change-Id: Ib3ffad405bc795ed6f4de4af411efc45721662b9 Reviewed-by: Qt CI Bot Reviewed-by: Axel Spoerl Reviewed-by: Santhosh Kumar --- diff --git a/src/plugins/platformthemes/gtk3/CMakeLists.txt b/src/plugins/platformthemes/gtk3/CMakeLists.txt index becfccc..6d3c7bf 100644 --- a/src/plugins/platformthemes/gtk3/CMakeLists.txt +++ b/src/plugins/platformthemes/gtk3/CMakeLists.txt @@ -35,6 +35,13 @@ Qt::GuiPrivate ) +qt_internal_extend_target(QGtk3ThemePlugin CONDITION QT_FEATURE_dbus + SOURCES + qgtk3portalinterface.cpp + LIBRARIES + Qt::DBus +) + qt_internal_extend_target(QGtk3ThemePlugin CONDITION QT_FEATURE_xlib LIBRARIES X11::X11 diff --git a/src/plugins/platformthemes/gtk3/qgtk3portalinterface.cpp b/src/plugins/platformthemes/gtk3/qgtk3portalinterface.cpp new file mode 100644 index 0000000..1ffdda7 --- /dev/null +++ b/src/plugins/platformthemes/gtk3/qgtk3portalinterface.cpp @@ -0,0 +1,123 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgtk3portalinterface_p.h" +#include "qgtk3storage_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcQGtk3PortalInterface, "qt.qpa.gtk"); + +using namespace Qt::StringLiterals; + +static constexpr QLatin1StringView appearanceInterface("org.freedesktop.appearance"); +static constexpr QLatin1StringView colorSchemeKey("color-scheme"); + +const QDBusArgument &operator>>(const QDBusArgument &argument, QMap &map) +{ + argument.beginMap(); + map.clear(); + + while (!argument.atEnd()) { + QString key; + QVariantMap value; + argument.beginMapEntry(); + argument >> key >> value; + argument.endMapEntry(); + map.insert(key, value); + } + + argument.endMap(); + return argument; +} + +QGtk3PortalInterface::QGtk3PortalInterface(QGtk3Storage *s) + : m_storage(s) { + qRegisterMetaType(); + qDBusRegisterMetaType>(); + + queryColorScheme(); + + if (!s) { + qCDebug(lcQGtk3PortalInterface) << "QGtk3PortalInterface instantiated without QGtk3Storage." + << "No reaction to runtime theme changes."; + } +} + +Qt::ColorScheme QGtk3PortalInterface::colorScheme() const +{ + return m_colorScheme; +} + +void QGtk3PortalInterface::queryColorScheme() { + QDBusConnection connection = QDBusConnection::sessionBus(); + QDBusMessage message = QDBusMessage::createMethodCall( + "org.freedesktop.portal.Desktop"_L1, + "/org/freedesktop/portal/desktop"_L1, + "org.freedesktop.portal.Settings"_L1, "ReadAll"_L1); + message << QStringList{ appearanceInterface }; + + QDBusPendingCall pendingCall = connection.asyncCall(message); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this); + QObject::connect( + watcher, &QDBusPendingCallWatcher::finished, this, + [this](QDBusPendingCallWatcher *watcher) { + QDBusPendingReply> reply = *watcher; + if (reply.isValid()) { + QMap settings = reply.value(); + if (!settings.isEmpty()) { + settingChanged(appearanceInterface, colorSchemeKey, + QDBusVariant(settings.value(appearanceInterface).value(colorSchemeKey))); + } + } else { + qCDebug(lcQGtk3PortalInterface) << "Failed to query org.freedesktop.portal.Settings: " + << reply.error().message(); + } + watcher->deleteLater(); + }); + + QDBusConnection::sessionBus().connect( + "org.freedesktop.portal.Desktop"_L1, "/org/freedesktop/portal/desktop"_L1, + "org.freedesktop.portal.Settings"_L1, "SettingChanged"_L1, this, + SLOT(settingChanged(QString, QString, QDBusVariant))); +} + +void QGtk3PortalInterface::settingChanged(const QString &group, const QString &key, + const QDBusVariant &value) +{ + if (group == appearanceInterface && key == colorSchemeKey) { + const uint colorScheme = value.variant().toUInt(); + // From org.freedesktop.portal.Settings.xml + // "1" - Prefer dark appearance + Qt::ColorScheme newColorScheme = colorScheme == 1 ? Qt::ColorScheme::Dark : Qt::ColorScheme::Light; + if (m_colorScheme != newColorScheme) { + m_colorScheme = newColorScheme; + if (m_storage) + m_storage->handleThemeChange(); + } + } +} + +QT_END_NAMESPACE + +#include "moc_qgtk3portalinterface_p.cpp" diff --git a/src/plugins/platformthemes/gtk3/qgtk3portalinterface_p.h b/src/plugins/platformthemes/gtk3/qgtk3portalinterface_p.h new file mode 100644 index 0000000..25a5f58 --- /dev/null +++ b/src/plugins/platformthemes/gtk3/qgtk3portalinterface_p.h @@ -0,0 +1,49 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QGTK3PORTALINTERFACE_H +#define QGTK3PORTALINTERFACE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDBusVariant; +class QGtk3Storage; + +Q_DECLARE_LOGGING_CATEGORY(lcQGtk3PortalInterface); + +class QGtk3PortalInterface : public QObject +{ + Q_OBJECT +public: + QGtk3PortalInterface(QGtk3Storage *s); + ~QGtk3PortalInterface() = default; + + Qt::ColorScheme colorScheme() const; + +private Q_SLOTS: + void settingChanged(const QString &group, const QString &key, + const QDBusVariant &value); +private: + void queryColorScheme(); + + Qt::ColorScheme m_colorScheme = Qt::ColorScheme::Unknown; + QGtk3Storage *m_storage = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QGTK3PORTALINTERFACE_H diff --git a/src/plugins/platformthemes/gtk3/qgtk3storage.cpp b/src/plugins/platformthemes/gtk3/qgtk3storage.cpp index 90c0282..2877b28 100644 --- a/src/plugins/platformthemes/gtk3/qgtk3storage.cpp +++ b/src/plugins/platformthemes/gtk3/qgtk3storage.cpp @@ -21,6 +21,9 @@ QGtk3Storage::QGtk3Storage() { m_interface.reset(new QGtk3Interface(this)); +#if QT_CONFIG(dbus) + m_portalInterface.reset(new QGtk3PortalInterface(this)); +#endif populateMap(); } @@ -273,7 +276,6 @@ */ void QGtk3Storage::handleThemeChange() { - clear(); populateMap(); QWindowSystemInterface::handleThemeChange(); } @@ -331,21 +333,32 @@ static QString m_themeName; // Distiguish initialization, theme change or call without theme change + Qt::ColorScheme newColorScheme = Qt::ColorScheme::Unknown; const QString newThemeName = themeName(); - if (m_themeName == newThemeName) + +#if QT_CONFIG(dbus) + // Prefer color scheme we get from xdg-desktop-portal as this is what GNOME + // relies on these days + newColorScheme = m_portalInterface->colorScheme(); +#endif + + if (newColorScheme == Qt::ColorScheme::Unknown) { + // Derive color scheme from theme name + newColorScheme = newThemeName.contains("dark"_L1, Qt::CaseInsensitive) + ? Qt::ColorScheme::Dark : m_interface->colorSchemeByColors(); + } + + if (m_themeName == newThemeName && m_colorScheme == newColorScheme) return; clear(); - // Derive color scheme from theme name - m_colorScheme = newThemeName.contains("dark"_L1, Qt::CaseInsensitive) - ? Qt::ColorScheme::Dark : m_interface->colorSchemeByColors(); - if (m_themeName.isEmpty()) { - qCDebug(lcQGtk3Interface) << "GTK theme initialized:" << newThemeName << m_colorScheme; + qCDebug(lcQGtk3Interface) << "GTK theme initialized:" << newThemeName << newColorScheme; } else { - qCDebug(lcQGtk3Interface) << "GTK theme changed to:" << newThemeName << m_colorScheme; + qCDebug(lcQGtk3Interface) << "GTK theme changed to:" << newThemeName << newColorScheme; } + m_colorScheme = newColorScheme; m_themeName = newThemeName; // create standard mapping or load from Json file? diff --git a/src/plugins/platformthemes/gtk3/qgtk3storage_p.h b/src/plugins/platformthemes/gtk3/qgtk3storage_p.h index 37c5bf5..4519226 100644 --- a/src/plugins/platformthemes/gtk3/qgtk3storage_p.h +++ b/src/plugins/platformthemes/gtk3/qgtk3storage_p.h @@ -16,6 +16,9 @@ // #include "qgtk3interface_p.h" +#if QT_CONFIG(dbus) +#include "qgtk3portalinterface_p.h" +#endif #include #include @@ -205,7 +208,9 @@ PaletteMap m_palettes; std::unique_ptr m_interface; - +#if QT_CONFIG(dbus) + std::unique_ptr m_portalInterface; +#endif Qt::ColorScheme m_colorScheme = Qt::ColorScheme::Unknown;