1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
|
/*
* 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;
/* Some shells, for example zsh, open a pipe in a way which will make the
* pipe target the parent process of i3status. If we detect that, we set
* the format and we are done. */
if (strcasecmp(parentname, "i3bar") == 0)
format = "none";
else if (strcasecmp(parentname, "dzen2") == 0)
format = "dzen2";
else if (strcasecmp(parentname, "xmobar") == 0)
format = "xmobar";
if (format)
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:
if (parentname)
free(parentname);
closedir(dir);
return format;
}
|