wmbattery: Bump to version 2.48.
[dockapps.git] / wmacpi / wmacpi.c
blob18427321c0c5d8690789853fadcfa59e06ba7bdd
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 <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"
38 #include "wmacpi.h"
40 #define WMACPI_VER "2.3"
42 /* main pixmap */
43 #ifdef LOW_COLOR
44 #include "master_low.xpm"
45 static char **master_xpm = master_low_xpm;
46 #else
47 #include "master.xpm"
48 #endif
50 /* Do NOT change the BASE_PERIOD without reading the code ... */
51 #define BASE_PERIOD 100000 /* base period, 100 ms (in usecs) */
53 struct dockapp {
54 int x_fd; /* X11 fd */
55 Display *display; /* display */
56 Window win; /* main window */
57 Pixmap pixmap; /* main pixmap */
58 Pixmap mask; /* mask pixmap */
59 Pixmap text; /* pixmap for text scroller */
60 unsigned short width; /* width of pixmap */
61 unsigned short height; /* height of pixmap */
62 int screen; /* current screen */
63 int tw; /* text width inside text pixmap */
64 int update; /* need to redraw? */
65 int blink; /* should we blink the LED? (critical battery) */
66 int bell; /* bell on critical low, or not? */
67 int scroll; /* scroll message text? */
68 int scroll_reset; /* reset the scrolling text */
69 int percent;
70 int period_length; /* length of the polling period, multiple of BASE_PERIOD */
73 /* globals */
74 struct dockapp *dockapp;
75 /* global_t *globals; */
77 /* this gives us a variable scroll rate, depending on the importance of the
78 * message . . . */
79 #define DEFAULT_SCROLL_RESET 150;
80 int scroll_reset = DEFAULT_SCROLL_RESET;
82 /* copy a chunk of pixmap around the app */
83 static void copy_xpm_area(int x, int y, int w, int h, int dx, int dy)
85 XCopyArea(DADisplay, dockapp->pixmap, dockapp->pixmap,
86 DAGC, x, y, w, h, dx, dy);
87 dockapp->update = 1;
90 /* display AC power symbol */
91 static void display_power_glyph(void)
93 copy_xpm_area(67, 38, 12, 7, 6, 17);
96 /* get rid of AC power symbol */
97 static void kill_power_glyph(void)
99 copy_xpm_area(67, 48, 12, 7, 6, 17);
102 /* display battery symbol */
103 static void display_battery_glyph(void)
105 copy_xpm_area(82, 38, 12, 7, 20, 17);
108 /* get rid of battery symbol */
109 static void kill_battery_glyph(void)
111 copy_xpm_area(82, 48, 12, 7, 20, 17);
114 /* clear the time display */
115 static void clear_time_display(void)
117 copy_xpm_area(114, 76, 31, 11, 7, 32);
120 /* set time display to -- -- */
121 static void invalid_time_display(void)
123 copy_xpm_area(122, 14, 31, 11, 7, 32);
126 static void reset_scroll(void) {
127 dockapp->scroll_reset = 1;
130 static void clear_text_area(void) {
131 copy_xpm_area(66, 9, 52, 7, 6, 50);
134 static void redraw_window(void)
136 if (dockapp->update) {
137 XCopyArea(dockapp->display, dockapp->pixmap, dockapp->win,
138 DAGC, 0, 0, 64, 64, 0, 0);
139 dockapp->update = 0;
143 static void new_window(char *display, char *name, int argc, char **argv)
145 XSizeHints *hints;
147 /* Initialise the dockapp window and appicon */
148 DAOpenDisplay(display, argc, argv);
149 DACreateIcon(name, 64, 64, argc, argv);
150 dockapp->display = DADisplay;
151 dockapp->x_fd = XConnectionNumber(dockapp->display);
152 dockapp->win = DAWindow;
154 XSelectInput(dockapp->display, dockapp->win,
155 ExposureMask | ButtonPressMask | ButtonReleaseMask |
156 StructureNotifyMask);
158 /* create the main pixmap . . . */
159 DAMakePixmapFromData(master_xpm, &dockapp->pixmap, &dockapp->mask,
160 &dockapp->width, &dockapp->height);
161 DASetPixmap(dockapp->pixmap);
162 DASetShape(dockapp->mask);
164 /* text area is 318x7, or 53 characters long */
165 dockapp->text = XCreatePixmap(dockapp->display, dockapp->win, 318, 7,
166 DefaultDepth(dockapp->display,
167 dockapp->screen));
168 if (!dockapp->text) {
169 pfatal("FATAL: Cannot create text scroll pixmap!\n");
170 exit(1);
173 /* force the window to stay this size - otherwise the user could
174 * resize us and see our panties^Wmaster pixmap . . . */
175 hints = XAllocSizeHints();
176 if(hints) {
177 hints->flags |= PMinSize | PMaxSize;
178 hints->min_width = 64;
179 hints->max_width = 64;
180 hints->min_height = 64;
181 hints->max_height = 64;
182 XSetWMNormalHints(dockapp->display, dockapp->win, hints);
183 XFree(hints);
186 DAShow();
189 static void copy_to_text_buffer(int sx, int sy, int w, int h, int dx, int dy)
191 XCopyArea(dockapp->display, dockapp->pixmap, dockapp->text,
192 DAGC, sx, sy, w, h, dx, dy);
195 static void copy_to_text_area(int sx, int sy, int w, int h, int dx, int dy)
197 XCopyArea(dockapp->display, dockapp->text, dockapp->pixmap,
198 DAGC, sx, sy, w, h, dx, dy);
201 static void scroll_text(void)
203 static int start, end, stop;
204 int x = 6; /* x coord of the start of the text area */
205 int y = 50; /* y coord */
206 int width = 51; /* width of the text area */
207 int height = 7; /* height of the text area */
208 int tw = dockapp->tw; /* width of the rendered text */
209 int sx, dx, w;
211 if (!dockapp->scroll)
212 return;
215 * Conceptually this is viewing the text through a scrolling
216 * window - the window starts out with the end immediately before
217 * the text, and stops when the start of the window is immediately
218 * after the end of the text.
220 * We begin with the start of the window at pixel (0 - width) and
221 * as we scroll we render only the portion of the window above
222 * pixel 0. The destination of the copy during this period starts
223 * out at the end of the text area and works left as more of the
224 * text is being copied, until a full window is being copied.
226 * As the end of the window moves out past the end of the text, we
227 * want to keep the destination at the beginning of the text area,
228 * but copy a smaller and smaller chunk of the text. Eventually the
229 * start of the window will scroll past the end of the text, at
230 * which point we stop doing any work and wait to be reset.
233 if (dockapp->scroll_reset) {
234 start = 0 - width;
235 end = 0;
236 stop = 0;
237 clear_text_area();
238 dockapp->scroll_reset = 0;
241 if (stop)
242 return;
244 w = 52;
245 if (end < 52)
246 w = end;
247 else if (end > tw)
248 w = 52 - (end - tw);
250 dx = x + 52 - w;
251 if (end > tw)
252 dx = x;
254 sx = start;
255 if (start < 0)
256 sx = 0;
258 if (start > tw)
259 stop = 1;
261 clear_text_area();
262 copy_to_text_area(sx, 0, w, height, dx, y);
263 start += 2;
264 end += 2;
266 dockapp->update = 1;
269 static void render_text(char *string)
271 int i, c, k;
273 /* drop out immediately if scrolling is disabled - we don't render
274 * any text at all, since there's not much else we could do
275 * sensibly without scrolling. */
276 if (!dockapp->scroll)
277 return;
279 if (strlen(string) > 53)
280 return;
282 /* prepare the text area by clearing it */
283 for (i = 0; i < 54; i++) {
284 copy_to_text_buffer(133, 57, 6, 8, i * 6, 0);
286 k = 0;
288 for (i = 0; string[i]; i++) {
289 c = toupper(string[i]);
290 if (c >= 'A' && c <= 'Z') { /* letter */
291 c = c - 'A';
292 copy_to_text_buffer(c * 6, 67, 6, 7, k, 0);
293 } else if (c >= '0' && c <= '9') { /* number */
294 c = c - '0';
295 copy_to_text_buffer(c * 6 + 66, 58, 6, 7, k, 0);
296 } else if (c == '.') {
297 copy_to_text_buffer(140, 58, 6, 7, k, 0);
298 } else if (c == '-') {
299 copy_to_text_buffer(126, 58, 6, 7, k, 0);
301 k += 6;
303 dockapp->tw = k; /* length of text segment */
304 /* re-scroll the message */
305 reset_scroll();
306 scroll_text();
309 static void clear_percentage(void)
311 /* clear the number */
312 copy_xpm_area(95, 47, 21, 9, 37, 16);
313 /* clear the bar */
314 copy_xpm_area(66, 18, 54, 8, 5, 5);
316 dockapp->percent = -1;
319 static void display_percentage(int percent)
321 unsigned int bar;
322 int width = 54; /* width of the bar */
323 float ratio = 100.0/width; /* ratio between the current percentage
324 * and the number of pixels in the bar */
326 if (percent == -1)
327 percent = 0;
329 if (dockapp->percent == percent)
330 return;
332 if (percent < 0)
333 percent = 0;
334 if (percent > 100)
335 percent = 100;
337 if (dockapp->percent == -1)
338 copy_xpm_area(127, 28, 5, 7, 52, 17);
340 if (percent < 100) { /* 0 - 99 */
341 copy_xpm_area(95, 48, 8, 7, 37, 17);
342 if (percent >= 10)
343 copy_xpm_area((percent / 10) * 6 + 67, 28, 5, 7, 40, 17);
344 copy_xpm_area((percent % 10) * 6 + 67, 28, 5, 7, 46, 17);
345 } else
346 copy_xpm_area(95, 37, 21, 9, 37, 16); /* 100% */
347 dockapp->percent = percent;
349 bar = (int)((float)percent / ratio);
351 copy_xpm_area(66, 0, bar, 8, 5, 5);
352 if (bar < 54)
353 copy_xpm_area(66 + bar, 18, 54 - bar, 8, bar + 5, 5);
356 static void display_time(int minutes)
358 static int ohour = -1, omin = -1;
359 int hour, min, tmp;
361 if (minutes <= 0) { /* error - clear the display */
362 invalid_time_display();
363 ohour = omin = -1;
364 return;
367 /* render time on the display */
368 hour = minutes / 60;
369 /* our display area only fits %2d:%2d, so we need to make sure
370 * what we're displaying will fit in those constraints. I don't
371 * think we're likely to see any batteries that do more than
372 * 100 hours any time soon, so it's fairly safe. */
373 if (hour >= 100) {
374 hour = 99;
375 min = 59;
376 } else
377 min = minutes % 60;
379 if (hour == ohour && min == omin)
380 return;
382 tmp = hour / 10;
383 copy_xpm_area(tmp * 7 + 1, 76, 6, 11, 7, 32);
384 tmp = hour % 10;
385 copy_xpm_area(tmp * 7 + 1, 76, 6, 11, 14, 32);
386 tmp = min / 10;
387 copy_xpm_area(tmp * 7 + 1, 76, 6, 11, 25, 32);
388 tmp = min % 10;
389 copy_xpm_area(tmp * 7 + 1, 76, 6, 11, 32, 32);
390 copy_xpm_area(71, 76, 3, 11, 21, 32);
391 ohour = hour;
392 omin = min;
396 * The reworked state handling stuff.
399 /* set the current state of the power panel */
400 enum panel_states {
401 PS_AC,
402 PS_BATT,
403 PS_NULL,
406 static void really_blink_power_glyph(void)
408 static int counter = 0;
410 if (counter == 10)
411 display_power_glyph();
412 else if (counter == 20)
413 kill_power_glyph();
414 else if (counter > 30)
415 counter = 0;
417 counter += dockapp->period_length;
420 static void blink_power_glyph(void)
422 if (dockapp->blink)
423 really_blink_power_glyph();
426 static void really_blink_battery_glyph(void)
428 static int counter = 0;
430 if (counter == 10)
431 display_battery_glyph();
432 else if (counter == 20)
433 kill_battery_glyph();
434 else if (counter > 30)
435 counter = 0;
437 counter += dockapp->period_length;
440 static void blink_battery_glyph(void)
442 if (dockapp->blink)
443 really_blink_battery_glyph();
446 static void set_power_panel(global_t *globals)
448 static enum panel_states power = PS_NULL;
449 battery_t *binfo = globals->binfo;
450 adapter_t *ap = &globals->adapter;
452 if (ap->power == AC) {
453 if (power != PS_AC) {
454 power = PS_AC;
455 kill_battery_glyph();
456 display_power_glyph();
458 } else if (ap->power == BATT) {
459 if (power != PS_BATT) {
460 power = PS_BATT;
461 kill_power_glyph();
462 display_battery_glyph();
466 if (globals->battery_count > 0) {
467 if (binfo->charge_state == CHARGE)
468 blink_power_glyph();
470 if ((binfo->state == CRIT) && (ap->power == BATT))
471 blink_battery_glyph();
475 void scroll_faster(double factor) {
476 scroll_reset = scroll_reset * factor;
479 void scroll_slower(double factor) {
480 scroll_reset = scroll_reset * factor;
483 void reset_scroll_speed(void) {
484 scroll_reset = DEFAULT_SCROLL_RESET;
488 * The message that needs to be displayed needs to be decided
489 * according to a heirarchy: a message like not present needs to take
490 * precedence over a global thing like the current power status, and
491 * something like a low battery warning should take precedence over
492 * the "on battery" message. Likewise, a battery charging message
493 * needs to take precedence over the on ac power message. The other
494 * question is how much of a precedence local messages should take
495 * over global ones . . .
497 * So, there are three possible sets of messages: not present, on-line
498 * and off-line messages. We need to decide which of those sets is
499 * appropriate right now, and then decide within them.
501 enum messages {
502 M_NB, /* no batteries */
503 M_NP, /* not present */
504 M_AC, /* on ac power */
505 M_CH, /* battery charging */
506 M_BATT, /* on battery */
507 M_LB, /* low battery */
508 M_CB, /* critical low battery */
509 M_HCB, /* battery reported critical capacity state */
510 M_NULL, /* empty starting state */
513 static void set_message(global_t *globals)
515 static enum messages state = M_NULL;
516 battery_t *binfo = globals->binfo;
517 adapter_t *ap = &globals->adapter;
519 if (globals->battery_count == 0) {
520 if (state != M_NB) {
521 state = M_NB;
522 reset_scroll_speed();
523 render_text("no batteries");
526 return;
529 /* battery not present case */
530 if (!binfo->present) {
531 if (state != M_NP) {
532 state = M_NP;
533 reset_scroll_speed();
534 render_text("not present");
536 } else if (ap->power == AC) {
537 if (binfo->charge_state == CHARGE) {
538 if (state != M_CH) {
539 state = M_CH;
540 reset_scroll_speed();
541 render_text("battery charging");
543 } else {
544 if (state != M_AC) {
545 state = M_AC;
546 reset_scroll_speed();
547 render_text("on ac power");
550 } else {
551 if (binfo->state == CRIT) {
552 if (state != M_CB) {
553 state = M_CB;
554 scroll_faster(0.75);
555 render_text("critical low battery");
557 } else if (binfo->state == LOW) {
558 if (state != M_LB) {
559 state = M_LB;
560 scroll_faster(0.85);
561 render_text("low battery");
563 } else {
564 if (state != M_BATT) {
565 state = M_BATT;
566 reset_scroll_speed();
567 render_text("on battery");
573 void set_time_display(global_t *globals)
575 if (globals->battery_count == 0) {
576 invalid_time_display();
577 return;
580 if (globals->binfo->charge_state == CHARGE)
581 display_time(globals->binfo->charge_time);
582 else if (globals->binfo->charge_state == DISCHARGE)
583 display_time(globals->rtime);
584 else
585 invalid_time_display();
588 void clear_batt_id_area(void)
590 copy_xpm_area(125, 40, 7, 11, 51, 32);
593 void set_batt_id_area(int bno)
595 int w = 7; /* Width of the number */
596 int h = 11; /* Height of the number */
597 int dx = 50; /* x coord of the target area */
598 int dy = 32; /* y coord of the target area */
599 int sx = (bno + 1) * 7; /* source x coord */
600 int sy = 76; /* source y coord */
602 copy_xpm_area(sx, sy, w, h, dx, dy);
605 #define VERSION "wmacpi version " WMACPI_VER "\nUsing libacpi version " LIBACPI_VER
607 void cli_wmacpi(global_t *globals, int samples)
609 int i, j, sleep_time = 0;
610 battery_t *binfo;
611 adapter_t *ap;
613 pdebug("samples: %d\n", samples);
614 if(samples > 1)
615 sleep_time = 1000000/samples;
617 /* we want to acquire samples over some period of time, so . . . */
618 for(i = 0; i < samples + 2; i++) {
619 for(j = 0; j < globals->battery_count; j++)
620 acquire_batt_info(globals, j);
621 acquire_global_info(globals);
622 usleep(sleep_time);
625 ap = &globals->adapter;
626 if(ap->power == AC) {
627 printf("On AC Power");
628 for(i = 0; i < globals->battery_count; i++) {
629 binfo = &batteries[i];
630 if(binfo->present && (binfo->charge_state == CHARGE)) {
631 printf("; Battery %s charging", binfo->name);
632 printf(", currently at %2d%%", binfo->percentage);
633 if(binfo->charge_time >= 0)
634 printf(", %2d:%02d remaining",
635 binfo->charge_time/60,
636 binfo->charge_time%60);
639 printf("\n");
640 } else if(ap->power == BATT) {
641 printf("On Battery");
642 for(i = 0; i < globals->battery_count; i++) {
643 binfo = &batteries[i];
644 if(binfo->present && (binfo->percentage >= 0))
645 printf(", Battery %s at %d%%", binfo->name,
646 binfo->percentage);
648 if(globals->rtime >= 0)
649 printf("; %d:%02d remaining", globals->rtime/60,
650 globals->rtime%60);
651 printf("\n");
653 return;
656 battery_t *switch_battery(global_t *globals, int battno)
658 globals->binfo = &batteries[battno];
659 pinfo("changing to monitor battery %s\n", globals->binfo->name);
660 set_batt_id_area(battno);
661 dockapp->update = 1;
663 return globals->binfo;
667 int main(int argc, char **argv)
669 char *display = NULL;
670 int sample_count = 0;
671 int batt_reinit, ac_reinit;
672 int batt_count = 0;
673 int ac_count = 0;
674 int cli = 0, samples = 1, critical = 10;
675 int samplerate = 20;
676 int scroll_count = 0;
677 enum rtime_mode rt_mode = RT_RATE;
678 int rt_forced = 0;
679 battery_t *binfo = NULL;
680 global_t *globals;
682 fd_set fds;
683 struct timeval tv_rate;
684 struct timeval tv = {0, 0};
686 DAProgramOption options[] = {
687 {"-r", "--no-scroll", "disable scrolling message", DONone, False, {NULL}},
688 {"-n", "--no-blink", "disable blinking of various UI elements", DONone, False, {NULL}},
689 {"-x", "--cmdline", "run in command line mode", DONone, False, {NULL}},
690 {"-f", "--force-capacity-mode", "force the use of capacity mode for calculating time remaining", DONone, False, {NULL}},
691 {"-d", "--display", "display or remote display", DOString, False, {&display}},
692 {"-c", "--critical", "set critical low alarm at <number> percent\n (default: 10 percent)", DONatural, False, {&critical}},
693 {"-m", "--battery", "battery number to monitor", DONatural, False, {&battery_no}},
694 {"-s", "--sample-rate", "number of times per minute to sample battery information\n default 20 (once every three seconds)", DONatural, False, {&samplerate}},
695 {"-V", "--verbosity", "Set verbosity", DONatural, False, {&verbosity}},
696 {"-a", "--samples", "number of samples to average over (cli mode only)", DONatural, False, {&samples}},
699 dockapp = calloc(1, sizeof(struct dockapp));
700 globals = calloc(1, sizeof(global_t));
702 dockapp->blink = 1;
703 dockapp->bell = 0;
704 dockapp->scroll = 1;
705 dockapp->scroll_reset = 0;
706 globals->crit_level = 10;
707 battery_no = 1;
709 /* after this many samples, we reinit the battery and AC adapter
710 * information.
711 * XXX: make these configurable . . . */
712 batt_reinit = 100;
713 ac_reinit = 1000;
715 /* this needs to be up here because we need to know what batteries
716 * are available /before/ we can decide if the battery we want to
717 * monitor is available. */
718 /* parse command-line options */
719 DAParseArguments(argc, argv, options, 10,
720 "A battery monitor dockapp for ACPI based systems",
721 VERSION);
723 if (options[0].used)
724 dockapp->scroll = 0;
725 if (options[1].used)
726 dockapp->blink = 0;
727 if (options[2].used)
728 cli = 1;
729 if (options[3].used) {
730 rt_mode = RT_CAP;
731 rt_forced = 1;
734 if (samplerate == 0) samplerate = 1;
735 if (samplerate > 600) samplerate = 600;
737 /* convert to number of base periods */
738 samplerate = ((60 * 1000000) / samplerate) / BASE_PERIOD;
740 if (!dockapp->scroll) {
741 if (!dockapp->blink) {
742 /* Adapt the period to the sample rate */
743 tv_rate.tv_usec = samplerate * BASE_PERIOD;
745 tv_rate.tv_sec = tv_rate.tv_usec / 1000000;
746 tv_rate.tv_usec = tv_rate.tv_usec - (tv_rate.tv_sec * 1000000);
748 dockapp->period_length = samplerate;
749 } else {
750 /* blinking is every second */
751 tv_rate.tv_sec = 1; /* BASE_PERIOD * 10 = 1 sec */
752 tv_rate.tv_usec = 0;
754 dockapp->period_length = 10;
756 } else {
757 /* scrolling is every BASE_PERIOD (100 ms) */
758 tv_rate.tv_sec = 0;
759 tv_rate.tv_usec = BASE_PERIOD;
761 dockapp->period_length = 1;
764 if (critical > 100) {
765 fprintf(stderr, "Please use values between 0 and 100%%\n");
766 fprintf(stderr, "Using default value of 10%%\n");
767 critical = 10;
769 globals->crit_level = critical;
771 if (battery_no >= MAXBATT) {
772 fprintf(stderr, "Please specify a battery number below %d\n", MAXBATT);
773 return 1;
775 pinfo("Monitoring battery %d\n", battery_no);
777 if (power_init(globals))
778 /* power_init functions handle printing error messages */
779 exit(1);
781 globals->rt_mode = rt_mode;
782 globals->rt_forced = rt_forced;
784 if (battery_no > globals->battery_count) {
785 pinfo("Battery %d not available for monitoring.\n", battery_no);
788 /* check for cli mode */
789 if (cli) {
790 cli_wmacpi(globals, samples);
791 exit(0);
793 /* check to see if we've got a valid DISPLAY env variable, as a simple check to see if
794 * we're running under X */
795 if (!getenv("DISPLAY")) {
796 pdebug("Not running under X - using cli mode\n");
797 cli_wmacpi(globals, samples);
798 exit(0);
801 battery_no--;
803 /* make new dockapp window */
804 /* Don't even /think/ of asking me why, but if I set the window name to
805 * "acpi", the app refuses to dock properly - it's just plain /weird/.
806 * So, wmacpi it is . . . */
807 new_window(display, "wmacpi", argc, argv);
809 /* get initial statistics */
810 acquire_all_info(globals);
812 if (globals->battery_count > 0) {
813 binfo = &batteries[battery_no];
814 globals->binfo = binfo;
815 set_batt_id_area(battery_no);
816 pinfo("monitoring battery %s\n", binfo->name);
819 clear_time_display();
820 set_power_panel(globals);
821 set_message(globals);
823 /* main loop */
824 while (1) {
825 Atom atom;
826 Atom wmdelwin;
827 XEvent event;
828 while (XPending(dockapp->display)) {
829 XNextEvent(dockapp->display, &event);
830 switch (event.type) {
831 case Expose:
832 /* update */
833 dockapp->update = 1;
834 while (XCheckTypedEvent(dockapp->display, Expose, &event));
835 redraw_window();
836 break;
837 case DestroyNotify:
838 XCloseDisplay(dockapp->display);
839 exit(0);
840 break;
841 case ButtonPress:
842 break;
843 case ButtonRelease:
844 if (globals->battery_count == 0)
845 break;
847 /* cycle through the known batteries. */
848 battery_no++;
849 battery_no = battery_no % globals->battery_count;
851 binfo = switch_battery(globals, battery_no);
852 break;
853 case ClientMessage:
854 /* what /is/ this crap?
855 * Turns out that libdockapp adds the WM_DELETE_WINDOW atom to
856 * the WM_PROTOCOLS property for the window, which means that
857 * rather than get a simple DestroyNotify message, we get a
858 * nice little message from the WM saying "hey, can you delete
859 * yourself, pretty please?". So, when running as a window
860 * rather than an icon, we're impossible to kill in a friendly
861 * manner, because we're expecting to die from a DestroyNotify
862 * and thus blithely ignoring the WM knocking on our window
863 * border . . .
865 * This simply checks for that scenario - it may fail oddly if
866 * something else comes to us via a WM_PROTOCOLS ClientMessage
867 * event, but I suspect it's not going to be an issue. */
868 wmdelwin = XInternAtom(dockapp->display, "WM_DELETE_WINDOW", 1);
869 atom = event.xclient.data.l[0];
870 if (atom == wmdelwin) {
871 XCloseDisplay(dockapp->display);
872 exit(0);
874 break;
878 /* XXX: some laptops have problems with sampling the battery
879 * regularly - apparently, the BIOS disables interrupts while
880 * reading from the battery, which is generally on a slow bus
881 * and is a slow device, so you get significant periods without
882 * interrupts. This causes interactivity to suffer . . .
884 * So, the workaround/fix for this is to sample at a much
885 * lower rate than we may update/refresh/expose/whatever. The
886 * user specifies how many times they want us to sample per
887 * minute; we use select() on our X events fd to wake up when
888 * there's some display work to be done, with the timeout set
889 * to whatever the time between samples is. When we hit our
890 * select() timeout we update our samples, otherwise we update
891 * the display.
893 * Note that this has a wrinkle when blinking and/or scrolling
894 * is enabled, since we need to update the display more
895 * frequently than we sample (most likely). In that case we
896 * set the timeout based on the display update cycle. */
898 /* have we completed our timeout, or were we woken up early? */
899 if ((tv.tv_sec != 0) || (tv.tv_usec != 0))
900 goto win_update;
902 tv = tv_rate;
904 sample_count += dockapp->period_length;
906 if (sample_count >= samplerate) {
907 if (globals->battery_count == 0) {
908 batt_count = 0;
910 reinit_batteries(globals);
912 /* battery appeared */
913 if (globals->battery_count > 0) {
914 if (battery_no > globals->battery_count)
915 battery_no = 0;
917 binfo = switch_battery(globals, battery_no);
921 acquire_all_info(globals);
923 /* we need to be able to reinitialise batteries and adapters, because
924 * they change - you can hotplug batteries on most laptops these days
925 * and who knows what kind of shit will be happening soon . . . */
926 if (batt_count++ >= batt_reinit) {
927 if(reinit_batteries(globals))
928 pfatal("Oh my god, the batteries are gone!\n");
929 batt_count = 0;
932 if (ac_count++ >= ac_reinit) {
933 if(reinit_ac_adapters(globals))
934 pfatal("What happened to our AC adapters?!?\n");
935 ac_count = 0;
937 sample_count = 0;
940 if (scroll_count++ >= scroll_reset) {
941 reset_scroll();
942 scroll_count = 0;
945 /* The old code had some kind of weird crap with timers and the like.
946 * As far as I can tell, it's meaningless - the time we want to display
947 * is the time calculated from the remaining capacity, as per the
948 * ACPI spec. The only thing I'd change is the handling of a charging
949 * state: my best guess, based on the behaviour I'm seeing with my
950 * Lifebook, is that the present rate value when charging is the rate
951 * at which the batteries are being charged, which would mean I'd just
952 * need to reverse the rtime calculation to be able to work out how
953 * much time remained until the batteries were fully charged . . .
954 * That would be rather useful, though given it would vary rather a lot
955 * it seems likely that it'd be little more than a rough guesstimate. */
956 set_time_display(globals);
957 set_power_panel(globals);
958 set_message(globals);
960 if (globals->battery_count == 0) {
961 clear_percentage();
962 clear_batt_id_area();
963 } else
964 display_percentage(binfo->percentage);
966 scroll_text();
968 win_update:
969 /* redraw_window, if anything changed */
970 redraw_window();
972 FD_ZERO(&fds);
973 FD_SET(dockapp->x_fd, &fds);
974 select(FD_SETSIZE, &fds, NULL, NULL, &tv);
976 return 0;