diff options
author | MohammadHossein Qanbari <[email protected]> | 2025-04-21 20:38:44 +0200 |
---|---|---|
committer | MohammadHossein Qanbari <[email protected]> | 2025-05-30 03:25:04 +0200 |
commit | 933338f947554d89414c7bc5507415fe6b5c0460 (patch) | |
tree | f935bac598b1d930f538bdcbf87c294394e3f203 | |
parent | e9eba1ef0d6a4f39da1c58f93f000e0bbcf1c25f (diff) |
Dbus: Enhance process of determining color scheme and contrast
The XDG portal has changed some interfaces since version 2. We already
support the changes in version 2. The DBus listener and GNOME theme do
not work properly if the machine uses version 1. This patch ensures
that the color scheme and contrast preferences work fine with the older
version.
Regarding contrast preference, the XDG desktop portal introduces the
`contrast` interface in version 2. This patch uses the GNOME settings
as a fallback method to determine the contrast preference in case the
XDG contrast interface is not accessible. The contrast value retrieved
from the DBus varies depending on the provider.
Regarding the color-scheme interface, the only difference between XDG
portal versions 1 and 2 is the method name. The method name to read the
color-scheme value from the portal setting changed to `ReadOne` from
`Read`. This is also used for reading the contrast value.
Change-Id: Id4d81ecf5465bbbab64c41e431a8d4d86e63849a
Reviewed-by: Oliver Eftevaag <[email protected]>
-rw-r--r-- | src/gui/platform/unix/qdbuslistener.cpp | 59 | ||||
-rw-r--r-- | src/gui/platform/unix/qgnometheme.cpp | 143 | ||||
-rw-r--r-- | src/gui/platform/unix/qkdetheme.cpp | 4 |
3 files changed, 170 insertions, 36 deletions
diff --git a/src/gui/platform/unix/qdbuslistener.cpp b/src/gui/platform/unix/qdbuslistener.cpp index 3c8b6f741b2..374b3a0f8ec 100644 --- a/src/gui/platform/unix/qdbuslistener.cpp +++ b/src/gui/platform/unix/qdbuslistener.cpp @@ -48,8 +48,39 @@ constexpr auto setting() { return "Setting"_L1; } constexpr auto dbusSignals() { return "DbusSignals"_L1; } constexpr auto root() { return "Q_L1.qpa.DBusSignals"_L1; } } // namespace JsonKeys -} +namespace XdgSettings { +// XDG Desktop Portal Settings (Preferred) +// https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Settings.html +constexpr auto contrastNamespace = "org.freedesktop.appearance"_L1; +constexpr auto contrastKey = "contrast"_L1; +// XDG portal provides the contrast preference value as uint: +// 0 for no-preference, and, 1 for high-contrast. +Qt::ContrastPreference convertContrastPreference(const QVariant &value) +{ + if (!value.isValid()) + return Qt::ContrastPreference::NoPreference; + return static_cast<Qt::ContrastPreference>(value.toUInt()); +} +} // namespace XdgSettings + +namespace GSettings { +// GNOME Destop Settings (Alternative) +// https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2069 +// https://gitlab.gnome.org/GNOME/gsettings-desktop-schemas/-/commit/0e97f1f571c495184f80d875c68f241261a50e30 +constexpr auto contrastNamespace = "org.gnome.desktop.a11y.interface"_L1; +constexpr auto contrastKey = "high-contrast"_L1; +// GSetting provides the contrast value as boolean: +// true for enabled high-contrast, and, false for disabled high-contrast. +Qt::ContrastPreference convertContrastPreference(const QVariant &value) +{ + if (!value.isValid()) + return Qt::ContrastPreference::NoPreference; + return value.toBool() ? Qt::ContrastPreference::HighContrast + : Qt::ContrastPreference::NoPreference; +} +} // namespace GSettings +} // namespace void QDBusListener::init(const QString &service, const QString &path, const QString &interface, const QString &signal) @@ -209,7 +240,11 @@ void QDBusListener::populateSignalMap() m_signalMap.insert(DBusKey("org.freedesktop.appearance"_L1, "color-scheme"_L1), ChangeSignal(Provider::Gnome, Setting::ColorScheme)); - m_signalMap.insert(DBusKey("org.freedesktop.appearance"_L1, "contrast"_L1), + m_signalMap.insert(DBusKey(XdgSettings::contrastNamespace, XdgSettings::contrastKey), + ChangeSignal(Provider::Gnome, Setting::Contrast)); + // Alternative solution if XDG desktop portal setting is not accessible, + // e.g. when using the XDG portal version 1. + m_signalMap.insert(DBusKey(GSettings::contrastNamespace, GSettings::contrastKey), ChangeSignal(Provider::Gnome, Setting::Contrast)); const QString &saveJsonFile = qEnvironmentVariable("QT_QPA_DBUS_SIGNALS_SAVE"); @@ -235,6 +270,24 @@ void QDBusListener::onSettingChanged(const QString &location, const QString &key if (!sig.has_value()) return; - emit settingChanged(sig.value().provider, sig.value().setting, value.variant()); + const Setting setting = sig.value().setting; + QVariant settingValue = value.variant(); + + switch (setting) { + case Setting::Contrast: + // To unify the value, it's necessary to convert the DBus value to Qt::ContrastPreference. + // Then the users of the value don't need to parse the raw value. + if (key == XdgSettings::contrastKey) + settingValue.setValue(XdgSettings::convertContrastPreference(settingValue)); + else if (key == GSettings::contrastKey) + settingValue.setValue(GSettings::convertContrastPreference(settingValue)); + else + Q_UNREACHABLE_IMPL(); + break; + default: + break; + } + + emit settingChanged(sig.value().provider, setting, settingValue); } QT_END_NAMESPACE diff --git a/src/gui/platform/unix/qgnometheme.cpp b/src/gui/platform/unix/qgnometheme.cpp index 0f34003c0ef..389ea0238aa 100644 --- a/src/gui/platform/unix/qgnometheme.cpp +++ b/src/gui/platform/unix/qgnometheme.cpp @@ -13,6 +13,7 @@ #include <QtDBus/QDBusMessage> #include <QtDBus/QDBusPendingCall> #include <QtDBus/QDBusReply> +#include <QtDBus/QDBusVariant> #endif #include <qpa/qwindowsysteminterface.h> @@ -21,6 +22,8 @@ QT_BEGIN_NAMESPACE #if QT_CONFIG(dbus) Q_STATIC_LOGGING_CATEGORY(lcQpaThemeGnome, "qt.qpa.theme.gnome") +using namespace Qt::StringLiterals; + namespace { // https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Settings.html enum class XDG_ColorScheme : uint { NoPreference, PreferDark, PreferLight }; @@ -54,6 +57,107 @@ constexpr XDG_ColorScheme convertColorScheme(Qt::ColorScheme colorScheme) break; } } + +class DBusInterface +{ + DBusInterface() = delete; + + constexpr static auto Service = "org.freedesktop.portal.Desktop"_L1; + constexpr static auto Path = "/org/freedesktop/portal/desktop"_L1; + +public: + static inline QVariant query(QLatin1StringView interface, QLatin1StringView method, + QLatin1StringView name_space, QLatin1StringView key); + static inline uint queryPortalVersion(); + static inline QLatin1StringView readOneMethod(); + static inline std::optional<Qt::ColorScheme> queryColorScheme(); + static inline std::optional<Qt::ContrastPreference> queryContrast(); +}; + +QVariant DBusInterface::query(QLatin1StringView interface, QLatin1StringView method, + QLatin1StringView name_space, QLatin1StringView key) +{ + QDBusConnection dbus = QDBusConnection::sessionBus(); + if (dbus.isConnected()) { + QDBusMessage message = QDBusMessage::createMethodCall( + DBusInterface::Service, DBusInterface::Path, interface, method); + message << name_space << key; + + QDBusReply<QVariant> reply = dbus.call(message); + if (Q_LIKELY(reply.isValid())) + return reply.value(); + } else { + qCWarning(lcQpaThemeGnome) << "dbus connection failed. Last error: " << dbus.lastError(); + } + + return {}; +} + +uint DBusInterface::queryPortalVersion() +{ + constexpr auto interface = "org.freedesktop.DBus.Properties"_L1; + constexpr auto method = "Get"_L1; + constexpr auto name_space = "org.freedesktop.portal.Settings"_L1; + constexpr auto key = "version"_L1; + + static uint version = 0; // cached version value + + if (version == 0) { + QVariant reply = query(interface, method, name_space, key); + if (reply.isValid()) + version = reply.toUInt(); // caches the value for the next calls + } + + return version; +} + +QLatin1StringView DBusInterface::readOneMethod() +{ + // Based on the documentation on flatpak: + // https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Settings.html + // The method name "Read" has changed to "ReadOne" since version 2. + const uint version = queryPortalVersion(); + if (version == 1) + return "Read"_L1; + return "ReadOne"_L1; +} + +std::optional<Qt::ColorScheme> DBusInterface::queryColorScheme() +{ + constexpr auto interface = "org.freedesktop.portal.Settings"_L1; + constexpr auto name_space = "org.freedesktop.appearance"_L1; + constexpr auto key = "color-scheme"_L1; + const auto method = readOneMethod(); + + QVariant reply = query(interface, method, name_space, key); + if (reply.isValid()) + return convertColorScheme( + XDG_ColorScheme{ reply.value<QDBusVariant>().variant().toUInt() }); + + return {}; +} + +std::optional<Qt::ContrastPreference> DBusInterface::queryContrast() +{ + constexpr auto interface = "org.freedesktop.portal.Settings"_L1; + const auto method = readOneMethod(); + + constexpr auto namespace_xdg_portal = "org.freedesktop.appearance"_L1; + constexpr auto key_xdg_portal = "contrast"_L1; + QVariant reply = query(interface, method, namespace_xdg_portal, key_xdg_portal); + if (reply.isValid()) + return static_cast<Qt::ContrastPreference>(reply.toUInt()); + + // Fall back to desktop-specific methods (GSettings for GNOME) + constexpr auto namespace_gsettings = "org.gnome.desktop.a11y.interface"_L1; + constexpr auto key_gsettings = "high-contrast"_L1; + reply = query(interface, method, namespace_gsettings, key_gsettings); + if (reply.isValid()) + return reply.toBool() ? Qt::ContrastPreference::HighContrast + : Qt::ContrastPreference::NoPreference; + + return {}; +} } // namespace #endif // QT_CONFIG(dbus) @@ -70,41 +174,16 @@ const char *QGnomeTheme::name = "gnome"; QGnomeThemePrivate::QGnomeThemePrivate() { #if QT_CONFIG(dbus) - static constexpr QLatin1String appearanceNamespace("org.freedesktop.appearance"); - static constexpr QLatin1String colorSchemeKey("color-scheme"); - static constexpr QLatin1String contrastKey("contrast"); + initDbus(); - QDBusConnection dbus = QDBusConnection::sessionBus(); - if (dbus.isConnected()) { - // ReadAll appears to omit the contrast setting on Ubuntu. - QDBusMessage message = QDBusMessage::createMethodCall(QLatin1String("org.freedesktop.portal.Desktop"), - QLatin1String("/org/freedesktop/portal/desktop"), - QLatin1String("org.freedesktop.portal.Settings"), - QLatin1String("ReadOne")); - - message << appearanceNamespace << colorSchemeKey; - QDBusReply<QVariant> reply = dbus.call(message); - if (Q_LIKELY(reply.isValid())) { - m_colorScheme = convertColorScheme(XDG_ColorScheme{ reply.value().toUInt() }); - QWindowSystemInterface::handleThemeChange(); - } + if (auto value = DBusInterface::queryColorScheme(); value.has_value()) + updateColorScheme(value.value()); - message.setArguments({}); - message << appearanceNamespace << contrastKey; - pendingCallWatcher = std::make_unique<QDBusPendingCallWatcher>(dbus.asyncCall(message)); - QObject::connect(pendingCallWatcher.get(), &QDBusPendingCallWatcher::finished, pendingCallWatcher.get(), [this](QDBusPendingCallWatcher *watcher) { - if (!watcher->isError()) { - QDBusPendingReply<QVariant> reply = *watcher; - if (Q_LIKELY(reply.isValid())) - updateHighContrast(static_cast<Qt::ContrastPreference>(reply.value().toUInt())); - } - initDbus(); - }); - } else { - qCWarning(lcQpaThemeGnome) << "dbus connection failed. Last error: " << dbus.lastError(); - } + if (auto value = DBusInterface::queryContrast(); value.has_value()) + updateHighContrast(value.value()); #endif // QT_CONFIG(dbus) } + QGnomeThemePrivate::~QGnomeThemePrivate() { if (systemFont) @@ -149,7 +228,7 @@ bool QGnomeThemePrivate::initDbus() m_themeName = value.toString(); break; case QDBusListener::Setting::Contrast: - updateHighContrast(static_cast<Qt::ContrastPreference>(value.toUInt())); + updateHighContrast(value.value<Qt::ContrastPreference>()); break; default: break; diff --git a/src/gui/platform/unix/qkdetheme.cpp b/src/gui/platform/unix/qkdetheme.cpp index 9f91218612c..c3de0213780 100644 --- a/src/gui/platform/unix/qkdetheme.cpp +++ b/src/gui/platform/unix/qkdetheme.cpp @@ -139,7 +139,9 @@ void QKdeThemePrivate::settingChangedHandler(QDBusListener::Provider provider, qCDebug(lcQpaThemeKde) << "KDE application style changed to:" << value.toString(); break; case QDBusListener::Setting::Contrast: - qCDebug(lcQpaThemeKde) << "KDE contrast setting changed to: " << static_cast<Qt::ContrastPreference>(value.toUInt()); + qCDebug(lcQpaThemeKde) << "KDE contrast setting changed to: " + << value.value<Qt::ContrastPreference>(); + break; } refresh(); |