]> git.sur5r.net Git - i3/i3status/blob - src/auto_detect_format.c
fix: use SYSCONFDIR in error message
[i3/i3status] / src / auto_detect_format.c
1 /*
2  * vim:ts=4:sw=4:expandtab
3  */
4 #include <config.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <unistd.h>
9 #include <stdbool.h>
10 #include <stdint.h>
11 #include <sys/types.h>
12 #include <sys/stat.h>
13 #include <fcntl.h>
14 #include <dirent.h>
15
16 #include "i3status.h"
17
18 /*
19  * Reads /proc/<pid>/stat and returns (via pointers) the name and parent pid of
20  * the specified pid.
21  * When false is returned, parsing failed and the contents of outname and
22  * outpid are undefined.
23  *
24  */
25 static bool parse_proc_stat(pid_t pid, char **outname, pid_t *outppid) {
26     char path[255];
27     /* the relevant contents (for us) are:
28      * <pid> (<program name>) <status> <ppid>
29      * which should well fit into one page of 4096 bytes */
30     char buffer[4096];
31
32     if (snprintf(path, sizeof(path), "/proc/%d/stat", pid) == -1 ||
33         !slurp(path, buffer, sizeof(buffer)))
34         return false;
35
36     char *leftbracket = strchr(buffer, '(');
37     char *rightbracket = strrchr(buffer, ')');
38     if (!leftbracket ||
39         !rightbracket ||
40         sscanf(rightbracket + 2, "%*c %d", outppid) != 1)
41         return false;
42     *rightbracket = '\0';
43     *outname = strdup(leftbracket + 1);
44     return true;
45 }
46
47 static char *format_for_process(const char *name) {
48     if (strcasecmp(name, "i3bar") == 0 || strcasecmp(name, "swaybar") == 0)
49         return "i3bar";
50     else if (strcasecmp(name, "dzen2") == 0)
51         return "dzen2";
52     else if (strcasecmp(name, "xmobar") == 0)
53         return "xmobar";
54     else
55         return NULL;
56 }
57
58 /*
59  * This function tries to automatically find out where i3status is being piped
60  * to and choses the appropriate output format.
61  *
62  * It is a little hackish but should work for most setups :).
63  *
64  * By iterating through /proc/<number>/stat and finding out the parent process
65  * id (just like pstree(1) or ps(1) work), we can get all children of our
66  * parent. When the output of i3status is being piped somewhere, the shell
67  * (parent process) spawns i3status and the destination process, so we will
68  * find our own process and the pipe target.
69  *
70  * We then check whether the pipe target’s name is known and chose the format.
71  *
72  */
73 char *auto_detect_format(void) {
74     /* If stdout is a tty, we output directly to a terminal. */
75     if (isatty(STDOUT_FILENO)) {
76         return "term";
77     }
78
79     pid_t myppid = getppid();
80     pid_t mypid = getpid();
81
82     DIR *dir;
83     struct dirent *entry;
84
85     char *format = NULL;
86
87     char *parentname;
88     pid_t parentpid;
89
90     if (!parse_proc_stat(myppid, &parentname, &parentpid))
91         return NULL;
92
93     if (strcmp(parentname, "sh") == 0) {
94         pid_t tmp_ppid = parentpid;
95         free(parentname);
96         fprintf(stderr, "i3status: auto-detection: parent process is \"sh\", looking at its parent\n");
97         if (!parse_proc_stat(tmp_ppid, &parentname, &parentpid))
98             return NULL;
99     }
100
101     /* Some shells, for example zsh, open a pipe in a way which will make the
102      * pipe target the parent process of i3status. If we detect that, we set
103      * the format and we are done. */
104     if ((format = format_for_process(parentname)) != NULL)
105         goto out;
106
107     if (!(dir = opendir("/proc")))
108         goto out;
109
110     while ((entry = readdir(dir)) != NULL) {
111         pid_t pid = (pid_t)atoi(entry->d_name);
112         if (pid == 0 || pid == mypid)
113             continue;
114
115         char *name = NULL;
116         pid_t ppid;
117         int loopcnt = 0;
118         /* Now we need to find out the name of the process.
119          * To avoid the possible race condition of the process existing already
120          * but not executing the destination (shell after fork() and before
121          * exec()), we check if the name equals its parent.
122          *
123          * We try this for up to 0.5 seconds, then we give up.
124          */
125         do {
126             /* give the scheduler a chance between each iteration, don’t hog
127              * the CPU too much */
128             if (name) {
129                 usleep(50);
130                 free(name);
131             }
132
133             if (!parse_proc_stat(pid, &name, &ppid))
134                 break;
135             if (ppid != myppid)
136                 break;
137         } while (strcmp(parentname, name) == 0 && loopcnt++ < 10000);
138
139         if (!name)
140             continue;
141
142         /* Check for known destination programs and set format */
143         char *newfmt = format_for_process(name);
144         free(name);
145
146         if (newfmt && format && strcmp(newfmt, format) != 0) {
147             fprintf(stderr, "i3status: cannot auto-configure, situation ambiguous (format \"%s\" *and* \"%s\" detected)\n", newfmt, format);
148             format = NULL;
149             break;
150         } else {
151             format = newfmt;
152         }
153     }
154
155     closedir(dir);
156
157 out:
158     if (parentname)
159         free(parentname);
160
161     return format;
162 }