Make QKdeTheme aware of runtime theme changes

When the KDE theme gets changed programatically or by the user in KDE
settings, Qt applications were not notified during run time.
The KDE theme was read during startup only, when the QApplication's
palette was constructed.

This patch implements a DBus connection to the SettingChanged signal.
QKdeTheme is notified about KDE theme changes, which trigger an
application palette reconstruction and all subsequent QEvents.
The implementation reacts to changes the in KDE settings "widgetStyle"
and "ColorTheme". The application palette is updated only if the
underlying settings change results in a palette modification.

Fixes: QTBUG-103093
Change-Id: If0ec0f0ba515ef3dcf9924283d3a818ac7d24a7b
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
(cherry picked from commit 223fdc7d52665b2fab24c0624b969bfdab067a2f)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Axel Spoerl 2022-08-26 07:34:52 +02:00 committed by Qt Cherry-pick Bot
parent d0745dfe88
commit 6ea58fb499

View File

@ -41,6 +41,9 @@
#include <algorithm>
QT_BEGIN_NAMESPACE
#ifndef QT_NO_DBUS
Q_LOGGING_CATEGORY(lcQpaThemeDBus, "qt.qpa.theme.dbus")
#endif
using namespace Qt::StringLiterals;
@ -98,7 +101,92 @@ static bool isDBusGlobalMenuAvailable()
static bool dbusGlobalMenuAvailable = checkDBusGlobalMenuAvailable();
return dbusGlobalMenuAvailable;
}
#endif
/*!
* \internal
* The QGenericUnixThemeDBusListener class listens to the SettingChanged DBus signal
* and translates it into the QDbusSettingType enum.
* 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:
QGenericUnixThemeDBusListener(const QString &service, const QString &path, const QString &interface, const QString &signal);
enum class SettingType {
KdeGlobalTheme,
KdeApplicationStyle,
Unknown
};
Q_ENUM(SettingType)
static SettingType toSettingType(const QString &location, const QString &key);
private Q_SLOTS:
void onSettingChanged(const QString &location, const QString &key, const QDBusVariant &value);
Q_SIGNALS:
void settingChanged(QGenericUnixThemeDBusListener::SettingType type, const QString &value);
};
QGenericUnixThemeDBusListener::QGenericUnixThemeDBusListener(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) {
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 KDE setting changes.\n"
<< "Check your DBus installation.";
}
#undef LOG
}
QGenericUnixThemeDBusListener::SettingType QGenericUnixThemeDBusListener::toSettingType(
const QString &location, const QString &key)
{
if (location == QLatin1StringView("org.kde.kdeglobals.KDE")
&& key == QLatin1StringView("widgetStyle"))
return SettingType::KdeApplicationStyle;
if (location == QLatin1StringView("org.kde.kdeglobals.General")
&& key == QLatin1StringView("ColorScheme"))
return SettingType::KdeGlobalTheme;
return SettingType::Unknown;
}
void QGenericUnixThemeDBusListener::onSettingChanged(const QString &location, const QString &key, const QDBusVariant &value)
{
const SettingType type = toSettingType(location, key);
if (type != SettingType::Unknown)
emit settingChanged(type, value.variant().toString());
}
#endif //QT_NO_DBUS
class QGenericUnixThemePrivate : public QPlatformThemePrivate
{
@ -231,11 +319,9 @@ 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)
{
@ -266,8 +352,59 @@ public:
int startDragDist = 10;
int startDragTime = 500;
int cursorBlinkRate = 1000;
#ifndef QT_NO_DBUS
private:
std::unique_ptr<QGenericUnixThemeDBusListener> dbus;
bool initDbus();
void settingChangedHandler(QGenericUnixThemeDBusListener::SettingType type, const QString &value);
#endif // QT_NO_DBUS
};
#ifndef QT_NO_DBUS
void QKdeThemePrivate::settingChangedHandler(QGenericUnixThemeDBusListener::SettingType type, const QString &value)
{
switch (type) {
case QGenericUnixThemeDBusListener::SettingType::KdeGlobalTheme:
qCDebug(lcQpaThemeDBus) << "KDE global theme changed to:" << value;
break;
case QGenericUnixThemeDBusListener::SettingType::KdeApplicationStyle:
qCDebug(lcQpaThemeDBus) << "KDE application style changed to:" << value;
break;
case QGenericUnixThemeDBusListener::SettingType::Unknown:
Q_UNREACHABLE();
}
refresh();
}
bool QKdeThemePrivate::initDbus()
{
constexpr QLatin1StringView service("");
constexpr QLatin1StringView path("/org/freedesktop/portal/desktop");
constexpr QLatin1StringView interface("org.freedesktop.portal.Settings");
constexpr QLatin1StringView signal("SettingChanged");
dbus.reset(new QGenericUnixThemeDBusListener(service, path, interface, signal));
Q_ASSERT(dbus);
// Wrap slot in a lambda to avoid inheriting QKdeThemePrivate from QObject
auto wrapper = [this](QGenericUnixThemeDBusListener::SettingType type, const QString &value) {
settingChangedHandler(type, value);
};
return QObject::connect(dbus.get(), &QGenericUnixThemeDBusListener::settingChanged, 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();
@ -368,6 +505,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);
@ -855,3 +994,7 @@ QStringList QGenericUnixTheme::themeNames()
}
QT_END_NAMESPACE
#ifndef QT_NO_DBUS
#include "qgenericunixthemes.moc"
#endif // QT_NO_DBUS