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>", µ } });
89
90 ffParseSemver(result, &major, &minor, µ);
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}