wmclockmon: rename `DFLAGS` `debug_CFLAGS`
[dockapps.git] / wmacpi / wmacpi.c
blobb6ec6c34d6816236daff82c8e44429a57a552a98
1 /* apm/acpi dockapp - phear it 1.34
2 * Copyright (C) 2000, 2001, 2002 timecop@japan.co.jp
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 * 02110-1301, USA.
20 #define _GNU_SOURCE
22 #include <libdockapp/dockapp.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <ctype.h>
28 #include <unistd.h>
29 #include <time.h>
31 #include <X11/X.h>
32 #include <X11/Xlib.h>
33 #include <X11/Xutil.h>
34 #include <X11/extensions/shape.h>
35 #include <X11/xpm.h>
37 #include "libacpi.h"
39 /* main pixmap */
40 #ifdef LOW_COLOR
41 #include "master_low.xpm"
42 static char **master_xpm = master_low_xpm;
43 #else
44 #include "master.xpm"
45 #endif
47 static int battery_no;
49 /* Do NOT change the BASE_PERIOD without reading the code ... */
50 #define BASE_PERIOD 100000 /* base period, 100 ms (in usecs) */
52 struct dockapp {
53 int x_fd; /* X11 fd */
54 Display *display; /* display */
55 Window win; /* main window */
56 Pixmap pixmap; /* main pixmap */
57 Pixmap mask; /* mask pixmap */
58 Pixmap text; /* pixmap for text scroller */
59 unsigned short width; /* width of pixmap */
60 unsigned short height; /* height of pixmap */
61 int screen; /* current screen */
62 int tw; /* text width inside text pixmap */
63 int update; /* need to redraw? */
64 int blink; /* should we blink the LED? (critical battery) */
65 int bell; /* bell on critical low, or not? */
66 int scroll; /* scroll message text? */
67 int scroll_reset; /* reset the scrolling text */
68 int percent;
69 int period_length; /* length of the polling period, multiple of BASE_PERIOD */
72 /* globals */
73 struct dockapp *dockapp;
74 /* global_t *globals; */
76 /* this gives us a variable scroll rate, depending on the importance of the
77 * message . . . */
78 #define DEFAULT_SCROLL_RESET 150;
79 int scroll_reset = DEFAULT_SCROLL_RESET;
81 /* copy a chunk of pixmap around the app */
82 static void copy_xpm_area(int x, int y, int w, int h, int dx, int dy)
84 XCopyArea(DADisplay, dockapp->pixmap, dockapp->pixmap,
85 DAGC, x, y, w, h, dx, dy);
86 dockapp->update = 1;
89 /* display AC power symbol */
90 static void display_power_glyph(void)
92 copy_xpm_area(67, 38, 12, 7, 6, 17);
95 /* get rid of AC power symbol */
96 static void kill_power_glyph(void)
98 copy_xpm_area(67, 48, 12, 7, 6, 17);
101 /* display battery symbol */
102 static void display_battery_glyph(void)
104 copy_xpm_area(82, 38, 12, 7, 20, 17);
107 /* get rid of battery symbol */
108 static void kill_battery_glyph(void)
110 copy_xpm_area(82, 48, 12, 7, 20, 17);
113 /* clear the time display */
114 static void clear_time_display(void)
116 copy_xpm_area(114, 76, 31, 11, 7, 32);
119 /* set time display to -- -- */
120 static void invalid_time_display(void)
122 copy_xpm_area(122, 14, 31, 11, 7, 32);
125 static void reset_scroll(void) {
126 dockapp->scroll_reset = 1;
129 static void clear_text_area(void) {
130 copy_xpm_area(66, 9, 52, 7, 6, 50);
133 static void redraw_window(void)
135 if (dockapp->update) {
136 XCopyArea(dockapp->display, dockapp->pixmap, dockapp->win,
137 DAGC, 0, 0, 64, 64, 0, 0);
138 dockapp->update = 0;
142 static void new_window(char *display, char *name, int argc, char **argv)
144 XSizeHints *hints;
146 /* Initialise the dockapp window and appicon */
147 DAOpenDisplay(display, argc, argv);
148 DACreateIcon(name, 64, 64, argc, argv);
149 dockapp->display = DADisplay;
150 dockapp->x_fd = XConnectionNumber(dockapp->display);
151 dockapp->win = DAWindow;
153 XSelectInput(dockapp->display, dockapp->win,
154 ExposureMask | ButtonPressMask | ButtonReleaseMask |
155 StructureNotifyMask);
157 /* create the main pixmap . . . */
158 DAMakePixmapFromData(master_xpm, &dockapp->pixmap, &dockapp->mask,
159 &dockapp->width, &dockapp->height);
160 DASetPixmap(dockapp->pixmap);
161 DASetShape(dockapp->mask);
163 /* text area is 318x7, or 53 characters long */
164 dockapp->text = XCreatePixmap(dockapp->display, dockapp->win, 318, 7,
165 DefaultDepth(dockapp->display,
166 dockapp->screen));
167 if (!dockapp->text) {
168 pfatal("FATAL: Cannot create text scroll pixmap!\n");
169 exit(1);
172 /* force the window to stay this size - otherwise the user could
173 * resize us and see our panties^Wmaster pixmap . . . */
174 hints = XAllocSizeHints();
175 if(hints) {
176 hints->flags |= PMinSize | PMaxSize;
177 hints->min_width = 64;
178 hints->max_width = 64;
179 hints->min_height = 64;
180 hints->max_height = 64;
181 XSetWMNormalHints(dockapp->display, dockapp->win, hints);
182 XFree(hints);
185 DAShow();
188 static void copy_to_text_buffer(int sx, int sy, int w, int h, int dx, int dy)
190 XCopyArea(dockapp->display, dockapp->pixmap, dockapp->text,
191 DAGC, sx, sy, w, h, dx, dy);
194 static void copy_to_text_area(int sx, int sy, int w, int h, int dx, int dy)
196 XCopyArea(dockapp->display, dockapp->text, dockapp->pixmap,
197 DAGC, sx, sy, w, h, dx, dy);
200 static void scroll_text(void)
202 static int start, end, stop;
203 int x = 6; /* x coord of the start of the text area */
204 int y = 50; /* y coord */
205 int width = 51; /* width of the text area */
206 int height = 7; /* height of the text area */
207 int tw = dockapp->tw; /* width of the rendered text */
208 int sx, dx, w;
210 if (!dockapp->scroll)
211 return;
214 * Conceptually this is viewing the text through a scrolling
215 * window - the window starts out with the end immediately before
216 * the text, and stops when the start of the window is immediately
217 * after the end of the text.
219 * We begin with the start of the window at pixel (0 - width) and
220 * as we scroll we render only the portion of the window above
221 * pixel 0. The destination of the copy during this period starts
222 * out at the end of the text area and works left as more of the
223 * text is being copied, until a full window is being copied.
225 * As the end of the window moves out past the end of the text, we
226 * want to keep the destination at the beginning of the text area,
227 * but copy a smaller and smaller chunk of the text. Eventually the
228 * start of the window will scroll past the end of the text, at
229 * which point we stop doing any work and wait to be reset.
232 if (dockapp->scroll_reset) {
233 start = 0 - width;
234 end = 0;
235 stop = 0;
236 clear_text_area();
237 dockapp->scroll_reset = 0;
240 if (stop)
241 return;
243 w = 52;
244 if (end < 52)
245 w = end;
246 else if (end > tw)
247 w = 52 - (end - tw);
249 dx = x + 52 - w;
250 if (end > tw)
251 dx = x;
253 sx = start;
254 if (start < 0)
255 sx = 0;
257 if (start > tw)
258 stop = 1;
260 clear_text_area();
261 copy_to_text_area(sx, 0, w, height, dx, y);
262 start += 2;
263 end += 2;
265 dockapp->update = 1;
268 static void render_text(char *string)
270 int i, c, k;
272 /* drop out immediately if scrolling is disabled - we don't render
273 * any text at all, since there's not much else we could do
274 * sensibly without scrolling. */
275 if (!dockapp->scroll)
276 return;
278 if (strlen(string) > 53)
279 return;
281 /* prepare the text area by clearing it */
282 for (i = 0; i < 54; i++) {
283 copy_to_text_buffer(133, 57, 6, 8, i * 6, 0);
285 k = 0;
287 for (i = 0; string[i]; i++) {
288 c = toupper(string[i]);
289 if (c >= 'A' && c <= 'Z') { /* letter */
290 c = c - 'A';
291 copy_to_text_buffer(c * 6, 67, 6, 7, k, 0);
292 } else if (c >= '0' && c <= '9') { /* number */
293 c = c - '0';
294 copy_to_text_buffer(c * 6 + 66, 58, 6, 7, k, 0);
295 } else if (c == '.') {
296 copy_to_text_buffer(140, 58, 6, 7, k, 0);
297 } else if (c == '-') {
298 copy_to_text_buffer(126, 58, 6, 7, k, 0);
300 k += 6;
302 dockapp->tw = k; /* length of text segment */
303 /* re-scroll the message */
304 reset_scroll();
305 scroll_text();
308 static void clear_percentage(void)
310 /* clear the number */
311 copy_xpm_area(95, 47, 21, 9, 37, 16);
312 /* clear the bar */
313 copy_xpm_area(66, 18, 54, 8, 5, 5);
315 dockapp->percent = -1;
318 static void display_percentage(int percent)
320 unsigned int bar;
321 int width = 54; /* width of the bar */
322 float ratio = 100.0/width; /* ratio between the current percentage
323 * and the number of pixels in the bar */
325 if (percent == -1)
326 percent = 0;
328 if (dockapp->percent == percent)
329 return;
331 if (percent < 0)
332 percent = 0;
333 if (percent > 100)
334 percent = 100;
336 if (dockapp->percent == -1)
337 copy_xpm_area(127, 28, 5, 7, 52, 17);
339 if (percent < 100) { /* 0 - 99 */
340 copy_xpm_area(95, 48, 8, 7, 37, 17);
341 if (percent >= 10)
342 copy_xpm_area((percent / 10) * 6 + 67, 28, 5, 7, 40, 17);
343 copy_xpm_area((percent % 10) * 6 + 67, 28, 5, 7, 46, 17);
344 } else
345 copy_xpm_area(95, 37, 21, 9, 37, 16); /* 100% */
346 dockapp->percent = percent;
348 bar = (int)((float)percent / ratio);
350 copy_xpm_area(66, 0, bar, 8, 5, 5);
351 if (bar < 54)
352 copy_xpm_area(66 + bar, 18, 54 - bar, 8, bar + 5, 5);
355 static void display_time(int minutes)
357 static int ohour = -1, omin = -1;
358 int hour, min, tmp;
360 if (minutes <= 0) { /* error - clear the display */
361 invalid_time_display();
362 ohour = omin = -1;
363 return;
366 /* render time on the display */
367 hour = minutes / 60;
368 /* our display area only fits %2d:%2d, so we need to make sure
369 * what we're displaying will fit in those constraints. I don't
370 * think we're likely to see any batteries that do more than
371 * 100 hours any time soon, so it's fairly safe. */
372 if (hour >= 100) {
373 hour = 99;
374 min = 59;
375 } else
376 min = minutes % 60;
378 if (hour == ohour && min == omin)
379 return;
381 tmp = hour / 10;
382 copy_xpm_area(tmp * 7 + 1, 76, 6, 11, 7, 32);
383 tmp = hour % 10;
384 copy_xpm_area(tmp * 7 + 1, 76, 6, 11, 14, 32);
385 tmp = min / 10;
386 copy_xpm_area(tmp * 7 + 1, 76, 6, 11, 25, 32);
387 tmp = min % 10;
388 copy_xpm_area(tmp * 7 + 1, 76, 6, 11, 32, 32);
389 copy_xpm_area(71, 76, 3, 11, 21, 32);
390 ohour = hour;
391 omin = min;
395 * The reworked state handling stuff.
398 /* set the current state of the power panel */
399 enum panel_states {
400 PS_AC,
401 PS_BATT,
402 PS_NULL,
405 static void really_blink_power_glyph(void)
407 static int counter = 0;
409 if (counter == 10)
410 display_power_glyph();
411 else if (counter == 20)
412 kill_power_glyph();
413 else if (counter > 30)
414 counter = 0;
416 counter += dockapp->period_length;
419 static void blink_power_glyph(void)
421 if (dockapp->blink)
422 really_blink_power_glyph();
425 static void really_blink_battery_glyph(void)
427 static int counter = 0;
429 if (counter == 10)
430 display_battery_glyph();
431 else if (counter == 20)
432 kill_battery_glyph();
433 else if (counter > 30)
434 counter = 0;
436 counter += dockapp->period_length;
439 static void blink_battery_glyph(void)
441 if (dockapp->blink)
442 really_blink_battery_glyph();
445 static void set_power_panel(global_t *globals)
447 static enum panel_states power = PS_NULL;
448 battery_t *binfo = globals->binfo;
449 adapter_t *ap = &globals->adapter;
451 if (ap->power == AC) {
452 if (power != PS_AC) {
453 power = PS_AC;
454 kill_battery_glyph();
455 display_power_glyph();
457 } else if (ap->power == BATT) {
458 if (power != PS_BATT) {
459 power = PS_BATT;
460 kill_power_glyph();
461 display_battery_glyph();
465 if (globals->battery_count > 0) {
466 if (binfo->charge_state == CHARGE)
467 blink_power_glyph();
469 if ((binfo->state == CRIT) && (ap->power == BATT))
470 blink_battery_glyph();
474 void scroll_faster(double factor) {
475 scroll_reset = scroll_reset * factor;
478 void scroll_slower(double factor) {
479 scroll_reset = scroll_reset * factor;
482 void reset_scroll_speed(void) {
483 scroll_reset = DEFAULT_SCROLL_RESET;
487 * The message that needs to be displayed needs to be decided
488 * according to a heirarchy: a message like not present needs to take
489 * precedence over a global thing like the current power status, and
490 * something like a low battery warning should take precedence over
491 * the "on battery" message. Likewise, a battery charging message
492 * needs to take precedence over the on ac power message. The other
493 * question is how much of a precedence local messages should take
494 * over global ones . . .
496 * So, there are three possible sets of messages: not present, on-line
497 * and off-line messages. We need to decide which of those sets is
498 * appropriate right now, and then decide within them.
500 enum messages {
501 M_NB, /* no batteries */
502 M_NP, /* not present */
503 M_AC, /* on ac power */
504 M_CH, /* battery charging */
505 M_BATT, /* on battery */
506 M_LB, /* low battery */
507 M_CB, /* critical low battery */
508 M_HCB, /* battery reported critical capacity state */
509 M_NULL, /* empty starting state */
512 static void set_message(global_t *globals)
514 static enum messages state = M_NULL;
515 battery_t *binfo = globals->binfo;
516 adapter_t *ap = &globals->adapter;
518 if (globals->battery_count == 0) {
519 if (state != M_NB) {
520 state = M_NB;
521 reset_scroll_speed();
522 render_text("no batteries");
525 return;
528 /* battery not present case */
529 if (!binfo->present) {
530 if (state != M_NP) {
531 state = M_NP;
532 reset_scroll_speed();
533 render_text("not present");
535 } else if (ap->power == AC) {
536 if (binfo->charge_state == CHARGE) {
537 if (state != M_CH) {
538 state = M_CH;
539 reset_scroll_speed();
540 render_text("battery charging");
542 } else {
543 if (state != M_AC) {
544 state = M_AC;
545 reset_scroll_speed();
546 render_text("on ac power");
549 } else {
550 if (binfo->state == CRIT) {
551 if (state != M_CB) {
552 state = M_CB;
553 scroll_faster(0.75);
554 render_text("critical low battery");
556 } else if (binfo->state == LOW) {
557 if (state != M_LB) {
558 state = M_LB;
559 scroll_faster(0.85);
560 render_text("low battery");
562 } else {
563 if (state != M_BATT) {
564 state = M_BATT;
565 reset_scroll_speed();
566 render_text("on battery");
572 void set_time_display(global_t *globals)
574 if (globals->battery_count == 0) {
575 invalid_time_display();
576 return;
579 if (globals->binfo->charge_state == CHARGE)
580 display_time(globals->binfo->charge_time);
581 else if (globals->binfo->charge_state == DISCHARGE)
582 display_time(globals->rtime);
583 else
584 invalid_time_display();
587 void clear_batt_id_area(void)
589 copy_xpm_area(125, 40, 7, 11, 51, 32);
592 void set_batt_id_area(int bno)
594 int w = 7; /* Width of the number */
595 int h = 11; /* Height of the number */
596 int dx = 50; /* x coord of the target area */
597 int dy = 32; /* y coord of the target area */
598 int sx = (bno + 1) * 7; /* source x coord */
599 int sy = 76; /* source y coord */
601 copy_xpm_area(sx, sy, w, h, dx, dy);
604 #define VERSION "wmacpi version " WMACPI_VER "\nUsing libacpi version " LIBACPI_VER
606 void cli_wmacpi(global_t *globals, int samples)
608 int i, j, sleep_time = 0;
609 battery_t *binfo;
610 adapter_t *ap;
612 pdebug("samples: %d\n", samples);
613 if(samples > 1)
614 sleep_time = 1000000/samples;
616 /* we want to acquire samples over some period of time, so . . . */
617 for(i = 0; i < samples + 2; i++) {
618 for(j = 0; j < globals->battery_count; j++)
619 acquire_batt_info(globals, j);
620 acquire_global_info(globals);
621 usleep(sleep_time);
624 ap = &globals->adapter;
625 if(ap->power == AC) {
626 printf("On AC Power");
627 for(i = 0; i < globals->battery_count; i++) {
628 binfo = &batteries[i];
629 if(binfo->present && (binfo->charge_state == CHARGE)) {
630 printf("; Battery %s charging", binfo->name);
631 printf(", currently at %2d%%", binfo->percentage);
632 if(binfo->charge_time >= 0)
633 printf(", %2d:%02d remaining",
634 binfo->charge_time/60,
635 binfo->charge_time%60);
638 printf("\n");
639 } else if(ap->power == BATT) {
640 printf("On Battery");
641 for(i = 0; i < globals->battery_count; i++) {
642 binfo = &batteries[i];
643 if(binfo->present && (binfo->percentage >= 0))
644 printf(", Battery %s at %d%%", binfo->name,
645 binfo->percentage);
647 if(globals->rtime >= 0)
648 printf("; %d:%02d remaining", globals->rtime/60,
649 globals->rtime%60);
650 printf("\n");
652 return;
655 battery_t *switch_battery(global_t *globals, int battno)
657 globals->binfo = &batteries[battno];
658 pinfo("changing to monitor battery %s\n", globals->binfo->name);
659 set_batt_id_area(battno);
660 dockapp->update = 1;
662 return globals->binfo;
666 int main(int argc, char **argv)
668 char *display = NULL;
669 int sample_count = 0;
670 int batt_reinit, ac_reinit;
671 int batt_count = 0;
672 int ac_count = 0;
673 int cli = 0, samples = 1, critical = 10;
674 int samplerate = 20;
675 int scroll_count = 0;
676 enum rtime_mode rt_mode = RT_RATE;
677 int rt_forced = 0;
678 battery_t *binfo = NULL;
679 global_t *globals;
681 fd_set fds;
682 struct timeval tv_rate;
683 struct timeval tv = {0, 0};
685 DAProgramOption options[] = {
686 {"-r", "--no-scroll", "disable scrolling message", DONone, False, {NULL}},
687 {"-n", "--no-blink", "disable blinking of various UI elements", DONone, False, {NULL}},
688 {"-x", "--cmdline", "run in command line mode", DONone, False, {NULL}},
689 {"-f", "--force-capacity-mode", "force the use of capacity mode for calculating time remaining", DONone, False, {NULL}},
690 {"-d", "--display", "display or remote display", DOString, False, {&display}},
691 {"-c", "--critical", "set critical low alarm at <number> percent\n (default: 10 percent)", DONatural, False, {&critical}},
692 {"-m", "--battery", "battery number to monitor", DONatural, False, {&battery_no}},
693 {"-s", "--sample-rate", "number of times per minute to sample battery information\n default 20 (once every three seconds)", DONatural, False, {&samplerate}},
694 {"-V", "--verbosity", "Set verbosity", DONatural, False, {&verbosity}},
695 {"-a", "--samples", "number of samples to average over (cli mode only)", DONatural, False, {&samples}},
698 dockapp = calloc(1, sizeof(struct dockapp));
699 globals = calloc(1, sizeof(global_t));
701 dockapp->blink = 1;
702 dockapp->bell = 0;
703 dockapp->scroll = 1;
704 dockapp->scroll_reset = 0;
705 globals->crit_level = 10;
706 battery_no = 1;
708 /* after this many samples, we reinit the battery and AC adapter
709 * information.
710 * XXX: make these configurable . . . */
711 batt_reinit = 100;
712 ac_reinit = 1000;
714 /* this needs to be up here because we need to know what batteries
715 * are available /before/ we can decide if the battery we want to
716 * monitor is available. */
717 /* parse command-line options */
718 DAParseArguments(argc, argv, options, 10,
719 "A battery monitor dockapp for ACPI based systems",
720 VERSION);
722 if (options[0].used)
723 dockapp->scroll = 0;
724 if (options[1].used)
725 dockapp->blink = 0;
726 if (options[2].used)
727 cli = 1;
728 if (options[3].used) {
729 rt_mode = RT_CAP;
730 rt_forced = 1;
733 if (samplerate == 0) samplerate = 1;
734 if (samplerate > 600) samplerate = 600;
736 /* convert to number of base periods */
737 samplerate = ((60 * 1000000) / samplerate) / BASE_PERIOD;
739 if (!dockapp->scroll) {
740 if (!dockapp->blink) {
741 /* Adapt the period to the sample rate */
742 tv_rate.tv_usec = samplerate * BASE_PERIOD;
744 tv_rate.tv_sec = tv_rate.tv_usec / 1000000;
745 tv_rate.tv_usec = tv_rate.tv_usec - (tv_rate.tv_sec * 1000000);
747 dockapp->period_length = samplerate;
748 } else {
749 /* blinking is every second */
750 tv_rate.tv_sec = 1; /* BASE_PERIOD * 10 = 1 sec */
751 tv_rate.tv_usec = 0;
753 dockapp->period_length = 10;
755 } else {
756 /* scrolling is every BASE_PERIOD (100 ms) */
757 tv_rate.tv_sec = 0;
758 tv_rate.tv_usec = BASE_PERIOD;
760 dockapp->period_length = 1;
763 if (critical > 100) {
764 fprintf(stderr, "Please use values between 0 and 100%%\n");
765 fprintf(stderr, "Using default value of 10%%\n");
766 critical = 10;
768 globals->crit_level = critical;
770 if (battery_no >= MAXBATT) {
771 fprintf(stderr, "Please specify a battery number below %d\n", MAXBATT);
772 return 1;
774 pinfo("Monitoring battery %d\n", battery_no);
776 if (power_init(globals))
777 /* power_init functions handle printing error messages */
778 exit(1);
780 globals->rt_mode = rt_mode;
781 globals->rt_forced = rt_forced;
783 if (battery_no > globals->battery_count) {
784 pinfo("Battery %d not available for monitoring.\n", battery_no);
787 /* check for cli mode */
788 if (cli) {
789 cli_wmacpi(globals, samples);
790 exit(0);
792 /* check to see if we've got a valid DISPLAY env variable, as a simple check to see if
793 * we're running under X */
794 if (!getenv("DISPLAY")) {
795 pdebug("Not running under X - using cli mode\n");
796 cli_wmacpi(globals, samples);
797 exit(0);
800 battery_no--;
802 /* make new dockapp window */
803 /* Don't even /think/ of asking me why, but if I set the window name to
804 * "acpi", the app refuses to dock properly - it's just plain /weird/.
805 * So, wmacpi it is . . . */
806 new_window(display, "wmacpi", argc, argv);
808 /* get initial statistics */
809 acquire_all_info(globals);
811 if (globals->battery_count > 0) {
812 binfo = &batteries[battery_no];
813 globals->binfo = binfo;
814 set_batt_id_area(battery_no);
815 pinfo("monitoring battery %s\n", binfo->name);
818 clear_time_display();
819 set_power_panel(globals);
820 set_message(globals);
822 /* main loop */
823 while (1) {
824 Atom atom;
825 Atom wmdelwin;
826 XEvent event;
827 while (XPending(dockapp->display)) {
828 XNextEvent(dockapp->display, &event);
829 switch (event.type) {
830 case Expose:
831 /* update */
832 dockapp->update = 1;
833 while (XCheckTypedEvent(dockapp->display, Expose, &event));
834 redraw_window();
835 break;
836 case DestroyNotify:
837 XCloseDisplay(dockapp->display);
838 exit(0);
839 break;
840 case ButtonPress:
841 break;
842 case ButtonRelease:
843 if (globals->battery_count == 0)
844 break;
846 /* cycle through the known batteries. */
847 battery_no++;
848 battery_no = battery_no % globals->battery_count;
850 binfo = switch_battery(globals, battery_no);
851 break;
852 case ClientMessage:
853 /* what /is/ this crap?
854 * Turns out that libdockapp adds the WM_DELETE_WINDOW atom to
855 * the WM_PROTOCOLS property for the window, which means that
856 * rather than get a simple DestroyNotify message, we get a
857 * nice little message from the WM saying "hey, can you delete
858 * yourself, pretty please?". So, when running as a window
859 * rather than an icon, we're impossible to kill in a friendly
860 * manner, because we're expecting to die from a DestroyNotify
861 * and thus blithely ignoring the WM knocking on our window
862 * border . . .
864 * This simply checks for that scenario - it may fail oddly if
865 * something else comes to us via a WM_PROTOCOLS ClientMessage
866 * event, but I suspect it's not going to be an issue. */
867 wmdelwin = XInternAtom(dockapp->display, "WM_DELETE_WINDOW", 1);
868 atom = event.xclient.data.l[0];
869 if (atom == wmdelwin) {
870 XCloseDisplay(dockapp->display);
871 exit(0);
873 break;
877 /* XXX: some laptops have problems with sampling the battery
878 * regularly - apparently, the BIOS disables interrupts while
879 * reading from the battery, which is generally on a slow bus
880 * and is a slow device, so you get significant periods without
881 * interrupts. This causes interactivity to suffer . . .
883 * So, the workaround/fix for this is to sample at a much
884 * lower rate than we may update/refresh/expose/whatever. The
885 * user specifies how many times they want us to sample per
886 * minute; we use select() on our X events fd to wake up when
887 * there's some display work to be done, with the timeout set
888 * to whatever the time between samples is. When we hit our
889 * select() timeout we update our samples, otherwise we update
890 * the display.
892 * Note that this has a wrinkle when blinking and/or scrolling
893 * is enabled, since we need to update the display more
894 * frequently than we sample (most likely). In that case we
895 * set the timeout based on the display update cycle. */
897 /* have we completed our timeout, or were we woken up early? */
898 if ((tv.tv_sec != 0) || (tv.tv_usec != 0))
899 goto win_update;
901 tv = tv_rate;
903 sample_count += dockapp->period_length;
905 if (sample_count >= samplerate) {
906 if (globals->battery_count == 0) {
907 batt_count = 0;
909 reinit_batteries(globals);
911 /* battery appeared */
912 if (globals->battery_count > 0) {
913 if (battery_no > globals->battery_count)
914 battery_no = 0;
916 binfo = switch_battery(globals, battery_no);
920 acquire_all_info(globals);
922 /* we need to be able to reinitialise batteries and adapters, because
923 * they change - you can hotplug batteries on most laptops these days
924 * and who knows what kind of shit will be happening soon . . . */
925 if (batt_count++ >= batt_reinit) {
926 if(reinit_batteries(globals))
927 pfatal("Oh my god, the batteries are gone!\n");
928 batt_count = 0;
931 if (ac_count++ >= ac_reinit) {
932 if(reinit_ac_adapters(globals))
933 pfatal("What happened to our AC adapters?!?\n");
934 ac_count = 0;
936 sample_count = 0;
939 if (scroll_count++ >= scroll_reset) {
940 reset_scroll();
941 scroll_count = 0;
944 /* The old code had some kind of weird crap with timers and the like.
945 * As far as I can tell, it's meaningless - the time we want to display
946 * is the time calculated from the remaining capacity, as per the
947 * ACPI spec. The only thing I'd change is the handling of a charging
948 * state: my best guess, based on the behaviour I'm seeing with my
949 * Lifebook, is that the present rate value when charging is the rate
950 * at which the batteries are being charged, which would mean I'd just
951 * need to reverse the rtime calculation to be able to work out how
952 * much time remained until the batteries were fully charged . . .
953 * That would be rather useful, though given it would vary rather a lot
954 * it seems likely that it'd be little more than a rough guesstimate. */
955 set_time_display(globals);
956 set_power_panel(globals);
957 set_message(globals);
959 if (globals->battery_count == 0) {
960 clear_percentage();
961 clear_batt_id_area();
962 } else
963 display_percentage(binfo->percentage);
965 scroll_text();
967 win_update:
968 /* redraw_window, if anything changed */
969 redraw_window();
971 FD_ZERO(&fds);
972 FD_SET(dockapp->x_fd, &fds);
973 select(FD_SETSIZE, &fds, NULL, NULL, &tv);
975 return 0;