master ohmu/fastfetch-openbsd / src / detection / gtk_qt / gtk.c
ohmu  ·  2026-10-15
  1#include "fastfetch.h"
  2#include "common/properties.h"
  3#include "common/settings.h"
  4#include "common/FFcache.h"
  5#include "detection/gtk_qt/gtk_qt.h"
  6#include "detection/displayserver/displayserver.h"
  7
  8static inline bool allPropertiesSet(FFGTKResult* result) {
  9    return result->theme.length > 0 &&
 10        result->icons.length > 0 &&
 11        result->font.length > 0;
 12}
 13
 14// The values read from the desktop settings daemons, shared by the GTK2/3/4 results. They are
 15// owned copies on purpose: `settings.c` hands out strings it leaks deliberately (see the
 16// "Leaks value.chars" notes there), so borrowing them past a `--dynamic-interval` round would
 17// leak one set per round. Copying them also lets `destroy` release them.
 18typedef struct FFGTKSettings {
 19    FFstrbuf theme;
 20    FFstrbuf icons;
 21    FFstrbuf font;
 22    FFstrbuf cursor;
 23    FFstrbuf wallpaper;
 24    int32_t cursorSize;
 25} FFGTKSettings;
 26
 27static inline void applyGTKSettings(FFGTKResult* result, const FFGTKSettings* settings) {
 28    if (result->theme.length == 0) {
 29        ffStrbufAppend(&result->theme, &settings->theme);
 30    }
 31
 32    if (result->icons.length == 0) {
 33        ffStrbufAppend(&result->icons, &settings->icons);
 34    }
 35
 36    if (result->font.length == 0) {
 37        ffStrbufAppend(&result->font, &settings->font);
 38    }
 39
 40    if (result->cursor.length == 0) {
 41        ffStrbufAppend(&result->cursor, &settings->cursor);
 42    }
 43
 44    if (result->cursorSize.length == 0 && settings->cursorSize > 0) {
 45        ffStrbufAppendSInt(&result->cursorSize, settings->cursorSize);
 46    }
 47
 48    if (result->wallpaper.length == 0) {
 49        ffStrbufAppend(&result->wallpaper, &settings->wallpaper);
 50    }
 51}
 52
 53static bool testXfconfWallpaperPropKey([[maybe_unused]] void* data, const char* key) {
 54    int count = 0;
 55    sscanf(key, "/backdrop/screen0/monitor%*[^/]/workspace0/last-image%n", &count);
 56    return count == 0;
 57}
 58
 59static FFGTKSettings gtkSettings;
 60
 61static void initGTKSettings(void* storage) {
 62    FFGTKSettings* settings = storage;
 63
 64    ffStrbufInit(&settings->theme);
 65    ffStrbufInit(&settings->icons);
 66    ffStrbufInit(&settings->font);
 67    ffStrbufInit(&settings->cursor);
 68    ffStrbufInit(&settings->wallpaper);
 69
 70    const char* themeName = nullptr;
 71    const char* iconsName = nullptr;
 72    const char* fontName = nullptr;
 73    const char* cursorTheme = nullptr;
 74    int cursorSize = 0;
 75    const char* wallpaper = nullptr;
 76
 77    const FFDisplayServerResult* wmde = ffConnectDisplayServer();
 78
 79    if (wmde->dePrettyName.length > 0) {
 80        if (ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_XFCE4)) {
 81            themeName = ffSettingsGetXFConf("xsettings", "/Net/ThemeName", FF_VARIANT_TYPE_STRING).strValue;
 82            iconsName = ffSettingsGetXFConf("xsettings", "/Net/IconThemeName", FF_VARIANT_TYPE_STRING).strValue;
 83            fontName = ffSettingsGetXFConf("xsettings", "/Gtk/FontName", FF_VARIANT_TYPE_STRING).strValue;
 84            cursorTheme = ffSettingsGetXFConf("xsettings", "/Gtk/CursorThemeName", FF_VARIANT_TYPE_STRING).strValue;
 85            cursorSize = ffSettingsGetXFConf("xsettings", "/Gtk/CursorThemeSize", FF_VARIANT_TYPE_INT).intValue;
 86            wallpaper = ffSettingsGetXFConfFirstMatch("xfce4-desktop", "/backdrop/screen0", FF_VARIANT_TYPE_STRING, nullptr, testXfconfWallpaperPropKey).strValue;
 87        } else if (ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_CINNAMON)) {
 88            themeName = ffSettingsGetGnome("/org/cinnamon/desktop/interface/gtk-theme", "org.cinnamon.desktop.interface", nullptr, "gtk-theme", FF_VARIANT_TYPE_STRING).strValue;
 89            iconsName = ffSettingsGetGnome("/org/cinnamon/desktop/interface/icon-theme", "org.cinnamon.desktop.interface", nullptr, "icon-theme", FF_VARIANT_TYPE_STRING).strValue;
 90            fontName = ffSettingsGetGnome("/org/cinnamon/desktop/interface/font-name", "org.cinnamon.desktop.interface", nullptr, "font-name", FF_VARIANT_TYPE_STRING).strValue;
 91            cursorTheme = ffSettingsGetGnome("/org/cinnamon/desktop/interface/cursor-theme", "org.cinnamon.desktop.interface", nullptr, "cursor-theme", FF_VARIANT_TYPE_STRING).strValue;
 92            cursorSize = ffSettingsGetGnome("/org/cinnamon/desktop/interface/cursor-size", "org.cinnamon.desktop.interface", nullptr, "cursor-size", FF_VARIANT_TYPE_INT).intValue;
 93            wallpaper = ffSettingsGetGnome("/org/cinnamon/desktop/background/picture-uri", "org.cinnamon.desktop.background", nullptr, "picture-uri", FF_VARIANT_TYPE_STRING).strValue;
 94        } else if (ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_MATE)) {
 95            themeName = ffSettingsGetGnome("/org/mate/interface/gtk-theme", "org.mate.interface", nullptr, "gtk-theme", FF_VARIANT_TYPE_STRING).strValue;
 96            iconsName = ffSettingsGetGnome("/org/mate/interface/icon-theme", "org.mate.interface", nullptr, "icon-theme", FF_VARIANT_TYPE_STRING).strValue;
 97            fontName = ffSettingsGetGnome("/org/mate/interface/font-name", "org.mate.interface", nullptr, "font-name", FF_VARIANT_TYPE_STRING).strValue;
 98            cursorTheme = ffSettingsGetGnome("/org/mate/peripherals-mouse/cursor-theme", "org.mate.peripherals-mouse", nullptr, "cursor-theme", FF_VARIANT_TYPE_STRING).strValue;
 99            cursorSize = ffSettingsGetGnome("/org/mate/peripherals-mouse/cursor-size", "org.mate.peripherals-mouse", nullptr, "cursor-size", FF_VARIANT_TYPE_INT).intValue;
100            wallpaper = ffSettingsGetGnome("/org/mate/desktop/background", "org.mate.background", nullptr, "picture-filename", FF_VARIANT_TYPE_STRING).strValue;
101        } else if (
102            ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_GNOME) ||
103            ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_GNOME_CLASSIC) ||
104            ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_UNITY) ||
105            ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_BUDGIE) ||
106            ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_NEBIDE)) {
107            themeName = ffSettingsGetGnome("/org/gnome/desktop/interface/gtk-theme", "org.gnome.desktop.interface", nullptr, "gtk-theme", FF_VARIANT_TYPE_STRING).strValue;
108            iconsName = ffSettingsGetGnome("/org/gnome/desktop/interface/icon-theme", "org.gnome.desktop.interface", nullptr, "icon-theme", FF_VARIANT_TYPE_STRING).strValue;
109            fontName = ffSettingsGetGnome("/org/gnome/desktop/interface/font-name", "org.gnome.desktop.interface", nullptr, "font-name", FF_VARIANT_TYPE_STRING).strValue;
110            cursorTheme = ffSettingsGetGnome("/org/gnome/desktop/interface/cursor-theme", "org.gnome.desktop.interface", nullptr, "cursor-theme", FF_VARIANT_TYPE_STRING).strValue;
111            cursorSize = ffSettingsGetGnome("/org/gnome/desktop/interface/cursor-size", "org.gnome.desktop.interface", nullptr, "cursor-size", FF_VARIANT_TYPE_INT).intValue;
112            wallpaper = ffSettingsGetGnome("/org/gnome/desktop/background/picture-uri", "org.gnome.desktop.background", nullptr, "picture-uri", FF_VARIANT_TYPE_STRING).strValue;
113        } else if (
114            ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_ENLIGHTENMENT)) {
115            ffEnlightenmentSettings enlightenmentSettings = {};
116            if (ffSettingsGetEnlightenmentProperty(&enlightenmentSettings)) {
117                themeName = enlightenmentSettings.theme;
118                iconsName = enlightenmentSettings.icon_theme;
119                fontName = enlightenmentSettings.font;
120                cursorTheme = enlightenmentSettings.use_e_cursor ? "Enlightenment" : "Application";
121                cursorSize = enlightenmentSettings.cursor_size;
122                wallpaper = enlightenmentSettings.desktop_default_background;
123            }
124        }
125    } else {
126        // Standalone WMs (Hyprland, sway, niri, i3, ...) report no DE and have no settings
127        // daemon, but GTK apps on them still read org.gnome.desktop.interface.
128        // Read via DConf, not GSettings: GSettings synthesizes the schema default for keys
129        // the user never set, DConf returns nothing. Runs last; applyGTKSettings() only
130        // fills still-empty fields, so config files always win.
131        themeName = ffSettingsGetDConf("/org/gnome/desktop/interface/gtk-theme", FF_VARIANT_TYPE_STRING).strValue;
132        iconsName = ffSettingsGetDConf("/org/gnome/desktop/interface/icon-theme", FF_VARIANT_TYPE_STRING).strValue;
133        fontName = ffSettingsGetDConf("/org/gnome/desktop/interface/font-name", FF_VARIANT_TYPE_STRING).strValue;
134        cursorTheme = ffSettingsGetDConf("/org/gnome/desktop/interface/cursor-theme", FF_VARIANT_TYPE_STRING).strValue;
135        cursorSize = ffSettingsGetDConf("/org/gnome/desktop/interface/cursor-size", FF_VARIANT_TYPE_INT).intValue;
136    }
137
138    // The strings above are owned by `settings.c`, which leaks them on purpose, so copy them into
139    // the cache instead of borrowing them: this runs again on every `--dynamic-interval` round.
140    ffStrbufSetS(&settings->theme, themeName);
141    ffStrbufSetS(&settings->icons, iconsName);
142    ffStrbufSetS(&settings->font, fontName);
143    ffStrbufSetS(&settings->cursor, cursorTheme);
144    ffStrbufSetS(&settings->wallpaper, wallpaper);
145    settings->cursorSize = cursorSize;
146}
147
148static void destroyGTKSettings(void* storage) {
149    FFGTKSettings* settings = storage;
150
151    ffStrbufDestroy(&settings->theme);
152    ffStrbufDestroy(&settings->icons);
153    ffStrbufDestroy(&settings->font);
154    ffStrbufDestroy(&settings->cursor);
155    ffStrbufDestroy(&settings->wallpaper);
156    settings->cursorSize = 0;
157}
158
159static FFcacheEntry ffCacheEntryGTKSettings = {
160    .name = "gtk-settings",
161    .storage = &gtkSettings,
162    .init = initGTKSettings,
163    .destroy = destroyGTKSettings,
164};
165
166static void detectGTKFromConfigFile(const char* filename, FFGTKResult* result) {
167    ffParsePropFileValues(filename, 5, (FFpropquery[]) { { "gtk-theme-name =", &result->theme }, { "gtk-icon-theme-name =", &result->icons }, { "gtk-font-name =", &result->font }, { "gtk-cursor-theme-name =", &result->cursor }, { "gtk-cursor-theme-size =", &result->cursorSize } });
168}
169
170static void detectGTKFromConfigDir(FFstrbuf* configDir, const char* version, FFGTKResult* result) {
171    uint32_t configDirLength = configDir->length;
172
173    // <configdir>/gtk-<version>.0/settings.ini
174    ffStrbufAppendS(configDir, "gtk-");
175    ffStrbufAppendS(configDir, version);
176    ffStrbufAppendS(configDir, ".0/settings.ini");
177    detectGTKFromConfigFile(configDir->chars, result);
178    ffStrbufSubstrBefore(configDir, configDirLength);
179    if (allPropertiesSet(result)) {
180        return;
181    }
182
183    // <configdir>/gtk-<version>.0/gtkrc
184    ffStrbufAppendS(configDir, "gtk-");
185    ffStrbufAppendS(configDir, version);
186    ffStrbufAppendS(configDir, ".0/gtkrc");
187    detectGTKFromConfigFile(configDir->chars, result);
188    ffStrbufSubstrBefore(configDir, configDirLength);
189    if (allPropertiesSet(result)) {
190        return;
191    }
192
193    // <configdir>/gtkrc-<version>.0
194    ffStrbufAppendS(configDir, "gtkrc-");
195    ffStrbufAppendS(configDir, version);
196    ffStrbufAppendS(configDir, ".0");
197    detectGTKFromConfigFile(configDir->chars, result);
198    ffStrbufSubstrBefore(configDir, configDirLength);
199    if (allPropertiesSet(result)) {
200        return;
201    }
202
203    // <configdir>/.gtkrc-<version>.0
204    ffStrbufAppendS(configDir, ".gtkrc-");
205    ffStrbufAppendS(configDir, version);
206    ffStrbufAppendS(configDir, ".0");
207    detectGTKFromConfigFile(configDir->chars, result);
208    ffStrbufSubstrBefore(configDir, configDirLength);
209}
210
211static void detectGTK(const char* version, FFGTKResult* result) {
212    // Mate, Cinnamon, GNOME, Unity, Budgie use dconf to save theme config
213    // On other DEs, this will do nothing
214    applyGTKSettings(result, ffCacheGet(&ffCacheEntryGTKSettings));
215    if (allPropertiesSet(result)) {
216        return;
217    }
218
219    // We need to do this because we use multiple threads on configDirs
220    FF_STRBUF_AUTO_DESTROY baseDir = ffStrbufCreateA(64);
221
222    FF_LIST_FOR_EACH (FFstrbuf, configDir, instance.state.platform.configDirs) {
223        ffStrbufSet(&baseDir, configDir);
224        detectGTKFromConfigDir(&baseDir, version, result);
225        if (allPropertiesSet(result)) {
226            break;
227        }
228    }
229}
230
231static void initGTKResult(const char* version, FFGTKResult* result) {
232    ffStrbufInit(&result->theme);
233    ffStrbufInit(&result->icons);
234    ffStrbufInit(&result->font);
235    ffStrbufInit(&result->cursor);
236    ffStrbufInit(&result->cursorSize);
237    ffStrbufInit(&result->wallpaper);
238    detectGTK(version, result);
239}
240
241static void destroyGTKResult(void* storage) {
242    FFGTKResult* result = storage;
243
244    ffStrbufDestroy(&result->theme);
245    ffStrbufDestroy(&result->icons);
246    ffStrbufDestroy(&result->font);
247    ffStrbufDestroy(&result->cursor);
248    ffStrbufDestroy(&result->cursorSize);
249    ffStrbufDestroy(&result->wallpaper);
250}
251
252static void initGTK2Result(void* storage) {
253    initGTKResult("2", storage);
254}
255
256static void initGTK3Result(void* storage) {
257    initGTKResult("3", storage);
258}
259
260static void initGTK4Result(void* storage) {
261    initGTKResult("4", storage);
262}
263
264// Each version gets its own result and its own entry, so a refresh drops all three (each one is
265// then rebuilt on demand, in the same lazy order as a single-shot run). They share the single
266// `gtk-settings` entry above, which keeps a round at one settings query instead of three.
267#define FF_DETECT_GTK_IMPL(version)       \
268    static FFGTKResult result;            \
269    static FFcacheEntry entry = {         \
270        .name = "gtk" #version,           \
271        .storage = &result,               \
272        .init = initGTK##version##Result, \
273        .destroy = destroyGTKResult,      \
274    };                                    \
275    return ffCacheGet(&entry);
276
277const FFGTKResult* ffDetectGTK2(void) {
278    FF_DETECT_GTK_IMPL(2)
279}
280
281const FFGTKResult* ffDetectGTK3(void) {
282    FF_DETECT_GTK_IMPL(3)
283}
284
285const FFGTKResult* ffDetectGTK4(void) {
286    FF_DETECT_GTK_IMPL(4)
287}
288
289#undef FF_DETECT_GTK_IMPL