]> mj.ucw.cz Git - misc.git/blob - batt.c
Ursary: More PA pieces
[misc.git] / batt.c
1 /*
2  *      A Simple Battery Status Display
3  *
4  *      (c) 2007 Martin Mares <mj@ucw.cz>
5  */
6
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <unistd.h>
11 #include <dirent.h>
12 #include <signal.h>
13 #include <time.h>
14 #include <xosd.h>
15 #include <X11/Xlib.h>
16
17 static xosd *osd;
18 static int total_full, total_capa, discharge_rate;
19 static int charge_time, discharge_time;
20 static int ac_online;
21 static unsigned int present_mask, charge_mask, discharge_mask;
22
23 #define CHECK_PERIOD 60
24 #define SHOW_DURATION 2
25 #define WARN_THRESHOLD 600
26
27 static char *parse_line(char *line)
28 {
29   char *t = strchr(line, '\n');
30   if (t)
31     *t = 0;
32   char *val = strchr(line, ':');
33   if (!val)
34     return NULL;
35   *val++ = 0;
36   while (*val == ' ' || *val == '\t')
37     *val++ = 0;
38   return val;
39 }
40
41 static void scan_ac(void)
42 {
43   ac_online = 0;
44
45   const char dir[] = "/proc/acpi/ac_adapter";
46   DIR *d = opendir(dir);
47   if (!d)
48     return;
49
50   struct dirent *e;
51   while (e = readdir(d))
52     {
53       if (e->d_name[0] == '.')
54         continue;
55       char n[sizeof(dir) + 1 + strlen(e->d_name) + 6];
56       sprintf(n, "%s/%s/state", dir, e->d_name);
57       FILE *f = fopen(n, "r");
58       if (!f)
59         continue;
60       char line[1024];
61       while (fgets(line, sizeof(line)-1, f))
62         {
63           char *val = parse_line(line);
64           if (!val)
65             continue;
66           if (!strcmp(line, "state") && !strcmp(val, "on-line"))
67             ac_online = 1;
68         }
69       fclose(f);
70     }
71   closedir(d);
72 }
73
74 static void scan_batt(void)
75 {
76   charge_time = discharge_time = 0;
77   total_full = total_capa = 0;
78   discharge_rate = 0;
79   present_mask = charge_mask = discharge_mask = 0;
80
81   const char dir[] = "/proc/acpi/battery";
82   DIR *d = opendir(dir);
83   if (!d)
84     return;
85
86   struct dirent *e;
87   while (e = readdir(d))
88     {
89       if (e->d_name[0] == '.')
90         continue;
91       char n[sizeof(dir) + 1 + strlen(e->d_name) + 6];
92       const char * const names[] = { "state", "info" };
93       int present = 0;
94       int rate = 0;
95       int charging = 0;
96       int remains = 0;
97       int last_full = 0;
98       int batt_id = -1;
99       for (int i=0; i<2; i++)
100         {
101           sprintf(n, "%s/%s/%s", dir, e->d_name, names[i]);
102           FILE *f = fopen(n, "r");
103           if (!f)
104             continue;
105           char line[1024];
106           while (fgets(line, sizeof(line)-1, f))
107             {
108               char *val = parse_line(line);
109               if (!val)
110                 continue;
111               // printf("<%s> <%s>\n", line, val);
112               if (!strcmp(line, "present"))
113                 present = !strcmp(val, "yes");
114               else if (!strcmp(line, "charging state"))
115                 charging = !strcmp(val, "charging");
116               else if (!strcmp(line, "present rate"))
117                 rate = atol(val);
118               else if (!strcmp(line, "remaining capacity"))
119                 remains = atol(val);
120               else if (!strcmp(line, "last full capacity"))
121                 last_full = atol(val);
122               else if (!strcmp(line, "serial number"))
123                 batt_id = atol(val);
124             }
125           fclose(f);
126         }
127       if (present)
128         {
129           if (batt_id < 0 || batt_id > 31)
130             batt_id = 0;
131           present_mask |= 1 << batt_id;
132           total_full += remains;
133           total_capa += last_full;
134           if (charging)
135             {
136               charge_mask |= 1 << batt_id;
137               int ch = (last_full - remains)*3600 / rate;
138               if (ch > charge_time)
139                 charge_time = ch;
140             }
141           else if (rate > 0)
142             {
143               discharge_mask |= 1 << batt_id;
144               discharge_rate += rate;
145             }
146         }
147     }
148   if (discharge_rate)
149     discharge_time = total_full*3600 / discharge_rate;
150   else
151     discharge_time = 1000000;
152
153   closedir(d);
154 }
155
156 static void scan(void)
157 {
158   scan_ac();
159   scan_batt();
160 }
161
162 static char *batt_mask(char *p, unsigned int mask)
163 {
164   if (present_mask & (present_mask-1))
165     {
166       char *p0 = p;
167       for (int i=0; mask; i++)
168         if (mask & (1 << i))
169           {
170             *p = (p == p0) ? ' ' : '+';
171             p++;
172             p += sprintf(p, "B%d", i);
173             mask &= ~(1 << i);
174           }
175     }
176   return p;
177 }
178
179 static void show(void)
180 {
181   char status[256];
182   char *p = status;
183   if (total_capa)
184     p += sprintf(p, "%d%%", 100*total_full/total_capa);
185   else
186     p += sprintf(p, "??%%");
187   if (discharge_mask && discharge_time < 1000000)
188     {
189       p += sprintf(p, "  %d:%02d  remains", discharge_time/3600, (discharge_time/60)%60);
190       batt_mask(p, discharge_mask);
191     }
192   else if (charge_mask)
193     {
194       p += sprintf(p, "  %d:%02d  charging", charge_time/3600, (charge_time/60)%60);
195       batt_mask(p, charge_mask);
196     }
197   else if (ac_online)
198     p += sprintf(p, " AC");
199   else
200     p += sprintf(p, " BATT");
201   xosd_display(osd, 0, XOSD_string, status);
202 }
203
204 static void my_sleep(int sec)
205 {
206   struct timespec ts = { .tv_sec = sec };
207   nanosleep(&ts, NULL);
208 }
209
210 static void sig_show(int sig)
211 {
212   alarm(0);
213   scan();
214   show();
215   my_sleep(SHOW_DURATION);
216   xosd_hide(osd);
217   alarm(CHECK_PERIOD);
218 }
219
220 static void sig_time(int sig)
221 {
222   char buf[256];
223   time_t now = time(NULL);
224   struct tm *tm = localtime(&now);
225   if (tm)
226     strftime(buf, sizeof(buf), "%d-%m-%Y  %H:%M:%S", tm);
227   else
228     strcpy(buf, "??:??:??");
229   xosd_display(osd, 0, XOSD_string, buf);
230   my_sleep(SHOW_DURATION);
231   xosd_hide(osd);
232 }
233
234 static void sig_check(int sig)
235 {
236   scan();
237   if (discharge_mask && discharge_time < WARN_THRESHOLD)
238     {
239       show();
240       my_sleep(SHOW_DURATION);
241       xosd_hide(osd);
242     }
243   alarm(CHECK_PERIOD);
244 }
245
246 int main(int argc, char **argv)
247 {
248   // If daemon mode is requested, fork first.
249   int daemon = (argc > 1 && !strcmp(argv[1], "--daemon"));
250   int watcher = daemon || (argc > 1 && !strcmp(argv[1], "--watch"));
251   if (daemon)
252     {
253       pid_t pid = fork();
254       if (pid < 0)
255         {
256           fprintf(stderr, "batt: Cannot fork: %m\n");
257           return 1;
258         }
259       if (pid > 0)
260         return 0;
261       setsid();
262     }
263
264   // Block SIGALRM and SIGUSR1, because libxosd will start its own thread
265   // and we don't want it to accept these signals later;
266   sigset_t our_signals;
267   sigemptyset(&our_signals);
268   sigaddset(&our_signals, SIGALRM);
269   sigaddset(&our_signals, SIGUSR1);
270   sigaddset(&our_signals, SIGUSR2);
271   sigprocmask(SIG_BLOCK, &our_signals, NULL);
272
273   osd = xosd_create(1);
274   if (!osd)
275     {
276       fprintf(stderr, "batt: Cannot create on-screen display\n");
277       return 1;
278     }
279   xosd_set_font(osd, "-bitstream-bitstream vera sans-bold-r-normal-*-*-320-*-*-p-*-*");
280   xosd_set_outline_offset(osd, 2);
281   xosd_set_outline_colour(osd, "black");
282   xosd_set_pos(osd, XOSD_middle);
283   xosd_set_align(osd, XOSD_center);
284
285   if (!watcher)
286     {
287       scan();
288       show();
289       my_sleep(SHOW_DURATION);
290       xosd_destroy(osd);
291       return 0;
292     }
293
294   // Set up signal handlers from which we do everything asynchronously.
295   struct sigaction sa = {
296     .sa_handler = sig_show,
297     .sa_mask = our_signals,
298   };
299   sigaction(SIGUSR1, &sa, NULL);
300   sa.sa_handler = sig_time;
301   sigaction(SIGUSR2, &sa, NULL);
302   sa.sa_handler = sig_check;
303   sigaction(SIGALRM, &sa, NULL);
304   sigprocmask(SIG_UNBLOCK, &our_signals, NULL);
305   alarm(1);
306
307   // We use this connection only for detecting that the session has ended.
308   // In such cases, we are terminated automatically by XLib.
309   Display *dpy = XOpenDisplay(NULL);
310   if (!dpy)
311     {
312       fprintf(stderr, "batt: Cannot open display\n");
313       return 1;
314     }
315   for (;;)
316     {
317       XEvent ev;
318       XNextEvent(dpy, &ev);
319     }
320
321   return 0;
322 }