QGnomeTheme, QGtk3Theme: Refactor and Simplify DBus Interactions

This patch refactors the DBus integration in both QGnomeTheme and
QGtk3Theme to centralize and simplify the portal and settings access
logic. Previously, the codebase contained duplicated and scattered DBus
logic for querying GNOME/GTK appearance settings, such as color scheme
and contrast, which were implemented separately in both QGnomeTheme and
QGtk3Theme.

The patch introduces a new QGnomePortalInterface class which
encapsulates all DBus interactions related to GNOME/GTK appearance
settings. The old DBus interface logic is removed from QGtk3Theme and
QGnomeTheme, and replaced with calls to the unified
QGnomePortalInterface. The update also ensures signal-based updates for
theme and appearance changes via the new interface.

Change-Id: I5440f7ac00f956b846b18bd890113af0044482f0
Reviewed-by: Oliver Eftevaag <oliver.eftevaag@qt.io>
Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
This commit is contained in:
MohammadHossein Qanbari 2025-04-23 16:04:55 +02:00
parent 933338f947
commit 2fe9eed3fd
16 changed files with 504 additions and 488 deletions

View File

@ -1059,6 +1059,11 @@ qt_internal_extend_target(Gui CONDITION UNIX AND (QT_FEATURE_xcb OR NOT MACOS) A
platform/unix/qgnometheme_p.h platform/unix/qgnometheme.cpp
)
qt_internal_extend_target(Gui CONDITION UNIX AND QT_FEATURE_dbus AND (QT_FEATURE_xcb OR QT_FEATURE_wayland)
SOURCES
platform/unix/qgnomeportalinterface.cpp platform/unix/qgnomeportalinterface_p.h
)
qt_internal_extend_target(Gui CONDITION TARGET Qt::DBus AND UNIX AND (QT_FEATURE_xcb OR NOT MACOS) AND (QT_FEATURE_xcb OR NOT UIKIT)
SOURCES
platform/unix/dbusmenu/qdbusmenuadaptor.cpp platform/unix/dbusmenu/qdbusmenuadaptor_p.h
@ -1068,6 +1073,7 @@ qt_internal_extend_target(Gui CONDITION TARGET Qt::DBus AND UNIX AND (QT_FEATURE
platform/unix/dbusmenu/qdbusmenutypes.cpp platform/unix/dbusmenu/qdbusmenutypes_p.h
platform/unix/dbusmenu/qdbusplatformmenu.cpp platform/unix/dbusmenu/qdbusplatformmenu_p.h
platform/unix/qdbuslistener_p.h platform/unix/qdbuslistener.cpp
platform/unix/qdbussettings_p.h platform/unix/qdbussettings.cpp
)
qt_internal_extend_target(Gui CONDITION QT_FEATURE_systemtrayicon AND TARGET Qt::DBus AND UNIX AND (QT_FEATURE_xcb OR NOT MACOS) AND (QT_FEATURE_xcb OR NOT UIKIT)

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qdbuslistener_p.h"
#include "qdbussettings_p.h"
#include <private/qguiapplication_p.h>
#include <qpa/qplatformintegration.h>
#include <qpa/qplatformservices.h>
@ -48,38 +49,6 @@ 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,
@ -237,14 +206,15 @@ void QDBusListener::populateSignalMap()
m_signalMap.insert(DBusKey("org.gnome.desktop.interface"_L1, "gtk-theme"_L1),
ChangeSignal(Provider::Gtk, Setting::Theme));
m_signalMap.insert(DBusKey("org.freedesktop.appearance"_L1, "color-scheme"_L1),
using namespace QDBusSettings;
m_signalMap.insert(DBusKey(XdgSettings::AppearanceNamespace, XdgSettings::ColorSchemeKey),
ChangeSignal(Provider::Gnome, Setting::ColorScheme));
m_signalMap.insert(DBusKey(XdgSettings::contrastNamespace, XdgSettings::contrastKey),
m_signalMap.insert(DBusKey(XdgSettings::AppearanceNamespace, 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),
m_signalMap.insert(DBusKey(GnomeSettings::AllyNamespace, GnomeSettings::ContrastKey),
ChangeSignal(Provider::Gnome, Setting::Contrast));
const QString &saveJsonFile = qEnvironmentVariable("QT_QPA_DBUS_SIGNALS_SAVE");
@ -274,13 +244,17 @@ void QDBusListener::onSettingChanged(const QString &location, const QString &key
QVariant settingValue = value.variant();
switch (setting) {
case Setting::ColorScheme:
settingValue.setValue(QDBusSettings::XdgSettings::convertColorScheme(settingValue));
break;
case Setting::Contrast:
using namespace QDBusSettings;
// 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)
if (key == XdgSettings::ContrastKey)
settingValue.setValue(XdgSettings::convertContrastPreference(settingValue));
else if (key == GSettings::contrastKey)
settingValue.setValue(GSettings::convertContrastPreference(settingValue));
else if (key == GnomeSettings::ContrastKey)
settingValue.setValue(GnomeSettings::convertContrastPreference(settingValue));
else
Q_UNREACHABLE_IMPL();
break;

View File

@ -0,0 +1,47 @@
// Copyright (C) 2025 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
#include "qdbussettings_p.h"
#include <QtCore/qvariant.h>
QT_BEGIN_NAMESPACE
namespace QDBusSettings::XdgSettings {
// https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Settings.html
enum class ColorScheme : uint { NoPreference, PreferDark, PreferLight };
} // namespace QDBusSettings::XdgSettings
Qt::ContrastPreference QDBusSettings::XdgSettings::convertContrastPreference(const QVariant &value)
{
// XDG portal provides the contrast preference value as uint:
// 0 for no-preference, and, 1 for high-contrast.
if (!value.isValid())
return Qt::ContrastPreference::NoPreference;
return static_cast<Qt::ContrastPreference>(value.toUInt());
}
Qt::ColorScheme QDBusSettings::XdgSettings::convertColorScheme(const QVariant &value)
{
switch (ColorScheme{ value.toUInt() }) {
case ColorScheme::NoPreference:
return Qt::ColorScheme::Unknown;
case ColorScheme::PreferDark:
return Qt::ColorScheme::Dark;
case ColorScheme::PreferLight:
return Qt::ColorScheme::Light;
}
Q_UNREACHABLE_RETURN(Qt::ColorScheme::Unknown);
}
Qt::ContrastPreference
QDBusSettings::GnomeSettings::convertContrastPreference(const QVariant &value)
{
// GSetting provides the contrast value as boolean:
// true for enabled high-contrast, and, false for disabled high-contrast.
if (!value.isValid())
return Qt::ContrastPreference::NoPreference;
return value.toBool() ? Qt::ContrastPreference::HighContrast
: Qt::ContrastPreference::NoPreference;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,46 @@
// Copyright (C) 2025 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 QDBUSSETTINGS_P_H
#define QDBUSSETTINGS_P_H
#include <QtCore/qnamespace.h>
#include <QtCore/qstring.h>
QT_REQUIRE_CONFIG(dbus);
//
// 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.
//
QT_BEGIN_NAMESPACE
namespace QDBusSettings {
// XDG Desktop Portal Settings (Preferred)
// https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Settings.html
namespace XdgSettings {
static constexpr QLatin1StringView AppearanceNamespace{ "org.freedesktop.appearance" };
static constexpr QLatin1StringView ColorSchemeKey{ "color-scheme" };
static constexpr QLatin1StringView ContrastKey{ "contrast" };
Qt::ContrastPreference convertContrastPreference(const QVariant &value);
Qt::ColorScheme convertColorScheme(const QVariant &value);
} // namespace XdgSettings
namespace GnomeSettings {
// GNOME Destop Settings (Alternative)
// https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2069
// https://gitlab.gnome.org/GNOME/gsettings-desktop-schemas/-/commit/0e97f1f571c495184f80d875c68f241261a50e30
static constexpr QLatin1StringView AllyNamespace{ "org.gnome.desktop.a11y.interface" };
static constexpr QLatin1StringView ContrastKey{ "high-contrast" };
Qt::ContrastPreference convertContrastPreference(const QVariant &value);
} // namespace GnomeSettings
} // namespace QDBusSettings
QT_END_NAMESPACE
#endif // QDBUSSETTINGS_P_H

View File

@ -0,0 +1,175 @@
// Copyright (C) 2025 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
#include "qgnomeportalinterface_p.h"
#include "qdbussettings_p.h"
#include <private/qdbusplatformmenu_p.h>
#include <QtDBus/QDBusMessage>
#include <QtDBus/QDBusPendingCall>
#include <QtDBus/QDBusReply>
#include <QtDBus/QDBusVariant>
QT_BEGIN_NAMESPACE
#if QT_CONFIG(dbus)
Q_STATIC_LOGGING_CATEGORY(lcQpaThemeGnome, "qt.qpa.theme.gnome")
#endif // QT_CONFIG(dbus)
using namespace Qt::StringLiterals;
QGnomePortalInterface::QGnomePortalInterface(QObject *parent)
: QObject(parent), m_dbus{ new QDBusListener }
{
QObject::connect(m_dbus.get(), &QDBusListener::settingChanged, this,
&QGnomePortalInterface::dbusSettingChanged);
querySettings();
}
/*!
\internal
\brief QGnomePortalInterface::colorScheme
This is a getter method for the color-scheme loaded from the DBus portal.
\param fallback indicates the fallback color-scheme.
\return returns the cached color-scheme. If the color-scheme was not loaded
it returns the \a fallback color-scheme.
*/
Qt::ColorScheme QGnomePortalInterface::colorScheme(Qt::ColorScheme fallback) const
{
if (!m_colorScheme.has_value())
return fallback;
return m_colorScheme.value();
}
/*!
\internal
\brief QGnomePortalInterface::contrastPreference
This is a getter method for the contrast-preference loaded from the DBus portal.
\param fallback indicates the fallback contrast-preference.
\return returns the cached contrast-preference if it was loaded. Otherwise,
returns the \a fallback contrast-preference.
*/
Qt::ContrastPreference
QGnomePortalInterface::contrastPreference(Qt::ContrastPreference fallback) const
{
if (!m_contrast.has_value())
return fallback;
return m_contrast.value();
}
void QGnomePortalInterface::querySettings()
{
QDBusConnection dbus = QDBusConnection::sessionBus();
if (!dbus.isConnected()) {
qCWarning(lcQpaThemeGnome) << "dbus connection failed. Last error: " << dbus.lastError();
return;
}
constexpr auto method = "ReadAll"_L1;
auto message = QDBusMessage::createMethodCall(s_service, s_path, s_interface, method);
message << QStringList{ QDBusSettings::XdgSettings::AppearanceNamespace,
QDBusSettings::GnomeSettings::AllyNamespace };
QDBusPendingCall pendingCall = dbus.asyncCall(message);
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this);
auto onWatcherFinished = [&](QDBusPendingCallWatcher *watcher) {
const QDBusMessage reply = watcher->reply();
if (QDBusMessage::ErrorMessage == reply.type()) {
qCWarning(lcQpaThemeGnome)
<< "dbus reply error: [" << reply.errorName() << "]" << reply.errorMessage();
watcher->deleteLater();
return;
}
const auto convertXdgColorScheme = [](const QVariant &value) {
using namespace QDBusSettings::XdgSettings;
return QVariant::fromValue(convertColorScheme(value));
};
constexpr auto XdgContrastKey = QDBusSettings::XdgSettings::ContrastKey;
const auto convertXdgContrast = [](const QVariant &value) {
using namespace QDBusSettings::XdgSettings;
return QVariant::fromValue(convertContrastPreference(value));
};
constexpr auto GnomeContrastKey = QDBusSettings::GnomeSettings::ContrastKey;
const auto convrtGnomeContrast = [](const QVariant &value) {
using namespace QDBusSettings::GnomeSettings;
return QVariant::fromValue(convertContrastPreference(value));
};
const QVariantList &args = reply.arguments();
for (const QVariant &arg_ : args) {
const QMap<QString, QVariantMap> arg = qdbus_cast<QMap<QString, QVariantMap>>(arg_);
for (auto aIt = arg.begin(); aIt != arg.end(); ++aIt) {
const QString &namespace_ = aIt.key();
const QVariantMap &settings = aIt.value();
for (auto sIt = settings.begin(); sIt != settings.end(); ++sIt) {
const QString &key = sIt.key();
const QVariant &value = sIt.value();
if ((key == QDBusSettings::XdgSettings::ColorSchemeKey)
&& (namespace_ == QDBusSettings::XdgSettings::AppearanceNamespace))
dbusSettingChanged(QDBusListener::Provider::Gnome,
QDBusListener::Setting::ColorScheme,
convertXdgColorScheme(value));
else if ((key == XdgContrastKey)
&& (namespace_ == QDBusSettings::XdgSettings::AppearanceNamespace))
dbusSettingChanged(QDBusListener::Provider::Gnome,
QDBusListener::Setting::Contrast,
convertXdgContrast(value));
else if ((key == GnomeContrastKey)
&& (namespace_ == QDBusSettings::GnomeSettings::AllyNamespace))
dbusSettingChanged(QDBusListener::Provider::Gnome,
QDBusListener::Setting::Contrast,
convrtGnomeContrast(value));
}
}
}
watcher->deleteLater();
};
connect(watcher, &QDBusPendingCallWatcher::finished, this, onWatcherFinished);
}
void QGnomePortalInterface::updateColorScheme(Qt::ColorScheme colorScheme)
{
if (m_colorScheme.has_value() && (m_colorScheme.value() == colorScheme))
return;
m_colorScheme = colorScheme;
emit colorSchemeChanged(colorScheme);
}
void QGnomePortalInterface::updateContrast(Qt::ContrastPreference contrast)
{
if (m_contrast.has_value() && (m_contrast.value() == contrast))
return;
m_contrast = contrast;
emit contrastChanged(contrast);
}
void QGnomePortalInterface::dbusSettingChanged(QDBusListener::Provider provider,
QDBusListener::Setting setting,
const QVariant &value)
{
if (provider != QDBusListener::Provider::Gnome && provider != QDBusListener::Provider::Gtk)
return;
switch (setting) {
case QDBusListener::Setting::ColorScheme:
updateColorScheme(value.value<Qt::ColorScheme>());
break;
case QDBusListener::Setting::Contrast:
updateContrast(value.value<Qt::ContrastPreference>());
break;
case QDBusListener::Setting::Theme:
emit themeNameChanged(value.toString());
break;
default:
break;
}
}
QT_END_NAMESPACE
#include "moc_qgnomeportalinterface_p.cpp"

View File

@ -0,0 +1,63 @@
// Copyright (C) 2025 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 QGNOMEPORTALINTERFACE_P_H
#define QGNOMEPORTALINTERFACE_P_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 <QtCore/qtconfigmacros.h>
QT_REQUIRE_CONFIG(dbus);
#include <QtCore/QObject>
#include <QtGui/private/qdbuslistener_p.h>
QT_BEGIN_NAMESPACE
class Q_GUI_EXPORT QGnomePortalInterface : public QObject
{
Q_OBJECT
public:
QGnomePortalInterface(QObject *parent = nullptr);
~QGnomePortalInterface() = default;
Qt::ColorScheme colorScheme(Qt::ColorScheme fallback = Qt::ColorScheme::Unknown) const;
Qt::ContrastPreference contrastPreference(
Qt::ContrastPreference fallback = Qt::ContrastPreference::NoPreference) const;
private:
void querySettings();
void updateColorScheme(Qt::ColorScheme colorScheme);
void updateContrast(Qt::ContrastPreference contrast);
Q_SIGNALS:
void colorSchemeChanged(Qt::ColorScheme);
void contrastChanged(Qt::ContrastPreference);
void themeNameChanged(const QString &themeName);
private Q_SLOTS:
void dbusSettingChanged(QDBusListener::Provider, QDBusListener::Setting, const QVariant &value);
private:
mutable uint m_version = 0; // cached version value
std::optional<Qt::ColorScheme> m_colorScheme;
std::optional<Qt::ContrastPreference> m_contrast;
std::unique_ptr<QDBusListener> m_dbus;
static constexpr QLatin1StringView s_service{ "org.freedesktop.portal.Desktop" };
static constexpr QLatin1StringView s_path{ "/org/freedesktop/portal/desktop" };
static constexpr QLatin1StringView s_interface{ "org.freedesktop.portal.Settings" };
};
QT_END_NAMESPACE
#endif // QGNOMEPORTALINTERFACE_P_H

View File

@ -5,163 +5,13 @@
#include <qpa/qplatformdialoghelper.h>
#include <qpa/qplatformfontdatabase.h>
#if QT_CONFIG(dbus)
#include "qdbuslistener_p.h"
#include <private/qdbustrayicon_p.h>
#include <private/qdbustrayicon_p.h>
#include <private/qdbusplatformmenu_p.h>
#include <private/qdbusmenubar_p.h>
#include <QtDBus/QDBusMessage>
#include <QtDBus/QDBusPendingCall>
#include <QtDBus/QDBusReply>
#include <QtDBus/QDBusVariant>
# include <private/qdbustrayicon_p.h>
# include <private/qdbusmenubar_p.h>
#endif
#include <qpa/qwindowsysteminterface.h>
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 };
constexpr Qt::ColorScheme convertColorScheme(XDG_ColorScheme colorScheme)
{
switch (colorScheme) {
case XDG_ColorScheme::NoPreference:
return Qt::ColorScheme::Unknown;
case XDG_ColorScheme::PreferDark:
return Qt::ColorScheme::Dark;
case XDG_ColorScheme::PreferLight:
return Qt::ColorScheme::Light;
default:
Q_UNREACHABLE_RETURN(Qt::ColorScheme::Unknown);
break;
}
}
constexpr XDG_ColorScheme convertColorScheme(Qt::ColorScheme colorScheme)
{
switch (colorScheme) {
case Qt::ColorScheme::Unknown:
return XDG_ColorScheme::NoPreference;
case Qt::ColorScheme::Light:
return XDG_ColorScheme::PreferLight;
case Qt::ColorScheme::Dark:
return XDG_ColorScheme::PreferDark;
default:
Q_UNREACHABLE_RETURN(XDG_ColorScheme::NoPreference);
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)
/*!
\class QGnomeTheme
\brief QGnomeTheme is a theme implementation for the Gnome desktop.
@ -174,13 +24,8 @@ const char *QGnomeTheme::name = "gnome";
QGnomeThemePrivate::QGnomeThemePrivate()
{
#if QT_CONFIG(dbus)
initDbus();
if (auto value = DBusInterface::queryColorScheme(); value.has_value())
updateColorScheme(value.value());
if (auto value = DBusInterface::queryContrast(); value.has_value())
updateHighContrast(value.value());
QObject::connect(&m_gnomePortal, &QGnomePortalInterface::themeNameChanged, &m_gnomePortal,
[this](const QString &themeName) { m_themeName = themeName; });
#endif // QT_CONFIG(dbus)
}
@ -205,43 +50,15 @@ void QGnomeThemePrivate::configureFonts(const QString &gtkFontName) const
qCDebug(lcQpaFonts) << "default fonts: system" << systemFont << "fixed" << fixedFont;
}
#if QT_CONFIG(dbus)
bool QGnomeThemePrivate::initDbus()
{
dbus.reset(new QDBusListener());
Q_ASSERT(dbus);
// Wrap slot in a lambda to avoid inheriting QGnomeThemePrivate from QObject
auto wrapper = [this](QDBusListener::Provider provider,
QDBusListener::Setting setting,
const QVariant &value) {
if (provider != QDBusListener::Provider::Gnome
&& provider != QDBusListener::Provider::Gtk) {
return;
}
switch (setting) {
case QDBusListener::Setting::ColorScheme:
updateColorScheme(convertColorScheme(XDG_ColorScheme{ value.toUInt() }));
break;
case QDBusListener::Setting::Theme:
m_themeName = value.toString();
break;
case QDBusListener::Setting::Contrast:
updateHighContrast(value.value<Qt::ContrastPreference>());
break;
default:
break;
}
};
return QObject::connect(dbus.get(), &QDBusListener::settingChanged, dbus.get(), wrapper);
}
Qt::ColorScheme QGnomeThemePrivate::colorScheme() const
{
if (m_colorScheme != Qt::ColorScheme::Unknown)
return m_colorScheme;
if (hasRequestedColorScheme())
return m_requestedColorScheme;
#if QT_CONFIG(dbus)
if (Qt::ColorScheme colorScheme = m_gnomePortal.colorScheme();
colorScheme != Qt::ColorScheme::Unknown)
return colorScheme;
// If the color scheme is set to Unknown by mistake or is not set at all,
// then maybe the theme name contains a hint about the color scheme.
@ -251,31 +68,31 @@ Qt::ColorScheme QGnomeThemePrivate::colorScheme() const
return Qt::ColorScheme::Light;
else if (m_themeName.contains(QLatin1StringView("dark"), Qt::CaseInsensitive))
return Qt::ColorScheme::Dark;
else
return Qt::ColorScheme::Unknown;
}
void QGnomeThemePrivate::updateColorScheme(Qt::ColorScheme colorScheme)
{
if (m_colorScheme == colorScheme)
return;
m_colorScheme = colorScheme;
QWindowSystemInterface::handleThemeChange();
}
void QGnomeThemePrivate::updateHighContrast(Qt::ContrastPreference contrast)
{
if (m_contrast == contrast)
return;
m_contrast = contrast;
QWindowSystemInterface::handleThemeChange();
}
#endif // QT_CONFIG(dbus)
// Fallback to Unknown if no color scheme is set or detected
return Qt::ColorScheme::Unknown;
}
bool QGnomeThemePrivate::hasRequestedColorScheme() const
{
return m_requestedColorScheme != Qt::ColorScheme::Unknown;
}
QGnomeTheme::QGnomeTheme()
: QGenericUnixTheme(new QGnomeThemePrivate())
{
#if QT_CONFIG(dbus)
Q_D(QGnomeTheme);
QGnomePortalInterface *portal = &d->m_gnomePortal;
QObject::connect(portal, &QGnomePortalInterface::colorSchemeChanged, portal,
[this](Qt::ColorScheme colorScheme) { updateColorScheme(colorScheme); });
QObject::connect(portal, &QGnomePortalInterface::contrastChanged, portal,
[this](Qt::ContrastPreference contrast) { updateHighContrast(contrast); });
#endif // QT_CONFIG(dbus)
}
QVariant QGnomeTheme::themeHint(QPlatformTheme::ThemeHint hint) const
@ -352,7 +169,38 @@ QString QGnomeTheme::gtkFontName() const
.arg(defaultSystemFontSize);
}
void QGnomeTheme::requestColorScheme(Qt::ColorScheme scheme)
{
Q_D(QGnomeTheme);
if (d->m_requestedColorScheme == scheme)
return;
QPlatformTheme::requestColorScheme(scheme);
d->m_requestedColorScheme = scheme;
QWindowSystemInterface::handleThemeChange();
}
Qt::ColorScheme QGnomeTheme::colorScheme() const
{
Q_D(const QGnomeTheme);
if (auto colorScheme = d->colorScheme(); colorScheme != Qt::ColorScheme::Unknown)
return colorScheme;
// If the color scheme is not set or detected, fall back to the default
return QPlatformTheme::colorScheme();
}
#if QT_CONFIG(dbus)
void QGnomeTheme::updateColorScheme(Qt::ColorScheme colorScheme)
{
Q_UNUSED(colorScheme);
QWindowSystemInterface::handleThemeChange();
}
void QGnomeTheme::updateHighContrast(Qt::ContrastPreference contrast)
{
Q_UNUSED(contrast);
QWindowSystemInterface::handleThemeChange();
}
QPlatformMenuBar *QGnomeTheme::createPlatformMenuBar() const
{
if (isDBusGlobalMenuAvailable())
@ -360,26 +208,21 @@ QPlatformMenuBar *QGnomeTheme::createPlatformMenuBar() const
return nullptr;
}
Qt::ColorScheme QGnomeTheme::colorScheme() const
{
return d_func()->colorScheme();
}
Qt::ContrastPreference QGnomeTheme::contrastPreference() const
{
return d_func()->m_contrast;
Q_D(const QGnomeTheme);
return d->m_gnomePortal.contrastPreference();
}
#endif
#if QT_CONFIG(dbus) && QT_CONFIG(systemtrayicon)
# if QT_CONFIG(systemtrayicon)
QPlatformSystemTrayIcon *QGnomeTheme::createPlatformSystemTrayIcon() const
{
if (shouldUseDBusTray())
return new QDBusTrayIcon();
return nullptr;
}
#endif
# endif // QT_CONFIG(systemtrayicon)
#endif // QT_CONFIG(dbus)
QString QGnomeTheme::standardButtonText(int button) const
{

View File

@ -19,17 +19,19 @@
#include <qpa/qplatformtheme.h>
#include <qpa/qplatformtheme_p.h>
#include <QtGui/QFont>
#if QT_CONFIG(dbus)
# include "qgnomeportalinterface_p.h"
#endif // QT_CONFIG(dbus)
QT_BEGIN_NAMESPACE
class QGnomeThemePrivate;
#if QT_CONFIG(dbus)
class QDBusListener;
class QDBusPendingCallWatcher;
#endif
class Q_GUI_EXPORT QGnomeTheme : public QGenericUnixTheme
{
protected:
Q_DECLARE_PRIVATE(QGnomeTheme)
public:
QGnomeTheme();
QVariant themeHint(ThemeHint hint) const override;
@ -39,47 +41,51 @@ public:
QString standardButtonText(int button) const override;
virtual QString gtkFontName() const;
virtual void requestColorScheme(Qt::ColorScheme) override;
virtual Qt::ColorScheme colorScheme() const override;
#if QT_CONFIG(dbus)
protected:
virtual void updateColorScheme(Qt::ColorScheme);
virtual void updateHighContrast(Qt::ContrastPreference);
public:
QPlatformMenuBar *createPlatformMenuBar() const override;
Qt::ColorScheme colorScheme() const override;
Qt::ContrastPreference contrastPreference() const override;
#endif
#if QT_CONFIG(dbus) && QT_CONFIG(systemtrayicon)
# if QT_CONFIG(systemtrayicon)
QPlatformSystemTrayIcon *createPlatformSystemTrayIcon() const override;
#endif
# endif // QT_CONFIG(systemtrayicon)
#endif // QT_CONFIG(dbus)
static const char *name;
};
class QGnomeThemePrivate : public QGenericUnixThemePrivate
class Q_GUI_EXPORT QGnomeThemePrivate : public QGenericUnixThemePrivate
{
friend QGnomeTheme;
public:
QGnomeThemePrivate();
~QGnomeThemePrivate();
void configureFonts(const QString &gtkFontName) const;
Qt::ColorScheme colorScheme() const;
bool hasRequestedColorScheme() const;
private:
mutable QFont *systemFont = nullptr;
mutable QFont *fixedFont = nullptr;
Qt::ColorScheme m_requestedColorScheme = Qt::ColorScheme::Unknown;
#if QT_CONFIG(dbus)
Qt::ColorScheme m_colorScheme = Qt::ColorScheme::Unknown;
Qt::ContrastPreference m_contrast = Qt::ContrastPreference::NoPreference;
private:
std::unique_ptr<QDBusListener> dbus;
std::unique_ptr<QDBusPendingCallWatcher> pendingCallWatcher;
QGnomePortalInterface m_gnomePortal;
QString m_themeName;
public:
Qt::ColorScheme colorScheme() const;
private:
bool initDbus();
void updateColorScheme(Qt::ColorScheme colorScheme);
void updateHighContrast(Qt::ContrastPreference contrast);
#endif // QT_CONFIG(dbus)
};
QT_END_NAMESPACE
#endif // QGNOMETHEME_P_H

View File

@ -130,7 +130,7 @@ void QKdeThemePrivate::settingChangedHandler(QDBusListener::Provider provider,
switch (setting) {
case QDBusListener::Setting::ColorScheme:
qCDebug(lcQpaThemeKde) << "KDE color theme changed to:" << value.toUInt();
qCDebug(lcQpaThemeKde) << "KDE color theme changed to:" << value.value<Qt::ColorScheme>();
break;
case QDBusListener::Setting::Theme:
qCDebug(lcQpaThemeKde) << "KDE global theme changed to:" << value.toString();

View File

@ -37,8 +37,6 @@ qt_internal_add_plugin(QGtk3ThemePlugin
)
qt_internal_extend_target(QGtk3ThemePlugin CONDITION QT_FEATURE_dbus
SOURCES
qgtk3portalinterface.cpp
LIBRARIES
Qt::DBus
)

View File

@ -1,123 +0,0 @@
// 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 <QtDBus/QDBusArgument>
#include <QtDBus/QDBusConnection>
#include <QtDBus/QDBusMessage>
#include <QtDBus/QDBusPendingCall>
#include <QtDBus/QDBusPendingCallWatcher>
#include <QtDBus/QDBusPendingReply>
#include <QtDBus/QDBusVariant>
#include <QtDBus/QtDBus>
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<QString, QVariantMap> &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<QDBusVariant>();
qDBusRegisterMetaType<QMap<QString, QVariantMap>>();
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<QMap<QString, QVariantMap>> reply = *watcher;
if (reply.isValid()) {
QMap<QString, QVariantMap> 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"

View File

@ -1,49 +0,0 @@
// 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 <QtCore/QObject>
#include <QtCore/QLoggingCategory>
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

View File

@ -15,6 +15,9 @@
#include "qgtk3json_p.h"
#include "qgtk3storage_p.h"
#include <qpa/qwindowsysteminterface.h>
#if QT_CONFIG(dbus)
# include <QtGui/private/qgnomeportalinterface_p.h>
#endif
QT_BEGIN_NAMESPACE
@ -22,11 +25,13 @@ QGtk3Storage::QGtk3Storage()
{
m_interface.reset(new QGtk3Interface(this));
#if QT_CONFIG(dbus)
m_portalInterface.reset(new QGtk3PortalInterface(this));
m_portalInterface.reset(new QGnomePortalInterface);
#endif
populateMap();
}
QGtk3Storage::~QGtk3Storage() { }
/*!
\internal
\enum QGtk3Storage::SourceType

View File

@ -16,9 +16,6 @@
//
#include "qgtk3interface_p.h"
#if QT_CONFIG(dbus)
#include "qgtk3portalinterface_p.h"
#endif
#include <QtCore/QJsonDocument>
#include <QtCore/QCache>
@ -30,11 +27,17 @@
#include <private/qflatmap_p.h>
QT_BEGIN_NAMESPACE
#if QT_CONFIG(dbus)
class QGnomePortalInterface;
#endif
class QGtk3Storage
{
Q_GADGET
public:
QGtk3Storage();
~QGtk3Storage();
// Enum documented in cpp file. Please keep it in line with updates made here.
enum class SourceType {
@ -242,7 +245,7 @@ private:
std::unique_ptr<QGtk3Interface> m_interface;
#if QT_CONFIG(dbus)
std::unique_ptr<QGtk3PortalInterface> m_portalInterface;
std::unique_ptr<QGnomePortalInterface> m_portalInterface;
#endif
Qt::ColorScheme m_colorScheme = Qt::ColorScheme::Unknown;

View File

@ -163,29 +163,34 @@ QString QGtk3Theme::gtkFontName() const
return QGnomeTheme::gtkFontName();
}
void QGtk3Theme::requestColorScheme(Qt::ColorScheme scheme)
{
if (m_requestedColorScheme == scheme)
return;
qCDebug(lcQGtk3Interface) << scheme << "has been requested. Theme supports color scheme:"
<< m_storage->colorScheme();
QPlatformTheme::requestColorScheme(scheme);
m_requestedColorScheme = scheme;
m_storage->handleThemeChange();
}
Qt::ColorScheme QGtk3Theme::colorScheme() const
{
Q_ASSERT(m_storage);
Q_D(const QGnomeTheme);
const Qt::ColorScheme colorScheme = d->colorScheme();
const bool hasRequestedColorScheme = d->hasRequestedColorScheme();
#ifdef QT_DEBUG
if (m_requestedColorScheme != Qt::ColorScheme::Unknown
&& m_requestedColorScheme != m_storage->colorScheme()) {
qCDebug(lcQGtk3Interface) << "Requested color scheme" << m_requestedColorScheme
if (hasRequestedColorScheme && colorScheme != m_storage->colorScheme()) {
qCDebug(lcQGtk3Interface) << "Requested color scheme" << colorScheme
<< "differs from theme color scheme" << m_storage->colorScheme();
}
#endif
return m_requestedColorScheme == Qt::ColorScheme::Unknown ? m_storage->colorScheme()
: m_requestedColorScheme;
return hasRequestedColorScheme ? colorScheme : m_storage->colorScheme();
}
void QGtk3Theme::requestColorScheme(Qt::ColorScheme scheme)
{
const Qt::ColorScheme oldColorScheme = colorScheme();
QGnomeTheme::requestColorScheme(scheme);
if (oldColorScheme == colorScheme())
return;
qCDebug(lcQGtk3Interface) << scheme << "has been requested. Theme supports color scheme:"
<< m_storage->colorScheme();
m_storage->handleThemeChange();
QWindowSystemInterface::sendWindowSystemEvents(QEventLoop::AllEvents);
}
bool QGtk3Theme::usePlatformNativeDialog(DialogType type) const
@ -234,18 +239,22 @@ bool QGtk3Theme::useNativeFileDialog()
const QPalette *QGtk3Theme::palette(Palette type) const
{
Q_ASSERT(m_storage);
Q_D(const QGnomeTheme);
const Qt::ColorScheme colorScheme = d->colorScheme();
const bool hasRequestedColorScheme = d->hasRequestedColorScheme();
#ifdef QT_DEBUG
if (m_requestedColorScheme != Qt::ColorScheme::Unknown
&& m_requestedColorScheme != m_storage->colorScheme()) {
if (hasRequestedColorScheme && colorScheme != m_storage->colorScheme()) {
qCDebug(lcQGtk3Interface) << "Current KDE theme doesn't support requested color scheme"
<< m_requestedColorScheme << "Falling back to fusion palette.";
<< colorScheme << "Falling back to fusion palette.";
return QPlatformTheme::palette(type);
}
#endif
return (m_requestedColorScheme != Qt::ColorScheme::Unknown
&& m_requestedColorScheme != m_storage->colorScheme())
? QPlatformTheme::palette(type) : m_storage->palette(type);
return (hasRequestedColorScheme && colorScheme != m_storage->colorScheme())
? QPlatformTheme::palette(type)
: m_storage->palette(type);
}
QPixmap QGtk3Theme::standardPixmap(StandardPixmap sp, const QSizeF &size) const
@ -268,4 +277,14 @@ QIcon QGtk3Theme::fileIcon(const QFileInfo &fileInfo,
return m_storage->fileIcon(fileInfo);
}
#if QT_CONFIG(dbus)
void QGtk3Theme::updateColorScheme(Qt::ColorScheme newColorScheme)
{
if (newColorScheme == colorScheme())
QGnomeTheme::updateColorScheme(newColorScheme);
else
m_storage->handleThemeChange();
}
#endif // QT_CONFIG(dbus)
QT_END_NAMESPACE

View File

@ -31,8 +31,11 @@ public:
QPlatformTheme::IconOptions iconOptions = { }) const override;
static const char *name;
private:
Qt::ColorScheme m_requestedColorScheme = Qt::ColorScheme::Unknown;
#if QT_CONFIG(dbus)
void updateColorScheme(Qt::ColorScheme) override;
#endif // QT_CONFIG(dbus)
static bool useNativeFileDialog();
std::unique_ptr<QGtk3Storage> m_storage;
};