chiark / gitweb /
wip
[chiark-utils.git] / cprogs / xacpi-simple.c
1 /*
2  * display outputs, per line:
3  *
4  *   Remaining: | Empty:        | Degraded:
5  *     blue     |  black        |  dimgrey      discharging
6  *     green    |  black        |  dimgrey      charging
7  *     cyan     |  black        |  dimgrey      charged
8  *     grey     |  black        |  dimgrey      charging&discharching!
9  *     blue     |  red          |  dimgrey      discharging - low!
10  *     green    |  red          |  dimgrey      charging - low
11  *     cyan     |  red          |  dimgrey      charged - low [1]
12  *     grey     |  red          |  dimgrey      charging&discharching, low [1]
13  *       ...  darkgreen  ...                    no batteries present
14  *       ...  yellow  ...                       error
15  *
16  * [1] battery must be quite badly degraded
17  */
18 /*
19  * Copyright (C) 2004 Ian Jackson <ian@davenant.greenend.org.uk>
20  *
21  * This is free software; you can redistribute it and/or modify
22  * it under the terms of the GNU General Public License as
23  * published by the Free Software Foundation; either version 3,
24  * or (at your option) any later version.
25  *
26  * This is distributed in the hope that it will be useful, but
27  * WITHOUT ANY WARRANTY; without even the implied warranty of
28  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
29  * GNU General Public License for more details.
30  *
31  * You should have received a copy of the GNU General Public
32  * License along with this file; if not, consult the Free Software
33  * Foundation's website at www.fsf.org, or the GNU Project website at
34  * www.gnu.org.
35  */
36
37 #include <stdio.h>
38 #include <assert.h>
39 #include <math.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <errno.h>
43 #include <unistd.h>
44 #include <ctype.h>
45 #include <stdint.h>
46
47 #include <sys/poll.h>
48 #include <sys/types.h>
49 #include <dirent.h>
50
51 #include <X11/Xlib.h>
52 #include <X11/Xutil.h>
53 #include <X11/Xresource.h>
54
55 #define TOP      60
56 #define BOTTOM 3600
57
58 #define TIMEOUT         5000 /* milliseconds */
59 #define TIMEOUT_ONERROR 3333 /* milliseconds */
60
61 static const char program_name[]= "xacpi-simple";
62
63 /*---------- general utility stuff and declarations ----------*/
64
65 static void fail(const char *m) {
66   fprintf(stderr,"error: %s\n", m);
67   exit(-1);
68 }
69 static void badusage(void) { fail("bad usage"); }
70
71 typedef struct fileinfo fileinfo;
72 struct fileinfo {
73   const char *filename;
74   void (*parse)(const fileinfo*);
75   const void *extra;
76 };
77
78 /*---------- structure of and results from /sys/class/power/... ----------*/
79 /* variables thisbat_... are the results from readbattery();
80  * if readbattery() succeeds they are all valid and not VAL_NOTFOUND
81  */
82
83 typedef struct batinfo_field {
84   const char *label;
85   uint64_t *valuep;
86   const char *enumarray[10];
87 } batinfo_field;
88
89 #define UEVENT_QUANTITY_FIELDS(q)                               \
90   q(design_capacity,    CHARGE_FULL_DESIGN ) /* uAh */          \
91   q(last_full_capacity, CHARGE_FULL        ) /* uAh */          \
92   q(present_rate,       CURRENT_NOW        ) /* uA  */          \
93   q(remaining_capacity, CHARGE_NOW         ) /* uAh */          \
94   q(present,            PRESENT            ) /* boolean */      \
95   q(online,             ONLINE             ) /* boolean */
96
97 #define UEVENT_ENUM_FIELDS(e)                                   \
98   e(type,    TYPE,    "Battery",     "Mains"                 )  \
99   e(state,   STATUS,  "Discharging", "Charging", "Charged"   )
100
101 #define CHGST_DISCHARGING 0 /* Reflects order in e(state,...) above     */
102 #define CHGST_CHARGING    1 /*  Also, much code assumes exadtly         */
103 #define CHGST_CHARGED     2 /*  these three possible states.            */
104 #define CHGST_ERROR       8 /* Except that this one is an extra bit.    */
105
106 #define Q_FLD(f,l)        { "POWER_SUPPLY_" #l, &thisbat_##f },
107 #define E_FLD(f,l,vl...)  { "POWER_SUPPLY_" #l, &thisbat_##f, { vl } },
108
109 #define ALL_FIELDS(i)                           \
110   UEVENT_QUANTITY_FIELDS(i)                     \
111   UEVENT_ENUM_FIELDS(i)                         \
112   i(thisbat_alarm)
113
114 #define F_VAR(f,...) static uint64_t thisbat_##f;
115   ALL_FIELDS(F_VAR)
116
117 static const batinfo_field uevent_fields[]= {
118   UEVENT_QUANTITY_FIELDS(Q_FLD)
119   UEVENT_ENUM_FIELDS(E_FLD)
120   { 0 }
121 };
122
123 #define VAL_NOTFOUND (~0UL)
124
125 static const fileinfo files[]= {
126   { "uevent",  parse_fields,  uevent_fields },
127   { "alarm",   parse_boolean, &thisbat_alarm },
128   { 0 }
129 };
130
131 /*---------- parsing of one battery in /proc/acpi/battery/... ----------*/
132
133 /* variables private to the parser and its error handlers */
134 static char batlinebuf[1000];
135 static FILE *batfile;
136 static const char *batdirname;
137 static const char *batfilename;
138 static const char *batlinevalue;
139
140 static int batfailf(const char *why) {
141   if (batlinevalue) {
142     fprintf(stderr,"battery/%s/%s: %s value `%s': %s\n",
143             batdirname,batfilename, batlinebuf,batlinevalue,why);
144   } else {
145     fprintf(stderr,"battery/%s/%s: %s: `%s'\n",
146             batdirname,batfilename, why, batlinebuf);
147   }
148   return -1;
149 }
150
151 static int batfaile(const char *syscall, const char *target) {
152   fprintf(stderr,"battery/%s: failed to %s %s: %s\n",
153           batdirname ? batdirname : "*", syscall, target, strerror(errno));
154   return -1;
155 }
156
157 static void chdir_base(void) {
158   int r;
159   
160   r= chdir("/sys/class/power_supply");
161   if (r) batfaile("chdir","/sys/class/power_supply");
162 }
163
164 static void tidybattery(void) {
165   if (batfile) { fclose(batfile); batfile=0; }
166   if (batdirname) { chdir_base(); batdirname=0; }
167 }
168
169 static void parse_fields(const fileinfo *cfile, char *batlinebuf) {
170   equals= strchr(batlinebuf,'=');
171   if (!equals)
172     return batfailf("line without a equals");
173   *equals++= 0;
174
175   for (field=fields; field->file; field++) {
176     if (!strcmp(field->label,batlinebuf))
177       goto found;
178   }
179   return;
180
181  found:
182   if (!field->enumarray[0]) {
183
184     *field->valuep= strtoull(equals,&ep,10);
185     if (*ep)
186       batfailf("value number syntax incorrect");
187
188   } else {
189         
190     for (*field->valuep=0, enumsearch=field->enumarray;
191          *enumsearch && strcmp(*enumsearch,equals);
192          (*field->valuep)++, enumsearch++);
193     if (!*enumsearch)
194       batfailf("unknown enum value");
195
196   }
197 }
198
199 static int readbattery(void) { /* 0=>ok, -1=>couldn't */
200   const batinfo_field *field;
201   const fileinfo *cfile;
202   const char *const *enumsearch;
203   char *colon, *ep, *sr, *p;
204   int r, l, missing;
205   
206   r= chdir(batdirname);
207   if (r) return batfaile("chdir",batdirname);
208
209 #define V_NOTFOUND(f,...) thisbat_##f = VAL_NOTFOUND;
210   ALL_FIELDS(V_NOTFOUND)
211   
212   for (field=fields; field->file; field++)
213     *field->valuep= VAL_NOTFOUND;
214
215   for (cfile=files;
216        (batfilename= cfile->filename);
217        cfile++++) {
218     batfile= fopen(batfilename,"r");
219     if (!batfile) return batfaile("open",batfilename);
220
221     for (;;) {
222       batlinevalue= 0;
223       
224       sr= fgets(batlinebuf,sizeof(batlinebuf),batfile);
225       if (ferror(batfile)) return batfaile("read",batfilename);
226       if (!sr && feof(batfile)) break;
227       l= strlen(batlinebuf);
228       assert(l>0);
229       if (batlinebuf[l-1] != '\n')
230         return batfailf("line too long");
231       batlinebuf[l-1]= 0;
232
233       cfile->parse(cfile, batlinebuf);
234     }
235
236     fclose(batfile);
237     batfile= 0;
238   }
239
240   if (!(thisbat_alarm_present==0 ||
241         thisbat_state_present==0 || thisbat_info_present==0)) {
242     if (thisbat_alarm_present == VAL_NOTFOUND)
243       thisbat_alarm_present= 1;
244     
245     for (field=fields, missing=0;
246          field->file;
247          field++) {
248       if (*field->valuep == VAL_NOTFOUND) {
249         fprintf(stderr,"battery/%s/%s: %s: not found\n",
250                 batdirname, field->file,field->label);
251         missing++;
252       }
253     }
254     if (missing) return -1;
255   }
256
257   r= chdir("..");
258   if (r) return batfaile("chdir","..");
259   batdirname= 0;
260
261   return 0;
262 }   
263
264 /*---------- data collection and analysis ----------*/
265
266 /* These next three variables are the results of the charging state */
267 static unsigned charging_state_mask; /* 1u<<CHGST_* | ... */
268 static double nondegraded_norm, fill_norm, ratepersec_norm;
269 static int alarm_level; /* 0=ok, 1=low */
270
271 #define QF(f,l,u) \
272   static double total_##f##_##l;
273   QUANTITY_FIELDS
274 #undef QF
275
276 static void acquiredata(void) {
277   DIR *di;
278   struct dirent *de;
279   int r;
280   
281   charging_state_mask= 0;
282
283 #define QF(f,l,u) \
284   total_##f##_##l= 0;
285   QUANTITY_FIELDS
286 #undef QF
287
288   di= opendir(".");  if (!di) batfaile("opendir","battery");
289   while ((de= readdir(di))) {
290     if (de->d_name[0]==0 || de->d_name[0]=='.') continue;
291
292     batdirname= de->d_name;
293     r= readbattery();
294     tidybattery();
295
296     if (r) { charging_state_mask |= CHGST_ERROR; continue; }
297
298     if (!thisbat_info_present || !thisbat_state_present)
299       continue;
300
301     charging_state_mask |= 1u << thisbat_state_charging_state;
302
303 #define QF(f,l,u) \
304     total_##f##_##l += thisbat_##f##_##l;
305     QUANTITY_FIELDS
306 #undef QF
307
308     if (thisbat_state_charging_state == CHGST_DISCHARGING)
309       /* negate it */
310       total_state_present_rate -= 2.0 * thisbat_state_present_rate;
311       
312   }
313   closedir(di);
314
315   if (total_info_design_capacity < 0.5)
316     total_info_design_capacity= 1.0;
317
318   if (total_info_last_full_capacity < total_state_remaining_capacity)
319     total_info_last_full_capacity= total_state_remaining_capacity;
320   if (total_info_design_capacity < total_info_last_full_capacity)
321     total_info_design_capacity= total_info_last_full_capacity;
322
323   alarm_level=
324     (total_state_remaining_capacity < total_alarm_alarm &&
325      !(charging_state_mask & 1u << CHGST_CHARGING));
326
327   nondegraded_norm= total_info_last_full_capacity / total_info_design_capacity;
328   fill_norm= total_state_remaining_capacity / total_info_design_capacity;
329   ratepersec_norm=  total_state_present_rate
330     / (3600.0 * total_info_design_capacity);
331 }
332
333 static void initacquire(void) {
334   chdir_base();
335 }  
336
337 /*---------- argument parsing ----------*/
338
339 #define COLOURS                                 \
340   C(blue,      discharging)                     \
341   C(green,     charging)                        \
342   C(cyan,      charged)                         \
343   C(grey,      confusing)                       \
344   C(black,     normal)                          \
345   C(red,       low)                             \
346   C(dimgrey,   degraded)                        \
347   C(darkgreen, absent)                          \
348   C(yellow,    error)                           \
349   C(white,     equilibrium)                     \
350   GC(remain)                                    \
351   GC(white)                                     \
352   GC(empty)
353
354 static XrmDatabase xrm;
355 static Display *disp;
356 static int screen;
357
358 static const char defaultresources[]=
359 #define GC(g)
360 #define C(c,u)                                  \
361   "*" #u "Color: " #c "\n"
362   COLOURS
363 #undef GC
364 #undef C
365   ;
366
367 #define S(s) ((char*)(s))
368 static const XrmOptionDescRec optiontable[]= {
369   { S("-display"),      S("*display"),      XrmoptionSepArg },
370   { S("-geometry"),     S("*geometry"),     XrmoptionSepArg },
371 #define GC(g)
372 #define C(c,u)                                                  \
373   { S("-" #u "Color"),  S("*" #u "Color"),  XrmoptionSepArg },  \
374   { S("-" #u "Colour"), S("*" #u "Color"),  XrmoptionSepArg },
375   COLOURS
376 #undef GC
377 #undef C
378 };
379
380 static const char *getresource(const char *want) {
381   char name_buf[256], class_buf[256];
382   XrmValue val;
383   char *rep_type_dummy;
384   int r;
385
386   assert(strlen(want) < 128);
387   sprintf(name_buf,"xacpi-simple.%s",want);
388   sprintf(class_buf,"Xacpi-Simple.%s",want);
389   
390   r= XrmGetResource(xrm, name_buf,class_buf, &rep_type_dummy, &val);
391   if (!r) return 0;
392   
393   return val.addr;
394 }
395
396 static void more_resources(const char *str, const char *why) {
397   XrmDatabase more;
398
399   if (!str) return;
400
401   more= XrmGetStringDatabase((char*)str);
402   if (!more) fail(why);
403   XrmCombineDatabase(more,&xrm,0);
404 }
405
406 static void parseargs(int argc, char **argv) {
407   Screen *screenscreen;
408   
409   XrmInitialize();
410
411   XrmParseCommand(&xrm, (XrmOptionDescRec*)optiontable,
412                   sizeof(optiontable)/sizeof(*optiontable),
413                   program_name, &argc, argv);
414
415   if (argc>1) badusage();
416
417   disp= XOpenDisplay(getresource("display"));
418   if (!disp) fail("could not open display");
419
420   screen= DefaultScreen(disp);
421
422   screenscreen= ScreenOfDisplay(disp,screen);
423   if (!screenscreen) fail("screenofdisplay");
424   more_resources(XScreenResourceString(screenscreen), "screen resources");
425   more_resources(XResourceManagerString(disp), "display resources");
426   more_resources(defaultresources, "default resources");
427
428
429 /*---------- display ----------*/
430
431 static Window win;
432 static int width, height;
433 static Colormap cmap;
434 static unsigned long lastbackground;
435
436 typedef struct {
437   GC gc;
438   unsigned long lastfg;
439 } Gcstate;
440
441 #define C(c,u) static unsigned long pix_##c;
442 #define GC(g) static Gcstate gc_##g;
443   COLOURS
444 #undef C
445 #undef GC
446
447 static void refresh(void);
448
449 #define CHGMASK_CHG_DIS (1u<<CHGST_CHARGING | 1u<<CHGST_DISCHARGING)
450
451 static void failr(const char *m, int r) {
452   fprintf(stderr,"error: %s (code %d)\n", m, r);
453   exit(-1);
454 }
455
456 static void setbackground(unsigned long newbg) {
457   int r;
458   
459   if (newbg == lastbackground) return;
460   r= XSetWindowBackground(disp,win,newbg);
461   if (!r) fail("XSetWindowBackground");
462   lastbackground= newbg;
463 }
464
465 static void setforeground(Gcstate *g, unsigned long px) {
466   XGCValues gcv;
467   int r;
468   
469   if (g->lastfg == px) return;
470   
471   memset(&gcv,0,sizeof(gcv));
472   g->lastfg= gcv.foreground= px;
473   r= XChangeGC(disp,g->gc,GCForeground,&gcv);
474   if (!r) fail("XChangeGC");
475 }
476
477 static void show_solid(unsigned long px) {
478   setbackground(px);
479   XClearWindow(disp,win);
480 }
481
482 static void show(void) {
483   double elap, then;
484   int i, leftmost_lit, leftmost_nondeg, beyond, first_beyond;
485
486   if (!charging_state_mask)
487     return show_solid(pix_darkgreen);
488
489   if (charging_state_mask & CHGST_ERROR)
490     return show_solid(pix_yellow);
491
492   setbackground(pix_dimgrey);
493   XClearWindow(disp,win);
494   
495   setforeground(&gc_remain,
496                 !(charging_state_mask & CHGMASK_CHG_DIS) ? pix_cyan :
497                 !(~charging_state_mask & CHGMASK_CHG_DIS) ? pix_grey :
498                 charging_state_mask & (1u<<CHGST_CHARGING)
499                 ? pix_green : pix_blue);
500                 
501   setforeground(&gc_empty, alarm_level ? pix_red : pix_black);
502
503   for (i=0, first_beyond=1; i<height; i++) {
504     elap= !i ? 0 :
505       height==2 ? BOTTOM :
506       TOP * exp( (double)i / (height-2) * log( (double)BOTTOM/TOP ) );
507     
508     then= fill_norm + ratepersec_norm * elap;
509
510     beyond=
511       ((charging_state_mask & (1u<<CHGST_DISCHARGING) && then <= 0.0) ||
512        (charging_state_mask & (1u<<CHGST_CHARGING) && then>=nondegraded_norm));
513
514     if (then <= 0.0) then= 0.0;
515     else if (then >= nondegraded_norm) then= nondegraded_norm;
516
517     leftmost_lit= width * then;
518     leftmost_nondeg= width * nondegraded_norm;
519
520     if (beyond && first_beyond) {
521       XDrawLine(disp, win, gc_white.gc, 0,i, leftmost_nondeg,i);
522       first_beyond= 0;
523     } else {
524       if (leftmost_lit < leftmost_nondeg)
525         XDrawLine(disp, win, gc_empty.gc,
526                   leftmost_lit,i, leftmost_nondeg,i);
527       if (leftmost_lit >= 0)
528         XDrawLine(disp, win, gc_remain.gc, 0,i, leftmost_lit,i);
529     }
530   }
531 }
532
533 static void initgc(Gcstate *gc_r) {
534   XGCValues gcv;
535
536   memset(&gcv,0,sizeof(gcv));
537   gcv.function= GXcopy;
538   gcv.line_width= 1;
539   gc_r->lastfg= gcv.foreground= pix_white;
540   gc_r->gc= XCreateGC(disp,win, GCFunction|GCLineWidth|GCForeground, &gcv);
541 }
542
543 static void colour(unsigned long *pix_r, const char *whichcolour) {
544   XColor xc;
545   const char *name;
546   Status st;
547
548   name= getresource(whichcolour);
549   if (!name) fail("get colour resource");
550   
551   st= XAllocNamedColor(disp,cmap,name,&xc,&xc);
552   if (!st) fail(name);
553   
554   *pix_r= xc.pixel;
555 }
556
557 static void initgraphics(int argc, char **argv) {
558   int xwmgr, r;
559   const char *geom_string;
560   XSizeHints *normal_hints;
561   XWMHints *wm_hints;
562   XClassHint *class_hint;
563   int pos_x, pos_y, gravity;
564   char *program_name_silly;
565   
566   program_name_silly= (char*)program_name;
567
568   normal_hints= XAllocSizeHints();
569   wm_hints= XAllocWMHints();
570   class_hint= XAllocClassHint();
571
572   if (!normal_hints || !wm_hints || !class_hint)
573     fail("could not alloc hint(s)");
574
575   geom_string= getresource("geometry");
576
577   xwmgr= XWMGeometry(disp,screen, geom_string,"128x32", 0,
578                  normal_hints,
579                  &pos_x, &pos_y,
580                  &width, &height,
581                  &gravity);
582
583   win= XCreateSimpleWindow(disp,DefaultRootWindow(disp),
584                            pos_x,pos_y,width,height,0,0,0);
585   cmap= DefaultColormap(disp,screen);
586   
587 #define C(c,u) colour(&pix_##c, #u "Color");
588 #define GC(g) initgc(&gc_##g);
589   COLOURS
590 #undef C
591 #undef GC
592
593   r= XSetWindowBackground(disp,win,pix_dimgrey);
594   if (!r) fail("init set background");
595   lastbackground= pix_dimgrey;
596
597   normal_hints->flags= PWinGravity;
598   normal_hints->win_gravity= gravity;
599   normal_hints->x= pos_x;
600   normal_hints->y= pos_y;
601   normal_hints->width= width;
602   normal_hints->height= height;
603   if ((xwmgr & XValue) || (xwmgr & YValue))
604     normal_hints->flags |= USPosition;
605
606   wm_hints->flags= InputHint;
607   wm_hints->input= False;
608
609   class_hint->res_name= program_name_silly;
610   class_hint->res_class= program_name_silly;
611
612   XmbSetWMProperties(disp,win, program_name,program_name,
613                      argv,argc, normal_hints, wm_hints, class_hint);
614
615   XSelectInput(disp,win, ExposureMask|StructureNotifyMask);
616   XMapWindow(disp,win);
617 }
618  
619 static void refresh(void) {
620   acquiredata();
621   show();
622 }
623
624 static void newgeometry(void) {
625   int dummy;
626   Window dummyw;
627   
628   XGetGeometry(disp,win, &dummyw,&dummy,&dummy, &width,&height, &dummy,&dummy);
629 }
630
631 static void eventloop(void) {
632   XEvent ev;
633   struct pollfd pfd;
634   int r, timeout;
635   
636   newgeometry();
637   refresh();
638
639   for (;;) {
640     XFlush(disp);
641
642     pfd.fd= ConnectionNumber(disp);
643     pfd.events= POLLIN|POLLERR;
644
645     timeout= !(charging_state_mask & CHGST_ERROR) ? TIMEOUT : TIMEOUT_ONERROR;
646     r= poll(&pfd,1,timeout);
647     if (r==-1 && errno!=EINTR) failr("poll",errno);
648
649     while (XPending(disp)) {
650       XNextEvent(disp,&ev);
651       if (ev.type == ConfigureNotify) {
652         XConfigureEvent *ce= (void*)&ev;
653         width= ce->width;
654         height= ce->height;
655       }
656     }
657     refresh();
658   }
659 }
660
661 int main(int argc, char **argv) {
662   parseargs(argc,argv);
663   initacquire();
664   initgraphics(argc,argv);
665   eventloop();
666   return 0;
667 }