7#include <private/qguiapplication_p.h>
8#include <qpa/qplatformintegration.h>
9#include <qpa/qplatformservices.h>
10#include <qdbusconnection.h>
12#include <qjsonarray.h>
13#include <qjsondocument.h>
14#include <qjsonobject.h>
17using namespace Qt::StringLiterals;
21
22
23
24
25
26
27
28
29QDBusListener::QDBusListener(
const QString &service,
30 const QString &path,
const QString &interface,
const QString &signal)
32 init (service, path, interface, signal);
37 const auto service = u""_s;
38 const auto path = u"/org/freedesktop/portal/desktop"_s;
39 const auto interface = u"org.freedesktop.portal.Settings"_s;
40 const auto signal = u"SettingChanged"_s;
42 init (service, path, interface, signal);
47constexpr auto dbusLocation() {
return "DBusLocation"_L1; }
48constexpr auto dbusKey() {
return "DBusKey"_L1; }
49constexpr auto provider() {
return "Provider"_L1; }
50constexpr auto setting() {
return "Setting"_L1; }
51constexpr auto dbusSignals() {
return "DbusSignals"_L1; }
52constexpr auto root() {
return "Q_L1.qpa.DBusSignals"_L1; }
56void QDBusListener::init(
const QString &service,
const QString &path,
57 const QString &interface,
const QString &signal)
60 const bool dBusRunning = dbus.isConnected();
61 bool dBusSignalConnected =
false;
62#define LOG service << path << interface << signal;
66 qRegisterMetaType<QDBusVariant>();
67 dBusSignalConnected = dbus.connect(service, path, interface, signal,
this,
68 SLOT(onSettingChanged(QString,QString,QDBusVariant)));
71 if (dBusSignalConnected) {
73 qCDebug(lcQpaThemeDBus) <<
LOG;
77 qCWarning(lcQpaThemeDBus) <<
"DBus connection failed:" <<
LOG;
80 qCWarning(lcQpaThemeDBus) <<
"Session DBus not running.";
82 qCWarning(lcQpaThemeDBus) <<
"Application will not react to setting changes.\n"
83 <<
"Check your DBus installation.";
90 Q_ASSERT(!fileName.isEmpty());
91#define CHECK(cond, warning)
93 qCWarning(lcQpaThemeDBus) << fileName << warning << "Falling back to default.";
97#define PARSE(var, enumeration, string)
101 const int val = QMetaEnum::fromType<enumeration>().keyToValue(string.toLatin1(), &success);
102 CHECK(success, "Parse Error: Invalid value" << string << "for" << #var);
103 var = static_cast<enumeration>(val);
106 QFile file(fileName);
107 CHECK(file.exists(), fileName <<
"doesn't exist.");
108 CHECK(file.open(QIODevice::ReadOnly),
"could not be opened for reading.");
110 QJsonParseError error;
111 QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error);
112 CHECK((error.error == QJsonParseError::NoError), error.errorString());
113 CHECK(doc.isObject(),
"Parse Error: Expected root object" <<
JsonKeys::root());
115 const QJsonObject &root = doc.object();
117 CHECK(root[JsonKeys::root()][JsonKeys::dbusSignals()].isArray(),
"Parse Error: Expected array" <<
JsonKeys::dbusSignals());
119 const QJsonArray &sigs = root[JsonKeys::root()][JsonKeys::dbusSignals()].toArray();
120 CHECK((sigs.count() > 0),
"Parse Error: Found empty array" <<
JsonKeys::dbusSignals());
122 for (
auto sig = sigs.constBegin(); sig != sigs.constEnd(); ++sig) {
123 CHECK(sig->isObject(),
"Parse Error: Expected object array" <<
JsonKeys::dbusSignals());
124 const QJsonObject &obj = sig->toObject();
125 CHECK(obj.contains(
JsonKeys::dbusLocation()),
"Parse Error: Expected key" <<
JsonKeys::dbusLocation());
129 const QString &location = obj[JsonKeys::dbusLocation()].toString();
130 const QString &key = obj[JsonKeys::dbusKey()].toString();
131 const QString &providerString = obj[JsonKeys::provider()].toString();
132 const QString &settingString = obj[JsonKeys::setting()].toString();
133 PARSE(provider, Provider, providerString);
134 PARSE(setting, Setting, settingString);
135 const DBusKey dkey(location, key);
136 CHECK (!m_signalMap.contains(dkey),
"Duplicate key" << location << key);
137 m_signalMap.insert(dkey, ChangeSignal(provider, setting));
142 if (m_signalMap.count() > 0)
143 qCInfo(lcQpaThemeDBus) <<
"Successfully imported" << fileName;
145 qCWarning(lcQpaThemeDBus) <<
"No data imported from" << fileName <<
"falling back to default.";
148 const int count = m_signalMap.count();
152 qCDebug(lcQpaThemeDBus) <<
"Listening to" << count <<
"signals:";
153 for (
auto it = m_signalMap.constBegin(); it != m_signalMap.constEnd(); ++it) {
154 qDebug() << it.key().key << it.key().location <<
"mapped to"
155 << it.value().provider << it.value().setting;
163 Q_ASSERT(!m_signalMap.isEmpty());
164 Q_ASSERT(!fileName.isEmpty());
165 QFile file(fileName);
166 if (!file.open(QIODevice::WriteOnly)) {
167 qCWarning(lcQpaThemeDBus) << fileName <<
"could not be opened for writing.";
172 for (
auto sig = m_signalMap.constBegin(); sig != m_signalMap.constEnd(); ++sig) {
173 const DBusKey &dkey = sig.key();
174 const ChangeSignal &csig = sig.value();
176 obj[
JsonKeys::dbusLocation()] = dkey.location;
177 obj[
JsonKeys::dbusKey()] = dkey.key;
178 obj[
JsonKeys::provider()] = QLatin1StringView(QMetaEnum::fromType<Provider>()
179 .valueToKey(
static_cast<
int>(csig.provider)));
180 obj[
JsonKeys::setting()] = QLatin1StringView(QMetaEnum::fromType<Setting>()
181 .valueToKey(
static_cast<
int>(csig.setting)));
185 obj[
JsonKeys::dbusSignals()] = sigs;
189 file.write(doc.toJson());
196 const QString &loadJsonFile = qEnvironmentVariable(
"QT_QPA_DBUS_SIGNALS");
197 if (!loadJsonFile.isEmpty())
198 loadJson(loadJsonFile);
199 if (!m_signalMap.isEmpty())
202 m_signalMap.insert(DBusKey(
"org.kde.kdeglobals.KDE"_L1,
"widgetStyle"_L1),
203 ChangeSignal(Provider::Kde, Setting::ApplicationStyle));
205 m_signalMap.insert(DBusKey(
"org.kde.kdeglobals.General"_L1,
"ColorScheme"_L1),
206 ChangeSignal(Provider::Kde, Setting::Theme));
208 m_signalMap.insert(DBusKey(
"org.gnome.desktop.interface"_L1,
"gtk-theme"_L1),
209 ChangeSignal(Provider::Gtk, Setting::Theme));
211 using namespace QDBusSettings;
212 m_signalMap.insert(DBusKey(XdgSettings::AppearanceNamespace, XdgSettings::ColorSchemeKey),
213 ChangeSignal(Provider::Gnome, Setting::ColorScheme));
215 m_signalMap.insert(DBusKey(XdgSettings::AppearanceNamespace, XdgSettings::ContrastKey),
216 ChangeSignal(Provider::Gnome, Setting::Contrast));
219 m_signalMap.insert(DBusKey(GnomeSettings::AllyNamespace, GnomeSettings::ContrastKey),
220 ChangeSignal(Provider::Gnome, Setting::Contrast));
222 const QString &saveJsonFile = qEnvironmentVariable(
"QT_QPA_DBUS_SIGNALS_SAVE");
223 if (!saveJsonFile.isEmpty())
224 saveJson(saveJsonFile);
227std::optional<QDBusListener::ChangeSignal>
228 QDBusListener::findSignal(
const QString &location,
const QString &key)
const
230 const DBusKey dkey(location, key);
231 std::optional<QDBusListener::ChangeSignal> ret;
232 const auto it = m_signalMap.find(dkey);
233 if (it != m_signalMap.cend())
234 ret.emplace(it.value());
239void QDBusListener::onSettingChanged(
const QString &location,
const QString &key,
const QDBusVariant &value)
241 auto sig = findSignal(location, key);
242 if (!sig.has_value())
245 const Setting setting = sig.value().setting;
246 QVariant settingValue = value.variant();
249 case Setting::ColorScheme:
250 settingValue.setValue(QDBusSettings::XdgSettings::convertColorScheme(settingValue));
252 case Setting::Contrast:
253 using namespace QDBusSettings;
256 if (key == XdgSettings::ContrastKey)
257 settingValue.setValue(XdgSettings::convertContrastPreference(settingValue));
258 else if (key == GnomeSettings::ContrastKey)
259 settingValue.setValue(GnomeSettings::convertContrastPreference(settingValue));
261 Q_UNREACHABLE_IMPL();
267 emit settingChanged(sig.value().provider, setting, settingValue);
\inmodule QtCore\reentrant
Combined button and popup list for selecting options.
#define PARSE(var, enumeration, string)
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)