master ohmu/fastfetch-openbsd / src / detection / displayserver / linux / wmde.c
ohmu  ·  2026-10-15
  1#include "displayserver_linux.h"
  2#include "common/io.h"
  3#include "common/properties.h"
  4#include "common/strutil.h"
  5#include "common/mallocHelper.h"
  6
  7#include <stdlib.h>
  8#include <string.h>
  9#include <unistd.h>
 10#include <libgen.h>
 11
 12#if __FreeBSD__
 13    #include <sys/sysctl.h>
 14    #include <sys/types.h>
 15    #include <sys/user.h>
 16#elif __OpenBSD__
 17    #include <sys/param.h>
 18    #include <sys/sysctl.h>
 19#elif __sun
 20    #include <procfs.h>
 21#elif __NetBSD__
 22    #include <sys/types.h>
 23    #include <sys/sysctl.h>
 24#endif
 25
 26static const char* parseEnv(void) {
 27    const char* env;
 28
 29    env = getenv("XDG_CURRENT_DESKTOP");
 30    if (ffStrSet(env)) {
 31        return env;
 32    }
 33
 34    env = getenv("XDG_SESSION_DESKTOP");
 35    if (ffStrSet(env)) {
 36        return env;
 37    }
 38
 39    env = getenv("CURRENT_DESKTOP");
 40    if (ffStrSet(env)) {
 41        return env;
 42    }
 43
 44    env = getenv("SESSION_DESKTOP");
 45    if (ffStrSet(env)) {
 46        return env;
 47    }
 48
 49    env = getenv("DESKTOP_SESSION");
 50    if (ffStrSet(env)) {
 51        return env;
 52    }
 53
 54    if (getenv("KDE_FULL_SESSION") != nullptr || getenv("KDE_SESSION_UID") != nullptr || getenv("KDE_SESSION_VERSION") != nullptr) {
 55        return "KDE";
 56    }
 57
 58    if (getenv("GNOME_DESKTOP_SESSION_ID") != nullptr) {
 59        return "GNOME";
 60    }
 61
 62    if (getenv("MATE_DESKTOP_SESSION_ID") != nullptr) {
 63        return "Mate";
 64    }
 65
 66    if (getenv("TDE_FULL_SESSION") != nullptr) {
 67        return "Trinity";
 68    }
 69
 70    if (getenv("HYPRLAND_CMD") != nullptr) {
 71        return "Hyprland";
 72    }
 73
 74    if (getenv("SWAYSOCK") != nullptr) {
 75        return "Sway";
 76    }
 77
 78    if (getenv("COSMIC_VERSION") != nullptr) {
 79        return "COSMIC";
 80    }
 81
 82#if __linux__ && !__ANDROID__
 83    if (
 84        getenv("WAYLAND_DISPLAY") != nullptr &&
 85        ffPathExists("/mnt/wslg/", FF_PATHTYPE_DIRECTORY)) {
 86        return "WSLg";
 87    }
 88#endif
 89
 90    return nullptr;
 91}
 92
 93static void applyPrettyNameIfWM(FFDisplayServerResult* result, const char* name) {
 94    if (!ffStrSet(name)) {
 95        return;
 96    }
 97
 98    if (
 99        ffStrEqualsIgnCase(name, "kwin") ||
100        ffStrStartsWithIgnCase(name, "kwin_") ||
101        ffStrEndsWithIgnCase(name, "-kwin_wayland") ||
102        ffStrEndsWithIgnCase(name, "-kwin_x11")) {
103        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_KWIN);
104    } else if (
105        ffStrEqualsIgnCase(name, "gnome-shell") ||
106        ffStrEqualsIgnCase(name, "gnome shell") ||
107        ffStrEqualsIgnCase(name, "gnome-session-binary") ||
108        ffStrEqualsIgnCase(name, "Mutter")) {
109        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_MUTTER);
110    } else if (
111        ffStrEqualsIgnCase(name, "cinnamon") ||
112        ffStrStartsWithIgnCase(name, "cinnamon-") ||
113        ffStrEqualsIgnCase(name, "Muffin") ||
114        ffStrEqualsIgnCase(name, "Mutter (Muffin)")) {
115        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_MUFFIN);
116    } else if (ffStrEqualsIgnCase(name, "sway")) {
117        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_SWAY);
118    } else if (ffStrEqualsIgnCase(name, "weston")) {
119        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_WESTON);
120    } else if (ffStrEqualsIgnCase(name, "wayfire")) {
121        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_WAYFIRE);
122    } else if (ffStrEqualsIgnCase(name, "openbox")) {
123        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_OPENBOX);
124    } else if (ffStrEqualsIgnCase(name, "xfwm4")) {
125        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_XFWM4);
126    } else if (ffStrEqualsIgnCase(name, "Marco") ||
127        ffStrEqualsIgnCase(name, "Metacity (Marco)")) {
128        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_MARCO);
129    } else if (ffStrEqualsIgnCase(name, "xmonad")) {
130        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_XMONAD);
131    } else if (ffStrEqualsIgnCase(name, "WSLg")) {
132        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_WSLG);
133    } else if (ffStrEqualsIgnCase(name, "dwm")) {
134        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_DWM);
135    } else if (ffStrEqualsIgnCase(name, "bspwm")) {
136        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_BSPWM);
137    } else if (ffStrEqualsIgnCase(name, "tinywm")) {
138        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_TINYWM);
139    } else if (ffStrEqualsIgnCase(name, "qtile")) {
140        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_QTILE);
141    } else if (ffStrEqualsIgnCase(name, "herbstluftwm")) {
142        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_HERBSTLUFTWM);
143    } else if (ffStrEqualsIgnCase(name, "icewm")) {
144        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_ICEWM);
145    } else if (ffStrEqualsIgnCase(name, "dtwm")) {
146        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_DTWM);
147    } else if (ffStrEqualsIgnCase(name, "fvwm")) {
148        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_FVWM);
149    } else if (ffStrEqualsIgnCase(name, "ctwm")) {
150        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_CTWM);
151    } else if (ffStrEqualsIgnCase(name, "hyprland")) {
152        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_HYPRLAND);
153    } else if (ffStrEqualsIgnCase(name, "ratpoison")) {
154        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_RATPOISON);
155    }
156}
157
158static void applyNameIfWM(FFDisplayServerResult* result, const char* processName) {
159    applyPrettyNameIfWM(result, processName);
160    if (result->wmPrettyName.length > 0) {
161        ffStrbufSetS(&result->wmProcessName, processName);
162    }
163}
164
165static void applyBetterWM(FFDisplayServerResult* result, const char* processName) {
166    if (!ffStrSet(processName)) {
167        return;
168    }
169
170    ffStrbufSetS(&result->wmProcessName, processName);
171
172    // If it is a known wm, this will set the pretty name
173    applyPrettyNameIfWM(result, processName);
174
175    // If it isn't a known wm, set the pretty name to the process name
176    if (result->wmPrettyName.length == 0) {
177        ffStrbufAppend(&result->wmPrettyName, &result->wmProcessName);
178    }
179}
180
181static void applyPrettyNameIfDE(FFDisplayServerResult* result, const char* name) {
182    if (!ffStrSet(name)) {
183        return;
184    }
185
186    else if (
187        ffStrEqualsIgnCase(name, "KDE") ||
188        ffStrEqualsIgnCase(name, "plasma") ||
189        ffStrEqualsIgnCase(name, "plasmashell") ||
190        ffStrEqualsIgnCase(name, "plasmawayland")) {
191        ffStrbufSetStatic(&result->deProcessName, "plasmashell");
192        ffStrbufSetStatic(&result->dePrettyName, FF_DE_PRETTY_PLASMA);
193        applyBetterWM(result, getenv("KDEWM"));
194    }
195
196    else if (
197        ffStrEqualsIgnCase(name, "GNOME") ||
198        ffStrEqualsIgnCase(name, "ubuntu:GNOME") ||
199        ffStrEqualsIgnCase(name, "ubuntu") ||
200        ffStrEqualsIgnCase(name, "gnome-shell")) {
201        ffStrbufSetStatic(&result->deProcessName, "gnome-shell");
202        const char* sessionMode = getenv("GNOME_SHELL_SESSION_MODE");
203        if (sessionMode && ffStrEquals(sessionMode, "classic")) {
204            ffStrbufSetStatic(&result->dePrettyName, FF_DE_PRETTY_GNOME_CLASSIC);
205        } else {
206            ffStrbufSetStatic(&result->dePrettyName, FF_DE_PRETTY_GNOME);
207        }
208    }
209
210    else if (
211        ffStrEqualsIgnCase(name, "X-Cinnamon") ||
212        ffStrEqualsIgnCase(name, "Cinnamon")) {
213        ffStrbufSetS(&result->deProcessName, "cinnamon");
214        ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_CINNAMON);
215    }
216
217    else if (
218        ffStrEqualsIgnCase(name, "XFCE") ||
219        ffStrEqualsIgnCase(name, "X-XFCE") ||
220        ffStrEqualsIgnCase(name, "XFCE4") ||
221        ffStrEqualsIgnCase(name, "X-XFCE4") ||
222        ffStrEqualsIgnCase(name, "xfce4-session")) {
223        ffStrbufSetS(&result->deProcessName, "xfce4-session");
224        ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_XFCE4);
225    }
226
227    else if (
228        ffStrEqualsIgnCase(name, "MATE") ||
229        ffStrEqualsIgnCase(name, "X-MATE") ||
230        ffStrEqualsIgnCase(name, "mate-session")) {
231        ffStrbufSetS(&result->deProcessName, "mate-session");
232        ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_MATE);
233    }
234
235    else if (
236        ffStrEqualsIgnCase(name, "LXQt") ||
237        ffStrEqualsIgnCase(name, "X-LXQt") ||
238        ffStrEqualsIgnCase(name, "lxqt-session")) {
239        ffStrbufSetS(&result->deProcessName, "lxqt-session");
240        ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_LXQT);
241        if (result->wmProcessName.length == 0) {
242            FF_STRBUF_AUTO_DESTROY wmProcessNameBuffer = ffStrbufCreate();
243            ffParsePropFileConfig("lxqt/session.conf", "window_manager =", &wmProcessNameBuffer);
244            applyBetterWM(result, wmProcessNameBuffer.chars);
245        }
246    }
247
248    else if (
249        ffStrEqualsIgnCase(name, "Budgie") ||
250        ffStrEqualsIgnCase(name, "X-Budgie") ||
251        ffStrEqualsIgnCase(name, "budgie-desktop") ||
252        ffStrEqualsIgnCase(name, "Budgie:GNOME")) {
253        ffStrbufSetS(&result->deProcessName, "budgie-desktop");
254        ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_BUDGIE);
255    }
256
257    else if (
258        ffStrEqualsIgnCase(name, "dtsession")) {
259        ffStrbufSetS(&result->deProcessName, "dtsession");
260        ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_CDE);
261    }
262
263    else if (
264        ffStrEqualsIgnCase(name, "ukui-session")) {
265        ffStrbufSetS(&result->deProcessName, "ukui-session");
266        ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_UKUI);
267    }
268
269    else if (
270        ffStrStartsWithIgnCase(name, "Unity:Unity")) {
271        ffStrbufSetS(&result->deProcessName, "unity-session");
272        ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_UNITY);
273    }
274
275    else if (ffStrEqualsIgnCase(name, "Enlightenment")) {
276        ffStrbufSetS(&result->deProcessName, "enlightenment_start");
277        ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_ENLIGHTENMENT);
278    }
279}
280
281static const char* getFromProcesses(FFDisplayServerResult* result) {
282    uint32_t userId = instance.state.platform.uid;
283
284#if __FreeBSD__
285    #ifdef __DragonFly__
286        #define ki_comm kp_comm
287    #endif
288
289    int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID, (int) userId };
290    size_t length = 0;
291
292    if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
293        return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, nullptr) failed";
294    }
295
296    FF_AUTO_FREE struct kinfo_proc* procs = (struct kinfo_proc*) malloc(length);
297    if (sysctl(request, ARRAY_SIZE(request), procs, &length, nullptr, 0) != 0) {
298        return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, procs) failed";
299    }
300
301    length /= sizeof(*procs);
302
303    for (struct kinfo_proc* proc = procs; proc < procs + length; ++proc) {
304        if (result->dePrettyName.length == 0) {
305            applyPrettyNameIfDE(result, proc->ki_comm);
306        }
307
308        if (result->wmPrettyName.length == 0) {
309            applyNameIfWM(result, proc->ki_comm);
310        }
311
312        if (result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) {
313            break;
314        }
315    }
316#elif __OpenBSD__
317    int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID, (int) userId, (int) sizeof(struct kinfo_proc), 0 };
318    size_t length = 0;
319
320    if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
321        return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, nullptr) failed";
322    }
323
324    FF_AUTO_FREE struct kinfo_proc* procs = (struct kinfo_proc*) malloc(length);
325    request[5] = (int) (length / sizeof(struct kinfo_proc)); // count must be non-zero for data fetch
326    if (sysctl(request, ARRAY_SIZE(request), procs, &length, nullptr, 0) != 0) {
327        return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, procs) failed";
328    }
329
330    int count = (int) (length / sizeof(struct kinfo_proc));
331    for (int i = 0; i < count; ++i) {
332        if (result->dePrettyName.length == 0) {
333            applyPrettyNameIfDE(result, procs[i].p_comm);
334        }
335
336        if (result->wmPrettyName.length == 0) {
337            applyNameIfWM(result, procs[i].p_comm);
338        }
339
340        if (result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) {
341            break;
342        }
343    }
344#elif __sun
345    FF_AUTO_CLOSE_DIR DIR* procdir = opendir("/proc");
346    if (procdir == nullptr) {
347        return "opendir(\"/proc\") failed";
348    }
349
350    FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreateA(64);
351    ffStrbufAppendS(&procPath, "/proc/");
352
353    uint32_t procPathLength = procPath.length;
354
355    struct dirent* dirent;
356    while ((dirent = readdir(procdir)) != nullptr) {
357        if (!ffCharIsDigit(dirent->d_name[0])) {
358            continue;
359        }
360
361        ffStrbufAppendS(&procPath, dirent->d_name);
362        ffStrbufAppendS(&procPath, "/psinfo");
363        psinfo_t proc;
364        if (ffReadFileData(procPath.chars, sizeof(proc), &proc) == sizeof(proc)) {
365            ffStrbufSubstrBefore(&procPath, procPathLength);
366
367            if (proc.pr_uid != userId) {
368                continue;
369            }
370
371            if (result->dePrettyName.length == 0) {
372                applyPrettyNameIfDE(result, proc.pr_fname);
373            }
374
375            if (result->wmPrettyName.length == 0) {
376                applyNameIfWM(result, proc.pr_fname);
377            }
378
379            if (result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) {
380                break;
381            }
382        }
383    }
384#elif __linux__ || __GNU__
385    FF_AUTO_CLOSE_DIR DIR* procdir = opendir("/proc");
386    if (procdir == nullptr) {
387        return "opendir(\"/proc\") failed";
388    }
389
390    FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreateA(64);
391    int procfd = dirfd(procdir);
392
393    struct dirent* dirent;
394    while ((dirent = readdir(procdir)) != nullptr) {
395        // Match only folders starting with a number (the pid folders)
396        if ((dirent->d_type != DT_DIR && dirent->d_type != DT_UNKNOWN) || !ffCharIsDigit(dirent->d_name[0])) {
397            continue;
398        }
399
400        ffStrbufSetS(&procPath, dirent->d_name);
401        uint32_t procFolderPathLength = procPath.length;
402
403        // Don't check for processes not owned by the current user.
404        char loginuid[32];
405        ffStrbufAppendS(&procPath, "/loginuid");
406        ssize_t bytesRead = ffReadFileDataRelative(procfd, procPath.chars, sizeof(loginuid) - 1, loginuid);
407        if (bytesRead <= 0) {
408            continue;
409        }
410        loginuid[bytesRead] = '\0';
411        if (strtol(loginuid, nullptr, 10) != (long) userId) {
412            continue;
413        }
414
415        ffStrbufSubstrBefore(&procPath, procFolderPathLength);
416
417        // We check the realpath for the process name
418        ffStrbufAppendS(&procPath, "/exe");
419        char exePath[PATH_MAX];
420        ssize_t exePathLength = readlinkat(procfd, procPath.chars, exePath, sizeof(exePath) - 1);
421        const char* processName;
422        if (exePathLength >= 0) {
423            exePath[exePathLength] = '\0';
424            processName = basename(exePath);
425        } else {
426            ffStrbufSubstrBefore(&procPath, procFolderPathLength);
427            ffStrbufAppendS(&procPath, "/comm");
428            exePathLength = ffReadFileDataRelative(procfd, procPath.chars, sizeof(exePath) - 1, exePath);
429            if (exePathLength <= 0) {
430                continue;
431            }
432            if (exePath[exePathLength - 1] == '\n') {
433                --exePathLength;
434            }
435            exePath[exePathLength] = '\0';
436            processName = exePath;
437        }
438
439        if (result->dePrettyName.length == 0) {
440            applyPrettyNameIfDE(result, processName);
441        }
442
443        if (result->wmPrettyName.length == 0) {
444            applyNameIfWM(result, processName);
445        }
446
447        if (result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) {
448            break;
449        }
450    }
451#elif __NetBSD__
452    int request[] = { CTL_KERN, KERN_PROC2, KERN_PROC_UID, (int) userId, sizeof(struct kinfo_proc2), INT_MAX };
453
454    size_t size = 0;
455    if (sysctl(request, ARRAY_SIZE(request), nullptr, &size, nullptr, 0) != 0) {
456        return "sysctl(KERN_PROC_UID, nullptr) failed";
457    }
458
459    FF_AUTO_FREE struct kinfo_proc2* procs = malloc(size);
460
461    if (sysctl(request, ARRAY_SIZE(request), procs, &size, nullptr, 0) != 0) {
462        return "sysctl(KERN_PROC_UID, procs) failed";
463    }
464
465    for (struct kinfo_proc2* proc = procs; proc < procs + (size / sizeof(struct kinfo_proc2)); proc++) {
466        if (result->dePrettyName.length == 0) {
467            applyPrettyNameIfDE(result, proc->p_comm);
468        }
469
470        if (result->wmPrettyName.length == 0) {
471            applyNameIfWM(result, proc->p_comm);
472        }
473
474        if (result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) {
475            break;
476        }
477    }
478#endif
479
480    return nullptr;
481}
482
483void ffdsDetectWMDE(FFDisplayServerResult* result) {
484#if __ANDROID__
485    if (ffStrbufIgnCaseEqualS(&result->wmProtocolName, FF_WM_PROTOCOL_SURFACEFLINGER)) {
486        return; // Only supported when connected to X11
487    }
488#endif
489
490    const char* env = parseEnv();
491
492    if (result->wmProcessName.length > 0) {
493        // If we found the processName via display server, use it.
494        // This will set the pretty name if it is a known WM, otherwise the prettyName to the processName
495        applyPrettyNameIfWM(result, result->wmProcessName.chars);
496        if (result->wmPrettyName.length == 0) {
497            ffStrbufSet(&result->wmPrettyName, &result->wmProcessName);
498        }
499    } else {
500        // if env is a known WM, use it
501        applyNameIfWM(result, env);
502    }
503
504    // Connecting to a display server only gives WM results, not DE results.
505    // If we find it in the environment, use that.
506    applyPrettyNameIfDE(result, env);
507
508    // If WM was found by connection to the sever, and DE in the environment, we can return
509    // This way we never call getFromProcDir(), which has slow initialization time
510    if (result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) {
511        return;
512    }
513
514    // Get missing WM / DE from processes.
515    getFromProcesses(result);
516
517    // Return if both wm and de are set, or if env doesn't contain anything
518    if (
519        (result->wmPrettyName.length > 0 && result->dePrettyName.length > 0) ||
520        !ffStrSet(env)) {
521        return;
522    }
523
524    // If nothing is set, use env as WM
525    else if (result->wmPrettyName.length == 0 && result->dePrettyName.length == 0) {
526        ffStrbufSetS(&result->wmProcessName, env);
527        ffStrbufSetS(&result->wmPrettyName, env);
528    }
529
530    // If only WM is not set, and DE doesn't equal env, use env as WM
531    else if (
532        result->wmPrettyName.length == 0 &&
533        ffStrbufIgnCaseCompS(&result->deProcessName, env) != 0 &&
534        ffStrbufIgnCaseCompS(&result->dePrettyName, env) != 0) {
535        ffStrbufSetS(&result->wmProcessName, env);
536        ffStrbufSetS(&result->wmPrettyName, env);
537    }
538
539    // If only DE is not set, and WM doesn't equal env, use env as DE
540    else if (
541        result->dePrettyName.length == 0 &&
542        ffStrbufIgnCaseCompS(&result->wmProcessName, env) != 0 &&
543        ffStrbufIgnCaseCompS(&result->wmPrettyName, env) != 0) {
544        ffStrbufSetS(&result->deProcessName, env);
545        ffStrbufSetS(&result->dePrettyName, env);
546    }
547}