master ohmu/fastfetch-openbsd / src / detection / gtk_qt / qt.c
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}