1// Copyright (C) 2022 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 "qgenericunixthemes_p.h"
5
6#include <QPalette>
7#include <QFont>
8#include <QGuiApplication>
9#include <QDir>
10#include <QFileInfo>
11#include <QFile>
12#include <QDebug>
13#include <QHash>
14#include <QLoggingCategory>
15#include <QVariant>
16#include <QStandardPaths>
17#include <QStringList>
18#if QT_CONFIG(mimetype)
19#include <QMimeDatabase>
20#endif
21#if QT_CONFIG(settings)
22#include <QSettings>
23#endif
24
25#include <qpa/qplatformfontdatabase.h> // lcQpaFonts
26#include <qpa/qplatformintegration.h>
27#include <qpa/qplatformservices.h>
28#include <qpa/qplatformdialoghelper.h>
29#include <qpa/qplatformtheme_p.h>
30
31#include <private/qguiapplication_p.h>
32#ifndef QT_NO_DBUS
33#include <QDBusConnectionInterface>
34#include <private/qdbusplatformmenu_p.h>
35#include <private/qdbusmenubar_p.h>
36#include <private/qflatmap_p.h>
37#include <QJsonDocument>
38#include <QJsonArray>
39#include <QJsonObject>
40#include <QJsonValue>
41#include <QJsonParseError>
42#endif
43#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
44#include <private/qdbustrayicon_p.h>
45#endif
46
47#include <algorithm>
48
49QT_BEGIN_NAMESPACE
50#ifndef QT_NO_DBUS
51Q_LOGGING_CATEGORY(lcQpaThemeDBus, "qt.qpa.theme.dbus")
52#endif
53
54using namespace Qt::StringLiterals;
55
56Q_DECLARE_LOGGING_CATEGORY(qLcTray)
57
58ResourceHelper::ResourceHelper()
59{
60 std::fill(palettes, palettes + QPlatformTheme::NPalettes, static_cast<QPalette *>(nullptr));
61 std::fill(fonts, fonts + QPlatformTheme::NFonts, static_cast<QFont *>(nullptr));
62}
63
64void ResourceHelper::clear()
65{
66 qDeleteAll(begin: palettes, end: palettes + QPlatformTheme::NPalettes);
67 qDeleteAll(begin: fonts, end: fonts + QPlatformTheme::NFonts);
68 std::fill(palettes, palettes + QPlatformTheme::NPalettes, static_cast<QPalette *>(nullptr));
69 std::fill(fonts, fonts + QPlatformTheme::NFonts, static_cast<QFont *>(nullptr));
70}
71
72const char *QGenericUnixTheme::name = "generic";
73
74// Default system font, corresponding to the value returned by 4.8 for
75// XRender/FontConfig which we can now assume as default.
76static const char defaultSystemFontNameC[] = "Sans Serif";
77static const char defaultFixedFontNameC[] = "monospace";
78enum { defaultSystemFontSize = 9 };
79
80#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
81static bool isDBusTrayAvailable() {
82 static bool dbusTrayAvailable = false;
83 static bool dbusTrayAvailableKnown = false;
84 if (!dbusTrayAvailableKnown) {
85 QDBusMenuConnection conn;
86 if (conn.isWatcherRegistered())
87 dbusTrayAvailable = true;
88 dbusTrayAvailableKnown = true;
89 qCDebug(qLcTray) << "D-Bus tray available:" << dbusTrayAvailable;
90 }
91 return dbusTrayAvailable;
92}
93#endif
94
95static QString mouseCursorTheme()
96{
97 static QString themeName = qEnvironmentVariable(varName: "XCURSOR_THEME");
98 return themeName;
99}
100
101static QSize mouseCursorSize()
102{
103 constexpr int defaultCursorSize = 24;
104 static const int xCursorSize = qEnvironmentVariableIntValue(varName: "XCURSOR_SIZE");
105 static const int s = xCursorSize > 0 ? xCursorSize : defaultCursorSize;
106 return QSize(s, s);
107}
108
109#ifndef QT_NO_DBUS
110static bool checkDBusGlobalMenuAvailable()
111{
112 const QDBusConnection connection = QDBusConnection::sessionBus();
113 static const QString registrarService = QStringLiteral("com.canonical.AppMenu.Registrar");
114 if (const auto iface = connection.interface())
115 return iface->isServiceRegistered(serviceName: registrarService);
116 return false;
117}
118
119static bool isDBusGlobalMenuAvailable()
120{
121 static bool dbusGlobalMenuAvailable = checkDBusGlobalMenuAvailable();
122 return dbusGlobalMenuAvailable;
123}
124
125/*!
126 * \internal
127 * The QGenericUnixThemeDBusListener class listens to the SettingChanged DBus signal
128 * and translates it into combinations of the enums \c Provider and \c Setting.
129 * Upon construction, it logs success/failure of the DBus connection.
130 *
131 * The signal settingChanged delivers the normalized setting type and the new value as a string.
132 * It is emitted on known setting types only.
133 */
134
135class QGenericUnixThemeDBusListener : public QObject
136{
137 Q_OBJECT
138
139public:
140
141 enum class Provider {
142 Kde,
143 Gtk,
144 Gnome,
145 };
146 Q_ENUM(Provider)
147
148 enum class Setting {
149 Theme,
150 ApplicationStyle,
151 ColorTheme,
152 };
153 Q_ENUM(Setting)
154
155 QGenericUnixThemeDBusListener();
156 QGenericUnixThemeDBusListener(const QString &service, const QString &path,
157 const QString &interface, const QString &signal);
158
159private Q_SLOTS:
160 void onSettingChanged(const QString &location, const QString &key, const QDBusVariant &value);
161
162Q_SIGNALS:
163 void settingChanged(QGenericUnixThemeDBusListener::Provider provider,
164 QGenericUnixThemeDBusListener::Setting setting,
165 const QString &value);
166
167private:
168 struct DBusKey
169 {
170 QString location;
171 QString key;
172 DBusKey(const QString &loc, const QString &k) : location(loc), key(k) {};
173 bool operator<(const DBusKey &other) const
174 {
175 return location + key < other.location + other.key;
176 }
177 };
178
179 struct ChangeSignal
180 {
181 Provider provider;
182 Setting setting;
183 ChangeSignal(Provider p, Setting s) : provider(p), setting(s) {}
184 ChangeSignal() {}
185 };
186
187 // Json keys
188 static constexpr QLatin1StringView s_dbusLocation = QLatin1StringView("DBusLocation");
189 static constexpr QLatin1StringView s_dbusKey = QLatin1StringView("DBusKey");
190 static constexpr QLatin1StringView s_provider = QLatin1StringView("Provider");
191 static constexpr QLatin1StringView s_setting = QLatin1StringView("Setting");
192 static constexpr QLatin1StringView s_signals = QLatin1StringView("DbusSignals");
193 static constexpr QLatin1StringView s_root = QLatin1StringView("Qt.qpa.DBusSignals");
194
195 QFlatMap <DBusKey, ChangeSignal> m_signalMap;
196
197 void init(const QString &service, const QString &path,
198 const QString &interface, const QString &signal);
199
200 std::optional<ChangeSignal> findSignal(const QString &location, const QString &key) const;
201 void populateSignalMap();
202 void loadJson(const QString &fileName);
203 void saveJson(const QString &fileName) const;
204};
205
206QGenericUnixThemeDBusListener::QGenericUnixThemeDBusListener(const QString &service,
207 const QString &path, const QString &interface, const QString &signal)
208{
209 init (service, path, interface, signal);
210}
211
212QGenericUnixThemeDBusListener::QGenericUnixThemeDBusListener()
213{
214 static constexpr QLatin1StringView service("");
215 static constexpr QLatin1StringView path("/org/freedesktop/portal/desktop");
216 static constexpr QLatin1StringView interface("org.freedesktop.portal.Settings");
217 static constexpr QLatin1StringView signal("SettingChanged");
218
219 init (service, path, interface, signal);
220}
221
222void QGenericUnixThemeDBusListener::init(const QString &service, const QString &path,
223 const QString &interface, const QString &signal)
224{
225 QDBusConnection dbus = QDBusConnection::sessionBus();
226 const bool dBusRunning = dbus.isConnected();
227 bool dBusSignalConnected = false;
228#define LOG service << path << interface << signal;
229
230 if (dBusRunning) {
231 populateSignalMap();
232 qRegisterMetaType<QDBusVariant>();
233 dBusSignalConnected = dbus.connect(service, path, interface, name: signal, receiver: this,
234 SLOT(onSettingChanged(QString,QString,QDBusVariant)));
235 }
236
237 if (dBusSignalConnected) {
238 // Connection successful
239 qCDebug(lcQpaThemeDBus) << LOG;
240 } else {
241 if (dBusRunning) {
242 // DBus running, but connection failed
243 qCWarning(lcQpaThemeDBus) << "DBus connection failed:" << LOG;
244 } else {
245 // DBus not running
246 qCWarning(lcQpaThemeDBus) << "Session DBus not running.";
247 }
248 qCWarning(lcQpaThemeDBus) << "Application will not react to setting changes.\n"
249 << "Check your DBus installation.";
250 }
251#undef LOG
252}
253
254void QGenericUnixThemeDBusListener::loadJson(const QString &fileName)
255{
256 Q_ASSERT(!fileName.isEmpty());
257#define CHECK(cond, warning)\
258 if (!cond) {\
259 qCWarning(lcQpaThemeDBus) << fileName << warning << "Falling back to default.";\
260 return;\
261 }
262
263#define PARSE(var, enumeration, string)\
264 enumeration var;\
265 {\
266 bool success;\
267 const int val = QMetaEnum::fromType<enumeration>().keyToValue(string.toLatin1(), &success);\
268 CHECK(success, "Parse Error: Invalid value" << string << "for" << #var);\
269 var = static_cast<enumeration>(val);\
270 }
271
272 QFile file(fileName);
273 CHECK(file.exists(), fileName << "doesn't exist.");
274 CHECK(file.open(QIODevice::ReadOnly), "could not be opened for reading.");
275
276 QJsonParseError error;
277 QJsonDocument doc = QJsonDocument::fromJson(json: file.readAll(), error: &error);
278 CHECK((error.error == QJsonParseError::NoError), error.errorString());
279 CHECK(doc.isObject(), "Parse Error: Expected root object" << s_root);
280
281 const QJsonObject &root = doc.object();
282 CHECK(root.contains(s_root), "Parse Error: Expected root object" << s_root);
283 CHECK(root[s_root][s_signals].isArray(), "Parse Error: Expected array" << s_signals);
284
285 const QJsonArray &sigs = root[s_root][s_signals].toArray();
286 CHECK((sigs.count() > 0), "Parse Error: Found empty array" << s_signals);
287
288 for (auto sig = sigs.constBegin(); sig != sigs.constEnd(); ++sig) {
289 CHECK(sig->isObject(), "Parse Error: Expected object array" << s_signals);
290 const QJsonObject &obj = sig->toObject();
291 CHECK(obj.contains(s_dbusLocation), "Parse Error: Expected key" << s_dbusLocation);
292 CHECK(obj.contains(s_dbusKey), "Parse Error: Expected key" << s_dbusKey);
293 CHECK(obj.contains(s_provider), "Parse Error: Expected key" << s_provider);
294 CHECK(obj.contains(s_setting), "Parse Error: Expected key" << s_setting);
295 const QString &location = obj[s_dbusLocation].toString();
296 const QString &key = obj[s_dbusKey].toString();
297 const QString &providerString = obj[s_provider].toString();
298 const QString &settingString = obj[s_setting].toString();
299 PARSE(provider, Provider, providerString);
300 PARSE(setting, Setting, settingString);
301 const DBusKey dkey(location, key);
302 CHECK (!m_signalMap.contains(dkey), "Duplicate key" << location << key);
303 m_signalMap.insert(key: dkey, value: ChangeSignal(provider, setting));
304 }
305#undef PARSE
306#undef CHECK
307
308 if (m_signalMap.count() > 0)
309 qCInfo(lcQpaThemeDBus) << "Successfully imported" << fileName;
310 else
311 qCWarning(lcQpaThemeDBus) << "No data imported from" << fileName << "falling back to default.";
312
313#ifdef QT_DEBUG
314 const int count = m_signalMap.count();
315 if (count == 0)
316 return;
317
318 qCDebug(lcQpaThemeDBus) << "Listening to" << count << "signals:";
319 for (auto it = m_signalMap.constBegin(); it != m_signalMap.constEnd(); ++it) {
320 qDebug() << it.key().key << it.key().location << "mapped to"
321 << it.value().provider << it.value().setting;
322 }
323
324#endif
325}
326
327void QGenericUnixThemeDBusListener::saveJson(const QString &fileName) const
328{
329 Q_ASSERT(!m_signalMap.isEmpty());
330 Q_ASSERT(!fileName.isEmpty());
331 QFile file(fileName);
332 if (!file.open(flags: QIODevice::WriteOnly)) {
333 qCWarning(lcQpaThemeDBus) << fileName << "could not be opened for writing.";
334 return;
335 }
336
337 QJsonArray sigs;
338 for (auto sig = m_signalMap.constBegin(); sig != m_signalMap.constEnd(); ++sig) {
339 const DBusKey &dkey = sig.key();
340 const ChangeSignal &csig = sig.value();
341 QJsonObject obj;
342 obj[s_dbusLocation] = dkey.location;
343 obj[s_dbusKey] = dkey.key;
344 obj[s_provider] = QLatin1StringView(QMetaEnum::fromType<Provider>()
345 .valueToKey(value: static_cast<int>(csig.provider)));
346 obj[s_setting] = QLatin1StringView(QMetaEnum::fromType<Setting>()
347 .valueToKey(value: static_cast<int>(csig.setting)));
348 sigs.append(value: obj);
349 }
350 QJsonObject obj;
351 obj[s_signals] = sigs;
352 QJsonObject root;
353 root[s_root] = obj;
354 QJsonDocument doc(root);
355 file.write(data: doc.toJson());
356 file.close();
357}
358
359void QGenericUnixThemeDBusListener::populateSignalMap()
360{
361 m_signalMap.clear();
362 const QString &loadJsonFile = qEnvironmentVariable(varName: "QT_QPA_DBUS_SIGNALS");
363 if (!loadJsonFile.isEmpty())
364 loadJson(fileName: loadJsonFile);
365 if (!m_signalMap.isEmpty())
366 return;
367
368 m_signalMap.insert(key: DBusKey("org.kde.kdeglobals.KDE"_L1, "widgetStyle"_L1),
369 value: ChangeSignal(Provider::Kde, Setting::ApplicationStyle));
370
371 m_signalMap.insert(key: DBusKey("org.kde.kdeglobals.General"_L1, "ColorScheme"_L1),
372 value: ChangeSignal(Provider::Kde, Setting::Theme));
373
374 m_signalMap.insert(key: DBusKey("org.gnome.desktop.interface"_L1, "gtk-theme"_L1),
375 value: ChangeSignal(Provider::Gtk, Setting::Theme));
376
377 m_signalMap.insert(key: DBusKey("org.freedesktop.appearance"_L1, "color-scheme"_L1),
378 value: ChangeSignal(Provider::Gnome, Setting::ColorTheme));
379
380 const QString &saveJsonFile = qEnvironmentVariable(varName: "QT_QPA_DBUS_SIGNALS_SAVE");
381 if (!saveJsonFile.isEmpty())
382 saveJson(fileName: saveJsonFile);
383}
384
385std::optional<QGenericUnixThemeDBusListener::ChangeSignal>
386 QGenericUnixThemeDBusListener::findSignal(const QString &location, const QString &key) const
387{
388 const DBusKey dkey(location, key);
389 std::optional<QGenericUnixThemeDBusListener::ChangeSignal> ret;
390 if (m_signalMap.contains(key: dkey))
391 ret.emplace(args: m_signalMap.value(key: dkey));
392
393 return ret;
394}
395
396void QGenericUnixThemeDBusListener::onSettingChanged(const QString &location, const QString &key, const QDBusVariant &value)
397{
398 auto sig = findSignal(location, key);
399 if (!sig.has_value())
400 return;
401
402 emit settingChanged(provider: sig.value().provider, setting: sig.value().setting, value: value.variant().toString());
403}
404
405#endif //QT_NO_DBUS
406
407class QGenericUnixThemePrivate : public QPlatformThemePrivate
408{
409public:
410 QGenericUnixThemePrivate()
411 : QPlatformThemePrivate()
412 , systemFont(QLatin1StringView(defaultSystemFontNameC), defaultSystemFontSize)
413 , fixedFont(QLatin1StringView(defaultFixedFontNameC), systemFont.pointSize())
414 {
415 fixedFont.setStyleHint(QFont::TypeWriter);
416 qCDebug(lcQpaFonts) << "default fonts: system" << systemFont << "fixed" << fixedFont;
417 }
418
419 const QFont systemFont;
420 QFont fixedFont;
421};
422
423QGenericUnixTheme::QGenericUnixTheme()
424 : QPlatformTheme(new QGenericUnixThemePrivate())
425{
426}
427
428const QFont *QGenericUnixTheme::font(Font type) const
429{
430 Q_D(const QGenericUnixTheme);
431 switch (type) {
432 case QPlatformTheme::SystemFont:
433 return &d->systemFont;
434 case QPlatformTheme::FixedFont:
435 return &d->fixedFont;
436 default:
437 return nullptr;
438 }
439}
440
441// Helper to return the icon theme paths from XDG.
442QStringList QGenericUnixTheme::xdgIconThemePaths()
443{
444 QStringList paths;
445 // Add home directory first in search path
446 const QFileInfo homeIconDir(QDir::homePath() + "/.icons"_L1);
447 if (homeIconDir.isDir())
448 paths.prepend(t: homeIconDir.absoluteFilePath());
449
450 paths.append(l: QStandardPaths::locateAll(type: QStandardPaths::GenericDataLocation,
451 QStringLiteral("icons"),
452 options: QStandardPaths::LocateDirectory));
453
454 return paths;
455}
456
457QStringList QGenericUnixTheme::iconFallbackPaths()
458{
459 QStringList paths;
460 const QFileInfo pixmapsIconsDir(QStringLiteral("/usr/share/pixmaps"));
461 if (pixmapsIconsDir.isDir())
462 paths.append(t: pixmapsIconsDir.absoluteFilePath());
463
464 return paths;
465}
466
467#ifndef QT_NO_DBUS
468QPlatformMenuBar *QGenericUnixTheme::createPlatformMenuBar() const
469{
470 if (isDBusGlobalMenuAvailable())
471 return new QDBusMenuBar();
472 return nullptr;
473}
474#endif
475
476#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
477QPlatformSystemTrayIcon *QGenericUnixTheme::createPlatformSystemTrayIcon() const
478{
479 if (isDBusTrayAvailable())
480 return new QDBusTrayIcon();
481 return nullptr;
482}
483#endif
484
485QVariant QGenericUnixTheme::themeHint(ThemeHint hint) const
486{
487 switch (hint) {
488 case QPlatformTheme::SystemIconFallbackThemeName:
489 return QVariant(QString(QStringLiteral("hicolor")));
490 case QPlatformTheme::IconThemeSearchPaths:
491 return xdgIconThemePaths();
492 case QPlatformTheme::IconFallbackSearchPaths:
493 return iconFallbackPaths();
494 case QPlatformTheme::DialogButtonBoxButtonsHaveIcons:
495 return QVariant(true);
496 case QPlatformTheme::StyleNames: {
497 QStringList styleNames;
498 styleNames << QStringLiteral("Fusion") << QStringLiteral("Windows");
499 return QVariant(styleNames);
500 }
501 case QPlatformTheme::KeyboardScheme:
502 return QVariant(int(X11KeyboardScheme));
503 case QPlatformTheme::UiEffects:
504 return QVariant(int(HoverEffect));
505 case QPlatformTheme::MouseCursorTheme:
506 return QVariant(mouseCursorTheme());
507 case QPlatformTheme::MouseCursorSize:
508 return QVariant(mouseCursorSize());
509 default:
510 break;
511 }
512 return QPlatformTheme::themeHint(hint);
513}
514
515// Helper functions for implementing QPlatformTheme::fileIcon() for XDG icon themes.
516static QList<QSize> availableXdgFileIconSizes()
517{
518 return QIcon::fromTheme(QStringLiteral("inode-directory")).availableSizes();
519}
520
521#if QT_CONFIG(mimetype)
522static QIcon xdgFileIcon(const QFileInfo &fileInfo)
523{
524 QMimeDatabase mimeDatabase;
525 QMimeType mimeType = mimeDatabase.mimeTypeForFile(fileInfo);
526 if (!mimeType.isValid())
527 return QIcon();
528 const QString &iconName = mimeType.iconName();
529 if (!iconName.isEmpty()) {
530 const QIcon icon = QIcon::fromTheme(name: iconName);
531 if (!icon.isNull())
532 return icon;
533 }
534 const QString &genericIconName = mimeType.genericIconName();
535 return genericIconName.isEmpty() ? QIcon() : QIcon::fromTheme(name: genericIconName);
536}
537#endif
538
539#if QT_CONFIG(settings)
540class QKdeThemePrivate : public QPlatformThemePrivate
541{
542
543public:
544 QKdeThemePrivate(const QStringList &kdeDirs, int kdeVersion);
545
546 static QString kdeGlobals(const QString &kdeDir, int kdeVersion)
547 {
548 if (kdeVersion > 4)
549 return kdeDir + "/kdeglobals"_L1;
550 return kdeDir + "/share/config/kdeglobals"_L1;
551 }
552
553 void refresh();
554 static QVariant readKdeSetting(const QString &key, const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings);
555 static void readKdeSystemPalette(const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings, QPalette *pal);
556 static QFont *kdeFont(const QVariant &fontValue);
557 static QStringList kdeIconThemeSearchPaths(const QStringList &kdeDirs);
558
559 const QStringList kdeDirs;
560 const int kdeVersion;
561
562 ResourceHelper resources;
563 QString iconThemeName;
564 QString iconFallbackThemeName;
565 QStringList styleNames;
566 int toolButtonStyle = Qt::ToolButtonTextBesideIcon;
567 int toolBarIconSize = 0;
568 bool singleClick = true;
569 bool showIconsOnPushButtons = true;
570 int wheelScrollLines = 3;
571 int doubleClickInterval = 400;
572 int startDragDist = 10;
573 int startDragTime = 500;
574 int cursorBlinkRate = 1000;
575 Qt::ColorScheme m_colorScheme = Qt::ColorScheme::Unknown;
576 void updateColorScheme(const QString &themeName);
577
578#ifndef QT_NO_DBUS
579private:
580 std::unique_ptr<QGenericUnixThemeDBusListener> dbus;
581 bool initDbus();
582 void settingChangedHandler(QGenericUnixThemeDBusListener::Provider provider,
583 QGenericUnixThemeDBusListener::Setting setting,
584 const QString &value);
585#endif // QT_NO_DBUS
586};
587
588#ifndef QT_NO_DBUS
589void QKdeThemePrivate::settingChangedHandler(QGenericUnixThemeDBusListener::Provider provider,
590 QGenericUnixThemeDBusListener::Setting setting,
591 const QString &value)
592{
593 if (provider != QGenericUnixThemeDBusListener::Provider::Kde)
594 return;
595
596 switch (setting) {
597 case QGenericUnixThemeDBusListener::Setting::ColorTheme:
598 qCDebug(lcQpaThemeDBus) << "KDE color theme changed to:" << value;
599 break;
600 case QGenericUnixThemeDBusListener::Setting::Theme:
601 qCDebug(lcQpaThemeDBus) << "KDE global theme changed to:" << value;
602 break;
603 case QGenericUnixThemeDBusListener::Setting::ApplicationStyle:
604 qCDebug(lcQpaThemeDBus) << "KDE application style changed to:" << value;
605 break;
606 }
607
608 refresh();
609}
610
611bool QKdeThemePrivate::initDbus()
612{
613 dbus.reset(p: new QGenericUnixThemeDBusListener());
614 Q_ASSERT(dbus);
615
616 // Wrap slot in a lambda to avoid inheriting QKdeThemePrivate from QObject
617 auto wrapper = [this](QGenericUnixThemeDBusListener::Provider provider,
618 QGenericUnixThemeDBusListener::Setting setting,
619 const QString &value) {
620 settingChangedHandler(provider, setting, value);
621 };
622
623 return QObject::connect(sender: dbus.get(), signal: &QGenericUnixThemeDBusListener::settingChanged, slot&: wrapper);
624}
625#endif // QT_NO_DBUS
626
627QKdeThemePrivate::QKdeThemePrivate(const QStringList &kdeDirs, int kdeVersion)
628 : kdeDirs(kdeDirs), kdeVersion(kdeVersion)
629{
630#ifndef QT_NO_DBUS
631 initDbus();
632#endif // QT_NO_DBUS
633}
634
635void QKdeThemePrivate::refresh()
636{
637 resources.clear();
638
639 toolButtonStyle = Qt::ToolButtonTextBesideIcon;
640 toolBarIconSize = 0;
641 styleNames.clear();
642 if (kdeVersion >= 5)
643 styleNames << QStringLiteral("breeze");
644 styleNames << QStringLiteral("Oxygen") << QStringLiteral("Fusion") << QStringLiteral("windows");
645 if (kdeVersion >= 5)
646 iconFallbackThemeName = iconThemeName = QStringLiteral("breeze");
647 else
648 iconFallbackThemeName = iconThemeName = QStringLiteral("oxygen");
649
650 QHash<QString, QSettings*> kdeSettings;
651
652 QPalette systemPalette = QPalette();
653 readKdeSystemPalette(kdeDirs, kdeVersion, kdeSettings, pal: &systemPalette);
654 resources.palettes[QPlatformTheme::SystemPalette] = new QPalette(systemPalette);
655 //## TODO tooltip color
656
657 const QVariant styleValue = readKdeSetting(QStringLiteral("widgetStyle"), kdeDirs, kdeVersion, kdeSettings);
658 if (styleValue.isValid()) {
659 const QString style = styleValue.toString();
660 if (style != styleNames.front())
661 styleNames.push_front(t: style);
662 }
663
664 const QVariant colorScheme = readKdeSetting(QStringLiteral("ColorScheme"), kdeDirs,
665 kdeVersion, kdeSettings);
666
667 if (colorScheme.isValid())
668 updateColorScheme(themeName: colorScheme.toString());
669 else
670 m_colorScheme = Qt::ColorScheme::Unknown;
671
672 const QVariant singleClickValue = readKdeSetting(QStringLiteral("KDE/SingleClick"), kdeDirs, kdeVersion, kdeSettings);
673 if (singleClickValue.isValid())
674 singleClick = singleClickValue.toBool();
675
676 const QVariant showIconsOnPushButtonsValue = readKdeSetting(QStringLiteral("KDE/ShowIconsOnPushButtons"), kdeDirs, kdeVersion, kdeSettings);
677 if (showIconsOnPushButtonsValue.isValid())
678 showIconsOnPushButtons = showIconsOnPushButtonsValue.toBool();
679
680 const QVariant themeValue = readKdeSetting(QStringLiteral("Icons/Theme"), kdeDirs, kdeVersion, kdeSettings);
681 if (themeValue.isValid())
682 iconThemeName = themeValue.toString();
683
684 const QVariant toolBarIconSizeValue = readKdeSetting(QStringLiteral("ToolbarIcons/Size"), kdeDirs, kdeVersion, kdeSettings);
685 if (toolBarIconSizeValue.isValid())
686 toolBarIconSize = toolBarIconSizeValue.toInt();
687
688 const QVariant toolbarStyleValue = readKdeSetting(QStringLiteral("Toolbar style/ToolButtonStyle"), kdeDirs, kdeVersion, kdeSettings);
689 if (toolbarStyleValue.isValid()) {
690 const QString toolBarStyle = toolbarStyleValue.toString();
691 if (toolBarStyle == "TextBesideIcon"_L1)
692 toolButtonStyle = Qt::ToolButtonTextBesideIcon;
693 else if (toolBarStyle == "TextOnly"_L1)
694 toolButtonStyle = Qt::ToolButtonTextOnly;
695 else if (toolBarStyle == "TextUnderIcon"_L1)
696 toolButtonStyle = Qt::ToolButtonTextUnderIcon;
697 }
698
699 const QVariant wheelScrollLinesValue = readKdeSetting(QStringLiteral("KDE/WheelScrollLines"), kdeDirs, kdeVersion, kdeSettings);
700 if (wheelScrollLinesValue.isValid())
701 wheelScrollLines = wheelScrollLinesValue.toInt();
702
703 const QVariant doubleClickIntervalValue = readKdeSetting(QStringLiteral("KDE/DoubleClickInterval"), kdeDirs, kdeVersion, kdeSettings);
704 if (doubleClickIntervalValue.isValid())
705 doubleClickInterval = doubleClickIntervalValue.toInt();
706
707 const QVariant startDragDistValue = readKdeSetting(QStringLiteral("KDE/StartDragDist"), kdeDirs, kdeVersion, kdeSettings);
708 if (startDragDistValue.isValid())
709 startDragDist = startDragDistValue.toInt();
710
711 const QVariant startDragTimeValue = readKdeSetting(QStringLiteral("KDE/StartDragTime"), kdeDirs, kdeVersion, kdeSettings);
712 if (startDragTimeValue.isValid())
713 startDragTime = startDragTimeValue.toInt();
714
715 const QVariant cursorBlinkRateValue = readKdeSetting(QStringLiteral("KDE/CursorBlinkRate"), kdeDirs, kdeVersion, kdeSettings);
716 if (cursorBlinkRateValue.isValid()) {
717 cursorBlinkRate = cursorBlinkRateValue.toInt();
718 cursorBlinkRate = cursorBlinkRate > 0 ? qBound(min: 200, val: cursorBlinkRate, max: 2000) : 0;
719 }
720
721 // Read system font, ignore 'smallestReadableFont'
722 if (QFont *systemFont = kdeFont(fontValue: readKdeSetting(QStringLiteral("font"), kdeDirs, kdeVersion, kdeSettings)))
723 resources.fonts[QPlatformTheme::SystemFont] = systemFont;
724 else
725 resources.fonts[QPlatformTheme::SystemFont] = new QFont(QLatin1StringView(defaultSystemFontNameC), defaultSystemFontSize);
726
727 if (QFont *fixedFont = kdeFont(fontValue: readKdeSetting(QStringLiteral("fixed"), kdeDirs, kdeVersion, kdeSettings))) {
728 resources.fonts[QPlatformTheme::FixedFont] = fixedFont;
729 } else {
730 fixedFont = new QFont(QLatin1StringView(defaultFixedFontNameC), defaultSystemFontSize);
731 fixedFont->setStyleHint(QFont::TypeWriter);
732 resources.fonts[QPlatformTheme::FixedFont] = fixedFont;
733 }
734
735 if (QFont *menuFont = kdeFont(fontValue: readKdeSetting(QStringLiteral("menuFont"), kdeDirs, kdeVersion, kdeSettings))) {
736 resources.fonts[QPlatformTheme::MenuFont] = menuFont;
737 resources.fonts[QPlatformTheme::MenuBarFont] = new QFont(*menuFont);
738 }
739
740 if (QFont *toolBarFont = kdeFont(fontValue: readKdeSetting(QStringLiteral("toolBarFont"), kdeDirs, kdeVersion, kdeSettings)))
741 resources.fonts[QPlatformTheme::ToolButtonFont] = toolBarFont;
742
743 QWindowSystemInterface::handleThemeChange();
744
745 qCDebug(lcQpaFonts) << "default fonts: system" << resources.fonts[QPlatformTheme::SystemFont]
746 << "fixed" << resources.fonts[QPlatformTheme::FixedFont];
747 qDeleteAll(c: kdeSettings);
748}
749
750QVariant QKdeThemePrivate::readKdeSetting(const QString &key, const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings)
751{
752 for (const QString &kdeDir : kdeDirs) {
753 QSettings *settings = kdeSettings.value(key: kdeDir);
754 if (!settings) {
755 const QString kdeGlobalsPath = kdeGlobals(kdeDir, kdeVersion);
756 if (QFileInfo(kdeGlobalsPath).isReadable()) {
757 settings = new QSettings(kdeGlobalsPath, QSettings::IniFormat);
758 kdeSettings.insert(key: kdeDir, value: settings);
759 }
760 }
761 if (settings) {
762 const QVariant value = settings->value(key);
763 if (value.isValid())
764 return value;
765 }
766 }
767 return QVariant();
768}
769
770// Reads the color from the KDE configuration, and store it in the
771// palette with the given color role if found.
772static inline bool kdeColor(QPalette *pal, QPalette::ColorRole role, const QVariant &value)
773{
774 if (!value.isValid())
775 return false;
776 const QStringList values = value.toStringList();
777 if (values.size() != 3)
778 return false;
779 pal->setBrush(acr: role, abrush: QColor(values.at(i: 0).toInt(), values.at(i: 1).toInt(), values.at(i: 2).toInt()));
780 return true;
781}
782
783void QKdeThemePrivate::readKdeSystemPalette(const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings, QPalette *pal)
784{
785 if (!kdeColor(pal, role: QPalette::Button, value: readKdeSetting(QStringLiteral("Colors:Button/BackgroundNormal"), kdeDirs, kdeVersion, kdeSettings))) {
786 // kcolorscheme.cpp: SetDefaultColors
787 const QColor defaultWindowBackground(214, 210, 208);
788 const QColor defaultButtonBackground(223, 220, 217);
789 *pal = QPalette(defaultButtonBackground, defaultWindowBackground);
790 return;
791 }
792
793 kdeColor(pal, role: QPalette::Window, value: readKdeSetting(QStringLiteral("Colors:Window/BackgroundNormal"), kdeDirs, kdeVersion, kdeSettings));
794 kdeColor(pal, role: QPalette::Text, value: readKdeSetting(QStringLiteral("Colors:View/ForegroundNormal"), kdeDirs, kdeVersion, kdeSettings));
795 kdeColor(pal, role: QPalette::WindowText, value: readKdeSetting(QStringLiteral("Colors:Window/ForegroundNormal"), kdeDirs, kdeVersion, kdeSettings));
796 kdeColor(pal, role: QPalette::Base, value: readKdeSetting(QStringLiteral("Colors:View/BackgroundNormal"), kdeDirs, kdeVersion, kdeSettings));
797 kdeColor(pal, role: QPalette::Highlight, value: readKdeSetting(QStringLiteral("Colors:Selection/BackgroundNormal"), kdeDirs, kdeVersion, kdeSettings));
798 kdeColor(pal, role: QPalette::HighlightedText, value: readKdeSetting(QStringLiteral("Colors:Selection/ForegroundNormal"), kdeDirs, kdeVersion, kdeSettings));
799 kdeColor(pal, role: QPalette::AlternateBase, value: readKdeSetting(QStringLiteral("Colors:View/BackgroundAlternate"), kdeDirs, kdeVersion, kdeSettings));
800 kdeColor(pal, role: QPalette::ButtonText, value: readKdeSetting(QStringLiteral("Colors:Button/ForegroundNormal"), kdeDirs, kdeVersion, kdeSettings));
801 kdeColor(pal, role: QPalette::Link, value: readKdeSetting(QStringLiteral("Colors:View/ForegroundLink"), kdeDirs, kdeVersion, kdeSettings));
802 kdeColor(pal, role: QPalette::LinkVisited, value: readKdeSetting(QStringLiteral("Colors:View/ForegroundVisited"), kdeDirs, kdeVersion, kdeSettings));
803 kdeColor(pal, role: QPalette::ToolTipBase, value: readKdeSetting(QStringLiteral("Colors:Tooltip/BackgroundNormal"), kdeDirs, kdeVersion, kdeSettings));
804 kdeColor(pal, role: QPalette::ToolTipText, value: readKdeSetting(QStringLiteral("Colors:Tooltip/ForegroundNormal"), kdeDirs, kdeVersion, kdeSettings));
805
806 // The above code sets _all_ color roles to "normal" colors. In KDE, the disabled
807 // color roles are calculated by applying various effects described in kdeglobals.
808 // We use a bit simpler approach here, similar logic than in qt_palette_from_color().
809 const QColor button = pal->color(cr: QPalette::Button);
810 int h, s, v;
811 button.getHsv(h: &h, s: &s, v: &v);
812
813 const QBrush whiteBrush = QBrush(Qt::white);
814 const QBrush buttonBrush = QBrush(button);
815 const QBrush buttonBrushDark = QBrush(button.darker(f: v > 128 ? 200 : 50));
816 const QBrush buttonBrushDark150 = QBrush(button.darker(f: v > 128 ? 150 : 75));
817 const QBrush buttonBrushLight150 = QBrush(button.lighter(f: v > 128 ? 150 : 75));
818 const QBrush buttonBrushLight = QBrush(button.lighter(f: v > 128 ? 200 : 50));
819
820 pal->setBrush(cg: QPalette::Disabled, cr: QPalette::WindowText, brush: buttonBrushDark);
821 pal->setBrush(cg: QPalette::Disabled, cr: QPalette::ButtonText, brush: buttonBrushDark);
822 pal->setBrush(cg: QPalette::Disabled, cr: QPalette::Button, brush: buttonBrush);
823 pal->setBrush(cg: QPalette::Disabled, cr: QPalette::Text, brush: buttonBrushDark);
824 pal->setBrush(cg: QPalette::Disabled, cr: QPalette::BrightText, brush: whiteBrush);
825 pal->setBrush(cg: QPalette::Disabled, cr: QPalette::Base, brush: buttonBrush);
826 pal->setBrush(cg: QPalette::Disabled, cr: QPalette::Window, brush: buttonBrush);
827 pal->setBrush(cg: QPalette::Disabled, cr: QPalette::Highlight, brush: buttonBrushDark150);
828 pal->setBrush(cg: QPalette::Disabled, cr: QPalette::HighlightedText, brush: buttonBrushLight150);
829
830 // set calculated colors for all groups
831 pal->setBrush(acr: QPalette::Light, abrush: buttonBrushLight);
832 pal->setBrush(acr: QPalette::Midlight, abrush: buttonBrushLight150);
833 pal->setBrush(acr: QPalette::Mid, abrush: buttonBrushDark150);
834 pal->setBrush(acr: QPalette::Dark, abrush: buttonBrushDark);
835}
836
837/*!
838 \class QKdeTheme
839 \brief QKdeTheme is a theme implementation for the KDE desktop (version 4 or higher).
840 \since 5.0
841 \internal
842 \ingroup qpa
843*/
844
845const char *QKdeTheme::name = "kde";
846
847QKdeTheme::QKdeTheme(const QStringList& kdeDirs, int kdeVersion)
848 : QPlatformTheme(new QKdeThemePrivate(kdeDirs,kdeVersion))
849{
850 d_func()->refresh();
851}
852
853QFont *QKdeThemePrivate::kdeFont(const QVariant &fontValue)
854{
855 if (fontValue.isValid()) {
856 // Read font value: Might be a QStringList as KDE stores fonts without quotes.
857 // Also retrieve the family for the constructor since we cannot use the
858 // default constructor of QFont, which accesses QGuiApplication::systemFont()
859 // causing recursion.
860 QString fontDescription;
861 QString fontFamily;
862 if (fontValue.userType() == QMetaType::QStringList) {
863 const QStringList list = fontValue.toStringList();
864 if (!list.isEmpty()) {
865 fontFamily = list.first();
866 fontDescription = list.join(sep: u',');
867 }
868 } else {
869 fontDescription = fontFamily = fontValue.toString();
870 }
871 if (!fontDescription.isEmpty()) {
872 QFont font(fontFamily);
873 if (font.fromString(fontDescription))
874 return new QFont(font);
875 }
876 }
877 return nullptr;
878}
879
880
881QStringList QKdeThemePrivate::kdeIconThemeSearchPaths(const QStringList &kdeDirs)
882{
883 QStringList paths = QGenericUnixTheme::xdgIconThemePaths();
884 const QString iconPath = QStringLiteral("/share/icons");
885 for (const QString &candidate : kdeDirs) {
886 const QFileInfo fi(candidate + iconPath);
887 if (fi.isDir())
888 paths.append(t: fi.absoluteFilePath());
889 }
890 return paths;
891}
892
893QVariant QKdeTheme::themeHint(QPlatformTheme::ThemeHint hint) const
894{
895 Q_D(const QKdeTheme);
896 switch (hint) {
897 case QPlatformTheme::UseFullScreenForPopupMenu:
898 return QVariant(true);
899 case QPlatformTheme::DialogButtonBoxButtonsHaveIcons:
900 return QVariant(d->showIconsOnPushButtons);
901 case QPlatformTheme::DialogButtonBoxLayout:
902 return QVariant(QPlatformDialogHelper::KdeLayout);
903 case QPlatformTheme::ToolButtonStyle:
904 return QVariant(d->toolButtonStyle);
905 case QPlatformTheme::ToolBarIconSize:
906 return QVariant(d->toolBarIconSize);
907 case QPlatformTheme::SystemIconThemeName:
908 return QVariant(d->iconThemeName);
909 case QPlatformTheme::SystemIconFallbackThemeName:
910 return QVariant(d->iconFallbackThemeName);
911 case QPlatformTheme::IconThemeSearchPaths:
912 return QVariant(d->kdeIconThemeSearchPaths(kdeDirs: d->kdeDirs));
913 case QPlatformTheme::IconPixmapSizes:
914 return QVariant::fromValue(value: availableXdgFileIconSizes());
915 case QPlatformTheme::StyleNames:
916 return QVariant(d->styleNames);
917 case QPlatformTheme::KeyboardScheme:
918 return QVariant(int(KdeKeyboardScheme));
919 case QPlatformTheme::ItemViewActivateItemOnSingleClick:
920 return QVariant(d->singleClick);
921 case QPlatformTheme::WheelScrollLines:
922 return QVariant(d->wheelScrollLines);
923 case QPlatformTheme::MouseDoubleClickInterval:
924 return QVariant(d->doubleClickInterval);
925 case QPlatformTheme::StartDragTime:
926 return QVariant(d->startDragTime);
927 case QPlatformTheme::StartDragDistance:
928 return QVariant(d->startDragDist);
929 case QPlatformTheme::CursorFlashTime:
930 return QVariant(d->cursorBlinkRate);
931 case QPlatformTheme::UiEffects:
932 return QVariant(int(HoverEffect));
933 case QPlatformTheme::MouseCursorTheme:
934 return QVariant(mouseCursorTheme());
935 case QPlatformTheme::MouseCursorSize:
936 return QVariant(mouseCursorSize());
937 default:
938 break;
939 }
940 return QPlatformTheme::themeHint(hint);
941}
942
943QIcon QKdeTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions) const
944{
945#if QT_CONFIG(mimetype)
946 return xdgFileIcon(fileInfo);
947#else
948 Q_UNUSED(fileInfo);
949 return QIcon();
950#endif
951}
952
953Qt::ColorScheme QKdeTheme::colorScheme() const
954{
955 return d_func()->m_colorScheme;
956}
957
958/*!
959 \internal
960 \brief QKdeTheme::setColorScheme - guess and set appearance for unix themes.
961 KDE themes do not have an appearance property.
962 The key words "dark" or "light" should be part of the theme name.
963 This is, however, not a mandatory convention.
964
965 If \param themeName contains a key word, the respective appearance is set.
966 If it doesn't, the appearance is heuristically determined by comparing text and base color
967 of the system palette.
968 */
969void QKdeThemePrivate::updateColorScheme(const QString &themeName)
970{
971 if (themeName.contains(s: QLatin1StringView("light"), cs: Qt::CaseInsensitive)) {
972 m_colorScheme = Qt::ColorScheme::Light;
973 return;
974 }
975 if (themeName.contains(s: QLatin1StringView("dark"), cs: Qt::CaseInsensitive)) {
976 m_colorScheme = Qt::ColorScheme::Dark;
977 return;
978 }
979
980 if (systemPalette) {
981 if (systemPalette->text().color().lightness() < systemPalette->base().color().lightness()) {
982 m_colorScheme = Qt::ColorScheme::Light;
983 return;
984 }
985 if (systemPalette->text().color().lightness() > systemPalette->base().color().lightness()) {
986 m_colorScheme = Qt::ColorScheme::Dark;
987 return;
988 }
989 }
990
991 m_colorScheme = Qt::ColorScheme::Unknown;
992}
993
994
995const QPalette *QKdeTheme::palette(Palette type) const
996{
997 Q_D(const QKdeTheme);
998 return d->resources.palettes[type];
999}
1000
1001const QFont *QKdeTheme::font(Font type) const
1002{
1003 Q_D(const QKdeTheme);
1004 return d->resources.fonts[type];
1005}
1006
1007QPlatformTheme *QKdeTheme::createKdeTheme()
1008{
1009 const QByteArray kdeVersionBA = qgetenv(varName: "KDE_SESSION_VERSION");
1010 const int kdeVersion = kdeVersionBA.toInt();
1011 if (kdeVersion < 4)
1012 return nullptr;
1013
1014 if (kdeVersion > 4)
1015 // Plasma 5 follows XDG spec
1016 // but uses the same config file format:
1017 return new QKdeTheme(QStandardPaths::standardLocations(type: QStandardPaths::GenericConfigLocation), kdeVersion);
1018
1019 // Determine KDE prefixes in the following priority order:
1020 // - KDEHOME and KDEDIRS environment variables
1021 // - ~/.kde(<version>)
1022 // - read prefixes from /etc/kde<version>rc
1023 // - fallback to /etc/kde<version>
1024
1025 QStringList kdeDirs;
1026 const QString kdeHomePathVar = QFile::decodeName(localFileName: qgetenv(varName: "KDEHOME"));
1027 if (!kdeHomePathVar.isEmpty())
1028 kdeDirs += kdeHomePathVar;
1029
1030 const QString kdeDirsVar = QFile::decodeName(localFileName: qgetenv(varName: "KDEDIRS"));
1031 if (!kdeDirsVar.isEmpty())
1032 kdeDirs += kdeDirsVar.split(sep: u':', behavior: Qt::SkipEmptyParts);
1033
1034 const QString kdeVersionHomePath = QDir::homePath() + "/.kde"_L1 + QLatin1StringView(kdeVersionBA);
1035 if (QFileInfo(kdeVersionHomePath).isDir())
1036 kdeDirs += kdeVersionHomePath;
1037
1038 const QString kdeHomePath = QDir::homePath() + "/.kde"_L1;
1039 if (QFileInfo(kdeHomePath).isDir())
1040 kdeDirs += kdeHomePath;
1041
1042 const QString kdeRcPath = "/etc/kde"_L1 + QLatin1StringView(kdeVersionBA) + "rc"_L1;
1043 if (QFileInfo(kdeRcPath).isReadable()) {
1044 QSettings kdeSettings(kdeRcPath, QSettings::IniFormat);
1045 kdeSettings.beginGroup(QStringLiteral("Directories-default"));
1046 kdeDirs += kdeSettings.value(QStringLiteral("prefixes")).toStringList();
1047 }
1048
1049 const QString kdeVersionPrefix = "/etc/kde"_L1 + QLatin1StringView(kdeVersionBA);
1050 if (QFileInfo(kdeVersionPrefix).isDir())
1051 kdeDirs += kdeVersionPrefix;
1052
1053 kdeDirs.removeDuplicates();
1054 if (kdeDirs.isEmpty()) {
1055 qWarning(msg: "Unable to determine KDE dirs");
1056 return nullptr;
1057 }
1058
1059 return new QKdeTheme(kdeDirs, kdeVersion);
1060}
1061
1062#ifndef QT_NO_DBUS
1063QPlatformMenuBar *QKdeTheme::createPlatformMenuBar() const
1064{
1065 if (isDBusGlobalMenuAvailable())
1066 return new QDBusMenuBar();
1067 return nullptr;
1068}
1069#endif
1070
1071#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
1072QPlatformSystemTrayIcon *QKdeTheme::createPlatformSystemTrayIcon() const
1073{
1074 if (isDBusTrayAvailable())
1075 return new QDBusTrayIcon();
1076 return nullptr;
1077}
1078#endif
1079
1080#endif // settings
1081
1082/*!
1083 \class QGnomeTheme
1084 \brief QGnomeTheme is a theme implementation for the Gnome desktop.
1085 \since 5.0
1086 \internal
1087 \ingroup qpa
1088*/
1089
1090const char *QGnomeTheme::name = "gnome";
1091
1092class QGnomeThemePrivate : public QPlatformThemePrivate
1093{
1094public:
1095 QGnomeThemePrivate();
1096 ~QGnomeThemePrivate();
1097
1098 void configureFonts(const QString &gtkFontName) const
1099 {
1100 Q_ASSERT(!systemFont);
1101 const int split = gtkFontName.lastIndexOf(c: QChar::Space);
1102 float size = QStringView{gtkFontName}.mid(pos: split + 1).toFloat();
1103 QString fontName = gtkFontName.left(n: split);
1104
1105 systemFont = new QFont(fontName, size);
1106 fixedFont = new QFont(QLatin1StringView(defaultFixedFontNameC), systemFont->pointSize());
1107 fixedFont->setStyleHint(QFont::TypeWriter);
1108 qCDebug(lcQpaFonts) << "default fonts: system" << systemFont << "fixed" << fixedFont;
1109 }
1110
1111 mutable QFont *systemFont = nullptr;
1112 mutable QFont *fixedFont = nullptr;
1113
1114#ifndef QT_NO_DBUS
1115 Qt::ColorScheme m_colorScheme = Qt::ColorScheme::Unknown;
1116private:
1117 std::unique_ptr<QGenericUnixThemeDBusListener> dbus;
1118 bool initDbus();
1119 void updateColorScheme(const QString &themeName);
1120#endif // QT_NO_DBUS
1121};
1122
1123QGnomeThemePrivate::QGnomeThemePrivate()
1124{
1125#ifndef QT_NO_DBUS
1126 initDbus();
1127#endif // QT_NO_DBUS
1128}
1129QGnomeThemePrivate::~QGnomeThemePrivate()
1130{
1131 if (systemFont)
1132 delete systemFont;
1133 if (fixedFont)
1134 delete fixedFont;
1135}
1136
1137#ifndef QT_NO_DBUS
1138bool QGnomeThemePrivate::initDbus()
1139{
1140 dbus.reset(p: new QGenericUnixThemeDBusListener());
1141 Q_ASSERT(dbus);
1142
1143 // Wrap slot in a lambda to avoid inheriting QGnomeThemePrivate from QObject
1144 auto wrapper = [this](QGenericUnixThemeDBusListener::Provider provider,
1145 QGenericUnixThemeDBusListener::Setting setting,
1146 const QString &value) {
1147 if (provider != QGenericUnixThemeDBusListener::Provider::Gnome
1148 && provider != QGenericUnixThemeDBusListener::Provider::Gtk) {
1149 return;
1150 }
1151
1152 if (setting == QGenericUnixThemeDBusListener::Setting::Theme)
1153 updateColorScheme(themeName: value);
1154 };
1155
1156 return QObject::connect(sender: dbus.get(), signal: &QGenericUnixThemeDBusListener::settingChanged, slot&: wrapper);
1157}
1158
1159void QGnomeThemePrivate::updateColorScheme(const QString &themeName)
1160{
1161 const auto oldColorScheme = m_colorScheme;
1162 if (themeName.contains(s: QLatin1StringView("light"), cs: Qt::CaseInsensitive)) {
1163 m_colorScheme = Qt::ColorScheme::Light;
1164 } else if (themeName.contains(s: QLatin1StringView("dark"), cs: Qt::CaseInsensitive)) {
1165 m_colorScheme = Qt::ColorScheme::Dark;
1166 } else {
1167 m_colorScheme = Qt::ColorScheme::Unknown;
1168 }
1169
1170 if (oldColorScheme != m_colorScheme)
1171 QWindowSystemInterface::handleThemeChange();
1172}
1173#endif // QT_NO_DBUS
1174
1175QGnomeTheme::QGnomeTheme()
1176 : QPlatformTheme(new QGnomeThemePrivate())
1177{
1178}
1179
1180QVariant QGnomeTheme::themeHint(QPlatformTheme::ThemeHint hint) const
1181{
1182 switch (hint) {
1183 case QPlatformTheme::DialogButtonBoxButtonsHaveIcons:
1184 return QVariant(true);
1185 case QPlatformTheme::DialogButtonBoxLayout:
1186 return QVariant(QPlatformDialogHelper::GnomeLayout);
1187 case QPlatformTheme::SystemIconThemeName:
1188 return QVariant(QStringLiteral("Adwaita"));
1189 case QPlatformTheme::SystemIconFallbackThemeName:
1190 return QVariant(QStringLiteral("gnome"));
1191 case QPlatformTheme::IconThemeSearchPaths:
1192 return QVariant(QGenericUnixTheme::xdgIconThemePaths());
1193 case QPlatformTheme::IconPixmapSizes:
1194 return QVariant::fromValue(value: availableXdgFileIconSizes());
1195 case QPlatformTheme::StyleNames: {
1196 QStringList styleNames;
1197 styleNames << QStringLiteral("Fusion") << QStringLiteral("windows");
1198 return QVariant(styleNames);
1199 }
1200 case QPlatformTheme::KeyboardScheme:
1201 return QVariant(int(GnomeKeyboardScheme));
1202 case QPlatformTheme::PasswordMaskCharacter:
1203 return QVariant(QChar(0x2022));
1204 case QPlatformTheme::UiEffects:
1205 return QVariant(int(HoverEffect));
1206 case QPlatformTheme::ButtonPressKeys:
1207 return QVariant::fromValue(
1208 value: QList<Qt::Key>({ Qt::Key_Space, Qt::Key_Return, Qt::Key_Enter, Qt::Key_Select }));
1209 case QPlatformTheme::PreselectFirstFileInDirectory:
1210 return true;
1211 case QPlatformTheme::MouseCursorTheme:
1212 return QVariant(mouseCursorTheme());
1213 case QPlatformTheme::MouseCursorSize:
1214 return QVariant(mouseCursorSize());
1215 default:
1216 break;
1217 }
1218 return QPlatformTheme::themeHint(hint);
1219}
1220
1221QIcon QGnomeTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions) const
1222{
1223#if QT_CONFIG(mimetype)
1224 return xdgFileIcon(fileInfo);
1225#else
1226 Q_UNUSED(fileInfo);
1227 return QIcon();
1228#endif
1229}
1230
1231const QFont *QGnomeTheme::font(Font type) const
1232{
1233 Q_D(const QGnomeTheme);
1234 if (!d->systemFont)
1235 d->configureFonts(gtkFontName: gtkFontName());
1236 switch (type) {
1237 case QPlatformTheme::SystemFont:
1238 return d->systemFont;
1239 case QPlatformTheme::FixedFont:
1240 return d->fixedFont;
1241 default:
1242 return nullptr;
1243 }
1244}
1245
1246QString QGnomeTheme::gtkFontName() const
1247{
1248 return QStringLiteral("%1 %2").arg(a: QLatin1StringView(defaultSystemFontNameC)).arg(a: defaultSystemFontSize);
1249}
1250
1251#ifndef QT_NO_DBUS
1252QPlatformMenuBar *QGnomeTheme::createPlatformMenuBar() const
1253{
1254 if (isDBusGlobalMenuAvailable())
1255 return new QDBusMenuBar();
1256 return nullptr;
1257}
1258
1259Qt::ColorScheme QGnomeTheme::colorScheme() const
1260{
1261 return d_func()->m_colorScheme;
1262}
1263
1264#endif
1265
1266#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
1267QPlatformSystemTrayIcon *QGnomeTheme::createPlatformSystemTrayIcon() const
1268{
1269 if (isDBusTrayAvailable())
1270 return new QDBusTrayIcon();
1271 return nullptr;
1272}
1273#endif
1274
1275QString QGnomeTheme::standardButtonText(int button) const
1276{
1277 switch (button) {
1278 case QPlatformDialogHelper::Ok:
1279 return QCoreApplication::translate(context: "QGnomeTheme", key: "&OK");
1280 case QPlatformDialogHelper::Save:
1281 return QCoreApplication::translate(context: "QGnomeTheme", key: "&Save");
1282 case QPlatformDialogHelper::Cancel:
1283 return QCoreApplication::translate(context: "QGnomeTheme", key: "&Cancel");
1284 case QPlatformDialogHelper::Close:
1285 return QCoreApplication::translate(context: "QGnomeTheme", key: "&Close");
1286 case QPlatformDialogHelper::Discard:
1287 return QCoreApplication::translate(context: "QGnomeTheme", key: "Close without Saving");
1288 default:
1289 break;
1290 }
1291 return QPlatformTheme::standardButtonText(button);
1292}
1293
1294/*!
1295 \brief Creates a UNIX theme according to the detected desktop environment.
1296*/
1297
1298QPlatformTheme *QGenericUnixTheme::createUnixTheme(const QString &name)
1299{
1300 if (name == QLatin1StringView(QGenericUnixTheme::name))
1301 return new QGenericUnixTheme;
1302#if QT_CONFIG(settings)
1303 if (name == QLatin1StringView(QKdeTheme::name))
1304 if (QPlatformTheme *kdeTheme = QKdeTheme::createKdeTheme())
1305 return kdeTheme;
1306#endif
1307 if (name == QLatin1StringView(QGnomeTheme::name))
1308 return new QGnomeTheme;
1309 return nullptr;
1310}
1311
1312QStringList QGenericUnixTheme::themeNames()
1313{
1314 QStringList result;
1315 if (QGuiApplication::desktopSettingsAware()) {
1316 const QByteArray desktopEnvironment = QGuiApplicationPrivate::platformIntegration()->services()->desktopEnvironment();
1317 QList<QByteArray> gtkBasedEnvironments;
1318 gtkBasedEnvironments << "GNOME"
1319 << "X-CINNAMON"
1320 << "UNITY"
1321 << "MATE"
1322 << "XFCE"
1323 << "LXDE";
1324 const QList<QByteArray> desktopNames = desktopEnvironment.split(sep: ':');
1325 for (const QByteArray &desktopName : desktopNames) {
1326 if (desktopEnvironment == "KDE") {
1327#if QT_CONFIG(settings)
1328 result.push_back(t: QLatin1StringView(QKdeTheme::name));
1329#endif
1330 } else if (gtkBasedEnvironments.contains(t: desktopName)) {
1331 // prefer the GTK3 theme implementation with native dialogs etc.
1332 result.push_back(QStringLiteral("gtk3"));
1333 // fallback to the generic Gnome theme if loading the GTK3 theme fails
1334 result.push_back(t: QLatin1StringView(QGnomeTheme::name));
1335 } else {
1336 // unknown, but lowercase the name (our standard practice) and
1337 // remove any "x-" prefix
1338 QString s = QString::fromLatin1(ba: desktopName.toLower());
1339 result.push_back(t: s.startsWith(s: "x-"_L1) ? s.mid(position: 2) : s);
1340 }
1341 }
1342 } // desktopSettingsAware
1343 result.append(t: QLatin1StringView(QGenericUnixTheme::name));
1344 return result;
1345}
1346
1347QT_END_NAMESPACE
1348
1349#ifndef QT_NO_DBUS
1350#include "qgenericunixthemes.moc"
1351#endif // QT_NO_DBUS
1352

source code of qtbase/src/gui/platform/unix/qgenericunixthemes.cpp