ohmu
·
2026-10-15
1#include "fastfetch.h"
2#include "common/properties.h"
3#include "common/io.h"
4#include "common/strutil.h"
5#include "common/FFcache.h"
6#include "common/mallocHelper.h"
7#include "detection/gtk_qt/gtk_qt.h"
8#include "detection/displayserver/displayserver.h"
9
10#include <stdlib.h>
11#include <string.h>
12
13static inline bool allValuesSet(const FFQtResult* result) {
14 return result->widgetStyle.length > 0 &&
15 result->colorScheme.length > 0 &&
16 result->icons.length > 0 &&
17 result->font.length > 0 &&
18 result->wallpaper.length > 0;
19}
20
21typedef enum PlasmaCategory: uint8_t {
22 PLASMA_CATEGORY_GENERAL,
23 PLASMA_CATEGORY_KDE,
24 PLASMA_CATEGORY_ICONS,
25 PLASMA_CATEGORY_OTHER
26} PlasmaCategory;
27
28static bool detectPlasmaFromFile(const char* filename, FFQtResult* result) {
29 FF_AUTO_CLOSE_FILE FILE* kdeglobals = fopen(filename, "r");
30 if (kdeglobals == nullptr) {
31 return false;
32 }
33
34 FF_AUTO_FREE char* line = nullptr;
35 size_t len = 0;
36
37 PlasmaCategory category = PLASMA_CATEGORY_OTHER;
38
39 while (getline(&line, &len, kdeglobals) != -1) {
40 if (line[0] == '[') {
41 char categoryName[32];
42 sscanf(line, "[%31[^]]", categoryName);
43
44 if (ffStrEqualsIgnCase(categoryName, "General")) {
45 category = PLASMA_CATEGORY_GENERAL;
46 } else if (ffStrEqualsIgnCase(categoryName, "KDE")) {
47 category = PLASMA_CATEGORY_KDE;
48 } else if (ffStrEqualsIgnCase(categoryName, "Icons")) {
49 category = PLASMA_CATEGORY_ICONS;
50 } else {
51 category = PLASMA_CATEGORY_OTHER;
52 }
53
54 continue;
55 }
56
57 if (category == PLASMA_CATEGORY_KDE && result->widgetStyle.length == 0) {
58 ffParsePropLine(line, "widgetStyle =", &result->widgetStyle);
59 } else if (category == PLASMA_CATEGORY_ICONS && result->icons.length == 0) {
60 ffParsePropLine(line, "Theme =", &result->icons);
61 } else if (category == PLASMA_CATEGORY_GENERAL) {
62 if (result->colorScheme.length == 0) {
63 ffParsePropLine(line, "ColorScheme =", &result->colorScheme);
64 }
65
66 if (result->font.length == 0) {
67 ffParsePropLine(line, "font =", &result->font);
68 }
69
70 // Before plasma 5.23, "Font" was the key instead of "font". Since a lot of distros ship older versions, we test for both.
71 if (result->font.length == 0) {
72 ffParsePropLine(line, "Font =", &result->font);
73 }
74 }
75 }
76
77 return true;
78}
79
80static void detectPlasma(FFQtResult* result) {
81 bool foundAFile = false;
82
83 // We need to do this because we use multiple threads on configDirs
84 FF_STRBUF_AUTO_DESTROY baseDir = ffStrbufCreateA(64);
85
86 FF_LIST_FOR_EACH (FFstrbuf, configDir, instance.state.platform.configDirs) {
87 ffStrbufSet(&baseDir, configDir);
88 ffStrbufAppendS(&baseDir, "kdeglobals");
89
90 if (detectPlasmaFromFile(baseDir.chars, result)) {
91 foundAFile = true;
92 }
93
94 ffStrbufSet(&baseDir, configDir);
95 ffStrbufAppendS(&baseDir, "plasma-org.kde.plasma.desktop-appletsrc");
96
97 ffParsePropFile(baseDir.chars, "Image=", &result->wallpaper);
98
99 if (allValuesSet(result)) {
100 return;
101 }
102 }
103
104 if (!foundAFile) {
105 return;
106 }
107
108 // In Plasma the default value is never set in the config file, but the whole key-value is discarded.
109 /// We must set these values by our self if the file exists (it always does here)
110 if (result->widgetStyle.length == 0) {
111 ffStrbufAppendS(&result->widgetStyle, "Breeze");
112 }
113
114 if (result->colorScheme.length == 0) {
115 ffStrbufAppendS(&result->colorScheme, "BreezeLight");
116 }
117
118 if (result->icons.length == 0) {
119 ffStrbufAppendS(&result->icons, "Breeze");
120 }
121
122 if (result->font.length == 0) {
123 ffStrbufAppendS(&result->font, "Noto Sans, 10");
124 }
125}
126
127static void detectLXQt(FFQtResult* result) {
128 ffParsePropFileConfigValues("lxqt/lxqt.conf", 3, (FFpropquery[]) { { "style = ", &result->widgetStyle }, { "icon_theme = ", &result->icons }, { "font = ", &result->font } });
129
130 ffParsePropFileConfig("pcmanfm-qt/lxqt/settings.conf", "Wallpaper=", &result->wallpaper);
131}
132
133static void detectQtCt(char qver, FFQtResult* result) {
134 // qt5ct and qt6ct are technically separate applications, but they're both
135 // by the same author and qt6ct understands qt5ct in qt6 applications as well.
136 char file[] = "qtXct/qtXct.conf";
137 file[2] = file[8] = qver;
138
139 FF_STRBUF_AUTO_DESTROY font = ffStrbufCreate();
140
141 ffParsePropFileConfigValues(file, 3, (FFpropquery[]) { { "style=", &result->widgetStyle }, { "icon_theme=", &result->icons }, { "general=", &font } });
142
143 if (ffStrbufStartsWithC(&font, '@')) {
144 // See QVariant notes on https://doc.qt.io/qt-5/qsettings.html and
145 // https://github.com/fastfetch-cli/fastfetch/issues/1053#issuecomment-2197254769
146 // Thankfully, newer versions use the more common font encoding.
147 ffStrbufSetNS(&font, 5, file);
148 } else if (qver == '5') {
149 // #1864
150 const char* p = font.chars;
151
152 while (*p) {
153 if (p[0] == '\\' && p[1] == 'x' && isxdigit(p[2]) && isxdigit(p[3]) && isxdigit(p[4]) && isxdigit(p[5])) {
154 uint32_t codepoint = (uint32_t) strtoul((char[]) { p[2], p[3], p[4], p[5], '\0' }, nullptr, 16);
155 ffStrbufAppendUtf32CodePoint(&result->font, codepoint);
156 p += 6;
157 } else {
158 ffStrbufAppendC(&result->font, *p++);
159 }
160 }
161 } else {
162 ffStrbufDestroy(&result->font);
163 ffStrbufInitMove(&result->font, &font);
164 }
165}
166
167static void detectKvantum(FFQtResult* result) {
168 ffParsePropFileConfigValues("Kvantum/kvantum.kvconfig", 1, (FFpropquery[]) {
169 { "theme=", &result->widgetStyle },
170 });
171}
172
173static FFQtResult result;
174
175static void initQtResult(void* storage) {
176 FFQtResult* qt = storage;
177
178 ffStrbufInit(&qt->widgetStyle);
179 ffStrbufInit(&qt->colorScheme);
180 ffStrbufInit(&qt->icons);
181 ffStrbufInit(&qt->font);
182 ffStrbufInit(&qt->wallpaper);
183
184 const FFDisplayServerResult* wmde = ffConnectDisplayServer();
185
186 if (ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_PLASMA)) {
187 detectPlasma(qt);
188 } else if (ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_LXQT)) {
189 detectLXQt(qt);
190 } else {
191 const char* qPlatformTheme = getenv("QT_QPA_PLATFORMTHEME");
192 if (qPlatformTheme && (ffStrEquals(qPlatformTheme, "qt5ct") || ffStrEquals(qPlatformTheme, "qt6ct"))) {
193 detectQtCt(qPlatformTheme[2], qt);
194 }
195 }
196
197 if (ffStrbufEqualS(&qt->widgetStyle, "kvantum") || ffStrbufEqualS(&qt->widgetStyle, "kvantum-dark")) {
198 ffStrbufClear(&qt->widgetStyle);
199 detectKvantum(qt);
200 }
201}
202
203static void destroyQtResult(void* storage) {
204 FFQtResult* qt = storage;
205
206 ffStrbufDestroy(&qt->widgetStyle);
207 ffStrbufDestroy(&qt->colorScheme);
208 ffStrbufDestroy(&qt->icons);
209 ffStrbufDestroy(&qt->font);
210 ffStrbufDestroy(&qt->wallpaper);
211}
212
213static FFcacheEntry ffCacheEntryQt = {
214 .name = "qt",
215 .storage = &result,
216 .init = initQtResult,
217 .destroy = destroyQtResult,
218};
219
220const FFQtResult* ffDetectQt(void) {
221 return ffCacheGet(&ffCacheEntryQt);
222}