From 31755119282f199704f23f97eeb89a888fb0188d Mon Sep 17 00:00:00 2001 From: Jan Grulich Date: Mon, 13 May 2024 11:27:58 +0200 Subject: [PATCH] QGtk3Theme: Add support for xdg-desktop-portal to get color scheme Resolves: RHEL-36168 --- qt6-qtbase.spec | 7 +- ...-support-for-xdp-to-get-color-scheme.patch | 310 ++++++++++++++++++ 2 files changed, 316 insertions(+), 1 deletion(-) create mode 100644 qtbase-qgtk3theme-add-support-for-xdp-to-get-color-scheme.patch diff --git a/qt6-qtbase.spec b/qt6-qtbase.spec index a7f3fc1..0d5074d 100644 --- a/qt6-qtbase.spec +++ b/qt6-qtbase.spec @@ -47,7 +47,7 @@ BuildRequires: pkgconfig(libsystemd) Name: qt6-qtbase Summary: Qt6 - QtBase components Version: 6.7.0 -Release: 2%{?dist} +Release: 3%{?dist} License: LGPL-3.0-only OR GPL-3.0-only WITH Qt-GPL-exception-1.0 Url: http://qt-project.org/ @@ -107,6 +107,7 @@ Patch100: qtbase-use-qgnomeplatform-as-default-platform-theme-on-gnome.patch ## upstream patches Patch200: qtbase-use-ifdefs-instead-if-for-cpp-lib-span.patch +Patch201: qtbase-qgtk3theme-add-support-for-xdp-to-get-color-scheme.patch ## RHEL specific patches # Patch300: qtbase-fix-tests.patch @@ -877,6 +878,10 @@ make check -k ||: %changelog +* Mon May 13 2024 Jan Grulich - 6.7.0-3 +- QGtk3Theme: Add support for xdg-desktop-portal to get color scheme + Resolves: RHEL-36168 + * Wed Apr 24 2024 Jan Grulich - 6.7.0-2 - Use bundled double-conversion in RHEL builds Resolves: RHEL-32788 diff --git a/qtbase-qgtk3theme-add-support-for-xdp-to-get-color-scheme.patch b/qtbase-qgtk3theme-add-support-for-xdp-to-get-color-scheme.patch new file mode 100644 index 0000000..1011da7 --- /dev/null +++ b/qtbase-qgtk3theme-add-support-for-xdp-to-get-color-scheme.patch @@ -0,0 +1,310 @@ +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; +