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 = >kSettings,
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