master ohmu/fastfetch-openbsd / src / detection / de / de_linux.c
ohmu  ·  2026-10-15
  1#include "de.h"
  2
  3#include "common/dbus.h"
  4#include "common/debug.h"
  5#include "common/io.h"
  6#include "common/library.h"
  7#include "common/parsing.h"
  8#include "common/properties.h"
  9#include "common/processing.h"
 10#include "common/binary.h"
 11#include "common/path.h"
 12#include "detection/displayserver/displayserver.h"
 13
 14#include <ctype.h>
 15
 16static void getKDE(FFstrbuf* result, [[maybe_unused]] FFDEOptions* options) {
 17    ffParsePropFile(FF_PATH_PKG_BASE "/share/wayland-sessions/plasma.desktop", "X-KDE-PluginInfo-Version =", result);
 18    if (result->length == 0) {
 19        ffParsePropFile(FF_PATH_PKG_BASE "/share/xsessions/plasmax11.desktop", "X-KDE-PluginInfo-Version =", result);
 20    }
 21
 22    if (result->length == 0) {
 23        ffParsePropFileData("xsessions/plasma.desktop", "X-KDE-PluginInfo-Version =", result);
 24    }
 25    if (result->length == 0) {
 26        ffParsePropFileData("xsessions/plasma5.desktop", "X-KDE-PluginInfo-Version =", result);
 27    }
 28
 29    if (result->length == 0) {
 30        ffParsePropFileData("wayland-sessions/plasmawayland.desktop", "X-KDE-PluginInfo-Version =", result);
 31    }
 32    if (result->length == 0) {
 33        ffParsePropFileData("wayland-sessions/plasmawayland5.desktop", "X-KDE-PluginInfo-Version =", result);
 34    }
 35
 36    if (result->length == 0) {
 37        if (ffProcessAppendStdOut(result, (char* const[]) { "plasmashell", "--version", nullptr }) == nullptr) { // plasmashell 5.27.5
 38            ffStrbufSubstrAfterLastC(result, ' ');
 39        }
 40    }
 41}
 42
 43static const char* getGnomeByDbus([[maybe_unused]] FFstrbuf* result) {
 44#ifdef FF_HAVE_DBUS
 45    FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {};
 46    if (ffDBusLoadData(DBUS_BUS_SESSION, &dbus) != nullptr) {
 47        FF_DEBUG("ffDBusLoadData() failed");
 48        return "ffDBusLoadData() failed";
 49    }
 50
 51    ffDBusGetPropertyString(&dbus, "org.gnome.Shell", "/org/gnome/Shell", "org.gnome.Shell", "ShellVersion", result);
 52    return nullptr;
 53#else  // FF_HAVE_DBUS
 54    FF_DEBUG("ffDBusLoadData() failed: dbus support not compiled in");
 55    return "ffDBusLoadData() failed: dbus support not compiled in";
 56#endif // FF_HAVE_DBUS
 57}
 58
 59static void getGnome(FFstrbuf* result, [[maybe_unused]] FFDEOptions* options) {
 60    getGnomeByDbus(result);
 61
 62    if (result->length == 0) {
 63        if (ffProcessAppendStdOut(result, (char* const[]) { "gnome-shell", "--version", nullptr }) == nullptr) { // GNOME Shell 44.1
 64            ffStrbufSubstrAfterLastC(result, ' ');
 65        }
 66    }
 67}
 68
 69static void getCinnamon(FFstrbuf* result, [[maybe_unused]] FFDEOptions* options) {
 70    ffStrbufSetS(result, getenv("CINNAMON_VERSION"));
 71
 72    if (result->length == 0) {
 73        ffParsePropFileData("applications/cinnamon.desktop", "X-GNOME-Bugzilla-Version =", result);
 74    }
 75
 76    if (result->length == 0) {
 77        if (ffProcessAppendStdOut(result, (char* const[]) { "cinnamon", "--version", nullptr }) == nullptr) { // Cinnamon 6.2.2
 78            ffStrbufSubstrAfterLastC(result, ' ');
 79        }
 80    }
 81}
 82
 83static void getMate(FFstrbuf* result, [[maybe_unused]] FFDEOptions* options) {
 84    FF_STRBUF_AUTO_DESTROY major = ffStrbufCreate();
 85    FF_STRBUF_AUTO_DESTROY minor = ffStrbufCreate();
 86    FF_STRBUF_AUTO_DESTROY micro = ffStrbufCreate();
 87
 88    ffParsePropFileDataValues("mate-about/mate-version.xml", 3, (FFpropquery[]) { { "<platform>", &major }, { "<minor>", &minor }, { "<micro>", &micro } });
 89
 90    ffParseSemver(result, &major, &minor, &micro);
 91
 92    if (result->length == 0) {
 93        ffProcessAppendStdOut(result, (char* const[]) { "mate-session", "--version", nullptr });
 94
 95        ffStrbufSubstrAfterFirstC(result, ' ');
 96        ffStrbufTrim(result, ' ');
 97    }
 98}
 99
100static const char* getXfce4ByLib(FFstrbuf* result) {
101#ifndef FF_DISABLE_DLOPEN
102    const char* xfce_version_string(void); // from `xfce4/libxfce4util/xfce-misutils.h
103    FF_LIBRARY_LOAD_MESSAGE(xfce4util, "libxfce4util" FF_LIBRARY_EXTENSION, 7);
104    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xfce4util, xfce_version_string);
105    ffStrbufSetS(result, ffxfce_version_string());
106    return nullptr;
107#else
108    FF_UNUSED(result);
109    FF_DEBUG("dlopen is disabled");
110    return "dlopen is disabled";
111#endif
112}
113
114static void getXFCE4(FFstrbuf* result, [[maybe_unused]] FFDEOptions* options) {
115    getXfce4ByLib(result);
116
117    if (result->length == 0) {
118        // This is somewhat slow
119        ffProcessAppendStdOut(result, (char* const[]) { "xfce4-session", "--version", nullptr });
120
121        ffStrbufSubstrBeforeFirstC(result, ')');
122        ffStrbufSubstrAfterLastC(result, ' ');
123        ffStrbufTrim(result, ' ');
124    }
125}
126
127static void getLXQt(FFstrbuf* result, [[maybe_unused]] FFDEOptions* options) {
128    ffParsePropFileData("gconfig/lxqt.pc", "Version:", result);
129
130    if (result->length == 0) {
131        ffParsePropFileData("cmake/lxqt/lxqt-config.cmake", "set ( LXQT_VERSION", result);
132    }
133    if (result->length == 0) {
134        ffParsePropFileData("cmake/lxqt/lxqt-config-version.cmake", "set ( PACKAGE_VERSION", result);
135    }
136
137    if (result->length == 0) {
138        // This is really, really, really slow. Thank you, LXQt developers
139        ffProcessAppendStdOut(result, (char* const[]) { "lxqt-session", "-v", nullptr });
140
141        result->length = 0; // don't set '\0' byte
142        ffParsePropLines(result->chars, "liblxqt", result);
143    }
144}
145
146static void getBudgie(FFstrbuf* result, [[maybe_unused]] FFDEOptions* options) {
147    ffParsePropFileData("budgie/budgie-version.xml", "<str>", result);
148}
149
150static void getUnity(FFstrbuf* result, [[maybe_unused]] FFDEOptions* options) {
151    if (ffParsePropFile("/usr/bin/unity", "parser = OptionParser(version= \"%prog ", result)) {
152        ffStrbufSubstrBeforeFirstC(result, '"');
153    }
154}
155
156static bool extractTdeVersion(const char* line, uint32_t len, void* userdata) {
157    int count = 0;
158    sscanf(line, "R%*d.%*d.%*d%n", &count);
159    if (count == 0) {
160        return true;
161    }
162
163    ffStrbufSetNS((FFstrbuf*) userdata, len, line);
164    return false;
165}
166
167static const char* getTrinity(FFstrbuf* result, [[maybe_unused]] FFDEOptions* options) {
168    FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();
169    const char* error = ffFindExecutableInPath("tde-config", &path);
170    if (error) {
171        FF_DEBUG("Failed to find tde-config path");
172        return "Failed to find tde-config path";
173    }
174
175    ffStrbufSubstrBeforeLastC(&path, '/');
176    ffStrbufAppendS(&path, "/../lib/libtdecore.so");
177
178    if (ffBinaryExtractStrings(path.chars, extractTdeVersion, result, strlen("R0.0.0")) == nullptr) {
179        return nullptr;
180    }
181
182    ffStrbufClear(&path);
183    if (ffProcessAppendStdOut(&path, (char* const[]) { "tde-config", "--version", nullptr }) == nullptr) {
184        ffParsePropLines(path.chars, "TDE: ", result);
185        return nullptr;
186    }
187
188    FF_DEBUG("All methods failed");
189    return "All methods failed";
190}
191
192static const char* getCosmic(FFstrbuf* result, [[maybe_unused]] FFDEOptions* options) {
193    const char* env = getenv("COSMIC_VERSION");
194    if (env) {
195        ffStrbufSetS(result, env);
196        return nullptr;
197    }
198
199    if (ffProcessAppendStdOut(result, (char* const[]) { "cosmic-comp", "--version", nullptr }) == nullptr) {
200        // cosmic-comp 0.1.0 (git commit fa88002ba41d2edec25dd7ffdee9719fbb928fc0)
201        ffStrbufSubstrAfterFirstC(result, ' ');
202        ffStrbufSubstrBeforeFirstC(result, ' ');
203        return nullptr;
204    }
205
206    FF_DEBUG("All methods failed");
207    return "All methods failed";
208}
209
210static const char* getEnlightenmentByDbus([[maybe_unused]] FFstrbuf* result) {
211#ifdef FF_HAVE_DBUS
212    FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {};
213    if (ffDBusLoadData(DBUS_BUS_SESSION, &dbus) != nullptr) {
214        FF_DEBUG("ffDBusLoadData() failed");
215        return "ffDBusLoadData() failed";
216    }
217
218    DBusMessage* reply = ffDBusGetMethodReply(&dbus, "org.enlightenment.wm.service", "/org/enlightenment/wm/RemoteObject", "org.enlightenment.wm.Core", "Version", nullptr, nullptr);
219    if (!reply) {
220        FF_DEBUG("ffDBusGetMethodReply() failed");
221        return "ffDBusGetMethodReply() failed";
222    }
223
224    DBusMessageIter rootIterator;
225    if (!dbus.lib->ffdbus_message_iter_init(reply, &rootIterator)) {
226        dbus.lib->ffdbus_message_unref(reply);
227        FF_DEBUG("dbus_message_iter_init() failed");
228        return "dbus_message_iter_init() failed";
229    }
230    if (!ffDBusGetString(&dbus, &rootIterator, result)) {
231        dbus.lib->ffdbus_message_unref(reply);
232        FF_DEBUG("ffDBusGetString() failed");
233        return "ffDBusGetString() failed";
234    }
235    dbus.lib->ffdbus_message_unref(reply);
236
237    return nullptr;
238#else  // FF_HAVE_DBUS
239    FF_DEBUG("ffDBusLoadData() failed: dbus support not compiled in");
240    return "ffDBusLoadData() failed: dbus support not compiled in";
241#endif // FF_HAVE_DBUS
242}
243
244static void getEnlightenment(FFstrbuf* result, [[maybe_unused]] FFDEOptions* options) {
245    getEnlightenmentByDbus(result);
246
247    if (result->length == 0) {
248        if (ffProcessAppendStdOut(result, (char* const[]) { "enlightenment", "--version", nullptr }) == nullptr) { // ...\nVersion: 0.27.1\n...
249            ffStrbufSubstrAfterFirstS(result, "Version: ");
250            ffStrbufSubstrBeforeFirstC(result, '\n');
251        }
252    }
253}
254const char* ffDetectDEVersion(const FFstrbuf* deName, FFstrbuf* result, FFDEOptions* options) {
255    if (!instance.config.general.detectVersion) {
256        FF_DEBUG("Disabled by config");
257        return "Disabled by config";
258    }
259
260    if (ffStrbufEqualS(deName, FF_DE_PRETTY_PLASMA)) {
261        getKDE(result, options);
262    } else if (ffStrbufEqualS(deName, FF_DE_PRETTY_GNOME)) {
263        getGnome(result, options);
264    } else if (ffStrbufEqualS(deName, FF_DE_PRETTY_GNOME_CLASSIC)) {
265        // GNOME Classic is GNOME Shell in classic mode, so gnome-shell reports the same version
266        getGnome(result, options);
267    } else if (ffStrbufEqualS(deName, FF_DE_PRETTY_CINNAMON)) {
268        getCinnamon(result, options);
269    } else if (ffStrbufEqualS(deName, FF_DE_PRETTY_XFCE4)) {
270        getXFCE4(result, options);
271    } else if (ffStrbufEqualS(deName, FF_DE_PRETTY_MATE)) {
272        getMate(result, options);
273    } else if (ffStrbufEqualS(deName, FF_DE_PRETTY_LXQT)) {
274        getLXQt(result, options);
275    } else if (ffStrbufEqualS(deName, FF_DE_PRETTY_BUDGIE)) {
276        getBudgie(result, options);
277    } else if (ffStrbufEqualS(deName, FF_DE_PRETTY_UNITY)) {
278        getUnity(result, options);
279    } else if (ffStrbufIgnCaseEqualS(deName, "trinity") || ffStrbufIgnCaseEqualS(deName, "tde")) {
280        getTrinity(result, options);
281    } else if (ffStrbufEqualS(deName, FF_DE_PRETTY_COSMIC)) {
282        getCosmic(result, options);
283    } else if (ffStrbufEqualS(deName, FF_DE_PRETTY_ENLIGHTENMENT)) {
284        getEnlightenment(result, options);
285    } else {
286        FF_DEBUG("Unsupported DE");
287        return "Unsupported DE";
288    }
289    return nullptr;
290}