7#include <private/qguiapplication_p.h>
8#include <qpa/qplatformintegration.h>
9#include <qpa/qplatformservices.h>
10#include <private/qdbustrayicon_p.h>
11#include <qjsonarray.h>
12#include <qjsondocument.h>
13#include <qjsonobject.h>
16using namespace Qt::StringLiterals;
20
21
22
23
24
25
26
27
28QDBusListener::QDBusListener(
const QString &service,
29 const QString &path,
const QString &interface,
const QString &signal)
31 init (service, path, interface, signal);
36 const auto service = u""_s;
37 const auto path = u"/org/freedesktop/portal/desktop"_s;
38 const auto interface = u"org.freedesktop.portal.Settings"_s;
39 const auto signal = u"SettingChanged"_s;
41 init (service, path, interface, signal);
46constexpr auto dbusLocation() {
return "DBusLocation"_L1; }
47constexpr auto dbusKey() {
return "DBusKey"_L1; }
48constexpr auto provider() {
return "Provider"_L1; }
49constexpr auto setting() {
return "Setting"_L1; }
50constexpr auto dbusSignals() {
return "DbusSignals"_L1; }
51constexpr auto root() {
return "Q_L1.qpa.DBusSignals"_L1; }
55void QDBusListener::init(
const QString &service,
const QString &path,
56 const QString &interface,
const QString &signal)
58 QDBusConnection dbus = QDBusConnection::sessionBus();
59 const bool dBusRunning = dbus.isConnected();
60 bool dBusSignalConnected =
false;
61#define LOG service << path << interface << signal;
65 qRegisterMetaType<QDBusVariant>();
66 dBusSignalConnected = dbus.connect(service, path, interface, signal,
this,
67 SLOT(onSettingChanged(QString,QString,QDBusVariant)));
70 if (dBusSignalConnected) {
72 qCDebug(lcQpaThemeDBus) <<
LOG;
76 qCWarning(lcQpaThemeDBus) <<
"DBus connection failed:" <<
LOG;
79 qCWarning(lcQpaThemeDBus) <<
"Session DBus not running.";
81 qCWarning(lcQpaThemeDBus) <<
"Application will not react to setting changes.\n"
82 <<
"Check your DBus installation.";
89 Q_ASSERT(!fileName.isEmpty());
90#define CHECK(cond, warning)
92 qCWarning(lcQpaThemeDBus) << fileName << warning << "Falling back to default.";
96#define PARSE(var, enumeration, string)
100 const int val = QMetaEnum::fromType<enumeration>().keyToValue(string.toLatin1(), &success);
101 CHECK(success, "Parse Error: Invalid value" << string << "for" << #var);
102 var = static_cast<enumeration>(val);
105 QFile file(fileName);
106 CHECK(file.exists(), fileName <<
"doesn't exist.");
107 CHECK(file.open(QIODevice::ReadOnly),
"could not be opened for reading.");
109 QJsonParseError error;
110 QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error);
111 CHECK((error.error == QJsonParseError::NoError), error.errorString());
112 CHECK(doc.isObject(),
"Parse Error: Expected root object" << JsonKeys::root());
114 const QJsonObject &root = doc.object();
115 CHECK(root.contains(
JsonKeys::root()),
"Parse Error: Expectned root object" << JsonKeys::root());
116 CHECK(root[JsonKeys::root()][JsonKeys::dbusSignals()].isArray(),
"Parse Error: Expected array" << JsonKeys::dbusSignals());
118 const QJsonArray &sigs = root[JsonKeys::root()][JsonKeys::dbusSignals()].toArray();
119 CHECK((sigs.count() > 0),
"Parse Error: Found empty array" << JsonKeys::dbusSignals());
121 for (
auto sig = sigs.constBegin(); sig != sigs.constEnd(); ++sig) {
122 CHECK(sig->isObject(),
"Parse Error: Expected object array" << JsonKeys::dbusSignals());
123 const QJsonObject &obj = sig->toObject();
124 CHECK(obj.contains(
JsonKeys::dbusLocation()),
"Parse Error: Expected key" << JsonKeys::dbusLocation());
125 CHECK(obj.contains(
JsonKeys::dbusKey()),
"Parse Error: Expected key" << JsonKeys::dbusKey());
126 CHECK(obj.contains(
JsonKeys::provider()),
"Parse Error: Expected key" << JsonKeys::provider());
127 CHECK(obj.contains(
JsonKeys::setting()),
"Parse Error: Expected key" << JsonKeys::setting());
128 const QString &location = obj[JsonKeys::dbusLocation()].toString();
129 const QString &key = obj[JsonKeys::dbusKey()].toString();
130 const QString &providerString = obj[JsonKeys::provider()].toString();
131 const QString &settingString = obj[JsonKeys::setting()].toString();
132 PARSE(provider, Provider, providerString);
133 PARSE(setting, Setting, settingString);
134 const DBusKey dkey(location, key);
135 CHECK (!m_signalMap.contains(dkey),
"Duplicate key" << location << key);
136 m_signalMap.insert(dkey, ChangeSignal(provider, setting));
141 if (m_signalMap.count() > 0)
142 qCInfo(lcQpaThemeDBus) <<
"Successfully imported" << fileName;
144 qCWarning(lcQpaThemeDBus) <<
"No data imported from" << fileName <<
"falling back to default.";
147 const int count = m_signalMap.count();
151 qCDebug(lcQpaThemeDBus) <<
"Listening to" << count <<
"signals:";
152 for (
auto it = m_signalMap.constBegin(); it != m_signalMap.constEnd(); ++it) {
153 qDebug() << it.key().key << it.key().location <<
"mapped to"
154 << it.value().provider << it.value().setting;
162 Q_ASSERT(!m_signalMap.isEmpty());
163 Q_ASSERT(!fileName.isEmpty());
164 QFile file(fileName);
165 if (!file.open(QIODevice::WriteOnly)) {
166 qCWarning(lcQpaThemeDBus) << fileName <<
"could not be opened for writing.";
171 for (
auto sig = m_signalMap.constBegin(); sig != m_signalMap.constEnd(); ++sig) {
172 const DBusKey &dkey = sig.key();
173 const ChangeSignal &csig = sig.value();
175 obj[
JsonKeys::dbusLocation()] = dkey.location;
176 obj[
JsonKeys::dbusKey()] = dkey.key;
177 obj[JsonKeys::provider()] = QLatin1StringView(QMetaEnum::fromType<Provider>()
178 .valueToKey(
static_cast<
int>(csig.provider)));
179 obj[JsonKeys::setting()] = QLatin1StringView(QMetaEnum::fromType<Setting>()
180 .valueToKey(
static_cast<
int>(csig.setting)));
184 obj[
JsonKeys::dbusSignals()] = sigs;
188 file.write(doc.toJson());
195 const QString &loadJsonFile = qEnvironmentVariable(
"QT_QPA_DBUS_SIGNALS");
196 if (!loadJsonFile.isEmpty())
197 loadJson(loadJsonFile);
198 if (!m_signalMap.isEmpty())
201 m_signalMap.insert(DBusKey(
"org.kde.kdeglobals.KDE"_L1,
"widgetStyle"_L1),
202 ChangeSignal(Provider::Kde, Setting::ApplicationStyle));
204 m_signalMap.insert(DBusKey(
"org.kde.kdeglobals.General"_L1,
"ColorScheme"_L1),
205 ChangeSignal(Provider::Kde, Setting::Theme));
207 m_signalMap.insert(DBusKey(
"org.gnome.desktop.interface"_L1,
"gtk-theme"_L1),
208 ChangeSignal(Provider::Gtk, Setting::Theme));
210 using namespace QDBusSettings;
211 m_signalMap.insert(DBusKey(XdgSettings::AppearanceNamespace, XdgSettings::ColorSchemeKey),
212 ChangeSignal(Provider::Gnome, Setting::ColorScheme));
214 m_signalMap.insert(DBusKey(XdgSettings::AppearanceNamespace, XdgSettings::ContrastKey),
215 ChangeSignal(Provider::Gnome, Setting::Contrast));
218 m_signalMap.insert(DBusKey(GnomeSettings::AllyNamespace, GnomeSettings::ContrastKey),
219 ChangeSignal(Provider::Gnome, Setting::Contrast));
221 const QString &saveJsonFile = qEnvironmentVariable(
"QT_QPA_DBUS_SIGNALS_SAVE");
222 if (!saveJsonFile.isEmpty())
223 saveJson(saveJsonFile);
226std::optional<QDBusListener::ChangeSignal>
227 QDBusListener::findSignal(
const QString &location,
const QString &key)
const
229 const DBusKey dkey(location, key);
230 std::optional<QDBusListener::ChangeSignal> ret;
231 const auto it = m_signalMap.find(dkey);
232 if (it != m_signalMap.cend())
233 ret.emplace(it.value());
238void QDBusListener::onSettingChanged(
const QString &location,
const QString &key,
const QDBusVariant &value)
240 auto sig = findSignal(location, key);
241 if (!sig.has_value())
244 const Setting setting = sig.value().setting;
245 QVariant settingValue = value.variant();
248 case Setting::ColorScheme:
249 settingValue.setValue(QDBusSettings::XdgSettings::convertColorScheme(settingValue));
251 case Setting::Contrast:
252 using namespace QDBusSettings;
255 if (key == XdgSettings::ContrastKey)
256 settingValue.setValue(XdgSettings::convertContrastPreference(settingValue));
257 else if (key == GnomeSettings::ContrastKey)
258 settingValue.setValue(GnomeSettings::convertContrastPreference(settingValue));
260 Q_UNREACHABLE_IMPL();
266 emit settingChanged(sig.value().provider, setting, settingValue);
\inmodule QtCore\reentrant
Q_STATIC_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core")
#define PARSE(var, enumeration, string)