Add virtual member QPlatformTheme::contrastPreference()

This function can be overridden by individual platform themes, in order
to read contrast settings from the platform's system settings, and
report the result back to Qt.

This information is relevant for our styles, and can be used to
determine color palette values, and additional elements like outline
thickness for controls/widgets.

Currently only the Windows, macOS, Gnome and Flatpak themes support this
feature.

Task-number: QTBUG-133595
Change-Id: I3aff519aa7f07c8b2fdcc1e7fb35ec719ab8efcc
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
This commit is contained in:
Oliver Eftevaag 2025-02-20 18:27:50 +01:00
parent 8c943392ae
commit 4ac89dad78
15 changed files with 125 additions and 21 deletions

View File

@ -54,6 +54,11 @@ namespace Qt {
Dark,
};
enum class ContrastPreference {
NoPreference,
HighContrast,
};
enum MouseButton {
NoButton = 0x00000000,
LeftButton = 0x00000001,
@ -1786,6 +1791,7 @@ namespace Qt {
Q_ENUM_NS(CursorShape)
Q_ENUM_NS(GlobalColor)
Q_ENUM_NS(ColorScheme)
Q_ENUM_NS(ContrastPreference)
Q_ENUM_NS(AspectRatioMode)
Q_ENUM_NS(TransformationMode)
Q_FLAG_NS(ImageConversionFlags)

View File

@ -484,6 +484,11 @@ void QPlatformTheme::requestColorScheme(Qt::ColorScheme scheme)
}
}
Qt::ContrastPreference QPlatformTheme::contrastPreference() const
{
return Qt::ContrastPreference::NoPreference;
}
/*!
\internal
\brief Return a color palette for type \a type.

View File

@ -319,6 +319,7 @@ public:
virtual QKeySequence standardButtonShortcut(int button) const;
#endif
virtual void requestColorScheme(Qt::ColorScheme scheme);
virtual Qt::ContrastPreference contrastPreference() const;
static QVariant defaultThemeHint(ThemeHint hint);
static QString defaultStandardButtonText(int button);

View File

@ -209,6 +209,9 @@ void QDBusListener::populateSignalMap()
m_signalMap.insert(DBusKey("org.freedesktop.appearance"_L1, "color-scheme"_L1),
ChangeSignal(Provider::Gnome, Setting::ColorScheme));
m_signalMap.insert(DBusKey("org.freedesktop.appearance"_L1, "contrast"_L1),
ChangeSignal(Provider::Gnome, Setting::Contrast));
const QString &saveJsonFile = qEnvironmentVariable("QT_QPA_DBUS_SIGNALS_SAVE");
if (!saveJsonFile.isEmpty())
saveJson(saveJsonFile);
@ -232,6 +235,6 @@ void QDBusListener::onSettingChanged(const QString &location, const QString &key
if (!sig.has_value())
return;
emit settingChanged(sig.value().provider, sig.value().setting, value.variant().toString());
emit settingChanged(sig.value().provider, sig.value().setting, value.variant());
}
QT_END_NAMESPACE

View File

@ -38,6 +38,7 @@ public:
Theme,
ApplicationStyle,
ColorScheme,
Contrast,
};
Q_ENUM(Setting)
@ -51,7 +52,7 @@ private Q_SLOTS:
Q_SIGNALS:
void settingChanged(QDBusListener::Provider provider,
QDBusListener::Setting setting,
const QString &value);
const QVariant &value);
private:
struct DBusKey

View File

@ -15,6 +15,10 @@
QT_BEGIN_NAMESPACE
#ifndef QT_NO_DBUS
Q_STATIC_LOGGING_CATEGORY(lcQpaThemeGnome, "qt.qpa.theme.gnome")
#endif // QT_NO_DBUS
/*!
\class QGnomeTheme
\brief QGnomeTheme is a theme implementation for the Gnome desktop.
@ -27,7 +31,30 @@ const char *QGnomeTheme::name = "gnome";
QGnomeThemePrivate::QGnomeThemePrivate()
{
#ifndef QT_NO_DBUS
initDbus();
QDBusMessage message = QDBusMessage::createMethodCall(QLatin1String("org.freedesktop.portal.Desktop"),
QLatin1String("/org/freedesktop/portal/desktop"),
QLatin1String("org.freedesktop.portal.Settings"),
QLatin1String("ReadOne"));
static constexpr QLatin1String appearanceNamespace("org.freedesktop.appearance");
static constexpr QLatin1String contrastKey("contrast");
message << appearanceNamespace << contrastKey;
QDBusConnection dbus = QDBusConnection::sessionBus();
if (!dbus.isConnected())
qCWarning(lcQpaThemeGnome) << "dbus connection failed. Last error: " << dbus.lastError();
QDBusPendingCall pendingCall = dbus.asyncCall(message);
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall);
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher, [this](QDBusPendingCallWatcher *watcher) {
if (!watcher->isError()) {
QDBusPendingReply<QVariant> reply = *watcher;
if (Q_LIKELY(reply.isValid()))
m_contrast = static_cast<Qt::ContrastPreference>(reply.value().toUInt());
}
watcher->deleteLater();
initDbus();
});
#endif // QT_NO_DBUS
}
QGnomeThemePrivate::~QGnomeThemePrivate()
@ -60,14 +87,22 @@ bool QGnomeThemePrivate::initDbus()
// Wrap slot in a lambda to avoid inheriting QGnomeThemePrivate from QObject
auto wrapper = [this](QDBusListener::Provider provider,
QDBusListener::Setting setting,
const QString &value) {
const QVariant &value) {
if (provider != QDBusListener::Provider::Gnome
&& provider != QDBusListener::Provider::Gtk) {
return;
}
if (setting == QDBusListener::Setting::Theme)
updateColorScheme(value);
switch (setting) {
case QDBusListener::Setting::Theme:
updateColorScheme(value.toString());
break;
case QDBusListener::Setting::Contrast:
updateHighContrast(static_cast<Qt::ContrastPreference>(value.toUInt()));
break;
default:
break;
}
};
return QObject::connect(dbus.get(), &QDBusListener::settingChanged, dbus.get(), wrapper);
@ -87,6 +122,15 @@ void QGnomeThemePrivate::updateColorScheme(const QString &themeName)
if (oldColorScheme != m_colorScheme)
QWindowSystemInterface::handleThemeChange();
}
void QGnomeThemePrivate::updateHighContrast(Qt::ContrastPreference contrast)
{
if (m_contrast == contrast)
return;
m_contrast = contrast;
QWindowSystemInterface::handleThemeChange();
}
#endif // QT_NO_DBUS
QGnomeTheme::QGnomeTheme()
@ -181,6 +225,11 @@ Qt::ColorScheme QGnomeTheme::colorScheme() const
return d_func()->m_colorScheme;
}
Qt::ContrastPreference QGnomeTheme::contrastPreference() const
{
return d_func()->m_contrast;
}
#endif
#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)

View File

@ -41,6 +41,7 @@ public:
#ifndef QT_NO_DBUS
QPlatformMenuBar *createPlatformMenuBar() const override;
Qt::ColorScheme colorScheme() const override;
Qt::ContrastPreference contrastPreference() const override;
#endif
#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
QPlatformSystemTrayIcon *createPlatformSystemTrayIcon() const override;
@ -62,10 +63,12 @@ public:
#ifndef QT_NO_DBUS
Qt::ColorScheme m_colorScheme = Qt::ColorScheme::Unknown;
Qt::ContrastPreference m_contrast = Qt::ContrastPreference::NoPreference;
private:
std::unique_ptr<QDBusListener> dbus;
bool initDbus();
void updateColorScheme(const QString &themeName);
void updateHighContrast(Qt::ContrastPreference contrast);
#endif // QT_NO_DBUS
};

View File

@ -114,7 +114,7 @@ private:
bool initDbus();
void settingChangedHandler(QDBusListener::Provider provider,
QDBusListener::Setting setting,
const QString &value);
const QVariant &value);
Qt::ColorScheme colorSchemeFromPalette() const;
#endif // QT_NO_DBUS
void clearResources();
@ -123,21 +123,23 @@ private:
#ifndef QT_NO_DBUS
void QKdeThemePrivate::settingChangedHandler(QDBusListener::Provider provider,
QDBusListener::Setting setting,
const QString &value)
const QVariant &value)
{
if (provider != QDBusListener::Provider::Kde)
return;
switch (setting) {
case QDBusListener::Setting::ColorScheme:
qCDebug(lcQpaThemeKde) << "KDE color theme changed to:" << value;
qCDebug(lcQpaThemeKde) << "KDE color theme changed to:" << value.toUInt();
break;
case QDBusListener::Setting::Theme:
qCDebug(lcQpaThemeKde) << "KDE global theme changed to:" << value;
qCDebug(lcQpaThemeKde) << "KDE global theme changed to:" << value.toString();
break;
case QDBusListener::Setting::ApplicationStyle:
qCDebug(lcQpaThemeKde) << "KDE application style changed to:" << value;
qCDebug(lcQpaThemeKde) << "KDE application style changed to:" << value.toString();
break;
case QDBusListener::Setting::Contrast:
qCDebug(lcQpaThemeKde) << "KDE contrast setting changed to: " << static_cast<Qt::ContrastPreference>(value.toUInt());
}
refresh();
@ -158,7 +160,7 @@ bool QKdeThemePrivate::initDbus()
// Wrap slot in a lambda to avoid inheriting QKdeThemePrivate from QObject
auto wrapper = [this](QDBusListener::Provider provider,
QDBusListener::Setting setting,
const QString &value) {
const QVariant &value) {
settingChangedHandler(provider, setting, value);
};

View File

@ -39,6 +39,7 @@ public:
QVariant themeHint(ThemeHint hint) const override;
Qt::ColorScheme colorScheme() const override;
Qt::ContrastPreference contrastPreference() const override;
QString standardButtonText(int button) const override;
QKeySequence standardButtonShortcut(int button) const override;

View File

@ -508,6 +508,12 @@ void QCocoaTheme::updateColorScheme()
m_colorScheme = qt_mac_applicationIsInDarkMode() ? Qt::ColorScheme::Dark : Qt::ColorScheme::Light;
}
Qt::ContrastPreference QCocoaTheme::contrastPreference() const
{
return NSWorkspace.sharedWorkspace.accessibilityDisplayShouldIncreaseContrast ? Qt::ContrastPreference::HighContrast
: Qt::ContrastPreference::NoPreference;
}
QString QCocoaTheme::standardButtonText(int button) const
{
return button == QPlatformDialogHelper::Discard ?

View File

@ -597,6 +597,12 @@ void QWindowsTheme::requestColorScheme(Qt::ColorScheme scheme)
handleSettingsChanged();
}
Qt::ContrastPreference QWindowsTheme::contrastPreference() const
{
return queryHighContrast() ? Qt::ContrastPreference::HighContrast
: Qt::ContrastPreference::NoPreference;
}
void QWindowsTheme::handleSettingsChanged()
{
const auto oldColorScheme = s_colorScheme;

View File

@ -33,6 +33,7 @@ public:
Qt::ColorScheme colorScheme() const override;
void requestColorScheme(Qt::ColorScheme scheme) override;
Qt::ContrastPreference contrastPreference() const override;
static void handleSettingsChanged();

View File

@ -233,7 +233,7 @@ const QPalette *QGtk3Theme::palette(Palette type) const
#ifdef QT_DEBUG
if (m_requestedColorScheme != Qt::ColorScheme::Unknown
&& m_requestedColorScheme != m_storage->colorScheme()) {
qCDebug(lcQGtk3Interface) << "Current KDE theme doesn't support reuqested color scheme"
qCDebug(lcQGtk3Interface) << "Current KDE theme doesn't support requested color scheme"
<< m_requestedColorScheme << "Falling back to fusion palette.";
return QPlatformTheme::palette(type);
}

View File

@ -20,6 +20,10 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
static constexpr QLatin1StringView appearanceInterface("org.freedesktop.appearance");
static constexpr QLatin1StringView colorSchemeKey("color-scheme");
static constexpr QLatin1StringView contrastKey("contrast");
class QXdgDesktopPortalThemePrivate : public QObject
{
Q_OBJECT
@ -67,9 +71,14 @@ public Q_SLOTS:
void settingChanged(const QString &group, const QString &key,
const QDBusVariant &value)
{
if (group == "org.freedesktop.appearance"_L1 && key == "color-scheme"_L1) {
colorScheme = colorSchemeFromXdgPref(static_cast<XdgColorschemePref>(value.variant().toUInt()));
QWindowSystemInterface::handleThemeChange();
if (group == appearanceInterface) {
if (key == colorSchemeKey) {
colorScheme = colorSchemeFromXdgPref(static_cast<XdgColorschemePref>(value.variant().toUInt()));
QWindowSystemInterface::handleThemeChange();
} else if (key == contrastKey) {
contrast = static_cast<Qt::ContrastPreference>(value.variant().toUInt());
QWindowSystemInterface::handleThemeChange();
}
}
}
@ -77,6 +86,7 @@ public:
QPlatformTheme *baseTheme = nullptr;
uint fileChooserPortalVersion = 0;
Qt::ColorScheme colorScheme = Qt::ColorScheme::Unknown;
Qt::ContrastPreference contrast = Qt::ContrastPreference::NoPreference;
};
QXdgDesktopPortalTheme::QXdgDesktopPortalTheme()
@ -123,15 +133,18 @@ QXdgDesktopPortalTheme::QXdgDesktopPortalTheme()
message = QDBusMessage::createMethodCall("org.freedesktop.portal.Desktop"_L1,
"/org/freedesktop/portal/desktop"_L1,
"org.freedesktop.portal.Settings"_L1,
"Read"_L1);
message << "org.freedesktop.appearance"_L1 << "color-scheme"_L1;
"ReadAll"_L1);
message << appearanceInterface;
// this must not be asyncCall() because we have to set appearance now
QDBusReply<QVariant> reply = QDBusConnection::sessionBus().call(message);
if (reply.isValid()) {
const QDBusVariant dbusVariant = qvariant_cast<QDBusVariant>(reply.value());
const QXdgDesktopPortalThemePrivate::XdgColorschemePref xdgPref = static_cast<QXdgDesktopPortalThemePrivate::XdgColorschemePref>(dbusVariant.variant().toUInt());
d->colorScheme = QXdgDesktopPortalThemePrivate::colorSchemeFromXdgPref(xdgPref);
const QMap<QString, QVariantMap> settingsMap = qvariant_cast<QMap<QString, QVariantMap>>(reply.value());
if (!settingsMap.isEmpty()) {
const auto xdgColorSchemePref = static_cast<QXdgDesktopPortalThemePrivate::XdgColorschemePref>(settingsMap.value(appearanceInterface).value(colorSchemeKey).toUInt());
d->colorScheme = QXdgDesktopPortalThemePrivate::colorSchemeFromXdgPref(xdgColorSchemePref);
d->contrast = static_cast<Qt::ContrastPreference>(settingsMap.value(appearanceInterface).value(contrastKey).toUInt());
}
}
QDBusConnection::sessionBus().connect(
@ -225,6 +238,12 @@ Qt::ColorScheme QXdgDesktopPortalTheme::colorScheme() const
return d->colorScheme;
}
Qt::ContrastPreference QXdgDesktopPortalTheme::contrastPreference() const
{
Q_D(const QXdgDesktopPortalTheme);
return d->contrast;
}
QPixmap QXdgDesktopPortalTheme::standardPixmap(StandardPixmap sp, const QSizeF &size) const
{
Q_D(const QXdgDesktopPortalTheme);

View File

@ -35,6 +35,7 @@ public:
QVariant themeHint(ThemeHint hint) const override;
Qt::ColorScheme colorScheme() const override;
Qt::ContrastPreference contrastPreference() const override;
QPixmap standardPixmap(StandardPixmap sp, const QSizeF &size) const override;
QIcon fileIcon(const QFileInfo &fileInfo,