diff options
Diffstat (limited to 'src/gui/platform/unix/qgenericunixthemes.cpp')
-rw-r--r-- | src/gui/platform/unix/qgenericunixthemes.cpp | 609 |
1 files changed, 537 insertions, 72 deletions
diff --git a/src/gui/platform/unix/qgenericunixthemes.cpp b/src/gui/platform/unix/qgenericunixthemes.cpp index 86f2a266ea..fc4b2296d2 100644 --- a/src/gui/platform/unix/qgenericunixthemes.cpp +++ b/src/gui/platform/unix/qgenericunixthemes.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 "qgenericunixthemes_p.h" @@ -69,6 +33,12 @@ #include <QDBusConnectionInterface> #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> @@ -77,6 +47,11 @@ #include <algorithm> QT_BEGIN_NAMESPACE +#ifndef QT_NO_DBUS +Q_LOGGING_CATEGORY(lcQpaThemeDBus, "qt.qpa.theme.dbus") +#endif + +using namespace Qt::StringLiterals; Q_DECLARE_LOGGING_CATEGORY(qLcTray) @@ -108,7 +83,7 @@ static bool isDBusTrayAvailable() { static bool dbusTrayAvailableKnown = false; if (!dbusTrayAvailableKnown) { QDBusMenuConnection conn; - if (conn.isStatusNotifierHostRegistered()) + if (conn.isWatcherRegistered()) dbusTrayAvailable = true; dbusTrayAvailableKnown = true; qCDebug(qLcTray) << "D-Bus tray available:" << dbusTrayAvailable; @@ -117,6 +92,20 @@ static bool isDBusTrayAvailable() { } #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() { @@ -132,15 +121,296 @@ 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() {} + }; + + // Json keys + static constexpr QLatin1StringView s_dbusLocation = QLatin1StringView("DBusLocation"); + static constexpr QLatin1StringView s_dbusKey = QLatin1StringView("DBusKey"); + static constexpr QLatin1StringView s_provider = QLatin1StringView("Provider"); + static constexpr QLatin1StringView s_setting = QLatin1StringView("Setting"); + static constexpr QLatin1StringView s_signals = QLatin1StringView("DbusSignals"); + static constexpr QLatin1StringView s_root = QLatin1StringView("Qt.qpa.DBusSignals"); + + 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; +}; + +QGenericUnixThemeDBusListener::QGenericUnixThemeDBusListener(const QString &service, + const QString &path, const QString &interface, const QString &signal) +{ + init (service, path, interface, signal); +} + +QGenericUnixThemeDBusListener::QGenericUnixThemeDBusListener() +{ + static constexpr QLatin1StringView service(""); + static constexpr QLatin1StringView path("/org/freedesktop/portal/desktop"); + static constexpr QLatin1StringView interface("org.freedesktop.portal.Settings"); + static constexpr QLatin1StringView signal("SettingChanged"); + + 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" << s_root); + + const QJsonObject &root = doc.object(); + CHECK(root.contains(s_root), "Parse Error: Expected root object" << s_root); + CHECK(root[s_root][s_signals].isArray(), "Parse Error: Expected array" << s_signals); + + const QJsonArray &sigs = root[s_root][s_signals].toArray(); + CHECK((sigs.count() > 0), "Parse Error: Found empty array" << s_signals); + + for (auto sig = sigs.constBegin(); sig != sigs.constEnd(); ++sig) { + CHECK(sig->isObject(), "Parse Error: Expected object array" << s_signals); + const QJsonObject &obj = sig->toObject(); + CHECK(obj.contains(s_dbusLocation), "Parse Error: Expected key" << s_dbusLocation); + CHECK(obj.contains(s_dbusKey), "Parse Error: Expected key" << s_dbusKey); + CHECK(obj.contains(s_provider), "Parse Error: Expected key" << s_provider); + CHECK(obj.contains(s_setting), "Parse Error: Expected key" << s_setting); + const QString &location = obj[s_dbusLocation].toString(); + const QString &key = obj[s_dbusKey].toString(); + const QString &providerString = obj[s_provider].toString(); + const QString &settingString = obj[s_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[s_dbusLocation] = dkey.location; + obj[s_dbusKey] = dkey.key; + obj[s_provider] = QLatin1StringView(QMetaEnum::fromType<Provider>() + .valueToKey(static_cast<int>(csig.provider))); + obj[s_setting] = QLatin1StringView(QMetaEnum::fromType<Setting>() + .valueToKey(static_cast<int>(csig.setting))); + sigs.append(obj); + } + QJsonObject obj; + obj[s_signals] = sigs; + QJsonObject root; + root[s_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(QLatin1String(defaultSystemFontNameC), defaultSystemFontSize) - , fixedFont(QLatin1String(defaultFixedFontNameC), systemFont.pointSize()) + , systemFont(QLatin1StringView(defaultSystemFontNameC), defaultSystemFontSize) + , fixedFont(QLatin1StringView(defaultFixedFontNameC), systemFont.pointSize()) { fixedFont.setStyleHint(QFont::TypeWriter); qCDebug(lcQpaFonts) << "default fonts: system" << systemFont << "fixed" << fixedFont; @@ -173,7 +443,7 @@ QStringList QGenericUnixTheme::xdgIconThemePaths() { QStringList paths; // Add home directory first in search path - const QFileInfo homeIconDir(QDir::homePath() + QLatin1String("/.icons")); + const QFileInfo homeIconDir(QDir::homePath() + "/.icons"_L1); if (homeIconDir.isDir()) paths.prepend(homeIconDir.absoluteFilePath()); @@ -232,6 +502,10 @@ QVariant QGenericUnixTheme::themeHint(ThemeHint hint) const return QVariant(int(X11KeyboardScheme)); case QPlatformTheme::UiEffects: return QVariant(int(HoverEffect)); + case QPlatformTheme::MouseCursorTheme: + return QVariant(mouseCursorTheme()); + case QPlatformTheme::MouseCursorSize: + return QVariant(mouseCursorSize()); default: break; } @@ -265,17 +539,15 @@ static QIcon xdgFileIcon(const QFileInfo &fileInfo) #if QT_CONFIG(settings) class QKdeThemePrivate : public QPlatformThemePrivate { + public: - QKdeThemePrivate(const QStringList &kdeDirs, int kdeVersion) - : kdeDirs(kdeDirs) - , kdeVersion(kdeVersion) - { } + QKdeThemePrivate(const QStringList &kdeDirs, int kdeVersion); static QString kdeGlobals(const QString &kdeDir, int kdeVersion) { if (kdeVersion > 4) - return kdeDir + QLatin1String("/kdeglobals"); - return kdeDir + QLatin1String("/share/config/kdeglobals"); + return kdeDir + "/kdeglobals"_L1; + return kdeDir + "/share/config/kdeglobals"_L1; } void refresh(); @@ -300,8 +572,66 @@ public: int startDragDist = 10; int startDragTime = 500; int cursorBlinkRate = 1000; + Qt::ColorScheme m_colorScheme = Qt::ColorScheme::Unknown; + void updateColorScheme(const QString &themeName); + +#ifndef QT_NO_DBUS +private: + std::unique_ptr<QGenericUnixThemeDBusListener> dbus; + bool initDbus(); + void settingChangedHandler(QGenericUnixThemeDBusListener::Provider provider, + QGenericUnixThemeDBusListener::Setting setting, + const QString &value); +#endif // QT_NO_DBUS }; +#ifndef QT_NO_DBUS +void QKdeThemePrivate::settingChangedHandler(QGenericUnixThemeDBusListener::Provider provider, + QGenericUnixThemeDBusListener::Setting setting, + const QString &value) +{ + if (provider != QGenericUnixThemeDBusListener::Provider::Kde) + return; + + switch (setting) { + case QGenericUnixThemeDBusListener::Setting::ColorScheme: + qCDebug(lcQpaThemeDBus) << "KDE color theme changed to:" << value; + break; + case QGenericUnixThemeDBusListener::Setting::Theme: + qCDebug(lcQpaThemeDBus) << "KDE global theme changed to:" << value; + break; + case QGenericUnixThemeDBusListener::Setting::ApplicationStyle: + qCDebug(lcQpaThemeDBus) << "KDE application style changed to:" << value; + break; + } + + refresh(); +} + +bool QKdeThemePrivate::initDbus() +{ + dbus.reset(new QGenericUnixThemeDBusListener()); + Q_ASSERT(dbus); + + // Wrap slot in a lambda to avoid inheriting QKdeThemePrivate from QObject + auto wrapper = [this](QGenericUnixThemeDBusListener::Provider provider, + QGenericUnixThemeDBusListener::Setting setting, + const QString &value) { + settingChangedHandler(provider, setting, value); + }; + + return QObject::connect(dbus.get(), &QGenericUnixThemeDBusListener::settingChanged, dbus.get(), wrapper); +} +#endif // QT_NO_DBUS + +QKdeThemePrivate::QKdeThemePrivate(const QStringList &kdeDirs, int kdeVersion) + : kdeDirs(kdeDirs), kdeVersion(kdeVersion) +{ +#ifndef QT_NO_DBUS + initDbus(); +#endif // QT_NO_DBUS +} + void QKdeThemePrivate::refresh() { resources.clear(); @@ -331,6 +661,14 @@ void QKdeThemePrivate::refresh() styleNames.push_front(style); } + const QVariant colorScheme = readKdeSetting(QStringLiteral("ColorScheme"), kdeDirs, + kdeVersion, kdeSettings); + + if (colorScheme.isValid()) + updateColorScheme(colorScheme.toString()); + else + m_colorScheme = Qt::ColorScheme::Unknown; + const QVariant singleClickValue = readKdeSetting(QStringLiteral("KDE/SingleClick"), kdeDirs, kdeVersion, kdeSettings); if (singleClickValue.isValid()) singleClick = singleClickValue.toBool(); @@ -350,11 +688,11 @@ void QKdeThemePrivate::refresh() const QVariant toolbarStyleValue = readKdeSetting(QStringLiteral("Toolbar style/ToolButtonStyle"), kdeDirs, kdeVersion, kdeSettings); if (toolbarStyleValue.isValid()) { const QString toolBarStyle = toolbarStyleValue.toString(); - if (toolBarStyle == QLatin1String("TextBesideIcon")) + if (toolBarStyle == "TextBesideIcon"_L1) toolButtonStyle = Qt::ToolButtonTextBesideIcon; - else if (toolBarStyle == QLatin1String("TextOnly")) + else if (toolBarStyle == "TextOnly"_L1) toolButtonStyle = Qt::ToolButtonTextOnly; - else if (toolBarStyle == QLatin1String("TextUnderIcon")) + else if (toolBarStyle == "TextUnderIcon"_L1) toolButtonStyle = Qt::ToolButtonTextUnderIcon; } @@ -384,12 +722,12 @@ void QKdeThemePrivate::refresh() if (QFont *systemFont = kdeFont(readKdeSetting(QStringLiteral("font"), kdeDirs, kdeVersion, kdeSettings))) resources.fonts[QPlatformTheme::SystemFont] = systemFont; else - resources.fonts[QPlatformTheme::SystemFont] = new QFont(QLatin1String(defaultSystemFontNameC), defaultSystemFontSize); + resources.fonts[QPlatformTheme::SystemFont] = new QFont(QLatin1StringView(defaultSystemFontNameC), defaultSystemFontSize); if (QFont *fixedFont = kdeFont(readKdeSetting(QStringLiteral("fixed"), kdeDirs, kdeVersion, kdeSettings))) { resources.fonts[QPlatformTheme::FixedFont] = fixedFont; } else { - fixedFont = new QFont(QLatin1String(defaultFixedFontNameC), defaultSystemFontSize); + fixedFont = new QFont(QLatin1StringView(defaultFixedFontNameC), defaultSystemFontSize); fixedFont->setStyleHint(QFont::TypeWriter); resources.fonts[QPlatformTheme::FixedFont] = fixedFont; } @@ -402,6 +740,8 @@ void QKdeThemePrivate::refresh() if (QFont *toolBarFont = kdeFont(readKdeSetting(QStringLiteral("toolBarFont"), kdeDirs, kdeVersion, kdeSettings))) resources.fonts[QPlatformTheme::ToolButtonFont] = toolBarFont; + QWindowSystemInterface::handleThemeChange(); + qCDebug(lcQpaFonts) << "default fonts: system" << resources.fonts[QPlatformTheme::SystemFont] << "fixed" << resources.fonts[QPlatformTheme::FixedFont]; qDeleteAll(kdeSettings); @@ -523,7 +863,7 @@ QFont *QKdeThemePrivate::kdeFont(const QVariant &fontValue) const QStringList list = fontValue.toStringList(); if (!list.isEmpty()) { fontFamily = list.first(); - fontDescription = list.join(QLatin1Char(',')); + fontDescription = list.join(u','); } } else { fontDescription = fontFamily = fontValue.toString(); @@ -590,6 +930,10 @@ QVariant QKdeTheme::themeHint(QPlatformTheme::ThemeHint hint) const return QVariant(d->cursorBlinkRate); case QPlatformTheme::UiEffects: return QVariant(int(HoverEffect)); + case QPlatformTheme::MouseCursorTheme: + return QVariant(mouseCursorTheme()); + case QPlatformTheme::MouseCursorSize: + return QVariant(mouseCursorSize()); default: break; } @@ -606,6 +950,48 @@ QIcon QKdeTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions #endif } +Qt::ColorScheme QKdeTheme::colorScheme() const +{ + return d_func()->m_colorScheme; +} + +/*! + \internal + \brief QKdeTheme::updateColorScheme - guess and set appearance for unix themes. + KDE themes do not have an appearance property. + The key words "dark" or "light" should be part of the theme name. + This is, however, not a mandatory convention. + + If \param themeName contains a key word, the respective appearance is set. + If it doesn't, the appearance is heuristically determined by comparing text and base color + of the system palette. + */ +void QKdeThemePrivate::updateColorScheme(const QString &themeName) +{ + if (themeName.contains(QLatin1StringView("light"), Qt::CaseInsensitive)) { + m_colorScheme = Qt::ColorScheme::Light; + return; + } + if (themeName.contains(QLatin1StringView("dark"), Qt::CaseInsensitive)) { + m_colorScheme = Qt::ColorScheme::Dark; + return; + } + + if (systemPalette) { + if (systemPalette->text().color().lightness() < systemPalette->base().color().lightness()) { + m_colorScheme = Qt::ColorScheme::Light; + return; + } + if (systemPalette->text().color().lightness() > systemPalette->base().color().lightness()) { + m_colorScheme = Qt::ColorScheme::Dark; + return; + } + } + + m_colorScheme = Qt::ColorScheme::Unknown; +} + + const QPalette *QKdeTheme::palette(Palette type) const { Q_D(const QKdeTheme); @@ -643,24 +1029,24 @@ QPlatformTheme *QKdeTheme::createKdeTheme() const QString kdeDirsVar = QFile::decodeName(qgetenv("KDEDIRS")); if (!kdeDirsVar.isEmpty()) - kdeDirs += kdeDirsVar.split(QLatin1Char(':'), Qt::SkipEmptyParts); + kdeDirs += kdeDirsVar.split(u':', Qt::SkipEmptyParts); - const QString kdeVersionHomePath = QDir::homePath() + QLatin1String("/.kde") + QLatin1String(kdeVersionBA); + const QString kdeVersionHomePath = QDir::homePath() + "/.kde"_L1 + QLatin1StringView(kdeVersionBA); if (QFileInfo(kdeVersionHomePath).isDir()) kdeDirs += kdeVersionHomePath; - const QString kdeHomePath = QDir::homePath() + QLatin1String("/.kde"); + const QString kdeHomePath = QDir::homePath() + "/.kde"_L1; if (QFileInfo(kdeHomePath).isDir()) kdeDirs += kdeHomePath; - const QString kdeRcPath = QLatin1String("/etc/kde") + QLatin1String(kdeVersionBA) + QLatin1String("rc"); + const QString kdeRcPath = "/etc/kde"_L1 + QLatin1StringView(kdeVersionBA) + "rc"_L1; if (QFileInfo(kdeRcPath).isReadable()) { QSettings kdeSettings(kdeRcPath, QSettings::IniFormat); kdeSettings.beginGroup(QStringLiteral("Directories-default")); kdeDirs += kdeSettings.value(QStringLiteral("prefixes")).toStringList(); } - const QString kdeVersionPrefix = QLatin1String("/etc/kde") + QLatin1String(kdeVersionBA); + const QString kdeVersionPrefix = "/etc/kde"_L1 + QLatin1StringView(kdeVersionBA); if (QFileInfo(kdeVersionPrefix).isDir()) kdeDirs += kdeVersionPrefix; @@ -706,8 +1092,8 @@ const char *QGnomeTheme::name = "gnome"; class QGnomeThemePrivate : public QPlatformThemePrivate { public: - QGnomeThemePrivate() : systemFont(nullptr), fixedFont(nullptr) {} - ~QGnomeThemePrivate() { delete systemFont; delete fixedFont; } + QGnomeThemePrivate(); + ~QGnomeThemePrivate(); void configureFonts(const QString >kFontName) const { @@ -717,15 +1103,75 @@ public: QString fontName = gtkFontName.left(split); systemFont = new QFont(fontName, size); - fixedFont = new QFont(QLatin1String(defaultFixedFontNameC), systemFont->pointSize()); + fixedFont = new QFont(QLatin1StringView(defaultFixedFontNameC), systemFont->pointSize()); fixedFont->setStyleHint(QFont::TypeWriter); qCDebug(lcQpaFonts) << "default fonts: system" << systemFont << "fixed" << fixedFont; } - mutable QFont *systemFont; - mutable QFont *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()) { @@ -757,6 +1203,15 @@ QVariant QGnomeTheme::themeHint(QPlatformTheme::ThemeHint hint) const 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()); default: break; } @@ -790,7 +1245,7 @@ const QFont *QGnomeTheme::font(Font type) const QString QGnomeTheme::gtkFontName() const { - return QStringLiteral("%1 %2").arg(QLatin1String(defaultSystemFontNameC)).arg(defaultSystemFontSize); + return QStringLiteral("%1 %2").arg(QLatin1StringView(defaultSystemFontNameC)).arg(defaultSystemFontSize); } #ifndef QT_NO_DBUS @@ -800,6 +1255,12 @@ QPlatformMenuBar *QGnomeTheme::createPlatformMenuBar() const 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) @@ -836,14 +1297,14 @@ QString QGnomeTheme::standardButtonText(int button) const QPlatformTheme *QGenericUnixTheme::createUnixTheme(const QString &name) { - if (name == QLatin1String(QGenericUnixTheme::name)) + if (name == QLatin1StringView(QGenericUnixTheme::name)) return new QGenericUnixTheme; #if QT_CONFIG(settings) - if (name == QLatin1String(QKdeTheme::name)) + if (name == QLatin1StringView(QKdeTheme::name)) if (QPlatformTheme *kdeTheme = QKdeTheme::createKdeTheme()) return kdeTheme; #endif - if (name == QLatin1String(QGnomeTheme::name)) + if (name == QLatin1StringView(QGnomeTheme::name)) return new QGnomeTheme; return nullptr; } @@ -864,23 +1325,27 @@ QStringList QGenericUnixTheme::themeNames() for (const QByteArray &desktopName : desktopNames) { if (desktopEnvironment == "KDE") { #if QT_CONFIG(settings) - result.push_back(QLatin1String(QKdeTheme::name)); + 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(QLatin1String(QGnomeTheme::name)); + 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(QLatin1String("x-")) ? s.mid(2) : s); + result.push_back(s.startsWith("x-"_L1) ? s.mid(2) : s); } } } // desktopSettingsAware - result.append(QLatin1String(QGenericUnixTheme::name)); + result.append(QLatin1StringView(QGenericUnixTheme::name)); return result; } QT_END_NAMESPACE + +#ifndef QT_NO_DBUS +#include "qgenericunixthemes.moc" +#endif // QT_NO_DBUS |