]> git.sur5r.net Git - i3/i3status/commitdiff
magic: try to auto-detect output_format by default (dzen2/i3bar/xmobar)
authorMichael Stapelberg <michael@stapelberg.de>
Wed, 13 Jul 2011 01:27:57 +0000 (03:27 +0200)
committerMichael Stapelberg <michael@stapelberg.de>
Wed, 13 Jul 2011 01:27:57 +0000 (03:27 +0200)
i3status.c
include/i3status.h
src/auto_detect_format.c [new file with mode: 0644]

index 372e53a37ee9de45505532160e69c884859c47e2..23f0b97ea066c0e6e3300763928d3432a71934e5 100644 (file)
@@ -173,7 +173,7 @@ int main(int argc, char *argv[]) {
         unsigned int j;
 
         cfg_opt_t general_opts[] = {
-                CFG_STR("output_format", "dzen2", CFGF_NONE),
+                CFG_STR("output_format", "auto", CFGF_NONE),
                 CFG_BOOL("colors", 1, CFGF_NONE),
                 CFG_STR("color_good", "#00FF00", CFGF_NONE),
                 CFG_STR("color_degraded", "#FFFF00", CFGF_NONE),
@@ -312,6 +312,17 @@ int main(int argc, char *argv[]) {
                 die("Could not get section \"general\"\n");
 
         char *output_str = cfg_getstr(cfg_general, "output_format");
+        if (strcasecmp(output_str, "auto") == 0) {
+                fprintf(stderr, "i3status: trying to auto-detect output_format setting\n");
+                output_str = auto_detect_format();
+                if (!output_str) {
+                        output_str = "none";
+                        fprintf(stderr, "i3status: falling back to \"none\"\n");
+                } else {
+                        fprintf(stderr, "i3status: auto-detected \"%s\"\n", output_str);
+                }
+        }
+
         if (strcasecmp(output_str, "dzen2") == 0)
                 output_format = O_DZEN2;
         else if (strcasecmp(output_str, "xmobar") == 0)
index ae1983cc19cc477ce16fb120f39fd269bd7ff906..125ca557f97850b9e3aff84210af4aa87d793ff7 100644 (file)
@@ -59,6 +59,9 @@ void print_seperator();
 char *color(const char *colorstr);
 char *endcolor() __attribute__ ((pure));
 
+/* src/auto_detect_format.c */
+char *auto_detect_format();
+
 void print_ipv6_info(const char *format_up, const char *format_down);
 void print_disk_info(const char *path, const char *format);
 void print_battery_info(int number, const char *format, bool last_full_capacity);
diff --git a/src/auto_detect_format.c b/src/auto_detect_format.c
new file mode 100644 (file)
index 0000000..b8dfdfc
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+
+#include "i3status.h"
+
+/*
+ * This function tries to automatically find out where i3status is being piped
+ * to and choses the appropriate output format.
+ *
+ * It is a little hackish but should work for most setups :).
+ *
+ * By iterating through /proc/<number>/stat and finding out the parent process
+ * id (just like pstree(1) or ps(1) work), we can get all children of our
+ * parent. When the output of i3status is being piped somewhere, the shell
+ * (parent process) spawns i3status and the destination process, so we will
+ * find our own process and the pipe target.
+ *
+ * We then check whether the pipe target’s name is known and chose the format.
+ *
+ */
+char *auto_detect_format() {
+    pid_t myppid = getppid();
+    pid_t mypid = getpid();
+
+    DIR *dir;
+    struct dirent *entry;
+    char path[255];
+    /* the relevant contents (for us) are:
+     * <pid> (<program name>) <status> <ppid>
+     * which should well fit into one page of 4096 bytes */
+    char buffer[4096];
+
+    char *format = NULL;
+
+    char *parentname = NULL;
+
+    if (!(dir = opendir("/proc")))
+        return NULL;
+
+    /* First pass: get the executable name of the parent.
+     * Upon error, we directly return NULL as we cannot continue without the
+     * name of our parent process. */
+    while ((entry = readdir(dir)) != NULL) {
+        pid_t pid = (pid_t)atoi(entry->d_name);
+        if (pid != myppid)
+            continue;
+
+        if (snprintf(path, sizeof(path), "/proc/%d/stat", pid) == -1 ||
+            !slurp(path, buffer, 4095))
+            goto out;
+
+        buffer[4095] = '\0';
+        char *leftbracket = strchr(buffer, '(');
+        char *rightbracket = strrchr(buffer, ')');
+        if (!leftbracket ||
+            !rightbracket ||
+            !(parentname = malloc((rightbracket - leftbracket))))
+            goto out;
+        *rightbracket = '\0';
+        strcpy(parentname, leftbracket + 1);
+    }
+
+    if (!parentname)
+        goto out;
+
+    rewinddir(dir);
+
+    while ((entry = readdir(dir)) != NULL) {
+        pid_t pid = (pid_t)atoi(entry->d_name);
+        if (pid == 0 || pid == mypid)
+            continue;
+
+        if (snprintf(path, sizeof(path), "/proc/%d/stat", pid) == -1)
+            continue;
+
+        char *name = NULL;
+        pid_t ppid;
+        int loopcnt = 0;
+        /* Now we need to find out the name of the process.
+         * To avoid the possible race condition of the process existing already
+         * but not executing the destination (shell after fork() and before
+         * exec()), we check if the name equals its parent.
+         *
+         * We try this for up to 0.5 seconds, then we give up.
+         */
+        do {
+            /* give the scheduler a chance between each iteration, don’t hog
+             * the CPU too much */
+            if (name)
+                usleep(50);
+
+            if (!slurp(path, buffer, 4095))
+                break;
+            buffer[4095] = '\0';
+            char *leftbracket = strchr(buffer, '(');
+            char *rightbracket = strrchr(buffer, ')');
+            if (!leftbracket ||
+                !rightbracket ||
+                sscanf(rightbracket + 2, "%*c %d", &ppid) != 1 ||
+                ppid != myppid)
+                break;
+            *rightbracket = '\0';
+            name = leftbracket + 1;
+        } while (strcmp(parentname, name) == 0 && loopcnt++ < 10000);
+
+        if (!name)
+            continue;
+
+        /* Check for known destination programs and set format */
+        char *newfmt = NULL;
+        if (strcasecmp(name, "i3bar") == 0)
+            newfmt = "none";
+        else if (strcasecmp(name, "dzen2") == 0)
+            newfmt = "dzen2";
+        else if (strcasecmp(name, "xmobar") == 0)
+            newfmt = "xmobar";
+
+        if (newfmt && format) {
+            fprintf(stderr, "i3status: cannot auto-configure, situation ambiguous (format \"%s\" *and* \"%s\" detected)\n", newfmt, format);
+            format = NULL;
+            break;
+        } else {
+            format = newfmt;
+        }
+    }
+
+out:
+    closedir(dir);
+
+    return format;
+}