1 /* wmbattery - display laptop battery info, dockable in WindowMaker
2 * Copyright (C) 1998-2014 Joey Hess <joey@kitenet.net>
3 * Copyright (C) 2014 Window Maker Developers Team
4 * <wmaker-dev@googlegroups.com>
6 * Portions of code derived from:
7 * wmapm - Copyright (C) 1998-1999 Chris D. Faulhaber <jedgar@fxp.org>
8 * wmmon - Copyright (C) 1998 Martijn Pieterse <pieterse@xs4all.nl>
9 * Copyright (C) 1998 Antoine Nulle <warp@xs4all.nl>
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of version 2 of the GNU General Public License
13 * as published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>. */
28 #include <X11/extensions/shape.h>
34 #include <sys/types.h>
36 #include "wmbattery.h"
43 #include "mask_nodial.xbm"
47 #include "simplehal.h"
53 Pixmap images
[NUM_IMAGES
];
54 Window root
, iconwin
, win
;
59 char *user_geom
= NULL
;
62 #define APM_STATUS_FILE "/dev/apm"
64 #define APM_STATUS_FILE "/proc/apm"
67 char *apm_status_file
= APM_STATUS_FILE
;
69 char *crit_audio_fn
= NULL
;
72 char *crit_command
= NULL
;
76 int use_simplehal
= 0;
84 int always_estimate_remaining
= 0;
85 int granularity_estimate_remaining
= 1;
86 int initial_state
= WithdrawnState
;
89 signed int low_pct
= -1;
90 signed int critical_pct
= -1;
92 void error(const char *fmt
, ...)
96 va_start(arglist
, fmt
);
97 fprintf(stderr
, "Error: ");
98 vfprintf(stderr
, fmt
, arglist
);
99 fprintf(stderr
, "\n");
105 #if defined (HAVE_MACHINE_APM_BIOS_H) || defined (HAVE_I386_APMVAR_H) /* BSD */
106 int apm_read(apm_info
*i
)
109 #ifdef HAVE_MACHINE_APM_BIOS_H /* FreeBSD */
110 unsigned long request
= APMIO_GETINFO
;
111 struct apm_info info
;
112 #else /* NetBSD or OpenBSD */
113 unsigned long request
= APM_IOC_GETPOWER
;
114 struct apm_power_info info
;
117 if ((fd
= open(apm_status_file
, O_RDONLY
)) == -1) {
120 if (ioctl(fd
, request
, &info
) == -1) {
125 #ifdef HAVE_MACHINE_APM_BIOS_H /* FreeBSD */
126 i
->ac_line_status
= info
.ai_acline
;
127 i
->battery_status
= info
.ai_batt_stat
;
128 i
->battery_flags
= (info
.ai_batt_stat
== 3) ? 8: 0;
129 i
->battery_percentage
= info
.ai_batt_life
;
130 i
->battery_time
= info
.ai_batt_time
;
131 i
->using_minutes
= 0;
132 #else /* NetBSD or OpenBSD */
133 i
->ac_line_status
= info
.ac_state
;
134 i
->battery_status
= info
.battery_state
;
135 i
->battery_flags
= (info
.battery_state
== 3) ? 8: 0;
136 i
->battery_percentage
= info
.battery_life
;
137 i
->battery_time
= info
.minutes_left
;
138 i
->using_minutes
= 1;
148 if (access(apm_status_file
, R_OK
))
152 #elif !defined(HAVE_LIBAPM)
153 int apm_read(apm_info
*i
)
163 int apm_change(apm_info
*cur
)
165 static int ac_line_status
= 0, battery_status
= 0, battery_flags
= 0,
166 battery_percentage
= 0, battery_time
= 0, using_minutes
= 0;
168 int i
= cur
->ac_line_status
== ac_line_status
&&
169 cur
->battery_status
== battery_status
&&
170 cur
->battery_flags
== battery_flags
&&
171 cur
->battery_percentage
== battery_percentage
&&
172 cur
->battery_time
== battery_time
&&
173 cur
->using_minutes
== using_minutes
;
175 ac_line_status
= cur
->ac_line_status
;
176 battery_status
= cur
->battery_status
;
177 battery_flags
= cur
->battery_flags
;
178 battery_percentage
= cur
->battery_percentage
;
179 battery_time
= cur
->battery_time
;
180 using_minutes
= cur
->using_minutes
;
185 /* Calculate battery estimate */
186 void estimate_timeleft(apm_info
*cur_info
)
188 /* Time of the last estimate */
189 static time_t estimate_time
;
190 /* Estimated time left */
191 static time_t estimate
;
192 /* Time when we last noticed a battery level change */
193 static time_t battery_change_time
;
194 /* The previous estimation we had before the battery level changed */
195 static time_t prev_estimate
;
196 /* Percentage at the last estimate */
197 static short percent
;
198 /* Where we charging or discharging the last time we were called? */
199 static short was_charging
= 1;
200 /* Have we made a guess lately? */
201 static short guessed_lately
;
205 short is_charging
= cur_info
->battery_flags
& BATTERY_FLAGS_CHARGING
;
208 if (time(&t
) == ((time_t)-1) && errno
!= 0)
212 /* AC is on and battery is not charging anymore or ... */
213 (cur_info
->ac_line_status
== AC_LINE_STATUS_ON
) && !is_charging
216 /* ... the charging state has changed */
217 is_charging
^ was_charging
220 battery_change_time
= t
;
228 /* No change: decrease estimate */
229 if ((percent
- cur_info
->battery_percentage
)
230 / granularity_estimate_remaining
== 0) {
231 estimate
-= t
- estimate_time
;
233 if (guessed_lately
&& estimate
< 0)
238 /* The battery level changed: calculate estimate based
239 * on change speed and previous estimate */
242 interval
= estimate_time
- battery_change_time
;
243 prev_estimate
= estimate
;
244 battery_change_time
= estimate_time
;
245 estimate
= (is_charging
246 ? (cur_info
->battery_percentage
- 100)
247 : cur_info
->battery_percentage
)
248 * interval
/ (percent
- cur_info
->battery_percentage
);
249 if (prev_estimate
> 0)
250 estimate
= (estimate
* 2 + prev_estimate
) / 3;
253 percent
= cur_info
->battery_percentage
;
254 was_charging
= is_charging
;
255 cur_info
->battery_time
= estimate
;
258 cur_info
->using_minutes
= 0;
261 /* Load up the images this program uses. */
262 void load_images(void)
265 char fn
[128]; /* enough? */
267 for (x
= 0; x
< NUM_IMAGES
; x
++) {
268 sprintf(fn
, "%s/%s.xpm", ICONDIR
, image_info
[x
].filename
);
269 if (XpmReadFileToPixmap(display
, root
, fn
, &images
[x
], NULL
, NULL
)) {
270 /* Check in current direcotry for fallback. */
271 sprintf(fn
, "%s.xpm", image_info
[x
].filename
);
272 if (XpmReadFileToPixmap(display
, root
, fn
, &images
[x
], NULL
, NULL
))
273 error("Failed to load %s\n", fn
);
278 void load_audio(void)
284 if (crit_audio_fn
== NULL
)
286 fd
= open(crit_audio_fn
, 0);
288 error("unable to open audio file");
289 if (fstat(fd
, &s
) == 0) {
290 crit_audio_size
= s
.st_size
;
291 crit_audio
= malloc(crit_audio_size
);
292 /* XXX: make this more robust? (loop?) */
293 if (read(fd
, crit_audio
, crit_audio_size
) != crit_audio_size
) {
296 error("unable to read audio file");
302 /* string replacement function by Laird Shaw, in public domain
303 * http://creativeandcritical.net/str-replace-c */
304 char *replace_str(const char *str
, const char *old
, const char *new)
308 size_t oldlen
= strlen(old
);
309 size_t count
, retlen
, newlen
= strlen(new);
311 if (oldlen
!= newlen
) {
312 for (count
= 0, p
= str
; (q
= strstr(p
, old
)) != NULL
; p
= q
+ oldlen
)
314 /* this is undefined if p - str > PTRDIFF_MAX */
315 retlen
= p
- str
+ strlen(p
) + count
* (newlen
- oldlen
);
317 retlen
= strlen(str
);
319 ret
= malloc(retlen
+ 1);
323 for (r
= ret
, p
= str
; (q
= strstr(p
, old
)) != NULL
; p
= q
+ oldlen
) {
324 /* this is undefined if q - p > PTRDIFF_MAX */
328 memcpy(r
, new, newlen
);
336 void cmd_crit(const char *cmd
, int percentage
, int time
)
338 char prc_str
[255] = "";
339 char min_str
[255] = "";
340 char sec_str
[255] = "";
343 char *command
= NULL
;
348 if (percentage
> 100 || percentage
< 0)
350 if (time
> 65535 || time
< 0)
353 sprintf(prc_str
, "%i", percentage
);
354 sprintf(min_str
, "%i", time
/ 60);
355 sprintf(sec_str
, "%i", time
% 60);
357 tmp_a
= replace_str(cmd
, STR_SUB_PERCENT
, prc_str
);
360 tmp_b
= replace_str(tmp_a
, STR_SUB_MINUTES
, min_str
);
363 command
= replace_str(tmp_b
, STR_SUB_SECONDS
, sec_str
);
367 ret
= system(command
);
369 error("unable to run command: %s", command
);
378 /* Returns the display to run on (or NULL for default). */
379 char *parse_commandline(int argc
, char *argv
[])
385 c
= getopt(argc
, argv
, "hd:g:if:b:w:c:l:es:a:x:vn");
388 printf("Usage: wmbattery [options]\n");
389 printf("\t-d <display>\tselects target display\n");
390 printf("\t-h\t\tdisplay this help\n");
391 printf("\t-g {+-}x{+-}y\tposition of the window\n");
392 printf("\t-i\t\tdisplay as icon\n");
393 printf("\t-b num\t\tnumber of battery to display\n");
394 printf("\t-w secs\t\tseconds between updates\n");
395 printf("\t-l percent\tlow percentage\n");
396 printf("\t-c percent\tcritical percentage\n");
397 printf("\t-e\t\tuse own time estimates\n");
398 printf("\t-s granularity\tignore fluctuations less than granularity%% (implies -e)\n");
399 printf("\t-a file\t\twhen critical send file to /dev/audio\n");
400 printf("\t-x command\twhen critical execute this command\n");
401 printf("\t-n\t\tdisable dial graphic\n");
402 printf("\t-v\t\tdisplay version number\n");
406 ret
= strdup(optarg
);
409 user_geom
= strdup(optarg
);
412 initial_state
= IconicState
;
415 battnum
= atoi(optarg
);
418 delay
= atoi(optarg
);
421 low_pct
= atoi(optarg
);
424 critical_pct
= atoi(optarg
);
427 always_estimate_remaining
= 1;
430 always_estimate_remaining
= 1;
431 granularity_estimate_remaining
= atoi(optarg
);
434 crit_audio_fn
= strdup(optarg
);
437 crit_command
= strdup(optarg
);
440 printf("wmbattery "PACKAGE_VERSION
"\n");
452 /* Sets up the window and icon and all the nasty X stuff. */
453 void make_window(char *display_name
, int argc
, char *argv
[])
455 XClassHint classhint
;
456 char *wname
= argv
[0];
459 int dummy
= 0, borderwidth
= 1;
460 XSizeHints sizehints
;
462 Pixel back_pix
, fore_pix
;
465 display
= XOpenDisplay(display_name
);
467 error("can't open display %s", XDisplayName(display_name
));
469 screen
= DefaultScreen(display
);
470 root
= RootWindow(display
, screen
);
473 sizehints
.flags
= USSize
| USPosition
;
476 XWMGeometry(display
, screen
, user_geom
, NULL
, borderwidth
,
477 &sizehints
, &sizehints
.x
, &sizehints
.y
,
478 &sizehints
.width
, &sizehints
.height
, &dummy
);
480 sizehints
.width
= 64;
481 sizehints
.height
= 64;
482 back_pix
= WhitePixel(display
, screen
);
483 fore_pix
= BlackPixel(display
, screen
);
484 win
= XCreateSimpleWindow(display
, root
, sizehints
.x
, sizehints
.y
,
485 sizehints
.width
, sizehints
.height
,
486 borderwidth
, fore_pix
, back_pix
);
487 iconwin
= XCreateSimpleWindow(display
, win
, sizehints
.x
,
488 sizehints
.y
, sizehints
.width
,
489 sizehints
.height
, borderwidth
,
493 XSetWMNormalHints(display
, win
, &sizehints
);
494 classhint
.res_name
= wname
;
495 classhint
.res_class
= wname
;
496 XSetClassHint(display
, win
, &classhint
);
498 if (!XStringListToTextProperty(&wname
, 1, &name
))
499 error("Can't allocate window name.");
501 XSetWMName(display
, win
, &name
);
503 /* Create GC for drawing */
504 gcv
.foreground
= fore_pix
;
505 gcv
.background
= back_pix
;
506 gcv
.graphics_exposures
= 0;
507 NormalGC
= XCreateGC(display
, root
,
508 GCForeground
| GCBackground
| GCGraphicsExposures
,
512 pixmask
= XCreateBitmapFromData(display
, win
, mask_bits
,
513 mask_width
, mask_height
);
515 pixmask
= XCreateBitmapFromData(display
, win
, mask_nodial_bits
,
516 mask_nodial_width
, mask_nodial_height
);
517 XShapeCombineMask(display
, win
, ShapeBounding
, 0, 0,
519 XShapeCombineMask(display
, iconwin
, ShapeBounding
, 0, 0,
522 wmhints
.initial_state
= initial_state
;
523 wmhints
.icon_window
= iconwin
;
524 wmhints
.icon_x
= sizehints
.x
;
525 wmhints
.icon_y
= sizehints
.y
;
526 wmhints
.window_group
= win
;
527 wmhints
.flags
= StateHint
| IconWindowHint
|
528 IconPositionHint
| WindowGroupHint
;
530 XSetWMHints(display
, win
, &wmhints
);
531 XSetCommand(display
, win
, argv
, argc
);
533 XSelectInput(display
, iconwin
, ExposureMask
);
534 XSelectInput(display
, win
, ExposureMask
);
536 XMapWindow(display
, win
);
539 void flush_expose(Window w
)
543 while (XCheckTypedWindowEvent(display
, w
, Expose
, &dummy
))
547 void redraw_window(void)
549 XCopyArea(display
, images
[FACE
], iconwin
, NormalGC
, 0, 0,
550 image_info
[FACE
].width
, image_info
[FACE
].height
, 0, 0);
551 flush_expose(iconwin
);
552 XCopyArea(display
, images
[FACE
], win
, NormalGC
, 0, 0,
553 image_info
[FACE
].width
, image_info
[FACE
].height
, 0, 0);
558 * Display an image, using XCopyArea. Can display only part of an image,
561 void copy_image(int image
, int xoffset
, int yoffset
,
562 int width
, int height
, int x
, int y
)
564 XCopyArea(display
, images
[image
], images
[FACE
], NormalGC
,
565 xoffset
, yoffset
, width
, height
, x
, y
);
569 * Display a letter in one of two fonts, at the specified x position.
570 * Note that 10 is passed for special characters `:' or `1' at the
573 void draw_letter(int letter
, int font
, int x
)
575 copy_image(font
, image_info
[font
].charwidth
* letter
, 0,
576 image_info
[font
].charwidth
, image_info
[font
].height
,
577 x
, image_info
[font
].y
);
580 /* Display an image at its normal location. */
581 void draw_image(int image
)
583 copy_image(image
, 0, 0,
584 image_info
[image
].width
, image_info
[image
].height
,
585 image_info
[image
].x
, image_info
[image
].y
);
588 void recalc_window(apm_info cur_info
)
590 int time_left
, hour_left
, min_left
, digit
, x
;
593 /* Display if it's plugged in. */
594 switch (cur_info
.ac_line_status
) {
595 case AC_LINE_STATUS_ON
:
599 draw_image(UNPLUGGED
);
602 /* Display the appropriate color battery. */
603 switch (cur_info
.battery_status
) {
604 case BATTERY_STATUS_HIGH
:
605 case BATTERY_STATUS_CHARGING
:
606 draw_image(BATTERY_HIGH
);
608 case BATTERY_STATUS_LOW
:
609 draw_image(BATTERY_LOW
);
611 case BATTERY_STATUS_CRITICAL
: /* blinking red battery */
613 draw_image(BATTERY_CRITICAL
);
615 draw_image(BATTERY_BLINK
);
619 draw_image(BATTERY_NONE
);
622 /* Show if the battery is charging. */
623 if (cur_info
.battery_flags
& BATTERY_FLAGS_CHARGING
)
624 draw_image(CHARGING
);
626 draw_image(NOCHARGING
);
629 * Display the percent left dial. This has the side effect of
630 * clearing the time left field.
632 x
= DIAL_MULTIPLIER
* cur_info
.battery_percentage
;
634 /* Start by displaying bright on the dial. */
635 copy_image(DIAL_BRIGHT
, 0, 0,
636 x
, image_info
[DIAL_BRIGHT
].height
,
637 image_info
[DIAL_BRIGHT
].x
,
638 image_info
[DIAL_BRIGHT
].y
);
640 /* Now display dim on the remainder of the dial. */
641 copy_image(DIAL_DIM
, x
, 0,
642 image_info
[DIAL_DIM
].width
- x
,
643 image_info
[DIAL_DIM
].height
,
644 image_info
[DIAL_DIM
].x
+ x
,
645 image_info
[DIAL_DIM
].y
);
647 /* Show percent remaining */
648 if (cur_info
.battery_percentage
>= 0) {
649 digit
= cur_info
.battery_percentage
/ 10;
651 /* 11 is the `1' for the hundreds place. */
652 draw_letter(11, SMALLFONT
, HUNDREDS_OFFSET
);
655 draw_letter(digit
, SMALLFONT
, TENS_OFFSET
);
656 digit
= cur_info
.battery_percentage
% 10;
657 draw_letter(digit
, SMALLFONT
, ONES_OFFSET
);
659 /* There is no battery, so we need to dim out the
660 * percent sign that is normally bright. */
661 draw_letter(10, SMALLFONT
, PERCENT_OFFSET
);
666 /* A negative number means that it is unknown. Dim the field. */
667 if (cur_info
.battery_time
< 0) {
668 draw_letter(10, BIGFONT
, COLON_OFFSET
);
673 if (cur_info
.using_minutes
)
674 time_left
= cur_info
.battery_time
;
676 time_left
= cur_info
.battery_time
/ 60;
677 hour_left
= time_left
/ 60;
678 min_left
= time_left
% 60;
679 digit
= hour_left
/ 10;
680 draw_letter(digit
, BIGFONT
, HOURS_TENS_OFFSET
);
681 digit
= hour_left
% 10;
682 draw_letter(digit
, BIGFONT
, HOURS_ONES_OFFSET
);
683 digit
= min_left
/ 10;
684 draw_letter(digit
, BIGFONT
, MINUTES_TENS_OFFSET
);
685 digit
= min_left
% 10;
686 draw_letter(digit
, BIGFONT
, MINUTES_ONES_OFFSET
);
696 audio
= open("/dev/audio", O_WRONLY
);
698 n
= write(audio
, crit_audio
, crit_audio_size
);
699 if (n
!= crit_audio_size
)
700 fprintf(stderr
, "write failed (%d/%d bytes)\n", n
, crit_audio_size
);
706 void alarmhandler(int sig
)
713 if (upower_read(1, &cur_info
) != 0)
714 error("Cannot read upower information.");
715 } else if (use_acpi
) {
719 if (acpi_read(battnum
, &cur_info
) != 0)
720 error("Cannot read ACPI information.");
723 else if (use_simplehal
) {
724 if (simplehal_read(battnum
, &cur_info
) != 0)
725 error("Cannot read HAL information.");
728 else if (!use_sonypi
) {
729 if (apm_read(&cur_info
) != 0)
730 error("Cannot read APM information.");
732 if (sonypi_read(&cur_info
) != 0)
733 error("Cannot read sonypi information.");
736 old_status
= cur_info
.battery_status
;
738 /* Always calculate remaining lifetime? apm and acpi both use a
739 * negative number here to indicate error, missing battery, or
740 * cannot determine time. */
741 if (always_estimate_remaining
|| cur_info
.battery_time
< 0)
742 estimate_timeleft(&cur_info
);
744 /* Override the battery status? */
745 if ((low_pct
> -1 || critical_pct
> -1) &&
746 cur_info
.ac_line_status
!= AC_LINE_STATUS_ON
) {
747 if (cur_info
.battery_percentage
<= critical_pct
)
748 cur_info
.battery_status
= BATTERY_STATUS_CRITICAL
;
749 else if (cur_info
.battery_percentage
<= low_pct
)
750 cur_info
.battery_status
= BATTERY_STATUS_LOW
;
752 cur_info
.battery_status
= BATTERY_STATUS_HIGH
;
755 /* If APM data changes redraw and wait for next update */
756 /* Always redraw if the status is critical, to make it blink. */
757 if (!apm_change(&cur_info
) || cur_info
.battery_status
== BATTERY_STATUS_CRITICAL
)
758 recalc_window(cur_info
);
760 if ((old_status
== BATTERY_STATUS_HIGH
) &&
761 (cur_info
.battery_status
== BATTERY_STATUS_LOW
)) {
763 } else if (cur_info
.battery_status
== BATTERY_STATUS_CRITICAL
) {
765 cmd_crit(crit_command
, cur_info
.battery_percentage
,
766 cur_info
.battery_time
);
772 void check_battery_num(int real
, int requested
)
774 if (requested
> real
|| requested
< 1) {
775 error("There %s only %i batter%s, and you asked for number %i.",
776 real
== 1 ? "is" : "are",
778 real
== 1 ? "y" : "ies",
783 int main(int argc
, char *argv
[])
785 make_window(parse_commandline(argc
, argv
), argc
, argv
);
787 /* Check for APM support (returns 0 on success). */
788 if (apm_exists() == 0) {
793 /* Check for hal support. */
794 else if (simplehal_supported()) {
801 else if (upower_supported()) {
806 /* Check for ACPI support. */
807 else if (acpi_supported() && acpi_batt_count
> 0) {
808 check_battery_num(acpi_batt_count
, battnum
);
811 delay
= 3; /* slow interface! */
812 } else if (sonypi_supported()) {
819 error("No APM, ACPI, UPOWER, HAL or SPIC support detected.");
825 signal(SIGALRM
, alarmhandler
);
826 alarmhandler(SIGALRM
);
830 XNextEvent(display
, &ev
);
831 if (ev
.type
== Expose
)