summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAxel Spoerl <[email protected]>2025-02-24 21:33:14 +0100
committerAxel Spoerl <[email protected]>2025-03-11 10:02:39 +0100
commit53fb13456fffe8bfd192f9197c6d1703854b49a2 (patch)
treeb968e001e4aab8dc39063acfabda0005a42e5e8b
parent3bbc9e29ef59683351cf35c19a8bd4a030615c64 (diff)
Refactor qgeneericunixthemes_p.h/cpp
Header and implementation file of QGenericUnixTheme contained several helper functions and classes. It also contained the implementation of QGnomeTheme and QKdeTheme, both of which are not generic. Split qgenericunixthemes_p.h/cpp up into separate files. Group all helpers as static member functions in QGenericUnixTheme. Inherit QGnomeTheme and QKdeTheme from QGenericUnixTheme. Task-number: QTBUG-132929 Change-Id: Idfa6198a2b7f669edd009dc165375a9b2960fcad Reviewed-by: Tor Arne Vestbø <[email protected]> Reviewed-by: Piotr Wierciński <[email protected]>
-rw-r--r--src/gui/CMakeLists.txt9
-rw-r--r--src/gui/platform/unix/qdbuslistener.cpp233
-rw-r--r--src/gui/platform/unix/qdbuslistener_p.h88
-rw-r--r--src/gui/platform/unix/qgenericunixtheme.cpp297
-rw-r--r--src/gui/platform/unix/qgenericunixtheme_p.h85
-rw-r--r--src/gui/platform/unix/qgenericunixthemes_p.h123
-rw-r--r--src/gui/platform/unix/qgnometheme.cpp214
-rw-r--r--src/gui/platform/unix/qgnometheme_p.h74
-rw-r--r--src/gui/platform/unix/qkdetheme.cpp (renamed from src/gui/platform/unix/qgenericunixthemes.cpp)841
-rw-r--r--src/gui/platform/unix/qkdetheme_p.h68
-rw-r--r--src/plugins/platforms/eglfs/api/qeglfsintegration.cpp2
-rw-r--r--src/plugins/platforms/vkkhrdisplay/qvkkhrdisplayintegration.cpp2
-rw-r--r--src/plugins/platforms/xcb/qxcbintegration.cpp3
-rw-r--r--src/plugins/platformthemes/gtk3/qgtk3theme.h2
14 files changed, 1103 insertions, 938 deletions
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index dd54e567c98..0002c33ae5d 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -1042,9 +1042,16 @@ qt_internal_extend_target(Gui CONDITION TARGET Qt::DBus AND UNIX AND (QT_FEATURE
Qt::DBus
)
+qt_internal_extend_target(Gui CONDITION UNIX AND QT_FEATURE_settings AND QT_FEATURE_dbus AND (QT_FEATURE_xcb OR QT_FEATURE_wayland)
+ SOURCES
+ platform/unix/qkdetheme_p.h platform/unix/qkdetheme.cpp
+ platform/unix/qdbuslistener_p.h platform/unix/qdbuslistener.cpp
+)
+
qt_internal_extend_target(Gui CONDITION UNIX AND (QT_FEATURE_xcb OR NOT MACOS) AND (QT_FEATURE_xcb OR NOT UIKIT)
SOURCES
- platform/unix/qgenericunixthemes.cpp platform/unix/qgenericunixthemes_p.h
+ platform/unix/qgenericunixtheme.cpp platform/unix/qgenericunixtheme_p.h
+ platform/unix/qgnometheme_p.h platform/unix/qgnometheme.cpp
)
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)
diff --git a/src/gui/platform/unix/qdbuslistener.cpp b/src/gui/platform/unix/qdbuslistener.cpp
new file mode 100644
index 00000000000..b787b4f24b1
--- /dev/null
+++ b/src/gui/platform/unix/qdbuslistener.cpp
@@ -0,0 +1,233 @@
+// 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 "qdbuslistener_p.h"
+#include <private/qguiapplication_p.h>
+#include <qpa/qplatformintegration.h>
+#include <qpa/qplatformservices.h>
+#include <private/qdbustrayicon_p.h>
+
+QT_BEGIN_NAMESPACE
+using namespace Qt::StringLiterals;
+Q_STATIC_LOGGING_CATEGORY(lcQpaThemeDBus, "qt.qpa.theme.dbus")
+
+/*!
+ \internal
+ The QDBusListener class listens to the SettingChanged DBus signal
+ and translates it into combinations of the enums \c Provider and \c Setting.
+ Upon construction, it logs success/failure of the DBus connection.
+
+ The signal settingChanged delivers the normalized setting type and the new value as a string.
+ It is emitted on known setting types only.
+ */
+QDBusListener::QDBusListener(const QString &service,
+ const QString &path, const QString &interface, const QString &signal)
+{
+ init (service, path, interface, signal);
+}
+
+QDBusListener::QDBusListener()
+{
+ const auto service = u""_s;
+ const auto path = u"/org/freedesktop/portal/desktop"_s;
+ const auto interface = u"org.freedesktop.portal.Settings"_s;
+ const auto signal = u"SettingChanged"_s;
+
+ init (service, path, interface, signal);
+}
+
+namespace {
+namespace JsonKeys {
+constexpr auto dbusLocation() { return "DBusLocation"_L1; }
+constexpr auto dbusKey() { return "DBusKey"_L1; }
+constexpr auto provider() { return "Provider"_L1; }
+constexpr auto setting() { return "Setting"_L1; }
+constexpr auto dbusSignals() { return "DbusSignals"_L1; }
+constexpr auto root() { return "Q_L1.qpa.DBusSignals"_L1; }
+} // namespace JsonKeys
+}
+
+
+void QDBusListener::init(const QString &service, const QString &path,
+ const QString &interface, const QString &signal)
+{
+ QDBusConnection dbus = QDBusConnection::sessionBus();
+ const bool dBusRunning = dbus.isConnected();
+ bool dBusSignalConnected = false;
+#define LOG service << path << interface << signal;
+
+ if (dBusRunning) {
+ populateSignalMap();
+ qRegisterMetaType<QDBusVariant>();
+ dBusSignalConnected = dbus.connect(service, path, interface, signal, this,
+ SLOT(onSettingChanged(QString,QString,QDBusVariant)));
+ }
+
+ if (dBusSignalConnected) {
+ // Connection successful
+ qCDebug(lcQpaThemeDBus) << LOG;
+ } else {
+ if (dBusRunning) {
+ // DBus running, but connection failed
+ qCWarning(lcQpaThemeDBus) << "DBus connection failed:" << LOG;
+ } else {
+ // DBus not running
+ qCWarning(lcQpaThemeDBus) << "Session DBus not running.";
+ }
+ qCWarning(lcQpaThemeDBus) << "Application will not react to setting changes.\n"
+ << "Check your DBus installation.";
+ }
+#undef LOG
+}
+
+void QDBusListener::loadJson(const QString &fileName)
+{
+ Q_ASSERT(!fileName.isEmpty());
+#define CHECK(cond, warning)\
+ if (!cond) {\
+ qCWarning(lcQpaThemeDBus) << fileName << warning << "Falling back to default.";\
+ return;\
+ }
+
+#define PARSE(var, enumeration, string)\
+ enumeration var;\
+ {\
+ bool success;\
+ const int val = QMetaEnum::fromType<enumeration>().keyToValue(string.toLatin1(), &success);\
+ CHECK(success, "Parse Error: Invalid value" << string << "for" << #var);\
+ var = static_cast<enumeration>(val);\
+ }
+
+ QFile file(fileName);
+ CHECK(file.exists(), fileName << "doesn't exist.");
+ CHECK(file.open(QIODevice::ReadOnly), "could not be opened for reading.");
+
+ QJsonParseError error;
+ QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error);
+ CHECK((error.error == QJsonParseError::NoError), error.errorString());
+ CHECK(doc.isObject(), "Parse Error: Expected root object" << JsonKeys::root());
+
+ const QJsonObject &root = doc.object();
+ CHECK(root.contains(JsonKeys::root()), "Parse Error: Expectned root object" << JsonKeys::root());
+ CHECK(root[JsonKeys::root()][JsonKeys::dbusSignals()].isArray(), "Parse Error: Expected array" << JsonKeys::dbusSignals());
+
+ const QJsonArray &sigs = root[JsonKeys::root()][JsonKeys::dbusSignals()].toArray();
+ CHECK((sigs.count() > 0), "Parse Error: Found empty array" << JsonKeys::dbusSignals());
+
+ for (auto sig = sigs.constBegin(); sig != sigs.constEnd(); ++sig) {
+ CHECK(sig->isObject(), "Parse Error: Expected object array" << JsonKeys::dbusSignals());
+ const QJsonObject &obj = sig->toObject();
+ CHECK(obj.contains(JsonKeys::dbusLocation()), "Parse Error: Expected key" << JsonKeys::dbusLocation());
+ CHECK(obj.contains(JsonKeys::dbusKey()), "Parse Error: Expected key" << JsonKeys::dbusKey());
+ CHECK(obj.contains(JsonKeys::provider()), "Parse Error: Expected key" << JsonKeys::provider());
+ CHECK(obj.contains(JsonKeys::setting()), "Parse Error: Expected key" << JsonKeys::setting());
+ const QString &location = obj[JsonKeys::dbusLocation()].toString();
+ const QString &key = obj[JsonKeys::dbusKey()].toString();
+ const QString &providerString = obj[JsonKeys::provider()].toString();
+ const QString &settingString = obj[JsonKeys::setting()].toString();
+ PARSE(provider, Provider, providerString);
+ PARSE(setting, Setting, settingString);
+ const DBusKey dkey(location, key);
+ CHECK (!m_signalMap.contains(dkey), "Duplicate key" << location << key);
+ m_signalMap.insert(dkey, ChangeSignal(provider, setting));
+ }
+#undef PARSE
+#undef CHECK
+
+ if (m_signalMap.count() > 0)
+ qCInfo(lcQpaThemeDBus) << "Successfully imported" << fileName;
+ else
+ qCWarning(lcQpaThemeDBus) << "No data imported from" << fileName << "falling back to default.";
+
+#ifdef QT_DEBUG
+ const int count = m_signalMap.count();
+ if (count == 0)
+ return;
+
+ qCDebug(lcQpaThemeDBus) << "Listening to" << count << "signals:";
+ for (auto it = m_signalMap.constBegin(); it != m_signalMap.constEnd(); ++it) {
+ qDebug() << it.key().key << it.key().location << "mapped to"
+ << it.value().provider << it.value().setting;
+ }
+
+#endif
+}
+
+void QDBusListener::saveJson(const QString &fileName) const
+{
+ Q_ASSERT(!m_signalMap.isEmpty());
+ Q_ASSERT(!fileName.isEmpty());
+ QFile file(fileName);
+ if (!file.open(QIODevice::WriteOnly)) {
+ qCWarning(lcQpaThemeDBus) << fileName << "could not be opened for writing.";
+ return;
+ }
+
+ QJsonArray sigs;
+ for (auto sig = m_signalMap.constBegin(); sig != m_signalMap.constEnd(); ++sig) {
+ const DBusKey &dkey = sig.key();
+ const ChangeSignal &csig = sig.value();
+ QJsonObject obj;
+ obj[JsonKeys::dbusLocation()] = dkey.location;
+ obj[JsonKeys::dbusKey()] = dkey.key;
+ obj[JsonKeys::provider()] = QLatin1StringView(QMetaEnum::fromType<Provider>()
+ .valueToKey(static_cast<int>(csig.provider)));
+ obj[JsonKeys::setting()] = QLatin1StringView(QMetaEnum::fromType<Setting>()
+ .valueToKey(static_cast<int>(csig.setting)));
+ sigs.append(obj);
+ }
+ QJsonObject obj;
+ obj[JsonKeys::dbusSignals()] = sigs;
+ QJsonObject root;
+ root[JsonKeys::root()] = obj;
+ QJsonDocument doc(root);
+ file.write(doc.toJson());
+ file.close();
+}
+
+void QDBusListener::populateSignalMap()
+{
+ m_signalMap.clear();
+ const QString &loadJsonFile = qEnvironmentVariable("QT_QPA_DBUS_SIGNALS");
+ if (!loadJsonFile.isEmpty())
+ loadJson(loadJsonFile);
+ if (!m_signalMap.isEmpty())
+ return;
+
+ m_signalMap.insert(DBusKey("org.kde.kdeglobals.KDE"_L1, "widgetStyle"_L1),
+ ChangeSignal(Provider::Kde, Setting::ApplicationStyle));
+
+ m_signalMap.insert(DBusKey("org.kde.kdeglobals.General"_L1, "ColorScheme"_L1),
+ ChangeSignal(Provider::Kde, Setting::Theme));
+
+ 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),
+ ChangeSignal(Provider::Gnome, Setting::ColorScheme));
+
+ const QString &saveJsonFile = qEnvironmentVariable("QT_QPA_DBUS_SIGNALS_SAVE");
+ if (!saveJsonFile.isEmpty())
+ saveJson(saveJsonFile);
+}
+
+std::optional<QDBusListener::ChangeSignal>
+ QDBusListener::findSignal(const QString &location, const QString &key) const
+{
+ const DBusKey dkey(location, key);
+ std::optional<QDBusListener::ChangeSignal> ret;
+ if (m_signalMap.contains(dkey))
+ ret.emplace(m_signalMap.value(dkey));
+
+ return ret;
+}
+
+void QDBusListener::onSettingChanged(const QString &location, const QString &key, const QDBusVariant &value)
+{
+ auto sig = findSignal(location, key);
+ if (!sig.has_value())
+ return;
+
+ emit settingChanged(sig.value().provider, sig.value().setting, value.variant().toString());
+}
+QT_END_NAMESPACE
diff --git a/src/gui/platform/unix/qdbuslistener_p.h b/src/gui/platform/unix/qdbuslistener_p.h
new file mode 100644
index 00000000000..cadcab76c54
--- /dev/null
+++ b/src/gui/platform/unix/qdbuslistener_p.h
@@ -0,0 +1,88 @@
+// 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 QDBUSLISTENER_P_H
+#define QDBUSLISTENER_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 <qpa/qplatformtheme.h>
+#include <private/qflatmap_p.h>
+#include <QDBusVariant>
+
+QT_BEGIN_NAMESPACE
+
+class QDBusListener : public QObject
+{
+ Q_OBJECT
+
+public:
+
+ enum class Provider {
+ Kde,
+ Gtk,
+ Gnome,
+ };
+ Q_ENUM(Provider)
+
+ enum class Setting {
+ Theme,
+ ApplicationStyle,
+ ColorScheme,
+ };
+ Q_ENUM(Setting)
+
+ QDBusListener();
+ QDBusListener(const QString &service, const QString &path,
+ const QString &interface, const QString &signal);
+
+private Q_SLOTS:
+ void onSettingChanged(const QString &location, const QString &key, const QDBusVariant &value);
+
+Q_SIGNALS:
+ void settingChanged(QDBusListener::Provider provider,
+ QDBusListener::Setting setting,
+ const QString &value);
+
+private:
+ struct DBusKey
+ {
+ QString location;
+ QString key;
+ DBusKey(const QString &loc, const QString &k) : location(loc), key(k) {};
+ bool operator<(const DBusKey &other) const
+ {
+ return location + key < other.location + other.key;
+ }
+ };
+
+ struct ChangeSignal
+ {
+ Provider provider;
+ Setting setting;
+ ChangeSignal(Provider p, Setting s) : provider(p), setting(s) {}
+ ChangeSignal() {}
+ };
+
+ QFlatMap <DBusKey, ChangeSignal> m_signalMap;
+
+ void init(const QString &service, const QString &path,
+ const QString &interface, const QString &signal);
+
+ std::optional<ChangeSignal> findSignal(const QString &location, const QString &key) const;
+ void populateSignalMap();
+ void loadJson(const QString &fileName);
+ void saveJson(const QString &fileName) const;
+};
+
+QT_END_NAMESPACE
+#endif // QDBUSLISTENER_P_H
diff --git a/src/gui/platform/unix/qgenericunixtheme.cpp b/src/gui/platform/unix/qgenericunixtheme.cpp
new file mode 100644
index 00000000000..a3c5495bc26
--- /dev/null
+++ b/src/gui/platform/unix/qgenericunixtheme.cpp
@@ -0,0 +1,297 @@
+// Copyright (C) 2022 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 "qgenericunixtheme_p.h"
+#include "qgnometheme_p.h"
+
+#include <QPalette>
+#include <QFont>
+#include <QGuiApplication>
+#include <QDir>
+#include <QFileInfo>
+#include <QFile>
+#include <QDebug>
+#include <QHash>
+#include <QLoggingCategory>
+#include <QVariant>
+#include <QStandardPaths>
+#include <QStringList>
+#if QT_CONFIG(mimetype)
+#include <QMimeDatabase>
+#endif
+#if QT_CONFIG(settings)
+#include <QSettings>
+#ifndef QT_NO_DBUS
+#include "qkdetheme_p.h"
+#endif
+#endif
+
+#include <qpa/qplatformfontdatabase.h> // lcQpaFonts
+#include <qpa/qplatformintegration.h>
+#include <qpa/qplatformservices.h>
+#include <qpa/qplatformdialoghelper.h>
+#include <qpa/qplatformtheme_p.h>
+
+#ifndef QT_NO_DBUS
+#include <QJsonDocument>
+#include <QJsonArray>
+#include <QJsonObject>
+#include <QJsonValue>
+#include <QJsonParseError>
+#ifndef QT_NO_SYSTEMTRAYICON
+#include <private/qdbustrayicon_p.h>
+#include <private/qdbusmenubar_p.h>
+#endif
+#endif
+
+#include <private/qguiapplication_p.h>
+#include <qpa/qplatformintegration.h>
+#include <QtCore/QStandardPaths>
+#ifndef QT_NO_DBUS
+#include <private/qdbustrayicon_p.h>
+#endif
+#if QT_CONFIG(mimetype)
+#include <QtCore/QMimeDatabase>
+#include <QtCore/QMimeData>
+#endif
+
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(qLcTray)
+using namespace Qt::StringLiterals;
+
+const char *QGenericUnixTheme::name = "generic";
+
+QGenericUnixThemePrivate::QGenericUnixThemePrivate()
+ : QPlatformThemePrivate()
+ , systemFont(QLatin1StringView(QGenericUnixTheme::defaultSystemFontNameC),
+ QGenericUnixTheme::defaultSystemFontSize)
+ , fixedFont(QLatin1StringView(QGenericUnixTheme::defaultFixedFontNameC),
+ systemFont.pointSize())
+{
+ fixedFont.setStyleHint(QFont::TypeWriter);
+ qCDebug(lcQpaFonts) << "default fonts: system" << systemFont << "fixed" << fixedFont;
+}
+
+QGenericUnixTheme::QGenericUnixTheme(QGenericUnixThemePrivate *p)
+ : QPlatformTheme(p)
+{}
+
+QGenericUnixTheme::QGenericUnixTheme()
+ : QPlatformTheme(new QGenericUnixThemePrivate())
+{}
+
+const QFont *QGenericUnixTheme::font(Font type) const
+{
+ Q_D(const QGenericUnixTheme);
+ switch (type) {
+ case QPlatformTheme::SystemFont:
+ return &d->systemFont;
+ case QPlatformTheme::FixedFont:
+ return &d->fixedFont;
+ default:
+ return nullptr;
+ }
+}
+
+#ifndef QT_NO_DBUS
+QPlatformMenuBar *QGenericUnixTheme::createPlatformMenuBar() const
+{
+ if (isDBusGlobalMenuAvailable())
+ return new QDBusMenuBar();
+ return nullptr;
+}
+#endif
+
+#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
+QPlatformSystemTrayIcon *QGenericUnixTheme::createPlatformSystemTrayIcon() const
+{
+ if (shouldUseDBusTray())
+ return new QDBusTrayIcon();
+ return nullptr;
+}
+#endif
+
+QVariant QGenericUnixTheme::themeHint(ThemeHint hint) const
+{
+ switch (hint) {
+ case QPlatformTheme::SystemIconFallbackThemeName:
+ return QVariant(QString(QStringLiteral("hicolor")));
+ case QPlatformTheme::IconThemeSearchPaths:
+ return xdgIconThemePaths();
+ case QPlatformTheme::IconFallbackSearchPaths:
+ return iconFallbackPaths();
+ case QPlatformTheme::DialogButtonBoxButtonsHaveIcons:
+ return QVariant(true);
+ case QPlatformTheme::StyleNames: {
+ QStringList styleNames;
+ styleNames << QStringLiteral("Fusion") << QStringLiteral("Windows");
+ return QVariant(styleNames);
+ }
+ case QPlatformTheme::KeyboardScheme:
+ return QVariant(int(X11KeyboardScheme));
+ case QPlatformTheme::UiEffects:
+ return QVariant(int(HoverEffect));
+ case QPlatformTheme::MouseCursorTheme:
+ return QVariant(mouseCursorTheme());
+ case QPlatformTheme::MouseCursorSize:
+ return QVariant(mouseCursorSize());
+ case QPlatformTheme::PreferFileIconFromTheme:
+ return true;
+ default:
+ break;
+ }
+ return QPlatformTheme::themeHint(hint);
+}
+
+QStringList QGenericUnixTheme::themeNames()
+{
+ QStringList result;
+ if (QGuiApplication::desktopSettingsAware()) {
+ const QByteArray desktopEnvironment = QGuiApplicationPrivate::platformIntegration()->services()->desktopEnvironment();
+ QList<QByteArray> gtkBasedEnvironments;
+ gtkBasedEnvironments << "GNOME"
+ << "X-CINNAMON"
+ << "PANTHEON"
+ << "UNITY"
+ << "MATE"
+ << "XFCE"
+ << "LXDE";
+ const QList<QByteArray> desktopNames = desktopEnvironment.split(':');
+ for (const QByteArray &desktopName : desktopNames) {
+ if (desktopEnvironment == "KDE") {
+#if !defined QT_NO_DBUS && QT_CONFIG(settings)
+ result.push_back(QLatin1StringView(QKdeTheme::name));
+#endif
+ } else if (gtkBasedEnvironments.contains(desktopName)) {
+ // prefer the GTK3 theme implementation with native dialogs etc.
+ result.push_back(QStringLiteral("gtk3"));
+ // fallback to the generic Gnome theme if loading the GTK3 theme fails
+ result.push_back(QLatin1StringView(QGnomeTheme::name));
+ } else {
+ // unknown, but lowercase the name (our standard practice) and
+ // remove any "x-" prefix
+ QString s = QString::fromLatin1(desktopName.toLower());
+ result.push_back(s.startsWith("x-"_L1) ? s.mid(2) : s);
+ }
+ }
+ } // desktopSettingsAware
+ result.append(QLatin1StringView(QGenericUnixTheme::name));
+ return result;
+}
+
+/*!
+ \internal
+ \brief Creates a UNIX theme according to the given theme \a name
+*/
+QPlatformTheme *QGenericUnixTheme::createUnixTheme(const QString &name)
+{
+ if (name == QLatin1StringView(QGenericUnixTheme::name))
+ return new QGenericUnixTheme;
+#if !defined QT_NO_DBUS && QT_CONFIG(settings)
+ if (name == QLatin1StringView(QKdeTheme::name))
+ return QKdeTheme::createKdeTheme();
+#endif
+ if (name == QLatin1StringView(QGnomeTheme::name))
+ return new QGnomeTheme;
+ return nullptr;
+}
+
+// Helper to return the icon theme paths from XDG.
+QStringList QGenericUnixTheme::xdgIconThemePaths()
+{
+ QStringList paths;
+ // Add home directory first in search path
+ const QFileInfo homeIconDir(QDir::homePath() + "/.icons"_L1);
+ if (homeIconDir.isDir())
+ paths.prepend(homeIconDir.absoluteFilePath());
+
+ paths.append(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation,
+ QStringLiteral("icons"),
+ QStandardPaths::LocateDirectory));
+
+ return paths;
+}
+
+QStringList QGenericUnixTheme::iconFallbackPaths()
+{
+ QStringList paths;
+ const QFileInfo pixmapsIconsDir(QStringLiteral("/usr/share/pixmaps"));
+ if (pixmapsIconsDir.isDir())
+ paths.append(pixmapsIconsDir.absoluteFilePath());
+
+ return paths;
+}
+
+QString QGenericUnixTheme::mouseCursorTheme()
+{
+ static QString themeName = qEnvironmentVariable("XCURSOR_THEME");
+ return themeName;
+}
+
+QSize QGenericUnixTheme::mouseCursorSize()
+{
+ constexpr int defaultCursorSize = 24;
+ static const int xCursorSize = qEnvironmentVariableIntValue("XCURSOR_SIZE");
+ static const int s = xCursorSize > 0 ? xCursorSize : defaultCursorSize;
+ return QSize(s, s);
+}
+
+#ifndef QT_NO_DBUS
+static bool checkDBusGlobalMenuAvailable()
+{
+ const QDBusConnection connection = QDBusConnection::sessionBus();
+ static const QString registrarService = QStringLiteral("com.canonical.AppMenu.Registrar");
+ if (const auto iface = connection.interface())
+ return iface->isServiceRegistered(registrarService);
+ return false;
+}
+
+bool QGenericUnixTheme::isDBusGlobalMenuAvailable()
+{
+ static bool dbusGlobalMenuAvailable = checkDBusGlobalMenuAvailable();
+ return dbusGlobalMenuAvailable;
+}
+#endif
+
+#if QT_CONFIG(mimetype)
+QIcon QGenericUnixTheme::xdgFileIcon(const QFileInfo &fileInfo)
+{
+ QMimeDatabase mimeDatabase;
+ QMimeType mimeType = mimeDatabase.mimeTypeForFile(fileInfo);
+ if (!mimeType.isValid())
+ return QIcon();
+ const QString &iconName = mimeType.iconName();
+ if (!iconName.isEmpty()) {
+ const QIcon icon = QIcon::fromTheme(iconName);
+ if (!icon.isNull())
+ return icon;
+ }
+ const QString &genericIconName = mimeType.genericIconName();
+ return genericIconName.isEmpty() ? QIcon() : QIcon::fromTheme(genericIconName);
+}
+#endif
+
+
+#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
+bool QGenericUnixTheme::shouldUseDBusTray()
+{
+ // There's no other tray implementation to fallback to on non-X11
+ // and QDBusTrayIcon can register the icon on the fly after creation
+ if (QGuiApplication::platformName() != "xcb"_L1)
+ return true;
+ const bool result = QDBusMenuConnection().isWatcherRegistered();
+ qCDebug(qLcTray) << "D-Bus tray available:" << result;
+ return result;
+}
+#endif
+
+// Helper functions for implementing QPlatformTheme::fileIcon() for XDG icon themes.
+QList<QSize> QGenericUnixTheme::availableXdgFileIconSizes()
+{
+ return QIcon::fromTheme(QStringLiteral("inode-directory")).availableSizes();
+}
+
+
+QT_END_NAMESPACE
diff --git a/src/gui/platform/unix/qgenericunixtheme_p.h b/src/gui/platform/unix/qgenericunixtheme_p.h
new file mode 100644
index 00000000000..21be456c3a9
--- /dev/null
+++ b/src/gui/platform/unix/qgenericunixtheme_p.h
@@ -0,0 +1,85 @@
+// Copyright (C) 2020 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 QGENERICUNIXTHEMES_H
+#define QGENERICUNIXTHEMES_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 <qpa/qplatformtheme.h>
+#include <qpa/qplatformtheme_p.h>
+#include <QtCore/QString>
+#include <QtCore/QStringList>
+#include <QtGui/QFont>
+#include <QtCore/private/qglobal_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QGenericUnixTheme;
+class QGenericUnixThemePrivate : public QPlatformThemePrivate
+{
+public:
+ QGenericUnixThemePrivate();
+ const QFont systemFont;
+ QFont fixedFont;
+};
+
+class Q_GUI_EXPORT QGenericUnixTheme : public QPlatformTheme
+{
+ Q_DECLARE_PRIVATE(QGenericUnixTheme)
+
+protected:
+ QGenericUnixTheme(QGenericUnixThemePrivate *p);
+
+public:
+ QGenericUnixTheme();
+
+ const QFont *font(Font type) const override;
+ QVariant themeHint(ThemeHint hint) const override;
+
+#ifndef QT_NO_DBUS
+ QPlatformMenuBar *createPlatformMenuBar() const override;
+#endif
+#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
+ QPlatformSystemTrayIcon *createPlatformSystemTrayIcon() const override;
+#endif
+
+ static const char *name;
+
+ // Default system font, corresponding to the value returned by 4.8 for
+ // XRender/FontConfig which we can now assume as default.
+ static constexpr char defaultSystemFontNameC[] = "Sans Serif";
+ static constexpr char defaultFixedFontNameC[] = "monospace";
+ enum { defaultSystemFontSize = 9 };
+
+ // Helpers
+ static QStringList xdgIconThemePaths();
+ static QPlatformTheme *createUnixTheme(const QString &name);
+ static QStringList themeNames();
+protected:
+ static QStringList iconFallbackPaths();
+ static bool isDBusGlobalMenuAvailable();
+ static QString mouseCursorTheme();
+ static QSize mouseCursorSize();
+ static QList<QSize> availableXdgFileIconSizes();
+
+#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
+ static bool shouldUseDBusTray();
+#endif
+#if QT_CONFIG(mimetype)
+ static QIcon xdgFileIcon(const QFileInfo &fileInfo);
+#endif
+};
+
+QT_END_NAMESPACE
+
+#endif // QGENERICUNIXTHEMES_H
diff --git a/src/gui/platform/unix/qgenericunixthemes_p.h b/src/gui/platform/unix/qgenericunixthemes_p.h
deleted file mode 100644
index 63b20651e6b..00000000000
--- a/src/gui/platform/unix/qgenericunixthemes_p.h
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright (C) 2020 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 QGENERICUNIXTHEMES_H
-#define QGENERICUNIXTHEMES_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 <qpa/qplatformtheme.h>
-#include <QtCore/QString>
-#include <QtCore/QStringList>
-#include <QtGui/QFont>
-#include <QtCore/private/qglobal_p.h>
-
-QT_BEGIN_NAMESPACE
-
-class ResourceHelper
-{
-public:
- ResourceHelper();
- ~ResourceHelper() { clear(); }
-
- void clear();
-
- QPalette *palettes[QPlatformTheme::NPalettes];
- QFont *fonts[QPlatformTheme::NFonts];
-};
-
-class QGenericUnixThemePrivate;
-
-class Q_GUI_EXPORT QGenericUnixTheme : public QPlatformTheme
-{
- Q_DECLARE_PRIVATE(QGenericUnixTheme)
-public:
- QGenericUnixTheme();
-
- static QPlatformTheme *createUnixTheme(const QString &name);
- static QStringList themeNames();
-
- const QFont *font(Font type) const override;
- QVariant themeHint(ThemeHint hint) const override;
-
- static QStringList xdgIconThemePaths();
- static QStringList iconFallbackPaths();
-#ifndef QT_NO_DBUS
- QPlatformMenuBar *createPlatformMenuBar() const override;
-#endif
-#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
- QPlatformSystemTrayIcon *createPlatformSystemTrayIcon() const override;
-#endif
-
- static const char *name;
-};
-
-#if QT_CONFIG(settings)
-class QKdeThemePrivate;
-
-class QKdeTheme : public QPlatformTheme
-{
- Q_DECLARE_PRIVATE(QKdeTheme)
-public:
- QKdeTheme(const QStringList& kdeDirs, int kdeVersion);
-
- static QPlatformTheme *createKdeTheme();
- QVariant themeHint(ThemeHint hint) const override;
-
- QIcon fileIcon(const QFileInfo &fileInfo,
- QPlatformTheme::IconOptions iconOptions = { }) const override;
-
- const QPalette *palette(Palette type = SystemPalette) const override;
- Qt::ColorScheme colorScheme() const override;
-
- const QFont *font(Font type) const override;
-#ifndef QT_NO_DBUS
- QPlatformMenuBar *createPlatformMenuBar() const override;
-#endif
-#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
- QPlatformSystemTrayIcon *createPlatformSystemTrayIcon() const override;
-#endif
-
- static const char *name;
-};
-#endif // settings
-
-class QGnomeThemePrivate;
-
-class Q_GUI_EXPORT QGnomeTheme : public QPlatformTheme
-{
- Q_DECLARE_PRIVATE(QGnomeTheme)
-public:
- QGnomeTheme();
- QVariant themeHint(ThemeHint hint) const override;
- QIcon fileIcon(const QFileInfo &fileInfo,
- QPlatformTheme::IconOptions = { }) const override;
- const QFont *font(Font type) const override;
- QString standardButtonText(int button) const override;
-
- virtual QString gtkFontName() const;
-#ifndef QT_NO_DBUS
- QPlatformMenuBar *createPlatformMenuBar() const override;
- Qt::ColorScheme colorScheme() const override;
-#endif
-#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
- QPlatformSystemTrayIcon *createPlatformSystemTrayIcon() const override;
-#endif
-
- static const char *name;
-};
-
-QPlatformTheme *qt_createUnixTheme();
-
-QT_END_NAMESPACE
-
-#endif // QGENERICUNIXTHEMES_H
diff --git a/src/gui/platform/unix/qgnometheme.cpp b/src/gui/platform/unix/qgnometheme.cpp
new file mode 100644
index 00000000000..0ed01b5cf72
--- /dev/null
+++ b/src/gui/platform/unix/qgnometheme.cpp
@@ -0,0 +1,214 @@
+// 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 "qgnometheme_p.h"
+#include <qpa/qplatformdialoghelper.h>
+#include <qpa/qplatformfontdatabase.h>
+#ifndef QT_NO_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>
+#endif
+#include <qpa/qwindowsysteminterface.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QGnomeTheme
+ \brief QGnomeTheme is a theme implementation for the Gnome desktop.
+ \since 5.0
+ \internal
+ \ingroup qpa
+*/
+const char *QGnomeTheme::name = "gnome";
+
+QGnomeThemePrivate::QGnomeThemePrivate()
+{
+#ifndef QT_NO_DBUS
+ initDbus();
+#endif // QT_NO_DBUS
+}
+QGnomeThemePrivate::~QGnomeThemePrivate()
+{
+ if (systemFont)
+ delete systemFont;
+ if (fixedFont)
+ delete fixedFont;
+}
+
+void QGnomeThemePrivate::configureFonts(const QString &gtkFontName) const
+{
+ Q_ASSERT(!systemFont);
+ const int split = gtkFontName.lastIndexOf(QChar::Space);
+ float size = QStringView{gtkFontName}.mid(split + 1).toFloat();
+ QString fontName = gtkFontName.left(split);
+
+ systemFont = new QFont(fontName, size);
+ fixedFont = new QFont(QLatin1StringView(QGenericUnixTheme::defaultFixedFontNameC), systemFont->pointSize());
+ fixedFont->setStyleHint(QFont::TypeWriter);
+ qCDebug(lcQpaFonts) << "default fonts: system" << systemFont << "fixed" << fixedFont;
+}
+
+#ifndef QT_NO_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 QString &value) {
+ if (provider != QDBusListener::Provider::Gnome
+ && provider != QDBusListener::Provider::Gtk) {
+ return;
+ }
+
+ if (setting == QDBusListener::Setting::Theme)
+ updateColorScheme(value);
+ };
+
+ return QObject::connect(dbus.get(), &QDBusListener::settingChanged, dbus.get(), wrapper);
+}
+
+void QGnomeThemePrivate::updateColorScheme(const QString &themeName)
+{
+ const auto oldColorScheme = m_colorScheme;
+ if (themeName.contains(QLatin1StringView("light"), Qt::CaseInsensitive)) {
+ m_colorScheme = Qt::ColorScheme::Light;
+ } else if (themeName.contains(QLatin1StringView("dark"), Qt::CaseInsensitive)) {
+ m_colorScheme = Qt::ColorScheme::Dark;
+ } else {
+ m_colorScheme = Qt::ColorScheme::Unknown;
+ }
+
+ if (oldColorScheme != m_colorScheme)
+ QWindowSystemInterface::handleThemeChange();
+}
+#endif // QT_NO_DBUS
+
+QGnomeTheme::QGnomeTheme()
+ : QGenericUnixTheme(new QGnomeThemePrivate())
+{
+}
+
+QVariant QGnomeTheme::themeHint(QPlatformTheme::ThemeHint hint) const
+{
+ switch (hint) {
+ case QPlatformTheme::DialogButtonBoxButtonsHaveIcons:
+ return QVariant(true);
+ case QPlatformTheme::DialogButtonBoxLayout:
+ return QVariant(QPlatformDialogHelper::GnomeLayout);
+ case QPlatformTheme::SystemIconThemeName:
+ return QVariant(QStringLiteral("Adwaita"));
+ case QPlatformTheme::SystemIconFallbackThemeName:
+ return QVariant(QStringLiteral("gnome"));
+ case QPlatformTheme::IconThemeSearchPaths:
+ return QVariant(xdgIconThemePaths());
+ case QPlatformTheme::IconPixmapSizes:
+ return QVariant::fromValue(availableXdgFileIconSizes());
+ case QPlatformTheme::StyleNames: {
+ QStringList styleNames;
+ styleNames << QStringLiteral("Fusion") << QStringLiteral("windows");
+ return QVariant(styleNames);
+ }
+ case QPlatformTheme::KeyboardScheme:
+ return QVariant(int(GnomeKeyboardScheme));
+ case QPlatformTheme::PasswordMaskCharacter:
+ return QVariant(QChar(0x2022));
+ case QPlatformTheme::UiEffects:
+ return QVariant(int(HoverEffect));
+ case QPlatformTheme::ButtonPressKeys:
+ return QVariant::fromValue(
+ QList<Qt::Key>({ Qt::Key_Space, Qt::Key_Return, Qt::Key_Enter, Qt::Key_Select }));
+ case QPlatformTheme::PreselectFirstFileInDirectory:
+ return true;
+ case QPlatformTheme::MouseCursorTheme:
+ return QVariant(mouseCursorTheme());
+ case QPlatformTheme::MouseCursorSize:
+ return QVariant(mouseCursorSize());
+ case QPlatformTheme::PreferFileIconFromTheme:
+ return true;
+ default:
+ break;
+ }
+ return QPlatformTheme::themeHint(hint);
+}
+
+QIcon QGnomeTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions) const
+{
+#if QT_CONFIG(mimetype)
+ return xdgFileIcon(fileInfo);
+#else
+ Q_UNUSED(fileInfo);
+ return QIcon();
+#endif
+}
+
+const QFont *QGnomeTheme::font(Font type) const
+{
+ Q_D(const QGnomeTheme);
+ if (!d->systemFont)
+ d->configureFonts(gtkFontName());
+ switch (type) {
+ case QPlatformTheme::SystemFont:
+ return d->systemFont;
+ case QPlatformTheme::FixedFont:
+ return d->fixedFont;
+ default:
+ return nullptr;
+ }
+}
+
+QString QGnomeTheme::gtkFontName() const
+{
+ return QStringLiteral("%1 %2").arg(QLatin1StringView(defaultSystemFontNameC))
+ .arg(defaultSystemFontSize);
+}
+
+#ifndef QT_NO_DBUS
+QPlatformMenuBar *QGnomeTheme::createPlatformMenuBar() const
+{
+ if (isDBusGlobalMenuAvailable())
+ return new QDBusMenuBar();
+ return nullptr;
+}
+
+Qt::ColorScheme QGnomeTheme::colorScheme() const
+{
+ return d_func()->m_colorScheme;
+}
+
+#endif
+
+#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
+QPlatformSystemTrayIcon *QGnomeTheme::createPlatformSystemTrayIcon() const
+{
+ if (shouldUseDBusTray())
+ return new QDBusTrayIcon();
+ return nullptr;
+}
+#endif
+
+QString QGnomeTheme::standardButtonText(int button) const
+{
+ switch (button) {
+ case QPlatformDialogHelper::Ok:
+ return QCoreApplication::translate("QGnomeTheme", "&OK");
+ case QPlatformDialogHelper::Save:
+ return QCoreApplication::translate("QGnomeTheme", "&Save");
+ case QPlatformDialogHelper::Cancel:
+ return QCoreApplication::translate("QGnomeTheme", "&Cancel");
+ case QPlatformDialogHelper::Close:
+ return QCoreApplication::translate("QGnomeTheme", "&Close");
+ case QPlatformDialogHelper::Discard:
+ return QCoreApplication::translate("QGnomeTheme", "Close without Saving");
+ default:
+ break;
+ }
+ return QPlatformTheme::standardButtonText(button);
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/platform/unix/qgnometheme_p.h b/src/gui/platform/unix/qgnometheme_p.h
new file mode 100644
index 00000000000..310af45ecb5
--- /dev/null
+++ b/src/gui/platform/unix/qgnometheme_p.h
@@ -0,0 +1,74 @@
+// 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 QGNOMETHEME_P_H
+#define QGNOMETHEME_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 "qgenericunixtheme_p.h"
+#include <qpa/qplatformtheme.h>
+#include <qpa/qplatformtheme_p.h>
+#include <QtGui/QFont>
+
+QT_BEGIN_NAMESPACE
+class QGnomeThemePrivate;
+#ifndef QT_NO_DBUS
+class QDBusListener;
+#endif
+
+class Q_GUI_EXPORT QGnomeTheme : public QGenericUnixTheme
+{
+ Q_DECLARE_PRIVATE(QGnomeTheme)
+public:
+ QGnomeTheme();
+ QVariant themeHint(ThemeHint hint) const override;
+ QIcon fileIcon(const QFileInfo &fileInfo,
+ QPlatformTheme::IconOptions = { }) const override;
+ const QFont *font(Font type) const override;
+ QString standardButtonText(int button) const override;
+
+ virtual QString gtkFontName() const;
+#ifndef QT_NO_DBUS
+ QPlatformMenuBar *createPlatformMenuBar() const override;
+ Qt::ColorScheme colorScheme() const override;
+#endif
+#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
+ QPlatformSystemTrayIcon *createPlatformSystemTrayIcon() const override;
+#endif
+
+ static const char *name;
+};
+
+class QGnomeThemePrivate : public QGenericUnixThemePrivate
+{
+public:
+ QGnomeThemePrivate();
+ ~QGnomeThemePrivate();
+
+ void configureFonts(const QString &gtkFontName) const;
+
+ mutable QFont *systemFont = nullptr;
+ mutable QFont *fixedFont = nullptr;
+
+#ifndef QT_NO_DBUS
+ Qt::ColorScheme m_colorScheme = Qt::ColorScheme::Unknown;
+private:
+ std::unique_ptr<QDBusListener> dbus;
+ bool initDbus();
+ void updateColorScheme(const QString &themeName);
+#endif // QT_NO_DBUS
+};
+
+
+QT_END_NAMESPACE
+#endif // QGNOMETHEME_P_H
diff --git a/src/gui/platform/unix/qgenericunixthemes.cpp b/src/gui/platform/unix/qkdetheme.cpp
index 67fd1733377..9b3c27c4b5c 100644
--- a/src/gui/platform/unix/qgenericunixthemes.cpp
+++ b/src/gui/platform/unix/qkdetheme.cpp
@@ -1,59 +1,22 @@
-// Copyright (C) 2022 The Qt Company Ltd.
+// 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 "qgenericunixthemes_p.h"
-
-#include <QPalette>
-#include <QFont>
-#include <QGuiApplication>
-#include <QDir>
-#include <QFileInfo>
-#include <QFile>
-#include <QDebug>
-#include <QHash>
-#include <QLoggingCategory>
-#include <QVariant>
-#include <QStandardPaths>
-#include <QStringList>
-#if QT_CONFIG(mimetype)
-#include <QMimeDatabase>
-#endif
-#if QT_CONFIG(settings)
-#include <QSettings>
-#endif
-
-#include <qpa/qplatformfontdatabase.h> // lcQpaFonts
-#include <qpa/qplatformintegration.h>
-#include <qpa/qplatformservices.h>
-#include <qpa/qplatformdialoghelper.h>
+#include "qkdetheme_p.h"
#include <qpa/qplatformtheme_p.h>
-
-#include <private/qguiapplication_p.h>
-#ifndef QT_NO_DBUS
-#include <QDBusConnectionInterface>
+#include <qpa/qplatformfontdatabase.h>
+#include <qpa/qplatformdialoghelper.h>
+#include <QPalette>
+#include <qpa/qwindowsysteminterface.h>
+#include "qdbuslistener_p.h"
+#include <private/qdbustrayicon_p.h>
#include <private/qdbusplatformmenu_p.h>
#include <private/qdbusmenubar_p.h>
-#include <private/qflatmap_p.h>
-#include <QJsonDocument>
-#include <QJsonArray>
-#include <QJsonObject>
-#include <QJsonValue>
-#include <QJsonParseError>
-#endif
-#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
-#include <private/qdbustrayicon_p.h>
-#endif
-
-#include <algorithm>
QT_BEGIN_NAMESPACE
-#ifndef QT_NO_DBUS
-Q_STATIC_LOGGING_CATEGORY(lcQpaThemeDBus, "qt.qpa.theme.dbus")
-#endif
using namespace Qt::StringLiterals;
-Q_DECLARE_LOGGING_CATEGORY(qLcTray)
+Q_STATIC_LOGGING_CATEGORY(lcQpaThemeKde, "qt.qpa.theme.kde")
ResourceHelper::ResourceHelper()
{
@@ -69,477 +32,7 @@ void ResourceHelper::clear()
std::fill(fonts, fonts + QPlatformTheme::NFonts, static_cast<QFont *>(nullptr));
}
-const char *QGenericUnixTheme::name = "generic";
-
-// Default system font, corresponding to the value returned by 4.8 for
-// XRender/FontConfig which we can now assume as default.
-static const char defaultSystemFontNameC[] = "Sans Serif";
-static const char defaultFixedFontNameC[] = "monospace";
-enum { defaultSystemFontSize = 9 };
-
-#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
-static bool shouldUseDBusTray() {
- // There's no other tray implementation to fallback to on non-X11
- // and QDBusTrayIcon can register the icon on the fly after creation
- if (QGuiApplication::platformName() != "xcb"_L1)
- return true;
- const bool result = QDBusMenuConnection().isWatcherRegistered();
- qCDebug(qLcTray) << "D-Bus tray available:" << result;
- return result;
-}
-#endif
-
-static QString mouseCursorTheme()
-{
- static QString themeName = qEnvironmentVariable("XCURSOR_THEME");
- return themeName;
-}
-
-static QSize mouseCursorSize()
-{
- constexpr int defaultCursorSize = 24;
- static const int xCursorSize = qEnvironmentVariableIntValue("XCURSOR_SIZE");
- static const int s = xCursorSize > 0 ? xCursorSize : defaultCursorSize;
- return QSize(s, s);
-}
-
-#ifndef QT_NO_DBUS
-static bool checkDBusGlobalMenuAvailable()
-{
- const QDBusConnection connection = QDBusConnection::sessionBus();
- static const QString registrarService = QStringLiteral("com.canonical.AppMenu.Registrar");
- if (const auto iface = connection.interface())
- return iface->isServiceRegistered(registrarService);
- return false;
-}
-
-static bool isDBusGlobalMenuAvailable()
-{
- static bool dbusGlobalMenuAvailable = checkDBusGlobalMenuAvailable();
- return dbusGlobalMenuAvailable;
-}
-
-/*!
- * \internal
- * The QGenericUnixThemeDBusListener class listens to the SettingChanged DBus signal
- * and translates it into combinations of the enums \c Provider and \c Setting.
- * Upon construction, it logs success/failure of the DBus connection.
- *
- * The signal settingChanged delivers the normalized setting type and the new value as a string.
- * It is emitted on known setting types only.
- */
-
-class QGenericUnixThemeDBusListener : public QObject
-{
- Q_OBJECT
-
-public:
-
- enum class Provider {
- Kde,
- Gtk,
- Gnome,
- };
- Q_ENUM(Provider)
-
- enum class Setting {
- Theme,
- ApplicationStyle,
- ColorScheme,
- };
- Q_ENUM(Setting)
-
- QGenericUnixThemeDBusListener();
- QGenericUnixThemeDBusListener(const QString &service, const QString &path,
- const QString &interface, const QString &signal);
-
-private Q_SLOTS:
- void onSettingChanged(const QString &location, const QString &key, const QDBusVariant &value);
-
-Q_SIGNALS:
- void settingChanged(QGenericUnixThemeDBusListener::Provider provider,
- QGenericUnixThemeDBusListener::Setting setting,
- const QString &value);
-
-private:
- struct DBusKey
- {
- QString location;
- QString key;
- DBusKey(const QString &loc, const QString &k) : location(loc), key(k) {};
- bool operator<(const DBusKey &other) const
- {
- return location + key < other.location + other.key;
- }
- };
-
- struct ChangeSignal
- {
- Provider provider;
- Setting setting;
- ChangeSignal(Provider p, Setting s) : provider(p), setting(s) {}
- ChangeSignal() {}
- };
-
- QFlatMap <DBusKey, ChangeSignal> m_signalMap;
-
- void init(const QString &service, const QString &path,
- const QString &interface, const QString &signal);
-
- std::optional<ChangeSignal> findSignal(const QString &location, const QString &key) const;
- void populateSignalMap();
- void loadJson(const QString &fileName);
- void saveJson(const QString &fileName) const;
-};
-
-namespace {
-namespace JsonKeys {
-constexpr auto dbusLocation() { return "DBusLocation"_L1; }
-constexpr auto dbusKey() { return "DBusKey"_L1; }
-constexpr auto provider() { return "Provider"_L1; }
-constexpr auto setting() { return "Setting"_L1; }
-constexpr auto dbusSignals() { return "DbusSignals"_L1; }
-constexpr auto root() { return "Qt.qpa.DBusSignals"_L1; }
-} // namespace JsonKeys
-}
-
-QGenericUnixThemeDBusListener::QGenericUnixThemeDBusListener(const QString &service,
- const QString &path, const QString &interface, const QString &signal)
-{
- init (service, path, interface, signal);
-}
-
-QGenericUnixThemeDBusListener::QGenericUnixThemeDBusListener()
-{
- const auto service = u""_s;
- const auto path = u"/org/freedesktop/portal/desktop"_s;
- const auto interface = u"org.freedesktop.portal.Settings"_s;
- const auto signal = u"SettingChanged"_s;
-
- init (service, path, interface, signal);
-}
-
-void QGenericUnixThemeDBusListener::init(const QString &service, const QString &path,
- const QString &interface, const QString &signal)
-{
- QDBusConnection dbus = QDBusConnection::sessionBus();
- const bool dBusRunning = dbus.isConnected();
- bool dBusSignalConnected = false;
-#define LOG service << path << interface << signal;
-
- if (dBusRunning) {
- populateSignalMap();
- qRegisterMetaType<QDBusVariant>();
- dBusSignalConnected = dbus.connect(service, path, interface, signal, this,
- SLOT(onSettingChanged(QString,QString,QDBusVariant)));
- }
-
- if (dBusSignalConnected) {
- // Connection successful
- qCDebug(lcQpaThemeDBus) << LOG;
- } else {
- if (dBusRunning) {
- // DBus running, but connection failed
- qCWarning(lcQpaThemeDBus) << "DBus connection failed:" << LOG;
- } else {
- // DBus not running
- qCWarning(lcQpaThemeDBus) << "Session DBus not running.";
- }
- qCWarning(lcQpaThemeDBus) << "Application will not react to setting changes.\n"
- << "Check your DBus installation.";
- }
-#undef LOG
-}
-
-void QGenericUnixThemeDBusListener::loadJson(const QString &fileName)
-{
- Q_ASSERT(!fileName.isEmpty());
-#define CHECK(cond, warning)\
- if (!cond) {\
- qCWarning(lcQpaThemeDBus) << fileName << warning << "Falling back to default.";\
- return;\
- }
-
-#define PARSE(var, enumeration, string)\
- enumeration var;\
- {\
- bool success;\
- const int val = QMetaEnum::fromType<enumeration>().keyToValue(string.toLatin1(), &success);\
- CHECK(success, "Parse Error: Invalid value" << string << "for" << #var);\
- var = static_cast<enumeration>(val);\
- }
-
- QFile file(fileName);
- CHECK(file.exists(), fileName << "doesn't exist.");
- CHECK(file.open(QIODevice::ReadOnly), "could not be opened for reading.");
-
- QJsonParseError error;
- QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error);
- CHECK((error.error == QJsonParseError::NoError), error.errorString());
- CHECK(doc.isObject(), "Parse Error: Expected root object" << JsonKeys::root());
-
- const QJsonObject &root = doc.object();
- CHECK(root.contains(JsonKeys::root()), "Parse Error: Expected root object" << JsonKeys::root());
- CHECK(root[JsonKeys::root()][JsonKeys::dbusSignals()].isArray(), "Parse Error: Expected array" << JsonKeys::dbusSignals());
-
- const QJsonArray &sigs = root[JsonKeys::root()][JsonKeys::dbusSignals()].toArray();
- CHECK((sigs.count() > 0), "Parse Error: Found empty array" << JsonKeys::dbusSignals());
-
- for (auto sig = sigs.constBegin(); sig != sigs.constEnd(); ++sig) {
- CHECK(sig->isObject(), "Parse Error: Expected object array" << JsonKeys::dbusSignals());
- const QJsonObject &obj = sig->toObject();
- CHECK(obj.contains(JsonKeys::dbusLocation()), "Parse Error: Expected key" << JsonKeys::dbusLocation());
- CHECK(obj.contains(JsonKeys::dbusKey()), "Parse Error: Expected key" << JsonKeys::dbusKey());
- CHECK(obj.contains(JsonKeys::provider()), "Parse Error: Expected key" << JsonKeys::provider());
- CHECK(obj.contains(JsonKeys::setting()), "Parse Error: Expected key" << JsonKeys::setting());
- const QString &location = obj[JsonKeys::dbusLocation()].toString();
- const QString &key = obj[JsonKeys::dbusKey()].toString();
- const QString &providerString = obj[JsonKeys::provider()].toString();
- const QString &settingString = obj[JsonKeys::setting()].toString();
- PARSE(provider, Provider, providerString);
- PARSE(setting, Setting, settingString);
- const DBusKey dkey(location, key);
- CHECK (!m_signalMap.contains(dkey), "Duplicate key" << location << key);
- m_signalMap.insert(dkey, ChangeSignal(provider, setting));
- }
-#undef PARSE
-#undef CHECK
-
- if (m_signalMap.count() > 0)
- qCInfo(lcQpaThemeDBus) << "Successfully imported" << fileName;
- else
- qCWarning(lcQpaThemeDBus) << "No data imported from" << fileName << "falling back to default.";
-
-#ifdef QT_DEBUG
- const int count = m_signalMap.count();
- if (count == 0)
- return;
-
- qCDebug(lcQpaThemeDBus) << "Listening to" << count << "signals:";
- for (auto it = m_signalMap.constBegin(); it != m_signalMap.constEnd(); ++it) {
- qDebug() << it.key().key << it.key().location << "mapped to"
- << it.value().provider << it.value().setting;
- }
-
-#endif
-}
-
-void QGenericUnixThemeDBusListener::saveJson(const QString &fileName) const
-{
- Q_ASSERT(!m_signalMap.isEmpty());
- Q_ASSERT(!fileName.isEmpty());
- QFile file(fileName);
- if (!file.open(QIODevice::WriteOnly)) {
- qCWarning(lcQpaThemeDBus) << fileName << "could not be opened for writing.";
- return;
- }
-
- QJsonArray sigs;
- for (auto sig = m_signalMap.constBegin(); sig != m_signalMap.constEnd(); ++sig) {
- const DBusKey &dkey = sig.key();
- const ChangeSignal &csig = sig.value();
- QJsonObject obj;
- obj[JsonKeys::dbusLocation()] = dkey.location;
- obj[JsonKeys::dbusKey()] = dkey.key;
- obj[JsonKeys::provider()] = QLatin1StringView(QMetaEnum::fromType<Provider>()
- .valueToKey(static_cast<int>(csig.provider)));
- obj[JsonKeys::setting()] = QLatin1StringView(QMetaEnum::fromType<Setting>()
- .valueToKey(static_cast<int>(csig.setting)));
- sigs.append(obj);
- }
- QJsonObject obj;
- obj[JsonKeys::dbusSignals()] = sigs;
- QJsonObject root;
- root[JsonKeys::root()] = obj;
- QJsonDocument doc(root);
- file.write(doc.toJson());
- file.close();
-}
-
-void QGenericUnixThemeDBusListener::populateSignalMap()
-{
- m_signalMap.clear();
- const QString &loadJsonFile = qEnvironmentVariable("QT_QPA_DBUS_SIGNALS");
- if (!loadJsonFile.isEmpty())
- loadJson(loadJsonFile);
- if (!m_signalMap.isEmpty())
- return;
-
- m_signalMap.insert(DBusKey("org.kde.kdeglobals.KDE"_L1, "widgetStyle"_L1),
- ChangeSignal(Provider::Kde, Setting::ApplicationStyle));
-
- m_signalMap.insert(DBusKey("org.kde.kdeglobals.General"_L1, "ColorScheme"_L1),
- ChangeSignal(Provider::Kde, Setting::Theme));
-
- 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),
- ChangeSignal(Provider::Gnome, Setting::ColorScheme));
-
- const QString &saveJsonFile = qEnvironmentVariable("QT_QPA_DBUS_SIGNALS_SAVE");
- if (!saveJsonFile.isEmpty())
- saveJson(saveJsonFile);
-}
-
-std::optional<QGenericUnixThemeDBusListener::ChangeSignal>
- QGenericUnixThemeDBusListener::findSignal(const QString &location, const QString &key) const
-{
- const DBusKey dkey(location, key);
- std::optional<QGenericUnixThemeDBusListener::ChangeSignal> ret;
- if (m_signalMap.contains(dkey))
- ret.emplace(m_signalMap.value(dkey));
-
- return ret;
-}
-
-void QGenericUnixThemeDBusListener::onSettingChanged(const QString &location, const QString &key, const QDBusVariant &value)
-{
- auto sig = findSignal(location, key);
- if (!sig.has_value())
- return;
-
- emit settingChanged(sig.value().provider, sig.value().setting, value.variant().toString());
-}
-
-#endif //QT_NO_DBUS
-
-class QGenericUnixThemePrivate : public QPlatformThemePrivate
-{
-public:
- QGenericUnixThemePrivate()
- : QPlatformThemePrivate()
- , systemFont(QLatin1StringView(defaultSystemFontNameC), defaultSystemFontSize)
- , fixedFont(QLatin1StringView(defaultFixedFontNameC), systemFont.pointSize())
- {
- fixedFont.setStyleHint(QFont::TypeWriter);
- qCDebug(lcQpaFonts) << "default fonts: system" << systemFont << "fixed" << fixedFont;
- }
-
- const QFont systemFont;
- QFont fixedFont;
-};
-
-QGenericUnixTheme::QGenericUnixTheme()
- : QPlatformTheme(new QGenericUnixThemePrivate())
-{
-}
-
-const QFont *QGenericUnixTheme::font(Font type) const
-{
- Q_D(const QGenericUnixTheme);
- switch (type) {
- case QPlatformTheme::SystemFont:
- return &d->systemFont;
- case QPlatformTheme::FixedFont:
- return &d->fixedFont;
- default:
- return nullptr;
- }
-}
-
-// Helper to return the icon theme paths from XDG.
-QStringList QGenericUnixTheme::xdgIconThemePaths()
-{
- QStringList paths;
- // Add home directory first in search path
- const QFileInfo homeIconDir(QDir::homePath() + "/.icons"_L1);
- if (homeIconDir.isDir())
- paths.prepend(homeIconDir.absoluteFilePath());
-
- paths.append(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation,
- QStringLiteral("icons"),
- QStandardPaths::LocateDirectory));
-
- return paths;
-}
-
-QStringList QGenericUnixTheme::iconFallbackPaths()
-{
- QStringList paths;
- const QFileInfo pixmapsIconsDir(QStringLiteral("/usr/share/pixmaps"));
- if (pixmapsIconsDir.isDir())
- paths.append(pixmapsIconsDir.absoluteFilePath());
-
- return paths;
-}
-
-#ifndef QT_NO_DBUS
-QPlatformMenuBar *QGenericUnixTheme::createPlatformMenuBar() const
-{
- if (isDBusGlobalMenuAvailable())
- return new QDBusMenuBar();
- return nullptr;
-}
-#endif
-
-#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
-QPlatformSystemTrayIcon *QGenericUnixTheme::createPlatformSystemTrayIcon() const
-{
- if (shouldUseDBusTray())
- return new QDBusTrayIcon();
- return nullptr;
-}
-#endif
-
-QVariant QGenericUnixTheme::themeHint(ThemeHint hint) const
-{
- switch (hint) {
- case QPlatformTheme::SystemIconFallbackThemeName:
- return QVariant(QString(QStringLiteral("hicolor")));
- case QPlatformTheme::IconThemeSearchPaths:
- return xdgIconThemePaths();
- case QPlatformTheme::IconFallbackSearchPaths:
- return iconFallbackPaths();
- case QPlatformTheme::DialogButtonBoxButtonsHaveIcons:
- return QVariant(true);
- case QPlatformTheme::StyleNames: {
- QStringList styleNames;
- styleNames << QStringLiteral("Fusion") << QStringLiteral("Windows");
- return QVariant(styleNames);
- }
- case QPlatformTheme::KeyboardScheme:
- return QVariant(int(X11KeyboardScheme));
- case QPlatformTheme::UiEffects:
- return QVariant(int(HoverEffect));
- case QPlatformTheme::MouseCursorTheme:
- return QVariant(mouseCursorTheme());
- case QPlatformTheme::MouseCursorSize:
- return QVariant(mouseCursorSize());
- case QPlatformTheme::PreferFileIconFromTheme:
- return true;
- default:
- break;
- }
- return QPlatformTheme::themeHint(hint);
-}
-
-// Helper functions for implementing QPlatformTheme::fileIcon() for XDG icon themes.
-static QList<QSize> availableXdgFileIconSizes()
-{
- return QIcon::fromTheme(QStringLiteral("inode-directory")).availableSizes();
-}
-
-#if QT_CONFIG(mimetype)
-static QIcon xdgFileIcon(const QFileInfo &fileInfo)
-{
- QMimeDatabase mimeDatabase;
- QMimeType mimeType = mimeDatabase.mimeTypeForFile(fileInfo);
- if (!mimeType.isValid())
- return QIcon();
- const QString &iconName = mimeType.iconName();
- if (!iconName.isEmpty()) {
- const QIcon icon = QIcon::fromTheme(iconName);
- if (!icon.isNull())
- return icon;
- }
- const QString &genericIconName = mimeType.genericIconName();
- return genericIconName.isEmpty() ? QIcon() : QIcon::fromTheme(genericIconName);
-}
-#endif
-
-#if QT_CONFIG(settings)
-class QKdeThemePrivate : public QPlatformThemePrivate
+class QKdeThemePrivate : public QGenericUnixThemePrivate
{
public:
@@ -624,31 +117,31 @@ public:
private:
mutable QHash<QString, QSettings *> kdeSettings;
#ifndef QT_NO_DBUS
- std::unique_ptr<QGenericUnixThemeDBusListener> dbus;
+ std::unique_ptr<QDBusListener> dbus;
bool initDbus();
- void settingChangedHandler(QGenericUnixThemeDBusListener::Provider provider,
- QGenericUnixThemeDBusListener::Setting setting,
+ void settingChangedHandler(QDBusListener::Provider provider,
+ QDBusListener::Setting setting,
const QString &value);
#endif // QT_NO_DBUS
};
#ifndef QT_NO_DBUS
-void QKdeThemePrivate::settingChangedHandler(QGenericUnixThemeDBusListener::Provider provider,
- QGenericUnixThemeDBusListener::Setting setting,
+void QKdeThemePrivate::settingChangedHandler(QDBusListener::Provider provider,
+ QDBusListener::Setting setting,
const QString &value)
{
- if (provider != QGenericUnixThemeDBusListener::Provider::Kde)
+ if (provider != QDBusListener::Provider::Kde)
return;
switch (setting) {
- case QGenericUnixThemeDBusListener::Setting::ColorScheme:
- qCDebug(lcQpaThemeDBus) << "KDE color theme changed to:" << value;
+ case QDBusListener::Setting::ColorScheme:
+ qCDebug(lcQpaThemeKde) << "KDE color theme changed to:" << value;
break;
- case QGenericUnixThemeDBusListener::Setting::Theme:
- qCDebug(lcQpaThemeDBus) << "KDE global theme changed to:" << value;
+ case QDBusListener::Setting::Theme:
+ qCDebug(lcQpaThemeKde) << "KDE global theme changed to:" << value;
break;
- case QGenericUnixThemeDBusListener::Setting::ApplicationStyle:
- qCDebug(lcQpaThemeDBus) << "KDE application style changed to:" << value;
+ case QDBusListener::Setting::ApplicationStyle:
+ qCDebug(lcQpaThemeKde) << "KDE application style changed to:" << value;
break;
}
@@ -657,17 +150,17 @@ void QKdeThemePrivate::settingChangedHandler(QGenericUnixThemeDBusListener::Prov
bool QKdeThemePrivate::initDbus()
{
- dbus.reset(new QGenericUnixThemeDBusListener());
+ dbus.reset(new QDBusListener());
Q_ASSERT(dbus);
// Wrap slot in a lambda to avoid inheriting QKdeThemePrivate from QObject
- auto wrapper = [this](QGenericUnixThemeDBusListener::Provider provider,
- QGenericUnixThemeDBusListener::Setting setting,
+ auto wrapper = [this](QDBusListener::Provider provider,
+ QDBusListener::Setting setting,
const QString &value) {
settingChangedHandler(provider, setting, value);
};
- return QObject::connect(dbus.get(), &QGenericUnixThemeDBusListener::settingChanged, dbus.get(), wrapper);
+ return QObject::connect(dbus.get(), &QDBusListener::settingChanged, dbus.get(), wrapper);
}
#endif // QT_NO_DBUS
@@ -909,12 +402,14 @@ void QKdeThemePrivate::refresh()
if (QFont *systemFont = kdeFont(readKdeSetting(KdeSetting::Font)))
resources.fonts[QPlatformTheme::SystemFont] = systemFont;
else
- resources.fonts[QPlatformTheme::SystemFont] = new QFont(QLatin1StringView(defaultSystemFontNameC), defaultSystemFontSize);
+ resources.fonts[QPlatformTheme::SystemFont] = new QFont(QLatin1StringView(QGenericUnixTheme::defaultSystemFontNameC),
+ QGenericUnixTheme::defaultSystemFontSize);
if (QFont *fixedFont = kdeFont(readKdeSetting(KdeSetting::Fixed))) {
resources.fonts[QPlatformTheme::FixedFont] = fixedFont;
} else {
- fixedFont = new QFont(QLatin1StringView(defaultFixedFontNameC), defaultSystemFontSize);
+ fixedFont = new QFont(QLatin1StringView(QGenericUnixTheme::defaultFixedFontNameC),
+ QGenericUnixTheme::defaultSystemFontSize);
fixedFont->setStyleHint(QFont::TypeWriter);
resources.fonts[QPlatformTheme::FixedFont] = fixedFont;
}
@@ -1043,7 +538,7 @@ void QKdeThemePrivate::readKdeSystemPalette(const QStringList &kdeDirs, int kdeV
const char *QKdeTheme::name = "kde";
QKdeTheme::QKdeTheme(const QStringList& kdeDirs, int kdeVersion)
- : QPlatformTheme(new QKdeThemePrivate(kdeDirs,kdeVersion))
+ : QGenericUnixTheme(new QKdeThemePrivate(kdeDirs, kdeVersion))
{
d_func()->refresh();
}
@@ -1276,278 +771,4 @@ QPlatformSystemTrayIcon *QKdeTheme::createPlatformSystemTrayIcon() const
}
#endif
-#endif // settings
-
-/*!
- \class QGnomeTheme
- \brief QGnomeTheme is a theme implementation for the Gnome desktop.
- \since 5.0
- \internal
- \ingroup qpa
-*/
-
-const char *QGnomeTheme::name = "gnome";
-
-class QGnomeThemePrivate : public QPlatformThemePrivate
-{
-public:
- QGnomeThemePrivate();
- ~QGnomeThemePrivate();
-
- void configureFonts(const QString &gtkFontName) const
- {
- Q_ASSERT(!systemFont);
- const int split = gtkFontName.lastIndexOf(QChar::Space);
- float size = QStringView{gtkFontName}.mid(split + 1).toFloat();
- QString fontName = gtkFontName.left(split);
-
- systemFont = new QFont(fontName, size);
- fixedFont = new QFont(QLatin1StringView(defaultFixedFontNameC), systemFont->pointSize());
- fixedFont->setStyleHint(QFont::TypeWriter);
- qCDebug(lcQpaFonts) << "default fonts: system" << systemFont << "fixed" << fixedFont;
- }
-
- mutable QFont *systemFont = nullptr;
- mutable QFont *fixedFont = nullptr;
-
-#ifndef QT_NO_DBUS
- Qt::ColorScheme m_colorScheme = Qt::ColorScheme::Unknown;
-private:
- std::unique_ptr<QGenericUnixThemeDBusListener> dbus;
- bool initDbus();
- void updateColorScheme(const QString &themeName);
-#endif // QT_NO_DBUS
-};
-
-QGnomeThemePrivate::QGnomeThemePrivate()
-{
-#ifndef QT_NO_DBUS
- initDbus();
-#endif // QT_NO_DBUS
-}
-QGnomeThemePrivate::~QGnomeThemePrivate()
-{
- if (systemFont)
- delete systemFont;
- if (fixedFont)
- delete fixedFont;
-}
-
-#ifndef QT_NO_DBUS
-bool QGnomeThemePrivate::initDbus()
-{
- dbus.reset(new QGenericUnixThemeDBusListener());
- Q_ASSERT(dbus);
-
- // Wrap slot in a lambda to avoid inheriting QGnomeThemePrivate from QObject
- auto wrapper = [this](QGenericUnixThemeDBusListener::Provider provider,
- QGenericUnixThemeDBusListener::Setting setting,
- const QString &value) {
- if (provider != QGenericUnixThemeDBusListener::Provider::Gnome
- && provider != QGenericUnixThemeDBusListener::Provider::Gtk) {
- return;
- }
-
- if (setting == QGenericUnixThemeDBusListener::Setting::Theme)
- updateColorScheme(value);
- };
-
- return QObject::connect(dbus.get(), &QGenericUnixThemeDBusListener::settingChanged, dbus.get(), wrapper);
-}
-
-void QGnomeThemePrivate::updateColorScheme(const QString &themeName)
-{
- const auto oldColorScheme = m_colorScheme;
- if (themeName.contains(QLatin1StringView("light"), Qt::CaseInsensitive)) {
- m_colorScheme = Qt::ColorScheme::Light;
- } else if (themeName.contains(QLatin1StringView("dark"), Qt::CaseInsensitive)) {
- m_colorScheme = Qt::ColorScheme::Dark;
- } else {
- m_colorScheme = Qt::ColorScheme::Unknown;
- }
-
- if (oldColorScheme != m_colorScheme)
- QWindowSystemInterface::handleThemeChange();
-}
-#endif // QT_NO_DBUS
-
-QGnomeTheme::QGnomeTheme()
- : QPlatformTheme(new QGnomeThemePrivate())
-{
-}
-
-QVariant QGnomeTheme::themeHint(QPlatformTheme::ThemeHint hint) const
-{
- switch (hint) {
- case QPlatformTheme::DialogButtonBoxButtonsHaveIcons:
- return QVariant(true);
- case QPlatformTheme::DialogButtonBoxLayout:
- return QVariant(QPlatformDialogHelper::GnomeLayout);
- case QPlatformTheme::SystemIconThemeName:
- return QVariant(QStringLiteral("Adwaita"));
- case QPlatformTheme::SystemIconFallbackThemeName:
- return QVariant(QStringLiteral("gnome"));
- case QPlatformTheme::IconThemeSearchPaths:
- return QVariant(QGenericUnixTheme::xdgIconThemePaths());
- case QPlatformTheme::IconPixmapSizes:
- return QVariant::fromValue(availableXdgFileIconSizes());
- case QPlatformTheme::StyleNames: {
- QStringList styleNames;
- styleNames << QStringLiteral("Fusion") << QStringLiteral("windows");
- return QVariant(styleNames);
- }
- case QPlatformTheme::KeyboardScheme:
- return QVariant(int(GnomeKeyboardScheme));
- case QPlatformTheme::PasswordMaskCharacter:
- return QVariant(QChar(0x2022));
- case QPlatformTheme::UiEffects:
- return QVariant(int(HoverEffect));
- case QPlatformTheme::ButtonPressKeys:
- return QVariant::fromValue(
- QList<Qt::Key>({ Qt::Key_Space, Qt::Key_Return, Qt::Key_Enter, Qt::Key_Select }));
- case QPlatformTheme::PreselectFirstFileInDirectory:
- return true;
- case QPlatformTheme::MouseCursorTheme:
- return QVariant(mouseCursorTheme());
- case QPlatformTheme::MouseCursorSize:
- return QVariant(mouseCursorSize());
- case QPlatformTheme::PreferFileIconFromTheme:
- return true;
- default:
- break;
- }
- return QPlatformTheme::themeHint(hint);
-}
-
-QIcon QGnomeTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions) const
-{
-#if QT_CONFIG(mimetype)
- return xdgFileIcon(fileInfo);
-#else
- Q_UNUSED(fileInfo);
- return QIcon();
-#endif
-}
-
-const QFont *QGnomeTheme::font(Font type) const
-{
- Q_D(const QGnomeTheme);
- if (!d->systemFont)
- d->configureFonts(gtkFontName());
- switch (type) {
- case QPlatformTheme::SystemFont:
- return d->systemFont;
- case QPlatformTheme::FixedFont:
- return d->fixedFont;
- default:
- return nullptr;
- }
-}
-
-QString QGnomeTheme::gtkFontName() const
-{
- return QStringLiteral("%1 %2").arg(QLatin1StringView(defaultSystemFontNameC)).arg(defaultSystemFontSize);
-}
-
-#ifndef QT_NO_DBUS
-QPlatformMenuBar *QGnomeTheme::createPlatformMenuBar() const
-{
- if (isDBusGlobalMenuAvailable())
- return new QDBusMenuBar();
- return nullptr;
-}
-
-Qt::ColorScheme QGnomeTheme::colorScheme() const
-{
- return d_func()->m_colorScheme;
-}
-
-#endif
-
-#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
-QPlatformSystemTrayIcon *QGnomeTheme::createPlatformSystemTrayIcon() const
-{
- if (shouldUseDBusTray())
- return new QDBusTrayIcon();
- return nullptr;
-}
-#endif
-
-QString QGnomeTheme::standardButtonText(int button) const
-{
- switch (button) {
- case QPlatformDialogHelper::Ok:
- return QCoreApplication::translate("QGnomeTheme", "&OK");
- case QPlatformDialogHelper::Save:
- return QCoreApplication::translate("QGnomeTheme", "&Save");
- case QPlatformDialogHelper::Cancel:
- return QCoreApplication::translate("QGnomeTheme", "&Cancel");
- case QPlatformDialogHelper::Close:
- return QCoreApplication::translate("QGnomeTheme", "&Close");
- case QPlatformDialogHelper::Discard:
- return QCoreApplication::translate("QGnomeTheme", "Close without Saving");
- default:
- break;
- }
- return QPlatformTheme::standardButtonText(button);
-}
-
-/*!
- \brief Creates a UNIX theme according to the detected desktop environment.
-*/
-
-QPlatformTheme *QGenericUnixTheme::createUnixTheme(const QString &name)
-{
- if (name == QLatin1StringView(QGenericUnixTheme::name))
- return new QGenericUnixTheme;
-#if QT_CONFIG(settings)
- if (name == QLatin1StringView(QKdeTheme::name))
- if (QPlatformTheme *kdeTheme = QKdeTheme::createKdeTheme())
- return kdeTheme;
-#endif
- if (name == QLatin1StringView(QGnomeTheme::name))
- return new QGnomeTheme;
- return nullptr;
-}
-
-QStringList QGenericUnixTheme::themeNames()
-{
- QStringList result;
- if (QGuiApplication::desktopSettingsAware()) {
- const QByteArray desktopEnvironment = QGuiApplicationPrivate::platformIntegration()->services()->desktopEnvironment();
- QList<QByteArray> gtkBasedEnvironments;
- gtkBasedEnvironments << "GNOME"
- << "X-CINNAMON"
- << "PANTHEON"
- << "UNITY"
- << "MATE"
- << "XFCE"
- << "LXDE";
- const QList<QByteArray> desktopNames = desktopEnvironment.split(':');
- for (const QByteArray &desktopName : desktopNames) {
- if (desktopEnvironment == "KDE") {
-#if QT_CONFIG(settings)
- result.push_back(QLatin1StringView(QKdeTheme::name));
-#endif
- } else if (gtkBasedEnvironments.contains(desktopName)) {
- // prefer the GTK3 theme implementation with native dialogs etc.
- result.push_back(QStringLiteral("gtk3"));
- // fallback to the generic Gnome theme if loading the GTK3 theme fails
- result.push_back(QLatin1StringView(QGnomeTheme::name));
- } else {
- // unknown, but lowercase the name (our standard practice) and
- // remove any "x-" prefix
- QString s = QString::fromLatin1(desktopName.toLower());
- result.push_back(s.startsWith("x-"_L1) ? s.mid(2) : s);
- }
- }
- } // desktopSettingsAware
- result.append(QLatin1StringView(QGenericUnixTheme::name));
- return result;
-}
-
QT_END_NAMESPACE
-
-#ifndef QT_NO_DBUS
-#include "qgenericunixthemes.moc"
-#endif // QT_NO_DBUS
diff --git a/src/gui/platform/unix/qkdetheme_p.h b/src/gui/platform/unix/qkdetheme_p.h
new file mode 100644
index 00000000000..878075585c3
--- /dev/null
+++ b/src/gui/platform/unix/qkdetheme_p.h
@@ -0,0 +1,68 @@
+// 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 QKDETHEME_P_H
+#define QKDETHEME_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 "qgenericunixtheme_p.h"
+#include <qpa/qplatformtheme.h>
+#include <QtCore/QString>
+#include <QtCore/QStringList>
+#include <QtGui/QFont>
+#include <QtCore/private/qglobal_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class ResourceHelper
+{
+public:
+ ResourceHelper();
+ ~ResourceHelper() { clear(); }
+
+ void clear();
+
+ QPalette *palettes[QPlatformTheme::NPalettes];
+ QFont *fonts[QPlatformTheme::NFonts];
+};
+
+class QKdeThemePrivate;
+class Q_GUI_EXPORT QKdeTheme : public QGenericUnixTheme
+{
+ Q_DECLARE_PRIVATE(QKdeTheme)
+public:
+ QKdeTheme(const QStringList& kdeDirs, int kdeVersion);
+
+ static QPlatformTheme *createKdeTheme();
+ QVariant themeHint(ThemeHint hint) const override;
+
+ QIcon fileIcon(const QFileInfo &fileInfo,
+ QPlatformTheme::IconOptions iconOptions = { }) const override;
+
+ const QPalette *palette(Palette type = SystemPalette) const override;
+ Qt::ColorScheme colorScheme() const override;
+ //void requestColorScheme(Qt::ColorScheme scheme) override;
+
+ const QFont *font(Font type) const override;
+#ifndef QT_NO_DBUS
+ QPlatformMenuBar *createPlatformMenuBar() const override;
+#endif
+#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
+ QPlatformSystemTrayIcon *createPlatformSystemTrayIcon() const override;
+#endif
+
+ static const char *name;
+};
+
+QT_END_NAMESPACE
+#endif // QKDETHEME_P_H
diff --git a/src/plugins/platforms/eglfs/api/qeglfsintegration.cpp b/src/plugins/platforms/eglfs/api/qeglfsintegration.cpp
index 13fa9ab42cb..4abe948117e 100644
--- a/src/plugins/platforms/eglfs/api/qeglfsintegration.cpp
+++ b/src/plugins/platforms/eglfs/api/qeglfsintegration.cpp
@@ -32,7 +32,7 @@
#endif
#include <QtGui/private/qgenericunixfontdatabase_p.h>
-#include <QtGui/private/qgenericunixthemes_p.h>
+#include <QtGui/private/qgenericunixtheme_p.h>
#include <QtGui/private/qgenericunixeventdispatcher_p.h>
#include <QtFbSupport/private/qfbvthandler_p.h>
#ifndef QT_NO_OPENGL
diff --git a/src/plugins/platforms/vkkhrdisplay/qvkkhrdisplayintegration.cpp b/src/plugins/platforms/vkkhrdisplay/qvkkhrdisplayintegration.cpp
index 05793f6e86a..36ebfe6c350 100644
--- a/src/plugins/platforms/vkkhrdisplay/qvkkhrdisplayintegration.cpp
+++ b/src/plugins/platforms/vkkhrdisplay/qvkkhrdisplayintegration.cpp
@@ -13,7 +13,7 @@
#include <QtGui/private/qwindow_p.h>
#include <QtGui/private/qgenericunixeventdispatcher_p.h>
#include <QtGui/private/qgenericunixfontdatabase_p.h>
-#include <QtGui/private/qgenericunixthemes_p.h>
+#include <QtGui/private/qgenericunixtheme_p.h>
#include <qpa/qplatformservices.h>
#include <QtFbSupport/private/qfbvthandler_p.h>
diff --git a/src/plugins/platforms/xcb/qxcbintegration.cpp b/src/plugins/platforms/xcb/qxcbintegration.cpp
index 5066a079614..f58010f3268 100644
--- a/src/plugins/platforms/xcb/qxcbintegration.cpp
+++ b/src/plugins/platforms/xcb/qxcbintegration.cpp
@@ -43,7 +43,8 @@
#endif
#include <qpa/qplatforminputcontextfactory_p.h>
-#include <private/qgenericunixthemes_p.h>
+#include <private/qgenericunixtheme_p.h>
+#include <private/qkdetheme_p.h>
#include <qpa/qplatforminputcontext.h>
#include <QtGui/QOpenGLContext>
diff --git a/src/plugins/platformthemes/gtk3/qgtk3theme.h b/src/plugins/platformthemes/gtk3/qgtk3theme.h
index 7eb62bf4c84..d2f2efff51f 100644
--- a/src/plugins/platformthemes/gtk3/qgtk3theme.h
+++ b/src/plugins/platformthemes/gtk3/qgtk3theme.h
@@ -5,7 +5,7 @@
#define QGTK3THEME_H
#include <private/qtguiglobal_p.h>
-#include <private/qgenericunixthemes_p.h>
+#include <private/qgnometheme_p.h>
#include "qgtk3storage_p.h"
QT_BEGIN_NAMESPACE