ohmu
·
2026-10-15
1#include "lm.h"
2#include "common/debug.h"
3#include "common/io.h"
4#include "common/mallocHelper.h"
5#include "common/properties.h"
6#include "common/processing.h"
7#include "common/strutil.h"
8
9#ifdef FF_HAVE_DBUS
10 #include "common/dbus.h"
11#endif
12
13#include <errno.h>
14#include <unistd.h>
15
16#if __FreeBSD__
17 #include <sys/sysctl.h>
18 #include <sys/types.h>
19 #include <sys/user.h>
20#elif __OpenBSD__
21 #include <sys/param.h>
22 #include <sys/sysctl.h>
23#elif __sun
24 #include <procfs.h>
25#elif __NetBSD__
26 #include <sys/types.h>
27 #include <sys/sysctl.h>
28#endif
29
30static const char* getGdmVersion(FFstrbuf* version) {
31 const char* error = ffProcessAppendStdOut(version, (char* const[]) { "gdm3", "--version", nullptr });
32 if (error || version->length == 0) {
33 error = ffProcessAppendStdOut(version, (char* const[]) { "gdm", "--version", nullptr });
34 if (error || version->length == 0) {
35 FF_DEBUG("Failed to get GDM version");
36 return "Failed to get GDM version";
37 }
38 }
39
40 // GDM 44.1
41 ffStrbufSubstrAfterFirstC(version, ' ');
42 return nullptr;
43}
44
45static const char* getSshdVersion(FFstrbuf* version) {
46 const char* error = ffProcessAppendStdErr(version, (char* const[]) { "sshd", "-V", nullptr });
47 if (error) {
48 return error;
49 }
50
51 // OpenSSH_9.0p1, OpenSSL 3.0.9 30 May 2023...
52 ffStrbufSubstrBeforeFirstC(version, ',');
53 ffStrbufSubstrAfterFirstC(version, '_');
54 return nullptr;
55}
56
57#ifdef FF_HAVE_ZLIB
58 #include "common/path.h"
59 #include "common/zlib.h"
60
61 #include <stdlib.h>
62
63static const char* getSddmVersion(FFstrbuf* version) {
64 const char* error = ffZlibLoad(FF_ZLIB_GZFILE);
65 if (error != nullptr) {
66 FF_DEBUG("The sddm version cannot be read: %s", error);
67 return error;
68 }
69
70 gzFile file = gzopen(FF_PATH_PKG_BASE "/share/man/man1/sddm.1.gz", "rb");
71 if (file == Z_NULL) {
72 FF_DEBUG("gzopen(\"/usr/share/man/man1/sddm.1.gz\", \"rb\") failed: %s", strerror(errno));
73 return "gzopen(\"/usr/share/man/man1/sddm.1.gz\", \"rb\") failed";
74 }
75
76 ffStrbufEnsureFree(version, 2047);
77 memset(version->chars, 0, version->allocated);
78 int size = gzread(file, version->chars, version->allocated - 1);
79 gzclose(file);
80
81 if (size <= 0) {
82 FF_DEBUG("gzread(file, version->chars, version->length) failed");
83 return "gzread(file, version->chars, version->length) failed";
84 }
85
86 version->length = (uint32_t) size;
87 uint32_t index = ffStrbufFirstIndexS(version, ".TH ");
88 if (index == version->length) {
89 FF_DEBUG(".TH is not found");
90 ffStrbufClear(version);
91 return ".TH is not found";
92 }
93
94 ffStrbufSubstrBefore(version, ffStrbufNextIndexC(version, index, '\n'));
95 ffStrbufSubstrAfter(version, index + (uint32_t) strlen(".TH "));
96
97 // "SDDM" 1 "May 2014" "sddm 0.20.0" "sddm"
98 ffStrbufSubstrBeforeLastC(version, ' ');
99 ffStrbufTrimRight(version, '"');
100 ffStrbufSubstrAfterLastC(version, ' ');
101
102 return nullptr;
103}
104#else
105static const char* getSddmVersion([[maybe_unused]] FFstrbuf* version) {
106 return "Fastfetch is built without libz support";
107}
108#endif
109
110static const char* getXfwmVersion(FFstrbuf* version) {
111 const char* error = ffProcessAppendStdOut(version, (char* const[]) { "xfwm4", "--version", nullptr });
112 if (error) {
113 return error;
114 }
115
116 // This is xfwm4 version 4.18.0 (revision 7e7473c5b) for Xfce 4.18...
117 ffStrbufSubstrAfterFirstS(version, "version ");
118 ffStrbufSubstrBeforeFirstC(version, ' ');
119
120 return nullptr;
121}
122
123static const char* getLightdmVersion(FFstrbuf* version) {
124 const char* error = ffProcessAppendStdErr(version, (char* const[]) { "lightdm", "--version", nullptr });
125 if (error) {
126 return error;
127 }
128
129 // lightdm 1.30.0
130 ffStrbufSubstrAfterFirstC(version, ' ');
131 ffStrbufTrimRight(version, '\n');
132
133 return nullptr;
134}
135
136#if __linux__ && !__ANDROID__
137#ifdef FF_HAVE_DBUS
138static const char* detectBySystemdDbus(FFLMResult* result) {
139 // This is the standard way to query the systemd session service name,
140 // and requires no $XDG_SESSION_ID being available
141 FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {};
142 if (ffDBusLoadData(DBUS_BUS_SYSTEM, &dbus) != nullptr) {
143 FF_DEBUG("Failed to load system DBus");
144 return "Failed to load system DBus";
145 }
146
147 if (!ffDBusGetPropertyString(
148 &dbus,
149 "org.freedesktop.login1",
150 "/org/freedesktop/login1/session/auto",
151 "org.freedesktop.login1.Session",
152 "Service",
153 &result->service)) {
154 FF_DEBUG("Failed to get systemd session Service property");
155 return "Failed to get systemd session Service property";
156 }
157
158 return nullptr;
159}
160#endif
161
162#define FF_SYSTEMD_SESSIONS_PATH "/run/systemd/sessions/"
163#define FF_SYSTEMD_USERS_PATH "/run/systemd/users/"
164
165static const char* detectBySystemdPrivate(FFLMResult* result) {
166 FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();
167
168 FF_STRBUF_AUTO_DESTROY sessionId = ffStrbufCreateS(getenv("XDG_SESSION_ID"));
169 if (sessionId.length == 0) {
170 // On some incorrectly configured systems, $XDG_SESSION_ID is not set. Try finding it ourself
171 // WARNING: This is private data. Do not parse
172 ffStrbufAppendS(&path, FF_SYSTEMD_USERS_PATH);
173 ffStrbufAppendUInt(&path, instance.state.platform.uid);
174
175 // This is actually buggy, and assumes current user is using DE
176 // `sd_pid_get_session` can be a better option, but we need to find a pid to use
177 if (!ffParsePropFile(path.chars, "DISPLAY=", &sessionId)) {
178 FF_DEBUG("Failed to get $XDG_SESSION_ID");
179 return "Failed to get $XDG_SESSION_ID";
180 }
181 }
182
183 ffStrbufClear(&path);
184 ffStrbufAppendS(&path, FF_SYSTEMD_SESSIONS_PATH);
185 ffStrbufAppend(&path, &sessionId);
186
187 // WARNING: This is private data. Do not parse
188 if (!ffParsePropFile(path.chars, "SERVICE=", &result->service)) {
189 return "Failed to parse " FF_SYSTEMD_SESSIONS_PATH "$XDG_SESSION_ID";
190 }
191
192 return nullptr;
193}
194#endif
195
196static const char* testLms(const char* psName) {
197 static const char* A[] = {
198 "atrium",
199 };
200 static const char* C[] = {
201 "cdm",
202 };
203 static const char* E[] = {
204 "entrance",
205 };
206 static const char* G[] = {
207 "gdm",
208 "gdm3",
209 "greetd",
210 };
211 static const char* L[] = {
212 "lemurs",
213 "lightdm",
214 "lxdm",
215 "ly",
216 };
217 static const char* P[] = {
218 "plasmalogin",
219 };
220 static const char* S[] = {
221 "sddm",
222 "slim",
223 };
224 static const char* T[] = {
225 "tbsm",
226 };
227 static const char* X[] = {
228 "xdm",
229 "xenodm",
230 };
231
232 switch (psName[0]) {
233 #define PS_CASE(letter, array) \
234 case letter: \
235 for (uint32_t i = 0; i < ARRAY_SIZE(array); ++i) { \
236 if (ffStrEqualsIgnCase(psName, array[i])) { \
237 return array[i]; \
238 } \
239 } \
240 break;
241 PS_CASE('a', A)
242 PS_CASE('c', C)
243 PS_CASE('e', E)
244 PS_CASE('g', G)
245 PS_CASE('l', L)
246 PS_CASE('p', P)
247 PS_CASE('s', S)
248 PS_CASE('t', T)
249 PS_CASE('x', X)
250 }
251 return nullptr;
252}
253
254const char* detectByProcesses(FFLMResult* result) {
255#if __FreeBSD__
256 #ifdef __DragonFly__
257 #define ki_comm kp_comm
258 #endif
259
260 int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID, 0 };
261 size_t length = 0;
262
263 if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
264 FF_DEBUG("sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, nullptr) failed: %s", strerror(errno));
265 return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, nullptr) failed";
266 }
267
268 FF_AUTO_FREE struct kinfo_proc* procs = (struct kinfo_proc*) malloc(length);
269 if (sysctl(request, ARRAY_SIZE(request), procs, &length, nullptr, 0) != 0) {
270 FF_DEBUG("sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, procs) failed: %s", strerror(errno));
271 return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, procs) failed";
272 }
273
274 length /= sizeof(*procs);
275
276 for (struct kinfo_proc* proc = procs; proc < procs + length; ++proc) {
277 const char* lm = testLms(proc->ki_comm);
278 if (lm) {
279 ffStrbufSetStatic(&result->service, lm);
280 break;
281 }
282 }
283#elif __OpenBSD__
284 int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID, 0, (int) sizeof(struct kinfo_proc), 0 };
285 size_t length = 0;
286
287 if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
288 FF_DEBUG("sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, nullptr) failed: %s", strerror(errno));
289 return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, nullptr) failed";
290 }
291
292 FF_AUTO_FREE struct kinfo_proc* procs = (struct kinfo_proc*) malloc(length);
293 request[5] = (int) (length / sizeof(struct kinfo_proc)); // count must be non-zero for data fetch
294 if (sysctl(request, ARRAY_SIZE(request), procs, &length, nullptr, 0) != 0) {
295 FF_DEBUG("sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, procs) failed: %s", strerror(errno));
296 return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, procs) failed";
297 }
298
299 int count = (int) (length / sizeof(struct kinfo_proc));
300 for (int i = 0; i < count; ++i) {
301 const char* lm = testLms(procs[i].p_comm);
302 if (lm) {
303 ffStrbufSetStatic(&result->service, lm);
304 break;
305 }
306 }
307#elif __sun
308 FF_AUTO_CLOSE_DIR DIR* procdir = opendir("/proc");
309 if (procdir == nullptr) {
310 FF_DEBUG("opendir(\"/proc\") failed: %s", strerror(errno));
311 return "opendir(\"/proc\") failed";
312 }
313
314 FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreateA(64);
315 ffStrbufAppendS(&procPath, "/proc/");
316
317 uint32_t procPathLength = procPath.length;
318
319 struct dirent* dirent;
320 while ((dirent = readdir(procdir)) != nullptr) {
321 if (!ffCharIsDigit(dirent->d_name[0])) {
322 continue;
323 }
324
325 ffStrbufAppendS(&procPath, dirent->d_name);
326 ffStrbufAppendS(&procPath, "/psinfo");
327 psinfo_t proc;
328 if (ffReadFileData(procPath.chars, sizeof(proc), &proc) == sizeof(proc)) {
329 ffStrbufSubstrBefore(&procPath, procPathLength);
330
331 if (proc.pr_uid != 0) {
332 continue;
333 }
334
335 const char* lm = testLms(proc.pr_fname);
336 if (lm) {
337 ffStrbufSetS(&result->service, lm);
338 break;
339 }
340 }
341 }
342#elif __linux__ || __GNU__
343 FF_AUTO_CLOSE_DIR DIR* procdir = opendir("/proc");
344 if (procdir == nullptr) {
345 FF_DEBUG("opendir(\"/proc\") failed: %s", strerror(errno));
346 return "opendir(\"/proc\") failed";
347 }
348
349 FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreateA(64);
350 int procfd = dirfd(procdir);
351
352 struct dirent* dirent;
353 while ((dirent = readdir(procdir)) != nullptr) {
354 // Match only folders starting with a number (the pid folders)
355 if (dirent->d_type != DT_DIR || !ffCharIsDigit(dirent->d_name[0])) {
356 continue;
357 }
358
359 ffStrbufSetS(&procPath, dirent->d_name);
360 uint32_t procFolderPathLength = procPath.length;
361
362 // Don't check for processes not owned by root (login managers run as root).
363 char loginuid[32];
364 ffStrbufAppendS(&procPath, "/loginuid");
365 ssize_t bytesRead = ffReadFileDataRelative(procfd, procPath.chars, sizeof(loginuid) - 1, loginuid);
366 if (bytesRead <= 0) {
367 continue;
368 }
369 loginuid[bytesRead] = '\0';
370
371 if (strtol(loginuid, nullptr, 10) != (long) (uint32_t) -1 /* no loginuid */) {
372 continue;
373 }
374
375 ffStrbufSubstrBefore(&procPath, procFolderPathLength);
376
377 ffStrbufAppendS(&procPath, "/comm");
378 char comm[256];
379 bytesRead = ffReadFileDataRelative(procfd, procPath.chars, sizeof(comm) - 1, comm);
380 if (bytesRead <= 0) {
381 continue;
382 }
383 if (comm[bytesRead - 1] == '\n') {
384 --bytesRead;
385 }
386 comm[bytesRead] = '\0';
387
388 const char* lm = testLms(comm);
389 if (lm) {
390 ffStrbufSetS(&result->service, lm);
391 break;
392 }
393 }
394#elif __NetBSD__
395 int request[] = { CTL_KERN, KERN_PROC2, KERN_PROC_UID, 0, sizeof(struct kinfo_proc2), INT_MAX };
396
397 size_t size = 0;
398 if (sysctl(request, ARRAY_SIZE(request), nullptr, &size, nullptr, 0) != 0) {
399 FF_DEBUG("sysctl(KERN_PROC_UID, nullptr) failed: %s", strerror(errno));
400 return "sysctl(KERN_PROC_UID, nullptr) failed";
401 }
402
403 FF_AUTO_FREE struct kinfo_proc2* procs = malloc(size);
404
405 if (sysctl(request, ARRAY_SIZE(request), procs, &size, nullptr, 0) != 0) {
406 FF_DEBUG("sysctl(KERN_PROC_UID, procs) failed: %s", strerror(errno));
407 return "sysctl(KERN_PROC_UID, procs) failed";
408 }
409
410 for (struct kinfo_proc2* proc = procs; proc < procs + (size / sizeof(struct kinfo_proc2)); proc++) {
411 const char* lm = testLms(proc->p_comm);
412 if (lm) {
413 ffStrbufSetStatic(&result->service, lm);
414 break;
415 }
416 }
417#endif
418
419 if (result->service.length == 0) {
420 FF_DEBUG("Failed to detect login manager by processes");
421 return "Failed to detect login manager by processes";
422 }
423
424 return nullptr;
425}
426
427const char* ffDetectLM(FFLMResult* result) {
428 const char* error = "";
429#if __linux__
430 #ifdef FF_HAVE_DBUS
431 error = detectBySystemdDbus(result);
432 #endif
433 if (error != nullptr) {
434 error = detectBySystemdPrivate(result);
435 }
436#endif
437
438 if (error != nullptr) {
439 error = detectByProcesses(result);
440 }
441
442 if (error != nullptr) {
443 return error;
444 }
445
446 switch (result->service.chars[0]) {
447 case 'a':
448 if (ffStrEqualsIgnCase(result->service.chars, "atrium")) {
449 ffStrbufSetStatic(&result->prettyName, "Atrium");
450 }
451 break;
452 case 'c':
453 if (ffStrEqualsIgnCase(result->service.chars, "cdm")) {
454 ffStrbufSetStatic(&result->prettyName, "CDM");
455 }
456 break;
457 case 'e':
458 if (ffStrEqualsIgnCase(result->service.chars, "entrance")) {
459 ffStrbufSetStatic(&result->prettyName, "Entrance");
460 }
461 break;
462 case 'g':
463 if (ffStrbufStartsWithIgnCaseS(&result->service, "gdm")) {
464 ffStrbufSetStatic(&result->prettyName, "GDM");
465 } else if (ffStrEqualsIgnCase(result->service.chars, "greetd")) {
466 ffStrbufSetStatic(&result->prettyName, "greetd");
467 }
468 break;
469 case 'l':
470 if (ffStrEqualsIgnCase(result->service.chars, "lemurs")) {
471 ffStrbufSetStatic(&result->prettyName, "Lemurs");
472 } else if (ffStrEqualsIgnCase(result->service.chars, "lightdm")) {
473 ffStrbufSetStatic(&result->prettyName, "LightDM");
474 } else if (ffStrEqualsIgnCase(result->service.chars, "lxdm")) {
475 ffStrbufSetStatic(&result->prettyName, "LXDM");
476 } else if (ffStrEqualsIgnCase(result->service.chars, "ly")) {
477 ffStrbufSetStatic(&result->prettyName, "Ly");
478 }
479 break;
480 case 'p':
481 if (ffStrEqualsIgnCase(result->service.chars, "plasmalogin")) {
482 ffStrbufSetStatic(&result->prettyName, "Plasma Login");
483 }
484 break;
485 case 's':
486 if (ffStrEqualsIgnCase(result->service.chars, "sddm")) {
487 ffStrbufSetStatic(&result->prettyName, "SDDM");
488 } else if (ffStrEqualsIgnCase(result->service.chars, "slim")) {
489 ffStrbufSetStatic(&result->prettyName, "SLiM");
490 }
491 break;
492 case 't':
493 if (ffStrEqualsIgnCase(result->service.chars, "tbsm")) {
494 ffStrbufSetStatic(&result->prettyName, "TBSM");
495 }
496 break;
497 case 'x':
498 if (ffStrEqualsIgnCase(result->service.chars, "xdm")) {
499 ffStrbufSetStatic(&result->prettyName, "XDM");
500 } else if (ffStrEqualsIgnCase(result->service.chars, "xenodm")) {
501 ffStrbufSetStatic(&result->prettyName, "Xenodm");
502 }
503 break;
504 }
505
506 if (result->prettyName.length == 0) {
507 ffStrbufSet(&result->prettyName, &result->service);
508 }
509
510 if (instance.config.general.detectVersion) {
511 if (ffStrbufStartsWithS(&result->service, "gdm")) {
512 getGdmVersion(&result->version);
513 } else if (ffStrbufStartsWithS(&result->service, "sddm")) {
514 getSddmVersion(&result->version);
515 } else if (ffStrbufStartsWithS(&result->service, "xfwm")) {
516 getXfwmVersion(&result->version);
517 } else if (ffStrbufStartsWithS(&result->service, "lightdm")) {
518 getLightdmVersion(&result->version);
519 } else if (ffStrbufStartsWithS(&result->service, "sshd")) {
520 getSshdVersion(&result->version);
521 }
522 }
523
524 return nullptr;
525}