Skip to content

Commit 53fb134

Browse files
committed
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ø <tor.arne.vestbo@qt.io> Reviewed-by: Piotr Wierciński <piotr.wiercinski@qt.io>
1 parent 3bbc9e2 commit 53fb134

14 files changed

+1103
-938
lines changed

src/gui/CMakeLists.txt

+8-1
Original file line numberDiff line numberDiff line change
@@ -1042,9 +1042,16 @@ qt_internal_extend_target(Gui CONDITION TARGET Qt::DBus AND UNIX AND (QT_FEATURE
10421042
Qt::DBus
10431043
)
10441044

1045+
qt_internal_extend_target(Gui CONDITION UNIX AND QT_FEATURE_settings AND QT_FEATURE_dbus AND (QT_FEATURE_xcb OR QT_FEATURE_wayland)
1046+
SOURCES
1047+
platform/unix/qkdetheme_p.h platform/unix/qkdetheme.cpp
1048+
platform/unix/qdbuslistener_p.h platform/unix/qdbuslistener.cpp
1049+
)
1050+
10451051
qt_internal_extend_target(Gui CONDITION UNIX AND (QT_FEATURE_xcb OR NOT MACOS) AND (QT_FEATURE_xcb OR NOT UIKIT)
10461052
SOURCES
1047-
platform/unix/qgenericunixthemes.cpp platform/unix/qgenericunixthemes_p.h
1053+
platform/unix/qgenericunixtheme.cpp platform/unix/qgenericunixtheme_p.h
1054+
platform/unix/qgnometheme_p.h platform/unix/qgnometheme.cpp
10481055
)
10491056

10501057
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)
+233
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
// Copyright (C) 2025 The Qt Company Ltd.
2+
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3+
4+
#include "qdbuslistener_p.h"
5+
#include <private/qguiapplication_p.h>
6+
#include <qpa/qplatformintegration.h>
7+
#include <qpa/qplatformservices.h>
8+
#include <private/qdbustrayicon_p.h>
9+
10+
QT_BEGIN_NAMESPACE
11+
using namespace Qt::StringLiterals;
12+
Q_STATIC_LOGGING_CATEGORY(lcQpaThemeDBus, "qt.qpa.theme.dbus")
13+
14+
/*!
15+
\internal
16+
The QDBusListener class listens to the SettingChanged DBus signal
17+
and translates it into combinations of the enums \c Provider and \c Setting.
18+
Upon construction, it logs success/failure of the DBus connection.
19+
20+
The signal settingChanged delivers the normalized setting type and the new value as a string.
21+
It is emitted on known setting types only.
22+
*/
23+
QDBusListener::QDBusListener(const QString &service,
24+
const QString &path, const QString &interface, const QString &signal)
25+
{
26+
init (service, path, interface, signal);
27+
}
28+
29+
QDBusListener::QDBusListener()
30+
{
31+
const auto service = u""_s;
32+
const auto path = u"/org/freedesktop/portal/desktop"_s;
33+
const auto interface = u"org.freedesktop.portal.Settings"_s;
34+
const auto signal = u"SettingChanged"_s;
35+
36+
init (service, path, interface, signal);
37+
}
38+
39+
namespace {
40+
namespace JsonKeys {
41+
constexpr auto dbusLocation() { return "DBusLocation"_L1; }
42+
constexpr auto dbusKey() { return "DBusKey"_L1; }
43+
constexpr auto provider() { return "Provider"_L1; }
44+
constexpr auto setting() { return "Setting"_L1; }
45+
constexpr auto dbusSignals() { return "DbusSignals"_L1; }
46+
constexpr auto root() { return "Q_L1.qpa.DBusSignals"_L1; }
47+
} // namespace JsonKeys
48+
}
49+
50+
51+
void QDBusListener::init(const QString &service, const QString &path,
52+
const QString &interface, const QString &signal)
53+
{
54+
QDBusConnection dbus = QDBusConnection::sessionBus();
55+
const bool dBusRunning = dbus.isConnected();
56+
bool dBusSignalConnected = false;
57+
#define LOG service << path << interface << signal;
58+
59+
if (dBusRunning) {
60+
populateSignalMap();
61+
qRegisterMetaType<QDBusVariant>();
62+
dBusSignalConnected = dbus.connect(service, path, interface, signal, this,
63+
SLOT(onSettingChanged(QString,QString,QDBusVariant)));
64+
}
65+
66+
if (dBusSignalConnected) {
67+
// Connection successful
68+
qCDebug(lcQpaThemeDBus) << LOG;
69+
} else {
70+
if (dBusRunning) {
71+
// DBus running, but connection failed
72+
qCWarning(lcQpaThemeDBus) << "DBus connection failed:" << LOG;
73+
} else {
74+
// DBus not running
75+
qCWarning(lcQpaThemeDBus) << "Session DBus not running.";
76+
}
77+
qCWarning(lcQpaThemeDBus) << "Application will not react to setting changes.\n"
78+
<< "Check your DBus installation.";
79+
}
80+
#undef LOG
81+
}
82+
83+
void QDBusListener::loadJson(const QString &fileName)
84+
{
85+
Q_ASSERT(!fileName.isEmpty());
86+
#define CHECK(cond, warning)\
87+
if (!cond) {\
88+
qCWarning(lcQpaThemeDBus) << fileName << warning << "Falling back to default.";\
89+
return;\
90+
}
91+
92+
#define PARSE(var, enumeration, string)\
93+
enumeration var;\
94+
{\
95+
bool success;\
96+
const int val = QMetaEnum::fromType<enumeration>().keyToValue(string.toLatin1(), &success);\
97+
CHECK(success, "Parse Error: Invalid value" << string << "for" << #var);\
98+
var = static_cast<enumeration>(val);\
99+
}
100+
101+
QFile file(fileName);
102+
CHECK(file.exists(), fileName << "doesn't exist.");
103+
CHECK(file.open(QIODevice::ReadOnly), "could not be opened for reading.");
104+
105+
QJsonParseError error;
106+
QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error);
107+
CHECK((error.error == QJsonParseError::NoError), error.errorString());
108+
CHECK(doc.isObject(), "Parse Error: Expected root object" << JsonKeys::root());
109+
110+
const QJsonObject &root = doc.object();
111+
CHECK(root.contains(JsonKeys::root()), "Parse Error: Expectned root object" << JsonKeys::root());
112+
CHECK(root[JsonKeys::root()][JsonKeys::dbusSignals()].isArray(), "Parse Error: Expected array" << JsonKeys::dbusSignals());
113+
114+
const QJsonArray &sigs = root[JsonKeys::root()][JsonKeys::dbusSignals()].toArray();
115+
CHECK((sigs.count() > 0), "Parse Error: Found empty array" << JsonKeys::dbusSignals());
116+
117+
for (auto sig = sigs.constBegin(); sig != sigs.constEnd(); ++sig) {
118+
CHECK(sig->isObject(), "Parse Error: Expected object array" << JsonKeys::dbusSignals());
119+
const QJsonObject &obj = sig->toObject();
120+
CHECK(obj.contains(JsonKeys::dbusLocation()), "Parse Error: Expected key" << JsonKeys::dbusLocation());
121+
CHECK(obj.contains(JsonKeys::dbusKey()), "Parse Error: Expected key" << JsonKeys::dbusKey());
122+
CHECK(obj.contains(JsonKeys::provider()), "Parse Error: Expected key" << JsonKeys::provider());
123+
CHECK(obj.contains(JsonKeys::setting()), "Parse Error: Expected key" << JsonKeys::setting());
124+
const QString &location = obj[JsonKeys::dbusLocation()].toString();
125+
const QString &key = obj[JsonKeys::dbusKey()].toString();
126+
const QString &providerString = obj[JsonKeys::provider()].toString();
127+
const QString &settingString = obj[JsonKeys::setting()].toString();
128+
PARSE(provider, Provider, providerString);
129+
PARSE(setting, Setting, settingString);
130+
const DBusKey dkey(location, key);
131+
CHECK (!m_signalMap.contains(dkey), "Duplicate key" << location << key);
132+
m_signalMap.insert(dkey, ChangeSignal(provider, setting));
133+
}
134+
#undef PARSE
135+
#undef CHECK
136+
137+
if (m_signalMap.count() > 0)
138+
qCInfo(lcQpaThemeDBus) << "Successfully imported" << fileName;
139+
else
140+
qCWarning(lcQpaThemeDBus) << "No data imported from" << fileName << "falling back to default.";
141+
142+
#ifdef QT_DEBUG
143+
const int count = m_signalMap.count();
144+
if (count == 0)
145+
return;
146+
147+
qCDebug(lcQpaThemeDBus) << "Listening to" << count << "signals:";
148+
for (auto it = m_signalMap.constBegin(); it != m_signalMap.constEnd(); ++it) {
149+
qDebug() << it.key().key << it.key().location << "mapped to"
150+
<< it.value().provider << it.value().setting;
151+
}
152+
153+
#endif
154+
}
155+
156+
void QDBusListener::saveJson(const QString &fileName) const
157+
{
158+
Q_ASSERT(!m_signalMap.isEmpty());
159+
Q_ASSERT(!fileName.isEmpty());
160+
QFile file(fileName);
161+
if (!file.open(QIODevice::WriteOnly)) {
162+
qCWarning(lcQpaThemeDBus) << fileName << "could not be opened for writing.";
163+
return;
164+
}
165+
166+
QJsonArray sigs;
167+
for (auto sig = m_signalMap.constBegin(); sig != m_signalMap.constEnd(); ++sig) {
168+
const DBusKey &dkey = sig.key();
169+
const ChangeSignal &csig = sig.value();
170+
QJsonObject obj;
171+
obj[JsonKeys::dbusLocation()] = dkey.location;
172+
obj[JsonKeys::dbusKey()] = dkey.key;
173+
obj[JsonKeys::provider()] = QLatin1StringView(QMetaEnum::fromType<Provider>()
174+
.valueToKey(static_cast<int>(csig.provider)));
175+
obj[JsonKeys::setting()] = QLatin1StringView(QMetaEnum::fromType<Setting>()
176+
.valueToKey(static_cast<int>(csig.setting)));
177+
sigs.append(obj);
178+
}
179+
QJsonObject obj;
180+
obj[JsonKeys::dbusSignals()] = sigs;
181+
QJsonObject root;
182+
root[JsonKeys::root()] = obj;
183+
QJsonDocument doc(root);
184+
file.write(doc.toJson());
185+
file.close();
186+
}
187+
188+
void QDBusListener::populateSignalMap()
189+
{
190+
m_signalMap.clear();
191+
const QString &loadJsonFile = qEnvironmentVariable("QT_QPA_DBUS_SIGNALS");
192+
if (!loadJsonFile.isEmpty())
193+
loadJson(loadJsonFile);
194+
if (!m_signalMap.isEmpty())
195+
return;
196+
197+
m_signalMap.insert(DBusKey("org.kde.kdeglobals.KDE"_L1, "widgetStyle"_L1),
198+
ChangeSignal(Provider::Kde, Setting::ApplicationStyle));
199+
200+
m_signalMap.insert(DBusKey("org.kde.kdeglobals.General"_L1, "ColorScheme"_L1),
201+
ChangeSignal(Provider::Kde, Setting::Theme));
202+
203+
m_signalMap.insert(DBusKey("org.gnome.desktop.interface"_L1, "gtk-theme"_L1),
204+
ChangeSignal(Provider::Gtk, Setting::Theme));
205+
206+
m_signalMap.insert(DBusKey("org.freedesktop.appearance"_L1, "color-scheme"_L1),
207+
ChangeSignal(Provider::Gnome, Setting::ColorScheme));
208+
209+
const QString &saveJsonFile = qEnvironmentVariable("QT_QPA_DBUS_SIGNALS_SAVE");
210+
if (!saveJsonFile.isEmpty())
211+
saveJson(saveJsonFile);
212+
}
213+
214+
std::optional<QDBusListener::ChangeSignal>
215+
QDBusListener::findSignal(const QString &location, const QString &key) const
216+
{
217+
const DBusKey dkey(location, key);
218+
std::optional<QDBusListener::ChangeSignal> ret;
219+
if (m_signalMap.contains(dkey))
220+
ret.emplace(m_signalMap.value(dkey));
221+
222+
return ret;
223+
}
224+
225+
void QDBusListener::onSettingChanged(const QString &location, const QString &key, const QDBusVariant &value)
226+
{
227+
auto sig = findSignal(location, key);
228+
if (!sig.has_value())
229+
return;
230+
231+
emit settingChanged(sig.value().provider, sig.value().setting, value.variant().toString());
232+
}
233+
QT_END_NAMESPACE
+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright (C) 2025 The Qt Company Ltd.
2+
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3+
4+
#ifndef QDBUSLISTENER_P_H
5+
#define QDBUSLISTENER_P_H
6+
7+
//
8+
// W A R N I N G
9+
// -------------
10+
//
11+
// This file is not part of the Qt API. It exists purely as an
12+
// implementation detail. This header file may change from version to
13+
// version without notice, or even be removed.
14+
//
15+
// We mean it.
16+
//
17+
18+
#include <qpa/qplatformtheme.h>
19+
#include <private/qflatmap_p.h>
20+
#include <QDBusVariant>
21+
22+
QT_BEGIN_NAMESPACE
23+
24+
class QDBusListener : public QObject
25+
{
26+
Q_OBJECT
27+
28+
public:
29+
30+
enum class Provider {
31+
Kde,
32+
Gtk,
33+
Gnome,
34+
};
35+
Q_ENUM(Provider)
36+
37+
enum class Setting {
38+
Theme,
39+
ApplicationStyle,
40+
ColorScheme,
41+
};
42+
Q_ENUM(Setting)
43+
44+
QDBusListener();
45+
QDBusListener(const QString &service, const QString &path,
46+
const QString &interface, const QString &signal);
47+
48+
private Q_SLOTS:
49+
void onSettingChanged(const QString &location, const QString &key, const QDBusVariant &value);
50+
51+
Q_SIGNALS:
52+
void settingChanged(QDBusListener::Provider provider,
53+
QDBusListener::Setting setting,
54+
const QString &value);
55+
56+
private:
57+
struct DBusKey
58+
{
59+
QString location;
60+
QString key;
61+
DBusKey(const QString &loc, const QString &k) : location(loc), key(k) {};
62+
bool operator<(const DBusKey &other) const
63+
{
64+
return location + key < other.location + other.key;
65+
}
66+
};
67+
68+
struct ChangeSignal
69+
{
70+
Provider provider;
71+
Setting setting;
72+
ChangeSignal(Provider p, Setting s) : provider(p), setting(s) {}
73+
ChangeSignal() {}
74+
};
75+
76+
QFlatMap <DBusKey, ChangeSignal> m_signalMap;
77+
78+
void init(const QString &service, const QString &path,
79+
const QString &interface, const QString &signal);
80+
81+
std::optional<ChangeSignal> findSignal(const QString &location, const QString &key) const;
82+
void populateSignalMap();
83+
void loadJson(const QString &fileName);
84+
void saveJson(const QString &fileName) const;
85+
};
86+
87+
QT_END_NAMESPACE
88+
#endif // QDBUSLISTENER_P_H

0 commit comments

Comments
 (0)