summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMohammadHossein Qanbari <[email protected]>2025-04-21 20:38:44 +0200
committerMohammadHossein Qanbari <[email protected]>2025-05-30 03:25:04 +0200
commit933338f947554d89414c7bc5507415fe6b5c0460 (patch)
treef935bac598b1d930f538bdcbf87c294394e3f203
parente9eba1ef0d6a4f39da1c58f93f000e0bbcf1c25f (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.cpp59
-rw-r--r--src/gui/platform/unix/qgnometheme.cpp143
-rw-r--r--src/gui/platform/unix/qkdetheme.cpp4
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();