Remove trailing whitespace.
[dockapps.git] / wmbattery / wmbattery.c
blobcecb90ef7f12cd652b9daac8cd7f64c6df9b5981
1 #include "config.h"
2 #include <stdio.h>
3 #include <unistd.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <X11/xpm.h>
7 #include <X11/extensions/shape.h>
8 #include <stdarg.h>
9 #include <signal.h>
10 #include <time.h>
11 #include <errno.h>
12 #include <fcntl.h>
13 #include <sys/types.h>
14 #include <sys/stat.h>
16 #ifdef HAVE_GETOPT_H
17 #include <getopt.h>
18 #endif
20 #include "wmbattery.h"
21 #include "mask.xbm"
22 #include "sonypi.h"
23 #include "acpi.h"
24 #ifdef HAL
25 #include "simplehal.h"
26 #endif
27 #ifdef UPOWER
28 #include "upower.h"
29 #endif
31 Pixmap images[NUM_IMAGES];
32 Window root, iconwin, win;
33 int screen;
34 XpmIcon icon;
35 Display *display;
36 GC NormalGC;
37 int pos[2] = {0, 0};
39 char *crit_audio_fn = NULL;
40 char *crit_audio;
41 int crit_audio_size;
43 int battnum = 1;
44 #ifdef HAL
45 int use_simplehal = 0;
46 #endif
47 #ifdef UPOWER
48 int use_upower = 0;
49 #endif
50 int use_sonypi = 0;
51 int use_acpi = 0;
52 int delay = 0;
53 int always_estimate_remaining = 0;
54 int granularity_estimate_remaining = 1;
55 int initial_state = WithdrawnState;
57 signed int low_pct = -1;
58 signed int critical_pct = -1;
60 void error(const char *fmt, ...) {
61 va_list arglist;
63 va_start(arglist, fmt);
64 fprintf(stderr, "Error: ");
65 vfprintf(stderr, fmt, arglist);
66 fprintf(stderr, "\n");
67 va_end(arglist);
69 exit(1);
72 int apm_change(apm_info *cur) {
73 static int ac_line_status = 0, battery_status = 0, battery_flags = 0,
74 battery_percentage = 0, battery_time = 0, using_minutes = 0;
76 int i = cur->ac_line_status == ac_line_status &&
77 cur->battery_status == battery_status &&
78 cur->battery_flags == battery_flags &&
79 cur->battery_percentage == battery_percentage &&
80 cur->battery_time == battery_time &&
81 cur->using_minutes == using_minutes;
83 ac_line_status = cur->ac_line_status;
84 battery_status = cur->battery_status;
85 battery_flags = cur->battery_flags;
86 battery_percentage = cur->battery_percentage;
87 battery_time = cur->battery_time;
88 using_minutes = cur->using_minutes;
90 return i;
93 /* Calculate battery estimate */
94 void estimate_timeleft(apm_info *cur_info) {
95 /* Time of the last estimate */
96 static time_t estimate_time = 0;
97 /* Estimated time left */
98 static time_t estimate = 0;
99 /* Time when we last noticed a battery level change */
100 static time_t battery_change_time = 0;
101 /* The previous estimation we had before the battery level changed */
102 static time_t prev_estimate = 0;
103 /* Percentage at the last estimate */
104 static short percent = 0;
105 /* Where we charging or discharging the last time we were called? */
106 static short was_charging = 1;
107 /* Have we made a guess lately? */
108 static short guessed_lately = 0;
110 time_t t;
111 int interval;
112 short is_charging = cur_info->battery_flags & BATTERY_FLAGS_CHARGING;
114 errno = 0;
115 if (time(&t) == ((time_t)-1) && errno != 0)
116 goto estim_values;
118 if ((
119 /* AC is on and battery is not charging anymore or ... */
120 (cur_info->ac_line_status == AC_LINE_STATUS_ON) && !is_charging
121 ) ||
123 /* ... the charging state has changed */
124 is_charging ^ was_charging
125 )) {
126 /* Reset counters */
127 battery_change_time = t;
128 estimate = -1;
129 guessed_lately = 0;
130 estimate_time = t;
131 prev_estimate = 0;
132 goto estim_values;
135 /* No change: decrease estimate */
136 if ((percent - cur_info->battery_percentage)
137 / granularity_estimate_remaining == 0) {
138 estimate -= t - estimate_time;
139 estimate_time = t;
140 if (guessed_lately && estimate < 0)
141 estimate = 0;
142 goto estim_values;
145 /* The battery level changed: calculate estimate based
146 * on change speed and previous estimate */
147 guessed_lately = 1;
148 estimate_time = t;
149 interval = estimate_time - battery_change_time;
150 prev_estimate = estimate;
151 battery_change_time = estimate_time;
152 estimate = (is_charging
153 ? (cur_info->battery_percentage - 100)
154 : cur_info->battery_percentage)
155 * interval / (percent - cur_info->battery_percentage);
156 if (prev_estimate > 0)
157 estimate = (estimate * 2 + prev_estimate) / 3;
159 estim_values:
160 percent = cur_info->battery_percentage;
161 was_charging = is_charging;
162 cur_info->battery_time = estimate;
163 if (estimate < 0)
164 estimate = 0;
165 cur_info->using_minutes = 0;
168 /* Load up the images this program uses. */
169 void load_images() {
170 int x;
171 char fn[128]; /* enough? */
173 for(x=0; x < NUM_IMAGES; x++) {
174 sprintf(fn, "%s/%s.xpm", ICONDIR, image_info[x].filename);
175 if (XpmReadFileToPixmap(display, root, fn, &images[x], NULL, NULL)) {
176 /* Check in current direcotry for fallback. */
177 sprintf(fn, "%s.xpm", image_info[x].filename);
178 if (XpmReadFileToPixmap(display, root, fn, &images[x], NULL, NULL)) {
179 error("Failed to load %s\n",fn);
185 void load_audio() {
186 int fd;
187 struct stat s;
189 crit_audio = NULL;
190 if (crit_audio_fn == NULL) {
191 return;
193 fd = open(crit_audio_fn, 0);
194 if (fd == -1) {
195 error("unable to open audio file");
197 if (fstat(fd, &s) == 0) {
198 crit_audio_size = s.st_size;
199 crit_audio = malloc(crit_audio_size);
200 /* XXX: make this more robust? (loop?) */
201 if (read(fd, crit_audio, crit_audio_size) != crit_audio_size) {
202 free(crit_audio);
203 crit_audio = NULL;
204 error("unable to read audio file");
207 close(fd);
210 /* Returns the display to run on (or NULL for default). */
211 char *parse_commandline(int argc, char *argv[]) {
212 int c=0;
213 char *ret=NULL;
214 char *s;
215 extern char *optarg;
217 while (c != -1) {
218 c=getopt(argc, argv, "hd:g:if:b:w:c:l:es:a:");
219 switch (c) {
220 case 'h':
221 printf("Usage: wmbattery [options]\n");
222 printf("\t-d <display>\tselects target display\n");
223 printf("\t-h\t\tdisplay this help\n");
224 printf("\t-g +x+y\t\tposition of the window\n");
225 printf("\t-i start \n");
226 printf("\t-b num\t\tnumber of battery to display\n");
227 printf("\t-w secs\t\tseconds between updates\n");
228 printf("\t-l percent\tlow percentage\n");
229 printf("\t-c percent\tcritical percentage\n");
230 printf("\t-e\t\tuse own time estimates\n");
231 printf("\t-s granularity\tignore fluctuations less than granularity%% (implies -e)\n");
232 printf("\t-a file\t\twhen critical send file to /dev/audio\n");
233 exit(0);
234 break;
235 case 'd':
236 ret=strdup(optarg);
237 break;
238 case 'g':
239 s = strtok(optarg, "+");
240 if (s) {
241 pos[0]=atoi(s);
242 if ((s = strtok(NULL, "+")) != NULL) {
243 pos[1]=atoi(s);
245 else {
246 pos[0]=0;
249 break;
250 case 'i':
251 initial_state = IconicState;
252 break;
253 case 'b':
254 battnum = atoi(optarg);
255 break;
256 case 'w':
257 delay = atoi(optarg);
258 break;
259 case 'l':
260 low_pct = atoi(optarg);
261 break;
262 case 'c':
263 critical_pct = atoi(optarg);
264 break;
265 case 'e':
266 always_estimate_remaining = 1;
267 break;
268 case 's':
269 always_estimate_remaining = 1;
270 granularity_estimate_remaining = atoi(optarg);
271 break;
272 case 'a':
273 crit_audio_fn = strdup(optarg);
274 break;
278 return ret;
281 /* Sets up the window and icon and all the nasty X stuff. */
282 void make_window(char *display_name, int argc, char *argv[]) {
283 XClassHint classhint;
284 char *wname = argv[0];
285 XTextProperty name;
286 XGCValues gcv;
287 int dummy=0, borderwidth = 1;
288 XSizeHints sizehints;
289 XWMHints wmhints;
290 Pixel back_pix, fore_pix;
291 Pixmap pixmask;
293 if (!(display = XOpenDisplay(display_name)))
294 error("can't open display %s",XDisplayName(display_name));
296 screen=DefaultScreen(display);
297 root=RootWindow(display, screen);
299 /* Create window. */
300 sizehints.flags = USSize | USPosition;
301 sizehints.x = 0;
302 sizehints.y = 0;
303 XWMGeometry(display, screen, "", NULL, borderwidth,
304 &sizehints, &sizehints.x, &sizehints.y,
305 &sizehints.width, &sizehints.height, &dummy);
307 sizehints.width = 64;
308 sizehints.height = 64;
309 sizehints.x = pos[0];
310 sizehints.y = pos[1];
311 back_pix = WhitePixel(display, screen);
312 fore_pix = BlackPixel(display, screen);
313 win = XCreateSimpleWindow(display, root, sizehints.x, sizehints.y,
314 sizehints.width, sizehints.height,
315 borderwidth, fore_pix, back_pix);
316 iconwin = XCreateSimpleWindow(display, win, sizehints.x,
317 sizehints.y, sizehints.width,
318 sizehints.height, borderwidth,
319 fore_pix, back_pix);
321 /* Activate hints */
322 XSetWMNormalHints(display, win, &sizehints);
323 classhint.res_name = wname;
324 classhint.res_class = wname;
325 XSetClassHint(display, win, &classhint);
327 if (! XStringListToTextProperty(&wname, 1, &name))
328 error("Can't allocate window name.");
330 XSetWMName(display, win, &name);
332 /* Create GC for drawing */
333 gcv.foreground = fore_pix;
334 gcv.background = back_pix;
335 gcv.graphics_exposures = 0;
336 NormalGC = XCreateGC(display, root,
337 GCForeground | GCBackground | GCGraphicsExposures,
338 &gcv);
340 pixmask = XCreateBitmapFromData(display, win, mask_bits,
341 mask_width,mask_height);
342 XShapeCombineMask(display, win, ShapeBounding, 0, 0,
343 pixmask, ShapeSet);
344 XShapeCombineMask(display, iconwin, ShapeBounding, 0, 0,
345 pixmask, ShapeSet);
347 wmhints.initial_state = initial_state;
348 wmhints.icon_window = iconwin;
349 wmhints.icon_x = sizehints.x;
350 wmhints.icon_y = sizehints.y;
351 wmhints.window_group = win;
352 wmhints.flags = StateHint | IconWindowHint |
353 IconPositionHint | WindowGroupHint;
355 XSetWMHints(display, win, &wmhints);
356 XSetCommand(display, win, argv, argc);
358 XSelectInput(display, iconwin, ExposureMask);
359 XSelectInput(display, win, ExposureMask);
361 XMapWindow(display, win);
364 void flush_expose(Window w) {
365 XEvent dummy;
367 while (XCheckTypedWindowEvent(display, w, Expose, &dummy));
370 void redraw_window() {
371 XCopyArea(display, images[FACE], iconwin, NormalGC, 0, 0,
372 image_info[FACE].width, image_info[FACE].height, 0,0);
373 flush_expose(iconwin);
374 XCopyArea(display, images[FACE], win, NormalGC, 0, 0,
375 image_info[FACE].width, image_info[FACE].height, 0,0);
376 flush_expose(win);
380 * Display an image, using XCopyArea. Can display only part of an image,
381 * located anywhere.
383 void copy_image(int image, int xoffset, int yoffset,
384 int width, int height, int x, int y) {
385 XCopyArea(display, images[image], images[FACE], NormalGC,
386 xoffset, yoffset, width, height, x, y);
390 * Display a letter in one of two fonts, at the specified x position.
391 * Note that 10 is passed for special characters `:' or `1' at the
392 * end of the font.
394 void draw_letter(int letter, int font, int x) {
395 copy_image(font, image_info[font].charwidth * letter, 0,
396 image_info[font].charwidth, image_info[font].height,
397 x, image_info[font].y);
400 /* Display an image at its normal location. */
401 void draw_image(int image) {
402 copy_image(image, 0, 0,
403 image_info[image].width, image_info[image].height,
404 image_info[image].x, image_info[image].y);
407 void recalc_window(apm_info cur_info) {
408 int time_left, hour_left, min_left, digit, x;
409 static int blinked = 0;
411 /* Display if it's plugged in. */
412 switch (cur_info.ac_line_status) {
413 case AC_LINE_STATUS_ON:
414 draw_image(PLUGGED);
415 break;
416 default:
417 draw_image(UNPLUGGED);
420 /* Display the appropriate color battery. */
421 switch (cur_info.battery_status) {
422 case BATTERY_STATUS_HIGH:
423 case BATTERY_STATUS_CHARGING:
424 draw_image(BATTERY_HIGH);
425 break;
426 case BATTERY_STATUS_LOW:
427 draw_image(BATTERY_LOW);
428 break;
429 case BATTERY_STATUS_CRITICAL: /* blinking red battery */
430 if (blinked)
431 draw_image(BATTERY_CRITICAL);
432 else
433 draw_image(BATTERY_BLINK);
434 blinked=!blinked;
435 break;
436 default:
437 draw_image(BATTERY_NONE);
440 /* Show if the battery is charging. */
441 if (cur_info.battery_flags & BATTERY_FLAGS_CHARGING) {
442 draw_image(CHARGING);
444 else {
445 draw_image(NOCHARGING);
449 * Display the percent left dial. This has the side effect of
450 * clearing the time left field.
452 x=DIAL_MULTIPLIER * cur_info.battery_percentage;
453 if (x >= 0) {
454 /* Start by displaying bright on the dial. */
455 copy_image(DIAL_BRIGHT, 0, 0,
456 x, image_info[DIAL_BRIGHT].height,
457 image_info[DIAL_BRIGHT].x,
458 image_info[DIAL_BRIGHT].y);
460 /* Now display dim on the remainder of the dial. */
461 copy_image(DIAL_DIM, x, 0,
462 image_info[DIAL_DIM].width - x,
463 image_info[DIAL_DIM].height,
464 image_info[DIAL_DIM].x + x,
465 image_info[DIAL_DIM].y);
467 /* Show percent remaining */
468 if (cur_info.battery_percentage >= 0) {
469 digit = cur_info.battery_percentage / 10;
470 if (digit == 10) {
471 /* 11 is the `1' for the hundreds place. */
472 draw_letter(11,SMALLFONT,HUNDREDS_OFFSET);
473 digit=0;
475 draw_letter(digit,SMALLFONT,TENS_OFFSET);
476 digit = cur_info.battery_percentage % 10;
477 draw_letter(digit,SMALLFONT,ONES_OFFSET);
479 else {
480 /* There is no battery, so we need to dim out the
481 * percent sign that is normally bright. */
482 draw_letter(10,SMALLFONT,PERCENT_OFFSET);
485 /* Show time left */
487 /* A negative number means that it is unknown. Dim the field. */
488 if (cur_info.battery_time < 0) {
489 draw_letter(10, BIGFONT, COLON_OFFSET);
490 redraw_window();
491 return;
494 if (cur_info.using_minutes)
495 time_left = cur_info.battery_time;
496 else
497 time_left = cur_info.battery_time / 60;
498 hour_left = time_left / 60;
499 min_left = time_left % 60;
500 digit = hour_left / 10;
501 draw_letter(digit,BIGFONT,HOURS_TENS_OFFSET);
502 digit = hour_left % 10;
503 draw_letter(digit,BIGFONT,HOURS_ONES_OFFSET);
504 digit = min_left / 10;
505 draw_letter(digit,BIGFONT,MINUTES_TENS_OFFSET);
506 digit = min_left % 10;
507 draw_letter(digit,BIGFONT,MINUTES_ONES_OFFSET);
509 redraw_window();
512 void snd_crit() {
513 int audio, n;
515 if (crit_audio) {
516 audio = open("/dev/audio", O_WRONLY);
517 if (audio >= 0) {
518 n = write(audio, crit_audio, crit_audio_size);
519 if (n != crit_audio_size) {
520 fprintf(stderr, "write failed (%d/%d bytes)\n", n, crit_audio_size);
522 close(audio);
527 void alarmhandler(int sig) {
528 apm_info cur_info;
529 int old_status;
531 #ifdef UPOWER
532 if (use_upower) {
533 if (upower_read(1, &cur_info) != 0)
534 error("Cannot read upower information.");
536 else if (use_acpi) {
537 #else
538 if (use_acpi) {
539 #endif
540 if (acpi_read(battnum, &cur_info) != 0)
541 error("Cannot read ACPI information.");
543 #ifdef HAL
544 else if (use_simplehal) {
545 if (simplehal_read(battnum, &cur_info) != 0)
546 error("Cannot read HAL information.");
548 #endif
549 else if (! use_sonypi) {
550 if (apm_read(&cur_info) != 0)
551 error("Cannot read APM information.");
553 else {
554 if (sonypi_read(&cur_info) != 0)
555 error("Cannot read sonypi information.");
558 old_status = cur_info.battery_status;
560 /* Always calculate remaining lifetime? apm and acpi both use a
561 * negative number here to indicate error, missing battery, or
562 * cannot determine time. */
563 if (always_estimate_remaining || cur_info.battery_time < 0)
564 estimate_timeleft(&cur_info);
566 /* Override the battery status? */
567 if ((low_pct > -1 || critical_pct > -1) &&
568 cur_info.ac_line_status != AC_LINE_STATUS_ON) {
569 if (cur_info.battery_percentage <= critical_pct)
570 cur_info.battery_status = BATTERY_STATUS_CRITICAL;
571 else if (cur_info.battery_percentage <= low_pct)
572 cur_info.battery_status = BATTERY_STATUS_LOW;
573 else
574 cur_info.battery_status = BATTERY_STATUS_HIGH;
577 /* If APM data changes redraw and wait for next update */
578 /* Always redraw if the status is critical, to make it blink. */
579 if (!apm_change(&cur_info) || cur_info.battery_status == BATTERY_STATUS_CRITICAL)
580 recalc_window(cur_info);
582 if ((old_status == BATTERY_STATUS_HIGH) &&
583 (cur_info.battery_status == BATTERY_STATUS_LOW)) {
584 snd_crit();
586 else if (cur_info.battery_status == BATTERY_STATUS_CRITICAL) {
587 snd_crit();
590 alarm(delay);
593 void check_battery_num(int real, int requested) {
594 if (requested > real || requested < 1) {
595 error("There %s only %i batter%s, and you asked for number %i.",
596 real == 1 ? "is" : "are",
597 real,
598 real == 1 ? "y" : "ies",
599 requested);
603 int main(int argc, char *argv[]) {
604 make_window(parse_commandline(argc, argv), argc ,argv);
606 /* Check for APM support (returns 0 on success). */
607 if (apm_exists() == 0) {
608 if (! delay)
609 delay = 1;
611 #ifdef HAL
612 /* Check for hal support. */
613 else if (simplehal_supported()) {
614 use_simplehal = 1;
615 if (! delay)
616 delay = 2;
618 #endif
619 #ifdef UPOWER
620 else if (upower_supported()) {
621 use_upower = 1;
623 #endif
624 /* Check for ACPI support. */
625 else if (acpi_supported() && acpi_batt_count > 0) {
626 check_battery_num(acpi_batt_count, battnum);
627 use_acpi = 1;
628 if (! delay)
629 delay = 3; /* slow interface! */
631 else if (sonypi_supported()) {
632 use_sonypi = 1;
633 low_pct = 10;
634 critical_pct = 5;
635 if (! delay)
636 delay = 1;
638 else {
639 error("No APM, ACPI, UPOWER, HAL or SPIC support detected.");
642 load_images();
643 load_audio();
645 signal(SIGALRM, alarmhandler);
646 alarmhandler(SIGALRM);
648 while (1) {
649 XEvent ev;
650 XNextEvent(display, &ev);
651 if (ev.type == Expose)
652 redraw_window();