7 #include <X11/extensions/shape.h>
13 #include <sys/types.h>
20 #include "wmbattery.h"
25 #include "simplehal.h"
31 Pixmap images
[NUM_IMAGES
];
32 Window root
, iconwin
, win
;
39 char *crit_audio_fn
= NULL
;
45 int use_simplehal
= 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
, ...) {
63 va_start(arglist
, fmt
);
64 fprintf(stderr
, "Error: ");
65 vfprintf(stderr
, fmt
, arglist
);
66 fprintf(stderr
, "\n");
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
;
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;
112 short is_charging
= cur_info
->battery_flags
& BATTERY_FLAGS_CHARGING
;
115 if (time(&t
) == ((time_t)-1) && errno
!= 0)
119 /* AC is on and battery is not charging anymore or ... */
120 (cur_info
->ac_line_status
== AC_LINE_STATUS_ON
) && !is_charging
123 /* ... the charging state has changed */
124 is_charging
^ was_charging
127 battery_change_time
= t
;
135 /* No change: decrease estimate */
136 if ((percent
- cur_info
->battery_percentage
)
137 / granularity_estimate_remaining
== 0) {
138 estimate
-= t
- estimate_time
;
140 if (guessed_lately
&& estimate
< 0)
145 /* The battery level changed: calculate estimate based
146 * on change speed and previous estimate */
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;
160 percent
= cur_info
->battery_percentage
;
161 was_charging
= is_charging
;
162 cur_info
->battery_time
= estimate
;
165 cur_info
->using_minutes
= 0;
168 /* Load up the images this program uses. */
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
);
190 if (crit_audio_fn
== NULL
) {
193 fd
= open(crit_audio_fn
, 0);
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
) {
204 error("unable to read audio file");
210 /* Returns the display to run on (or NULL for default). */
211 char *parse_commandline(int argc
, char *argv
[]) {
218 c
=getopt(argc
, argv
, "hd:g:if:b:w:c:l:es:a:");
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");
239 s
= strtok(optarg
, "+");
242 if ((s
= strtok(NULL
, "+")) != NULL
) {
251 initial_state
= IconicState
;
254 battnum
= atoi(optarg
);
257 delay
= atoi(optarg
);
260 low_pct
= atoi(optarg
);
263 critical_pct
= atoi(optarg
);
266 always_estimate_remaining
= 1;
269 always_estimate_remaining
= 1;
270 granularity_estimate_remaining
= atoi(optarg
);
273 crit_audio_fn
= strdup(optarg
);
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];
287 int dummy
=0, borderwidth
= 1;
288 XSizeHints sizehints
;
290 Pixel back_pix
, fore_pix
;
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
);
300 sizehints
.flags
= USSize
| USPosition
;
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
,
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
,
340 pixmask
= XCreateBitmapFromData(display
, win
, mask_bits
,
341 mask_width
,mask_height
);
342 XShapeCombineMask(display
, win
, ShapeBounding
, 0, 0,
344 XShapeCombineMask(display
, iconwin
, ShapeBounding
, 0, 0,
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
) {
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);
380 * Display an image, using XCopyArea. Can display only part of an image,
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
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
:
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
);
426 case BATTERY_STATUS_LOW
:
427 draw_image(BATTERY_LOW
);
429 case BATTERY_STATUS_CRITICAL
: /* blinking red battery */
431 draw_image(BATTERY_CRITICAL
);
433 draw_image(BATTERY_BLINK
);
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
);
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
;
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;
471 /* 11 is the `1' for the hundreds place. */
472 draw_letter(11,SMALLFONT
,HUNDREDS_OFFSET
);
475 draw_letter(digit
,SMALLFONT
,TENS_OFFSET
);
476 digit
= cur_info
.battery_percentage
% 10;
477 draw_letter(digit
,SMALLFONT
,ONES_OFFSET
);
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
);
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
);
494 if (cur_info
.using_minutes
)
495 time_left
= cur_info
.battery_time
;
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
);
516 audio
= open("/dev/audio", O_WRONLY
);
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
);
527 void alarmhandler(int sig
) {
533 if (upower_read(1, &cur_info
) != 0)
534 error("Cannot read upower information.");
540 if (acpi_read(battnum
, &cur_info
) != 0)
541 error("Cannot read ACPI information.");
544 else if (use_simplehal
) {
545 if (simplehal_read(battnum
, &cur_info
) != 0)
546 error("Cannot read HAL information.");
549 else if (! use_sonypi
) {
550 if (apm_read(&cur_info
) != 0)
551 error("Cannot read APM information.");
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
;
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
)) {
586 else if (cur_info
.battery_status
== BATTERY_STATUS_CRITICAL
) {
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",
598 real
== 1 ? "y" : "ies",
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) {
612 /* Check for hal support. */
613 else if (simplehal_supported()) {
620 else if (upower_supported()) {
624 /* Check for ACPI support. */
625 else if (acpi_supported() && acpi_batt_count
> 0) {
626 check_battery_num(acpi_batt_count
, battnum
);
629 delay
= 3; /* slow interface! */
631 else if (sonypi_supported()) {
639 error("No APM, ACPI, UPOWER, HAL or SPIC support detected.");
645 signal(SIGALRM
, alarmhandler
);
646 alarmhandler(SIGALRM
);
650 XNextEvent(display
, &ev
);
651 if (ev
.type
== Expose
)