1// Copyright (C) 2025 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 "qkdetheme_p.h"
5#include <qpa/qplatformtheme_p.h>
6#include <qpa/qplatformfontdatabase.h>
7#include <qpa/qplatformdialoghelper.h>
8#include <QPalette>
9#include <qpa/qwindowsysteminterface.h>
10#include "qdbuslistener_p.h"
11#include <private/qdbustrayicon_p.h>
12#include <private/qdbusplatformmenu_p.h>
13#include <private/qdbusmenubar_p.h>
14#include <QSettings>
15#include <QStandardPaths>
16
17QT_BEGIN_NAMESPACE
18
19using namespace Qt::StringLiterals;
20
21Q_STATIC_LOGGING_CATEGORY(lcQpaThemeKde, "qt.qpa.theme.kde")
22
23class QKdeThemePrivate : public QGenericUnixThemePrivate
24{
25
26public:
27 enum class KdeSettingType {
28 Root,
29 KDE,
30 Icons,
31 ToolBarIcons,
32 ToolBarStyle,
33 Fonts,
34 Colors,
35 };
36
37 enum class KdeSetting {
38 WidgetStyle,
39 ColorScheme,
40 SingleClick,
41 ShowIconsOnPushButtons,
42 IconTheme,
43 ToolBarIconSize,
44 ToolButtonStyle,
45 WheelScrollLines,
46 DoubleClickInterval,
47 StartDragDistance,
48 StartDragTime,
49 CursorBlinkRate,
50 Font,
51 Fixed,
52 MenuFont,
53 ToolBarFont,
54 ButtonBackground,
55 WindowBackground,
56 ViewForeground,
57 WindowForeground,
58 ViewBackground,
59 SelectionBackground,
60 SelectionForeground,
61 ViewBackgroundAlternate,
62 ButtonForeground,
63 ViewForegroundLink,
64 ViewForegroundVisited,
65 TooltipBackground,
66 TooltipForeground,
67 };
68
69 QKdeThemePrivate(const QStringList &kdeDirs, int kdeVersion);
70 ~QKdeThemePrivate() { clearResources(); }
71
72 static QString kdeGlobals(const QString &kdeDir, int kdeVersion)
73 {
74 if (kdeVersion > 4)
75 return kdeDir + "/kdeglobals"_L1;
76 return kdeDir + "/share/config/kdeglobals"_L1;
77 }
78
79 void refresh();
80 static QVariant readKdeSetting(KdeSetting s, const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &settings);
81 QVariant readKdeSetting(KdeSetting s) const;
82 void clearKdeSettings() const;
83 static void readKdeSystemPalette(const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings, QPalette *pal);
84 static QFont *kdeFont(const QVariant &fontValue);
85 static QStringList kdeIconThemeSearchPaths(const QStringList &kdeDirs);
86
87 const QStringList kdeDirs;
88 const int kdeVersion;
89
90 QString iconThemeName;
91 QString iconFallbackThemeName;
92 QStringList styleNames;
93 int toolButtonStyle = Qt::ToolButtonTextBesideIcon;
94 int toolBarIconSize = 0;
95 bool singleClick = true;
96 bool showIconsOnPushButtons = true;
97 int wheelScrollLines = 3;
98 int doubleClickInterval = 400;
99 int startDragDist = 10;
100 int startDragTime = 500;
101 int cursorBlinkRate = 1000;
102 Qt::ColorScheme m_colorScheme = Qt::ColorScheme::Unknown;
103 Qt::ColorScheme m_requestedColorScheme = Qt::ColorScheme::Unknown;
104 std::unique_ptr<QPalette> systemPalette;
105 QFont *fonts[QPlatformTheme::NFonts];
106 void updateColorScheme(const QString &themeName);
107 bool hasRequestedColorScheme() const { return m_requestedColorScheme != Qt::ColorScheme::Unknown
108 && m_requestedColorScheme != m_colorScheme; }
109
110private:
111 mutable QHash<QString, QSettings *> kdeSettings;
112#if QT_CONFIG(dbus)
113 std::unique_ptr<QDBusListener> dbus;
114 bool initDbus();
115 void settingChangedHandler(QDBusListener::Provider provider,
116 QDBusListener::Setting setting,
117 const QVariant &value);
118 Qt::ColorScheme colorSchemeFromPalette() const;
119#endif // QT_CONFIG(dbus)
120 void clearResources();
121};
122
123#if QT_CONFIG(dbus)
124void QKdeThemePrivate::settingChangedHandler(QDBusListener::Provider provider,
125 QDBusListener::Setting setting,
126 const QVariant &value)
127{
128 if (provider != QDBusListener::Provider::Kde)
129 return;
130
131 switch (setting) {
132 case QDBusListener::Setting::ColorScheme:
133 qCDebug(lcQpaThemeKde) << "KDE color theme changed to:" << value.value<Qt::ColorScheme>();
134 break;
135 case QDBusListener::Setting::Theme:
136 qCDebug(lcQpaThemeKde) << "KDE global theme changed to:" << value.toString();
137 break;
138 case QDBusListener::Setting::ApplicationStyle:
139 qCDebug(lcQpaThemeKde) << "KDE application style changed to:" << value.toString();
140 break;
141 case QDBusListener::Setting::Contrast:
142 qCDebug(lcQpaThemeKde) << "KDE contrast setting changed to: "
143 << value.value<Qt::ContrastPreference>();
144 break;
145 }
146
147 refresh();
148}
149
150void QKdeThemePrivate::clearResources()
151{
152 qDeleteAll(begin: fonts, end: fonts + QPlatformTheme::NFonts);
153 std::fill(first: fonts, last: fonts + QPlatformTheme::NFonts, value: static_cast<QFont *>(nullptr));
154 systemPalette.reset();
155}
156
157bool QKdeThemePrivate::initDbus()
158{
159 dbus.reset(p: new QDBusListener());
160 Q_ASSERT(dbus);
161
162 // Wrap slot in a lambda to avoid inheriting QKdeThemePrivate from QObject
163 auto wrapper = [this](QDBusListener::Provider provider,
164 QDBusListener::Setting setting,
165 const QVariant &value) {
166 settingChangedHandler(provider, setting, value);
167 };
168
169 return QObject::connect(sender: dbus.get(), signal: &QDBusListener::settingChanged, context: dbus.get(), slot&: wrapper);
170}
171#endif // QT_CONFIG(dbus)
172
173QKdeThemePrivate::QKdeThemePrivate(const QStringList &kdeDirs, int kdeVersion)
174 : kdeDirs(kdeDirs), kdeVersion(kdeVersion)
175{
176 std::fill(first: fonts, last: fonts + QPlatformTheme::NFonts, value: static_cast<QFont *>(nullptr));
177#if QT_CONFIG(dbus)
178 initDbus();
179#endif // QT_CONFIG(dbus)
180}
181
182static constexpr QLatin1StringView settingsPrefix(QKdeThemePrivate::KdeSettingType type)
183{
184 switch (type) {
185 case QKdeThemePrivate::KdeSettingType::Root:
186 return QLatin1StringView();
187 case QKdeThemePrivate::KdeSettingType::KDE:
188 return QLatin1StringView("KDE/");
189 case QKdeThemePrivate::KdeSettingType::Fonts:
190 return QLatin1StringView();
191 case QKdeThemePrivate::KdeSettingType::Colors:
192 return QLatin1StringView("Colors:");
193 case QKdeThemePrivate::KdeSettingType::Icons:
194 return QLatin1StringView("Icons/");
195 case QKdeThemePrivate::KdeSettingType::ToolBarIcons:
196 return QLatin1StringView("ToolbarIcons/");
197 case QKdeThemePrivate::KdeSettingType::ToolBarStyle:
198 return QLatin1StringView("Toolbar style/");
199 }
200 // GCC 8.x does not treat __builtin_unreachable() as constexpr
201# if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900)
202 // NOLINTNEXTLINE(qt-use-unreachable-return): Triggers on Clang, breaking GCC 8
203 Q_UNREACHABLE();
204# endif
205 return {};
206}
207
208static constexpr QKdeThemePrivate::KdeSettingType settingsType(QKdeThemePrivate::KdeSetting setting)
209{
210#define CASE(s, type) case QKdeThemePrivate::KdeSetting::s:\
211 return QKdeThemePrivate::KdeSettingType::type
212
213 switch (setting) {
214 CASE(WidgetStyle, Root);
215 CASE(ColorScheme, Root);
216 CASE(SingleClick, KDE);
217 CASE(ShowIconsOnPushButtons, KDE);
218 CASE(IconTheme, Icons);
219 CASE(ToolBarIconSize, ToolBarIcons);
220 CASE(ToolButtonStyle, ToolBarStyle);
221 CASE(WheelScrollLines, KDE);
222 CASE(DoubleClickInterval, KDE);
223 CASE(StartDragDistance, KDE);
224 CASE(StartDragTime, KDE);
225 CASE(CursorBlinkRate, KDE);
226 CASE(Font, Root);
227 CASE(Fixed, Root);
228 CASE(MenuFont, Root);
229 CASE(ToolBarFont, Root);
230 CASE(ButtonBackground, Colors);
231 CASE(WindowBackground, Colors);
232 CASE(ViewForeground, Colors);
233 CASE(WindowForeground, Colors);
234 CASE(ViewBackground, Colors);
235 CASE(SelectionBackground, Colors);
236 CASE(SelectionForeground, Colors);
237 CASE(ViewBackgroundAlternate, Colors);
238 CASE(ButtonForeground, Colors);
239 CASE(ViewForegroundLink, Colors);
240 CASE(ViewForegroundVisited, Colors);
241 CASE(TooltipBackground, Colors);
242 CASE(TooltipForeground, Colors);
243 };
244 // GCC 8.x does not treat __builtin_unreachable() as constexpr
245# if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900)
246 // NOLINTNEXTLINE(qt-use-unreachable-return): Triggers on Clang, breaking GCC 8
247 Q_UNREACHABLE();
248# endif
249 return QKdeThemePrivate::KdeSettingType::Root;
250}
251#undef CASE
252
253static constexpr QLatin1StringView settingsKey(QKdeThemePrivate::KdeSetting setting)
254{
255 switch (setting) {
256 case QKdeThemePrivate::KdeSetting::WidgetStyle:
257 return QLatin1StringView("widgetStyle");
258 case QKdeThemePrivate::KdeSetting::ColorScheme:
259 return QLatin1StringView("ColorScheme");
260 case QKdeThemePrivate::KdeSetting::SingleClick:
261 return QLatin1StringView("SingleClick");
262 case QKdeThemePrivate::KdeSetting::ShowIconsOnPushButtons:
263 return QLatin1StringView("ShowIconsOnPushButtons");
264 case QKdeThemePrivate::KdeSetting::IconTheme:
265 return QLatin1StringView("Theme");
266 case QKdeThemePrivate::KdeSetting::ToolBarIconSize:
267 return QLatin1StringView("Size");
268 case QKdeThemePrivate::KdeSetting::ToolButtonStyle:
269 return QLatin1StringView("ToolButtonStyle");
270 case QKdeThemePrivate::KdeSetting::WheelScrollLines:
271 return QLatin1StringView("WheelScrollLines");
272 case QKdeThemePrivate::KdeSetting::DoubleClickInterval:
273 return QLatin1StringView("DoubleClickInterval");
274 case QKdeThemePrivate::KdeSetting::StartDragDistance:
275 return QLatin1StringView("StartDragDist");
276 case QKdeThemePrivate::KdeSetting::StartDragTime:
277 return QLatin1StringView("StartDragTime");
278 case QKdeThemePrivate::KdeSetting::CursorBlinkRate:
279 return QLatin1StringView("CursorBlinkRate");
280 case QKdeThemePrivate::KdeSetting::Font:
281 return QLatin1StringView("font");
282 case QKdeThemePrivate::KdeSetting::Fixed:
283 return QLatin1StringView("fixed");
284 case QKdeThemePrivate::KdeSetting::MenuFont:
285 return QLatin1StringView("menuFont");
286 case QKdeThemePrivate::KdeSetting::ToolBarFont:
287 return QLatin1StringView("toolBarFont");
288 case QKdeThemePrivate::KdeSetting::ButtonBackground:
289 return QLatin1StringView("Button/BackgroundNormal");
290 case QKdeThemePrivate::KdeSetting::WindowBackground:
291 return QLatin1StringView("Window/BackgroundNormal");
292 case QKdeThemePrivate::KdeSetting::ViewForeground:
293 return QLatin1StringView("View/ForegroundNormal");
294 case QKdeThemePrivate::KdeSetting::WindowForeground:
295 return QLatin1StringView("Window/ForegroundNormal");
296 case QKdeThemePrivate::KdeSetting::ViewBackground:
297 return QLatin1StringView("View/BackgroundNormal");
298 case QKdeThemePrivate::KdeSetting::SelectionBackground:
299 return QLatin1StringView("Selection/BackgroundNormal");
300 case QKdeThemePrivate::KdeSetting::SelectionForeground:
301 return QLatin1StringView("Selection/ForegroundNormal");
302 case QKdeThemePrivate::KdeSetting::ViewBackgroundAlternate:
303 return QLatin1StringView("View/BackgroundAlternate");
304 case QKdeThemePrivate::KdeSetting::ButtonForeground:
305 return QLatin1StringView("Button/ForegroundNormal");
306 case QKdeThemePrivate::KdeSetting::ViewForegroundLink:
307 return QLatin1StringView("View/ForegroundLink");
308 case QKdeThemePrivate::KdeSetting::ViewForegroundVisited:
309 return QLatin1StringView("View/ForegroundVisited");
310 case QKdeThemePrivate::KdeSetting::TooltipBackground:
311 return QLatin1StringView("Tooltip/BackgroundNormal");
312 case QKdeThemePrivate::KdeSetting::TooltipForeground:
313 return QLatin1StringView("Tooltip/ForegroundNormal");
314 };
315 // GCC 8.x does not treat __builtin_unreachable() as constexpr
316# if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900)
317 // NOLINTNEXTLINE(qt-use-unreachable-return): Triggers on Clang, breaking GCC 8
318 Q_UNREACHABLE();
319# endif
320 return {};
321}
322
323void QKdeThemePrivate::refresh()
324{
325 clearResources();
326 clearKdeSettings();
327
328 toolButtonStyle = Qt::ToolButtonTextBesideIcon;
329 toolBarIconSize = 0;
330 styleNames.clear();
331 if (kdeVersion >= 5)
332 styleNames << QStringLiteral("breeze");
333 styleNames << QStringLiteral("Oxygen") << QStringLiteral("Fusion") << QStringLiteral("windows");
334 if (kdeVersion >= 5)
335 iconFallbackThemeName = iconThemeName = QStringLiteral("breeze");
336 else
337 iconFallbackThemeName = iconThemeName = QStringLiteral("oxygen");
338
339 systemPalette.reset(p: new QPalette(QPalette()));
340 readKdeSystemPalette(kdeDirs, kdeVersion, kdeSettings, pal: systemPalette.get());
341
342 const QVariant styleValue = readKdeSetting(s: KdeSetting::WidgetStyle);
343 if (styleValue.isValid()) {
344 const QString style = styleValue.toString();
345 if (style != styleNames.front())
346 styleNames.push_front(t: style);
347 }
348
349 const QVariant colorScheme = readKdeSetting(s: KdeSetting::ColorScheme);
350
351 updateColorScheme(themeName: colorScheme.toString());
352
353 const QVariant singleClickValue = readKdeSetting(s: KdeSetting::SingleClick);
354 if (singleClickValue.isValid())
355 singleClick = singleClickValue.toBool();
356 else if (kdeVersion >= 6) // Plasma 6 defaults to double-click
357 singleClick = false;
358 else // earlier version to single-click
359 singleClick = true;
360
361 const QVariant showIconsOnPushButtonsValue = readKdeSetting(s: KdeSetting::ShowIconsOnPushButtons);
362 if (showIconsOnPushButtonsValue.isValid())
363 showIconsOnPushButtons = showIconsOnPushButtonsValue.toBool();
364
365 const QVariant themeValue = readKdeSetting(s: KdeSetting::IconTheme);
366 if (themeValue.isValid())
367 iconThemeName = themeValue.toString();
368
369 const QVariant toolBarIconSizeValue = readKdeSetting(s: KdeSetting::ToolBarIconSize);
370 if (toolBarIconSizeValue.isValid())
371 toolBarIconSize = toolBarIconSizeValue.toInt();
372
373 const QVariant toolbarStyleValue = readKdeSetting(s: KdeSetting::ToolButtonStyle);
374 if (toolbarStyleValue.isValid()) {
375 const QString toolBarStyle = toolbarStyleValue.toString();
376 if (toolBarStyle == "TextBesideIcon"_L1)
377 toolButtonStyle = Qt::ToolButtonTextBesideIcon;
378 else if (toolBarStyle == "TextOnly"_L1)
379 toolButtonStyle = Qt::ToolButtonTextOnly;
380 else if (toolBarStyle == "TextUnderIcon"_L1)
381 toolButtonStyle = Qt::ToolButtonTextUnderIcon;
382 }
383
384 const QVariant wheelScrollLinesValue = readKdeSetting(s: KdeSetting::WheelScrollLines);
385 if (wheelScrollLinesValue.isValid())
386 wheelScrollLines = wheelScrollLinesValue.toInt();
387
388 const QVariant doubleClickIntervalValue = readKdeSetting(s: KdeSetting::DoubleClickInterval);
389 if (doubleClickIntervalValue.isValid())
390 doubleClickInterval = doubleClickIntervalValue.toInt();
391
392 const QVariant startDragDistValue = readKdeSetting(s: KdeSetting::StartDragDistance);
393 if (startDragDistValue.isValid())
394 startDragDist = startDragDistValue.toInt();
395
396 const QVariant startDragTimeValue = readKdeSetting(s: KdeSetting::StartDragTime);
397 if (startDragTimeValue.isValid())
398 startDragTime = startDragTimeValue.toInt();
399
400 const QVariant cursorBlinkRateValue = readKdeSetting(s: KdeSetting::CursorBlinkRate);
401 if (cursorBlinkRateValue.isValid()) {
402 cursorBlinkRate = cursorBlinkRateValue.toInt();
403 cursorBlinkRate = cursorBlinkRate > 0 ? qBound(min: 200, val: cursorBlinkRate, max: 2000) : 0;
404 }
405
406 // Read system font, ignore 'smallestReadableFont'
407 if (QFont *systemFont = kdeFont(fontValue: readKdeSetting(s: KdeSetting::Font)))
408 fonts[QPlatformTheme::SystemFont] = systemFont;
409 else
410 fonts[QPlatformTheme::SystemFont] = new QFont(QLatin1StringView(QGenericUnixTheme::defaultSystemFontNameC),
411 QGenericUnixTheme::defaultSystemFontSize);
412
413 if (QFont *fixedFont = kdeFont(fontValue: readKdeSetting(s: KdeSetting::Fixed))) {
414 fonts[QPlatformTheme::FixedFont] = fixedFont;
415 } else {
416 fixedFont = new QFont(QLatin1StringView(QGenericUnixTheme::defaultFixedFontNameC),
417 QGenericUnixTheme::defaultSystemFontSize);
418 fixedFont->setStyleHint(QFont::TypeWriter);
419 fonts[QPlatformTheme::FixedFont] = fixedFont;
420 }
421
422 if (QFont *menuFont = kdeFont(fontValue: readKdeSetting(s: KdeSetting::MenuFont))) {
423 fonts[QPlatformTheme::MenuFont] = menuFont;
424 fonts[QPlatformTheme::MenuBarFont] = new QFont(*menuFont);
425 }
426
427 if (QFont *toolBarFont = kdeFont(fontValue: readKdeSetting(s: KdeSetting::ToolBarFont)))
428 fonts[QPlatformTheme::ToolButtonFont] = toolBarFont;
429
430 QWindowSystemInterface::handleThemeChange();
431
432 qCDebug(lcQpaFonts) << "default fonts: system" << fonts[QPlatformTheme::SystemFont]
433 << "fixed" << fonts[QPlatformTheme::FixedFont];
434 qDeleteAll(c: kdeSettings);
435}
436
437QVariant QKdeThemePrivate::readKdeSetting(KdeSetting s, const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings)
438{
439 for (const QString &kdeDir : kdeDirs) {
440 QSettings *settings = kdeSettings.value(key: kdeDir);
441 if (!settings) {
442 const QString kdeGlobalsPath = kdeGlobals(kdeDir, kdeVersion);
443 if (QFileInfo(kdeGlobalsPath).isReadable()) {
444 settings = new QSettings(kdeGlobalsPath, QSettings::IniFormat);
445 kdeSettings.insert(key: kdeDir, value: settings);
446 }
447 }
448 if (settings) {
449 const QString key = settingsPrefix(settingsType(s)) + settingsKey(s);
450 const QVariant value = settings->value(key);
451 if (value.isValid())
452 return value;
453 }
454 }
455 return QVariant();
456}
457
458QVariant QKdeThemePrivate::readKdeSetting(KdeSetting s) const
459{
460 return readKdeSetting(s, kdeDirs, kdeVersion, kdeSettings);
461}
462
463void QKdeThemePrivate::clearKdeSettings() const
464{
465 kdeSettings.clear();
466}
467
468// Reads the color from the KDE configuration, and store it in the
469// palette with the given color role if found.
470static inline bool kdeColor(QPalette *pal, QPalette::ColorRole role, const QVariant &value)
471{
472 if (!value.isValid())
473 return false;
474 const QStringList values = value.toStringList();
475 if (values.size() != 3)
476 return false;
477 pal->setBrush(acr: role, abrush: QColor(values.at(i: 0).toInt(), values.at(i: 1).toInt(), values.at(i: 2).toInt()));
478 return true;
479}
480
481void QKdeThemePrivate::readKdeSystemPalette(const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings, QPalette *pal)
482{
483 if (!kdeColor(pal, role: QPalette::Button, value: readKdeSetting(s: KdeSetting::ButtonBackground, kdeDirs, kdeVersion, kdeSettings))) {
484 // kcolorscheme.cpp: SetDefaultColors
485 const QColor defaultWindowBackground(214, 210, 208);
486 const QColor defaultButtonBackground(223, 220, 217);
487 *pal = QPalette(defaultButtonBackground, defaultWindowBackground);
488 return;
489 }
490
491 kdeColor(pal, role: QPalette::Window, value: readKdeSetting(s: KdeSetting::WindowBackground, kdeDirs, kdeVersion, kdeSettings));
492 kdeColor(pal, role: QPalette::Text, value: readKdeSetting(s: KdeSetting::ViewForeground, kdeDirs, kdeVersion, kdeSettings));
493 kdeColor(pal, role: QPalette::WindowText, value: readKdeSetting(s: KdeSetting::WindowForeground, kdeDirs, kdeVersion, kdeSettings));
494 kdeColor(pal, role: QPalette::Base, value: readKdeSetting(s: KdeSetting::ViewBackground, kdeDirs, kdeVersion, kdeSettings));
495 kdeColor(pal, role: QPalette::Highlight, value: readKdeSetting(s: KdeSetting::SelectionBackground, kdeDirs, kdeVersion, kdeSettings));
496 kdeColor(pal, role: QPalette::HighlightedText, value: readKdeSetting(s: KdeSetting::SelectionForeground, kdeDirs, kdeVersion, kdeSettings));
497 kdeColor(pal, role: QPalette::AlternateBase, value: readKdeSetting(s: KdeSetting::ViewBackgroundAlternate, kdeDirs, kdeVersion, kdeSettings));
498 kdeColor(pal, role: QPalette::ButtonText, value: readKdeSetting(s: KdeSetting::ButtonForeground, kdeDirs, kdeVersion, kdeSettings));
499 kdeColor(pal, role: QPalette::Link, value: readKdeSetting(s: KdeSetting::ViewForegroundLink, kdeDirs, kdeVersion, kdeSettings));
500 kdeColor(pal, role: QPalette::LinkVisited, value: readKdeSetting(s: KdeSetting::ViewForegroundVisited, kdeDirs, kdeVersion, kdeSettings));
501 kdeColor(pal, role: QPalette::ToolTipBase, value: readKdeSetting(s: KdeSetting::TooltipBackground, kdeDirs, kdeVersion, kdeSettings));
502 kdeColor(pal, role: QPalette::ToolTipText, value: readKdeSetting(s: KdeSetting::TooltipForeground, kdeDirs, kdeVersion, kdeSettings));
503
504 // The above code sets _all_ color roles to "normal" colors. In KDE, the disabled
505 // color roles are calculated by applying various effects described in kdeglobals.
506 // We use a bit simpler approach here, similar logic than in qt_palette_from_color().
507 const QColor button = pal->color(cr: QPalette::Button);
508 int h, s, v;
509 button.getHsv(h: &h, s: &s, v: &v);
510
511 const QBrush whiteBrush = QBrush(Qt::white);
512 const QBrush buttonBrush = QBrush(button);
513 const QBrush buttonBrushDark = QBrush(button.darker(f: v > 128 ? 200 : 50));
514 const QBrush buttonBrushDark150 = QBrush(button.darker(f: v > 128 ? 150 : 75));
515 const QBrush buttonBrushLight150 = QBrush(button.lighter(f: v > 128 ? 150 : 75));
516 const QBrush buttonBrushLight = QBrush(button.lighter(f: v > 128 ? 200 : 50));
517
518 pal->setBrush(cg: QPalette::Disabled, cr: QPalette::WindowText, brush: buttonBrushDark);
519 pal->setBrush(cg: QPalette::Disabled, cr: QPalette::ButtonText, brush: buttonBrushDark);
520 pal->setBrush(cg: QPalette::Disabled, cr: QPalette::Button, brush: buttonBrush);
521 pal->setBrush(cg: QPalette::Disabled, cr: QPalette::Text, brush: buttonBrushDark);
522 pal->setBrush(cg: QPalette::Disabled, cr: QPalette::BrightText, brush: whiteBrush);
523 pal->setBrush(cg: QPalette::Disabled, cr: QPalette::Base, brush: buttonBrush);
524 pal->setBrush(cg: QPalette::Disabled, cr: QPalette::Window, brush: buttonBrush);
525 pal->setBrush(cg: QPalette::Disabled, cr: QPalette::Highlight, brush: buttonBrushDark150);
526 pal->setBrush(cg: QPalette::Disabled, cr: QPalette::HighlightedText, brush: buttonBrushLight150);
527
528 // set calculated colors for all groups
529 pal->setBrush(acr: QPalette::Light, abrush: buttonBrushLight);
530 pal->setBrush(acr: QPalette::Midlight, abrush: buttonBrushLight150);
531 pal->setBrush(acr: QPalette::Mid, abrush: buttonBrushDark150);
532 pal->setBrush(acr: QPalette::Dark, abrush: buttonBrushDark);
533}
534
535/*!
536 \class QKdeTheme
537 \brief QKdeTheme is a theme implementation for the KDE desktop (version 4 or higher).
538 \since 5.0
539 \internal
540 \ingroup qpa
541*/
542
543const char *QKdeTheme::name = "kde";
544
545QKdeTheme::QKdeTheme(const QStringList& kdeDirs, int kdeVersion)
546 : QGenericUnixTheme(new QKdeThemePrivate(kdeDirs, kdeVersion))
547{
548 d_func()->refresh();
549}
550
551QKdeTheme::~QKdeTheme()
552 = default;
553
554QFont *QKdeThemePrivate::kdeFont(const QVariant &fontValue)
555{
556 if (fontValue.isValid()) {
557 // Read font value: Might be a QStringList as KDE stores fonts without quotes.
558 // Also retrieve the family for the constructor since we cannot use the
559 // default constructor of QFont, which accesses QGuiApplication::systemFont()
560 // causing recursion.
561 QString fontDescription;
562 QString fontFamily;
563 if (fontValue.userType() == QMetaType::QStringList) {
564 const QStringList list = fontValue.toStringList();
565 if (!list.isEmpty()) {
566 fontFamily = list.first();
567 fontDescription = list.join(sep: u',');
568 }
569 } else {
570 fontDescription = fontFamily = fontValue.toString();
571 }
572 if (!fontDescription.isEmpty()) {
573 QFont font(fontFamily);
574 if (font.fromString(fontDescription))
575 return new QFont(font);
576 }
577 }
578 return nullptr;
579}
580
581
582QStringList QKdeThemePrivate::kdeIconThemeSearchPaths(const QStringList &kdeDirs)
583{
584 QStringList paths = QGenericUnixTheme::xdgIconThemePaths();
585 const QString iconPath = QStringLiteral("/share/icons");
586 for (const QString &candidate : kdeDirs) {
587 const QFileInfo fi(candidate + iconPath);
588 if (fi.isDir())
589 paths.append(t: fi.absoluteFilePath());
590 }
591 return paths;
592}
593
594QVariant QKdeTheme::themeHint(QPlatformTheme::ThemeHint hint) const
595{
596 Q_D(const QKdeTheme);
597 switch (hint) {
598 case QPlatformTheme::UseFullScreenForPopupMenu:
599 return QVariant(true);
600 case QPlatformTheme::DialogButtonBoxButtonsHaveIcons:
601 return QVariant(d->showIconsOnPushButtons);
602 case QPlatformTheme::DialogButtonBoxLayout:
603 return QVariant(QPlatformDialogHelper::KdeLayout);
604 case QPlatformTheme::ToolButtonStyle:
605 return QVariant(d->toolButtonStyle);
606 case QPlatformTheme::ToolBarIconSize:
607 return QVariant(d->toolBarIconSize);
608 case QPlatformTheme::SystemIconThemeName:
609 return QVariant(d->iconThemeName);
610 case QPlatformTheme::SystemIconFallbackThemeName:
611 return QVariant(d->iconFallbackThemeName);
612 case QPlatformTheme::IconThemeSearchPaths:
613 return QVariant(d->kdeIconThemeSearchPaths(kdeDirs: d->kdeDirs));
614 case QPlatformTheme::IconPixmapSizes:
615 return QVariant::fromValue(value: availableXdgFileIconSizes());
616 case QPlatformTheme::StyleNames:
617 return QVariant(d->styleNames);
618 case QPlatformTheme::KeyboardScheme:
619 return QVariant(int(KdeKeyboardScheme));
620 case QPlatformTheme::ItemViewActivateItemOnSingleClick:
621 return QVariant(d->singleClick);
622 case QPlatformTheme::WheelScrollLines:
623 return QVariant(d->wheelScrollLines);
624 case QPlatformTheme::MouseDoubleClickInterval:
625 return QVariant(d->doubleClickInterval);
626 case QPlatformTheme::StartDragTime:
627 return QVariant(d->startDragTime);
628 case QPlatformTheme::StartDragDistance:
629 return QVariant(d->startDragDist);
630 case QPlatformTheme::CursorFlashTime:
631 return QVariant(d->cursorBlinkRate);
632 case QPlatformTheme::UiEffects:
633 return QVariant(int(HoverEffect));
634 case QPlatformTheme::MouseCursorTheme:
635 return QVariant(mouseCursorTheme());
636 case QPlatformTheme::MouseCursorSize:
637 return QVariant(mouseCursorSize());
638 case QPlatformTheme::PreferFileIconFromTheme:
639 return true;
640 default:
641 break;
642 }
643 return QPlatformTheme::themeHint(hint);
644}
645
646QIcon QKdeTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions) const
647{
648#if QT_CONFIG(mimetype)
649 return xdgFileIcon(fileInfo);
650#else
651 Q_UNUSED(fileInfo);
652 return QIcon();
653#endif
654}
655
656/*!
657 \internal
658 \reimp
659 \brief QKdeTheme::requestColorScheme Programmatically request a color scheme
660 If \a scheme is \c Dark or \c Light, \a scheme is applied to the application,
661 independently from the current KDE theme.
662 If \a scheme is \c Unknown, the current KDE theme's color scheme will be applied instead.
663 This is the default behavior.
664
665 \note
666 A KDE theme is considered either \c Dark or \c Light. When the requested color scheme
667 doesn't match the current KDE theme, a default \c Dark or \c Light fusion palette
668 is used instead.
669 \sa QKdeThemePrivate::hasRequestedColorScheme
670*/
671
672/*!
673 \internal
674 \brief QKdeThemePrivate::hasRequestedColorScheme Check if fusion palette fallback is necessary.
675 This internal helper function returns true, if
676 \list
677 \li a color scheme has been programmatically requested, and
678 \li the requested color scheme differs from the current KDE theme's color scheme.
679 \endlist
680 \sa QKdeTheme:requestColorScheme
681*/
682
683void QKdeTheme::requestColorScheme(Qt::ColorScheme scheme)
684{
685 Q_D(QKdeTheme);
686 if (d->m_requestedColorScheme == scheme)
687 return;
688 qCDebug(lcQpaThemeKde) << scheme << "has been requested. Theme supports color scheme:"
689 << d->m_colorScheme;
690 d->m_requestedColorScheme = scheme;
691 d->refresh();
692}
693
694Qt::ColorScheme QKdeTheme::colorScheme() const
695{
696 Q_D(const QKdeTheme);
697#ifdef QT_DEBUG
698 if (d->hasRequestedColorScheme()) {
699 qCDebug(lcQpaThemeKde) << "Reuqested color scheme" << d->m_requestedColorScheme
700 << "differs from theme color scheme" << d->m_colorScheme;
701 }
702#endif
703 return d->hasRequestedColorScheme() ? d->m_requestedColorScheme
704 : d->m_colorScheme;
705}
706
707/*!
708 \internal
709 \brief QKdeTheme::updateColorScheme - guess and set a color scheme for unix themes.
710 KDE themes do not have a color scheme property.
711 The key words "dark" or "light" are usually part of the theme name.
712 This is, however, not a mandatory convention.
713
714 If \param themeName contains a valid key word, the respective color scheme is set.
715 If it doesn't, the color scheme is heuristically determined by comparing text and base color
716 of the system palette.
717 */
718void QKdeThemePrivate::updateColorScheme(const QString &themeName)
719{
720 if (themeName.contains(s: QLatin1StringView("light"), cs: Qt::CaseInsensitive)) {
721 m_colorScheme = Qt::ColorScheme::Light;
722 return;
723 }
724 if (themeName.contains(s: QLatin1StringView("dark"), cs: Qt::CaseInsensitive)) {
725 m_colorScheme = Qt::ColorScheme::Dark;
726 return;
727 }
728
729 m_colorScheme = colorSchemeFromPalette();
730}
731
732Qt::ColorScheme QKdeThemePrivate::colorSchemeFromPalette() const
733{
734 if (!systemPalette)
735 return Qt::ColorScheme::Unknown;
736 if (systemPalette->text().color().lightness() < systemPalette->base().color().lightness())
737 return Qt::ColorScheme::Light;
738 if (systemPalette->text().color().lightness() > systemPalette->base().color().lightness())
739 return Qt::ColorScheme::Dark;
740 return Qt::ColorScheme::Unknown;
741}
742
743const QPalette *QKdeTheme::palette(Palette type) const
744{
745 Q_D(const QKdeTheme);
746 if (d->hasRequestedColorScheme()) {
747 qCDebug(lcQpaThemeKde) << "Current KDE theme doesn't support reuqested color scheme"
748 << d->m_requestedColorScheme << "Falling back to fusion palette:"
749 << type;
750 return QPlatformTheme::palette(type);
751 }
752
753 return d->systemPalette.get();
754}
755
756const QFont *QKdeTheme::font(Font type) const
757{
758 Q_D(const QKdeTheme);
759 return d->fonts[type];
760}
761
762QPlatformTheme *QKdeTheme::createKdeTheme()
763{
764 const QByteArray kdeVersionBA = qgetenv(varName: "KDE_SESSION_VERSION");
765 const int kdeVersion = kdeVersionBA.toInt();
766 if (kdeVersion < 4)
767 return nullptr;
768
769 if (kdeVersion > 4)
770 // Plasma 5 follows XDG spec
771 // but uses the same config file format:
772 return new QKdeTheme(QStandardPaths::standardLocations(type: QStandardPaths::GenericConfigLocation), kdeVersion);
773
774 // Determine KDE prefixes in the following priority order:
775 // - KDEHOME and KDEDIRS environment variables
776 // - ~/.kde(<version>)
777 // - read prefixes from /etc/kde<version>rc
778 // - fallback to /etc/kde<version>
779
780 QStringList kdeDirs;
781 const QString kdeHomePathVar = qEnvironmentVariable(varName: "KDEHOME");
782 if (!kdeHomePathVar.isEmpty())
783 kdeDirs += kdeHomePathVar;
784
785 const QString kdeDirsVar = qEnvironmentVariable(varName: "KDEDIRS");
786 if (!kdeDirsVar.isEmpty())
787 kdeDirs += kdeDirsVar.split(sep: u':', behavior: Qt::SkipEmptyParts);
788
789 const QString kdeVersionHomePath = QDir::homePath() + "/.kde"_L1 + QLatin1StringView(kdeVersionBA);
790 if (QFileInfo(kdeVersionHomePath).isDir())
791 kdeDirs += kdeVersionHomePath;
792
793 const QString kdeHomePath = QDir::homePath() + "/.kde"_L1;
794 if (QFileInfo(kdeHomePath).isDir())
795 kdeDirs += kdeHomePath;
796
797 const QString kdeRcPath = "/etc/kde"_L1 + QLatin1StringView(kdeVersionBA) + "rc"_L1;
798 if (QFileInfo(kdeRcPath).isReadable()) {
799 QSettings kdeSettings(kdeRcPath, QSettings::IniFormat);
800 kdeSettings.beginGroup(QStringLiteral("Directories-default"));
801 kdeDirs += kdeSettings.value(QStringLiteral("prefixes")).toStringList();
802 }
803
804 const QString kdeVersionPrefix = "/etc/kde"_L1 + QLatin1StringView(kdeVersionBA);
805 if (QFileInfo(kdeVersionPrefix).isDir())
806 kdeDirs += kdeVersionPrefix;
807
808 kdeDirs.removeDuplicates();
809 if (kdeDirs.isEmpty()) {
810 qWarning(msg: "Unable to determine KDE dirs");
811 return nullptr;
812 }
813
814 return new QKdeTheme(kdeDirs, kdeVersion);
815}
816
817#if QT_CONFIG(dbus)
818QPlatformMenuBar *QKdeTheme::createPlatformMenuBar() const
819{
820 if (isDBusGlobalMenuAvailable())
821 return new QDBusMenuBar();
822 return nullptr;
823}
824#endif
825
826#if QT_CONFIG(dbus) && QT_CONFIG(systemtrayicon)
827QPlatformSystemTrayIcon *QKdeTheme::createPlatformSystemTrayIcon() const
828{
829 if (shouldUseDBusTray())
830 return new QDBusTrayIcon();
831 return nullptr;
832}
833#endif
834
835QT_END_NAMESPACE
836

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