summaryrefslogtreecommitdiff
path: root/src/auto_detect_format.c
diff options
context:
space:
mode:
authorMichael Stapelberg <michael@stapelberg.de>2011-07-13 03:27:57 +0200
committerMichael Stapelberg <michael@stapelberg.de>2011-07-13 03:27:57 +0200
commit7ab9167536e4ca0ccb5c5a797e693c484e0456d6 (patch)
tree26a60cb557cb4719dc429e3c157dd8790fa76451 /src/auto_detect_format.c
parenta46984d06ccc234d07ced0bda80fa8d6f3f21076 (diff)
magic: try to auto-detect output_format by default (dzen2/i3bar/xmobar)
Diffstat (limited to 'src/auto_detect_format.c')
-rw-r--r--src/auto_detect_format.c142
1 files changed, 142 insertions, 0 deletions
diff --git a/src/auto_detect_format.c b/src/auto_detect_format.c
new file mode 100644
index 0000000..b8dfdfc
--- /dev/null
+++ b/src/auto_detect_format.c
@@ -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;
+}