summaryrefslogtreecommitdiffstats
path: root/src/detection/wm/wm_linux.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/detection/wm/wm_linux.c')
-rw-r--r--src/detection/wm/wm_linux.c335
1 files changed, 335 insertions, 0 deletions
diff --git a/src/detection/wm/wm_linux.c b/src/detection/wm/wm_linux.c
new file mode 100644
index 0000000..a62a4af
--- /dev/null
+++ b/src/detection/wm/wm_linux.c
@@ -0,0 +1,335 @@
+#include "wm.h"
+
+#include "common/processing.h"
+#include "common/io.h"
+#include "common/binary.h"
+#include "common/path.h"
+#include "common/strutil.h"
+#include "common/debug.h"
+
+const char* ffDetectWMPlugin(FF_A_UNUSED FFstrbuf* pluginName) {
+ return "Not supported on this platform";
+}
+
+static bool extractCommonWmVersion(const char* line, FF_A_UNUSED uint32_t len, void* userdata) {
+ int count = 0;
+ sscanf(line, "%*d.%*d.%*d%n", &count);
+ if (count == 0) {
+ return true;
+ }
+
+ ffStrbufSetNS((FFstrbuf*) userdata, len, line);
+ return false;
+}
+
+#if !__ANDROID__
+static bool extractHyprlandVersion(const char* line, uint32_t len, void* userdata) {
+ if (line[0] != 'v') {
+ return true;
+ }
+ ++line;
+ --len;
+ int count = 0;
+ sscanf(line, "%*d.%*d.%*d%n", &count);
+ if (count == 0) {
+ return true;
+ }
+
+ ffStrbufSetNS((FFstrbuf*) userdata, len, line);
+ return false;
+}
+
+static const char* getHyprland(FFstrbuf* result) {
+ FF_DEBUG("Detecting Hyprland version");
+ FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
+
+ FF_DEBUG("Checking for " FASTFETCH_TARGET_DIR_USR "/include/hyprland/src/version.h"
+ " file");
+ if (ffReadFileBuffer(FASTFETCH_TARGET_DIR_USR "/include/hyprland/src/version.h", result)) {
+ FF_DEBUG("Found version.h file, extracting version");
+ if (ffStrbufSubstrAfterFirstS(result, "\n#define GIT_TAG ")) {
+ ffStrbufSubstrAfterFirstC(result, '"');
+ ffStrbufSubstrBeforeFirstC(result, '"');
+ ffStrbufTrimLeft(result, 'v');
+ FF_DEBUG("Extracted version from version.h: %s", result->chars);
+ return NULL;
+ }
+ FF_DEBUG("Failed to extract version from version.h");
+ ffStrbufClear(result);
+ } else {
+ FF_DEBUG("version.h file not found, trying Hyprland executable");
+ }
+
+ const char* error = ffFindExecutableInPath("Hyprland", &buffer);
+ if (error) {
+ FF_DEBUG("Error finding Hyprland executable: %s", error);
+ return "Failed to find Hyprland executable path";
+ }
+ FF_DEBUG("Found Hyprland executable at: %s", buffer.chars);
+
+ ffBinaryExtractStrings(buffer.chars, extractHyprlandVersion, result, (uint32_t) strlen("v0.0.0"));
+ if (result->length > 0) {
+ FF_DEBUG("Extracted version from binary strings: %s", result->chars);
+ return NULL;
+ }
+ FF_DEBUG("Failed to extract version from binary strings, trying --version option");
+
+ if (ffProcessAppendStdOut(result, (char* const[]) { buffer.chars, "--version", NULL }) == NULL) {
+ // Hyprland 0.48.1 built from branch at commit 29e2e59...
+ // Date: ...
+ // Tag: v0.48.1, commits: 5937
+ // ...
+
+ FF_DEBUG("Raw version output: %s", result->chars);
+ // Use tag if available
+ if (ffStrbufSubstrAfterFirstS(result, "\nTag: v")) {
+ ffStrbufSubstrBeforeFirstC(result, ',');
+ FF_DEBUG("Extracted version from Tag: %s", result->chars);
+ } else {
+ ffStrbufSubstrAfterFirstC(result, ' ');
+ ffStrbufSubstrBeforeFirstC(result, ' ');
+ FF_DEBUG("Extracted version from output: %s", result->chars);
+ }
+ return NULL;
+ }
+ FF_DEBUG("Failed to run Hyprland --version command");
+
+ return "Failed to run command `Hyprland --version`";
+}
+
+static bool extractSwayVersion(const char* line, FF_A_UNUSED uint32_t len, void* userdata) {
+ FFstrbuf* result = (FFstrbuf*) userdata;
+ if (!ffStrStartsWith(line, "sway")) {
+ return true;
+ }
+ if (ffStrStartsWith(line + 4, " version ")) {
+ ffStrbufSetNS(result, len - (uint32_t) strlen("sway version "), line + strlen("sway version "));
+ ffStrbufTrimRightSpace(result);
+ return false;
+ } else {
+ char swayfxVer[32], swayVer[32];
+ if (sscanf(line + 4, "fx version %31[^ ] (based on sway %31[^)])", swayfxVer, swayVer) == 2) {
+ ffStrbufSetF(result, "%s [swayfx %s]", swayVer, swayfxVer);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static const char* getSway(FFstrbuf* result) {
+ FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();
+ const char* error = ffFindExecutableInPath("sway", &path);
+ if (error) {
+ return "Failed to find sway executable path";
+ }
+
+ ffBinaryExtractStrings(path.chars, extractSwayVersion, result, (uint32_t) strlen("sway version 0.0.0"));
+ if (result->length > 0) {
+ return NULL;
+ }
+
+ FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
+ if (ffProcessAppendStdOut(&buffer, (char* const[]) { path.chars, "--version", NULL }) == NULL) { // sway version 1.10
+ return extractSwayVersion(buffer.chars, buffer.length, result) ? "Failed to parse sway version output" : NULL;
+ }
+
+ return "Failed to run command `sway --version`";
+}
+
+static const char* getLabwc(FFstrbuf* result) {
+ FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();
+ const char* error = ffFindExecutableInPath("labwc", &path);
+ if (error) {
+ return "Failed to find labwc executable path";
+ }
+
+ ffBinaryExtractStrings(path.chars, extractCommonWmVersion, result, (uint32_t) strlen("0.0.0"));
+ if (result->length > 0) {
+ return NULL;
+ }
+
+ if (ffProcessAppendStdOut(result, (char* const[]) { path.chars, "--version", NULL }) == NULL) { // labwc 0.9.0 (+xwayland +nls +rsvg +libsfdo)
+ ffStrbufSubstrAfterFirstC(result, ' ');
+ ffStrbufSubstrBeforeFirstC(result, ' ');
+ return NULL;
+ }
+
+ return "Failed to run command `labwc --version`";
+}
+
+static const char* getNiri(FFstrbuf* result) {
+ if (ffProcessAppendStdOut(result, (char* const[]) { "niri", "--version", NULL }) == NULL) { // niri 25.11 (commit b35bcae)
+ ffStrbufSubstrAfterFirstC(result, ' ');
+ ffStrbufSubstrBeforeLastC(result, '(');
+ ffStrbufTrimRightSpace(result);
+ return NULL;
+ }
+
+ return "Failed to run command `niri --version`";
+}
+
+ #ifdef __linux__
+static const char* getWslg(FFstrbuf* result) {
+ if (!ffAppendFileBuffer("/mnt/wslg/versions.txt", result)) {
+ return "Failed to read /mnt/wslg/versions.txt";
+ }
+
+ if (!ffStrbufStartsWithS(result, "WSLg ")) {
+ return "Failed to find WSLg version";
+ }
+
+ ffStrbufSubstrBeforeFirstC(result, '\n');
+ ffStrbufSubstrBeforeFirstC(result, '+');
+ ffStrbufSubstrAfterFirstC(result, ':');
+ ffStrbufTrimLeft(result, ' ');
+ return NULL;
+}
+ #endif
+
+#endif // !__ANDROID__
+
+static bool extractI3Version(const char* line, FF_A_UNUSED uint32_t len, void* userdata) {
+ int count = 0;
+ sscanf(line, "%*d.%*d%n", &count);
+ if (count == 0) {
+ return true;
+ }
+
+ ffStrbufSetNS((FFstrbuf*) userdata, len, line);
+ return false;
+}
+
+static const char* getI3(FFstrbuf* result) {
+ FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();
+ const char* error = ffFindExecutableInPath("i3", &path);
+ if (error) {
+ return "Failed to find i3 executable path";
+ }
+
+ ffBinaryExtractStrings(path.chars, extractI3Version, result, (uint32_t) strlen("0.0"));
+ if (result->length > 0) {
+ return NULL;
+ }
+
+ if (ffProcessAppendStdOut(result, (char* const[]) { path.chars, "--version", NULL }) == NULL) { // i3 version 1.10 C 2009...
+ ffStrbufSubstrAfterFirstS(result, "version ");
+ ffStrbufSubstrBeforeFirstC(result, ' ');
+ return NULL;
+ }
+
+ return "Failed to run command `i3 --version`";
+}
+
+static const char* getCtwm(FFstrbuf* result) {
+ FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();
+ const char* error = ffFindExecutableInPath("ctwm", &path);
+ if (error) {
+ return "Failed to find ctwm executable path";
+ }
+
+ ffBinaryExtractStrings(path.chars, extractCommonWmVersion, result, (uint32_t) strlen("0.0.0"));
+ if (result->length > 0) {
+ return NULL;
+ }
+
+ if (ffProcessAppendStdOut(result, (char* const[]) { path.chars, "--version", NULL }) == NULL) { // ctwm version 4.0.1\n...
+ ffStrbufSubstrBeforeFirstC(result, '\n');
+ ffStrbufSubstrAfterLastC(result, ' ');
+ return NULL;
+ }
+
+ return "Failed to run command `ctwm --version`";
+}
+
+static const char* getFvwm(FFstrbuf* result) {
+ FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();
+ const char* error = ffFindExecutableInPath("fvwm", &path);
+ if (error) {
+ return "Failed to find fvwm executable path";
+ }
+
+ ffBinaryExtractStrings(path.chars, extractCommonWmVersion, result, (uint32_t) strlen("0.0.0"));
+ if (result->length > 0) {
+ return NULL;
+ }
+
+ if (ffProcessAppendStdOut(result, (char* const[]) { path.chars, "-version", NULL }) == NULL) { // [FVWM][main]: fvwm Version 2.2.5\n...
+ ffStrbufSubstrBeforeFirstC(result, '\n');
+ ffStrbufSubstrAfterLastC(result, ' ');
+ return NULL;
+ }
+
+ return "Failed to run command `fvwm -version`";
+}
+
+static const char* getOpenbox(FFstrbuf* result) {
+ FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();
+ const char* error = ffFindExecutableInPath("openbox", &path);
+ if (error) {
+ return "Failed to find openbox executable path";
+ }
+
+ ffBinaryExtractStrings(path.chars, extractCommonWmVersion, result, (uint32_t) strlen("0.0.0"));
+ if (result->length > 0) {
+ return NULL;
+ }
+
+ if (ffProcessAppendStdOut(result, (char* const[]) { path.chars, "--version", NULL }) == NULL) { // Openbox 3.6.1\n...
+ ffStrbufSubstrBeforeFirstC(result, '\n');
+ ffStrbufSubstrAfterLastC(result, ' ');
+ return NULL;
+ }
+
+ return "Failed to run command `openbox --version`";
+}
+
+const char* ffDetectWMVersion(const FFstrbuf* wmName, FFstrbuf* result, FF_A_UNUSED FFWMOptions* options) {
+ if (!wmName) {
+ return "No WM detected";
+ }
+
+#if !__ANDROID__
+ // Wayland compositors
+ if (ffStrbufIgnCaseEqualS(wmName, "Hyprland")) {
+ return getHyprland(result);
+ }
+
+ if (ffStrbufEqualS(wmName, "sway")) {
+ return getSway(result);
+ }
+
+ if (ffStrbufEqualS(wmName, "labwc")) {
+ return getLabwc(result);
+ }
+
+ if (ffStrbufEqualS(wmName, "niri")) {
+ return getNiri(result);
+ }
+
+ #if __linux__
+ if (ffStrbufEqualS(wmName, "WSLg")) {
+ return getWslg(result);
+ }
+ #endif
+#endif
+
+ // X11 WMs
+ if (ffStrbufEqualS(wmName, "i3")) {
+ return getI3(result);
+ }
+
+ if (ffStrbufEqualS(wmName, "ctwm")) {
+ return getCtwm(result);
+ }
+
+ if (ffStrbufEqualS(wmName, "fvwm")) {
+ return getFvwm(result);
+ }
+
+ if (ffStrbufEqualS(wmName, "Openbox")) {
+ return getOpenbox(result);
+ }
+
+ return "Unsupported WM";
+}