4 * A WindowMaker dockable application that allows laptop users
5 * to graphically monitor the status of their power source.
6 * (I.e. whether or not AC or battery is in use as well as
7 * how long it will take to drain or charge the battery.)
9 * Originally written (and copyrighted under GPL) by
10 * Chris D. Faulhaber <jedgar@fxp.org>. Version 3.0
11 * is an extensively modified version of version 2.0
12 * by Michael G. Henderson <mghenderson@lanl.gov>.
15 * This program is free software; you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation; either version 2, or (at your option)
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
25 * You should have received a copy of the GNU General Public License
26 * along with this program (see the file COPYING); if not, write to the
27 * Free Software Foundation, Inc.,
28 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
30 * Portions of code derived from:
31 * apm/apmd/libapm : (c) 1996 Rickard E. Faith (r.faith@ieee.org)
32 * wmmon : (c) 1998 Martijn Pieterse (pieterse@xs4all.nl) and
33 * Antoine Nulle (warp@xs4all.nl)
35 * Thanx to Timecop <timecop@linuxwarez.com> for pointing out/helping to
36 * Toggle fix the meter mismatch.
45 * 3.1 -Released: June 1, 1999.
46 * + Added support for time left on FreeBSD 3.x/4.x
47 * (Chris D. Faulhaber <jedgar@fxp.org>)
50 * 3.01 -Released: January 3, 1999.
52 * + Added a LowColor Pixmap for the poor saps using 8-bit displays
53 * on laptops. There are a *lot* of laptops out there that are only
54 * 8-bit. Use the "-l" command-line option to invoke.
57 * 3.0 -Released: December 15, 1998.
58 * A Major overhaul performed. Changes include;
60 * + Added buttons to place laptop into "Suspend" (button labeled `Z')
61 * or "Standby" (button labeled `S') mode. Buttons are separated
62 * by status LEDs to minimize accidentally clicking on the wrong
63 * one. I used `Z' for suspend because its like the laptop is
64 * catching some Zs (i.e. going to sleep).
66 * + Replaced the 3 rectangular red/yellow/green status indicators
67 * with 3 small round LEDs and moved them to a viewport at the
68 * bottom between the two buttons. This array of LEDs could in future
69 * be moved to a single LED in the main viewport to make room for
70 * other things at this location (perhaps more buttons if apm supports
71 * more things like truning off LCD, etc).
73 * + Created user-definable LowLevel and CriticalLevel thresholds. Yellow LED
74 * comes on when Battery Percentage hits the LowLevel threshold. Red comes on
75 * when you reach CriticalLevel threshold.
77 * + Made red status LED blink for extra noticability. User can define blink rate.
78 * A BlinkRate of 0 turns off blinking.
80 * + Moved all of the other indicators into a single viewport above the
81 * buttons and status LEDs.
83 * + Changed the red-dark-green colorbar to a banded blue LED bar that is tipped
84 * with a color indicating capacity level. The tip color goes through
85 * green-yellow-orange-red. A series of single-pixel dots is always present
86 * below the bar to indicate its range. This is needed now, because
87 * the bar is free-floating in the viewport. The single-pixel dots can be
88 * seen easily on an LCD - the type of monitor wmapm is likely to be used.
90 * + Changed the `CHARGING' indicator with a single red `C' indicator at the
91 * upper left of the APP.
93 * + Changed percentage indicator so that it can display 100%. (Used to only go
96 * + Changed time indicator to have a +/- sign depending on whether you are
97 * charging up or draining down. (+ means you have that much battery life
98 * left before its discharged. - means you have that much time to wait until
99 * the battery is fully charged.)
101 * + Fixed a problem with very large "TimeLeft" values. If the time is greater
102 * than the maximum time displayable 99 hours and 59 minutes, a ---:-- is
103 * listed instead. Since the time is based on measured charge/discharge rates,
104 * when the battery is doing neither, the time is essentially infinite. On my
105 * (M Henderson's) laptop, the time left indicated 32766 when this happened.
106 * FreeBSD systems should also show a ---:-- indicator. Dont have FreeBSD though
107 * so I couldnt test it....
109 * + Changed Makefile to suid the apm program. This is needed to allow users to
110 * invoke the standby and suspend capabilities in apm.
112 * + Sped up the loop to catch button press and expose events. But the querying of
113 * /proc/apm is still done about once a second...
115 * + Added alert feature. User can use command line option -A <T1 T2> to turn on alerts
116 * via wall. T1 and T2 are the time in seconds between updates for Low and Critical
117 * status. By default the alerts are turned off.
119 * + Various sundry code cleanups.
122 * 2.0 - Added FreeBSD support.
124 * 1.3 - Fixed an annoying little problem with the the meter
125 * not properly lowering as the battery is depleted.
126 * Also did some code cleanup, enhanced the Makefile which
127 * now includes 'make install'.
130 * 1.2 - Fixed bug that showed 100% battery capacity
131 * as 90% (I never noticed since my battery seems
132 * to max out at 98%).
133 * Thanx to Brice Ruth <bruth@ltic.com> for pointing out/helping fix the
134 * 100% bug (err...feature).
137 * 1.1 - Removed libapm dependency; tweaked some code.
140 * 1.0 - Initial release version.
150 #include <sys/battery.h>
156 #include <sys/file.h>
157 #include <sys/ioctl.h>
158 #include <machine/apm_bios.h>
167 #include <libdockapp/wmgeneral.h>
168 #include "wmapm_master.xpm"
169 #include "wmapm_master_LowColor.xpm"
170 #include "wmapm_mask.xbm"
175 int apm_read(struct my_apm_info
*i
);
178 int apm_read(apm_info_t temp_info
);
182 void ParseCMDLine(int argc
, char *argv
[]);
183 void pressEvent(XButtonEvent
*xev
);
188 int CriticalLevel
= 10;
190 float BlinkRate
= 3.0; /* blinks per second */
191 float UpdateRate
= 0.8; /* Number of updates per second */
192 int Beep
= 0; /* Controls beeping when you get to CriticalLevel: Off by default */
193 int Volume
= 50; /* ring bell at 50% volume */
194 int Alert
= 0; /* Controls whether alert is sent to all users via wall: Off by default */
195 int UseLowColorPixmap
= 0; /* Use a lower number of colors for the poor saps on 8-bit displays -- common
197 float LAlertRate
= 300.0; /* send alert every 5 minutes when Low */
198 float CAlertRate
= 120.0; /* send alert every 2 minutes when Critical */
210 int main(int argc
, char *argv
[]) {
213 struct my_apm_info my_cur_info
;
219 struct apm_info temp_info
;
222 int m
, mMax
, n
, nMax
, k
, Toggle
;
223 long int r
, rMax
, s
, sMax
;
233 UpdateRate
= 1.0/1.25;
239 * Parse any command line arguments.
241 ParseCMDLine(argc
, argv
);
243 BlinkRate
= (BlinkRate
>= 0.0) ? BlinkRate
: -1.0*BlinkRate
;
244 UpdateRate
= (UpdateRate
>= 0.0) ? UpdateRate
: -1.0*UpdateRate
;
247 nMax
= (int)( 1.0e6
/(2.0*UpdateRate
*DELAY
) );
248 mMax
= (BlinkRate
> 0.0) ? (int)( 1.0e6
/(2.0*BlinkRate
*DELAY
) ) : nMax
;
249 rMax
= (int)( LAlertRate
*1.0e6
/(2.0*DELAY
) );
250 sMax
= (int)( CAlertRate
*1.0e6
/(2.0*DELAY
) );
261 * Check for APM support
265 fprintf(stderr
, "No APM support in kernel\n");
267 fprintf(stderr
, "Unable to access APM info\n");
275 if (UseLowColorPixmap
)
276 openXwindow(argc
, argv
, wmapm_master_LowColor
, wmapm_mask_bits
, wmapm_mask_width
, wmapm_mask_height
);
278 openXwindow(argc
, argv
, wmapm_master
, wmapm_mask_bits
, wmapm_mask_width
, wmapm_mask_height
);
284 * Loop until we die...
294 * Only process apm info only every nMax cycles of this
295 * loop. We run it faster to catch the xevents like button
296 * presses and expose events, etc...
298 * DELAY is set at 0.00625 seconds, so process apm info
299 * every 1.25 seconds...
306 #if defined(Linux) || defined(SunOS)
307 if (apm_read(&my_cur_info
)) {
310 if (apm_read(&temp_info
)) {
313 fprintf(stderr
, "Cannot read APM information: %i\n");
318 #ifdef FreeBSD /* Convert status's */
319 my_cur_info
.ac_line_status
= (int)temp_info
.ai_acline
;
320 my_cur_info
.battery_status
= (int)temp_info
.ai_batt_stat
;
321 my_cur_info
.battery_percentage
= (int)temp_info
.ai_batt_life
;
322 my_cur_info
.battery_time
= (int)temp_info
.ai_batt_time
;
333 switch (my_cur_info
.ac_line_status
) {
337 * AC on-line. I.e. we are "plugged-in".
339 copyXPMArea(68, 6, 26, 7, 31, 35);
343 * AC off-line. I.e. we are using battery.
345 copyXPMArea(68, 20, 26, 7, 31, 35);
353 * Paste up the default charge status and time
355 copyXPMArea(104, 6, 5, 7, 6, 7);
356 copyXPMArea(83, 93, 41, 9, 15, 7);
363 * Check to see if we are charging.
365 if ( (int)(my_cur_info
.battery_status
) == 3){
368 * Battery Status: Charging.
370 copyXPMArea(98, 6, 5, 7, 6, 7);
371 copyXPMArea(75, 81, 1, 2, 17, 9);
372 copyXPMArea(75, 81, 1, 2, 17, 12);
383 copyXPMArea(42, 106, 13, 11, 5, 48);
384 copyXPMArea(57, 106, 13, 11, 46, 48);
394 * Paste up the "Time Left". This time means:
396 * If not charging: Time left before battery drains to 0%
397 * If charging: Time left before battery gets to maximum
401 if (my_cur_info
.battery_time
>= ((my_cur_info
.using_minutes
) ? 1440 : 86400) ) {
404 if (my_cur_info
.battery_time
>= 86400) {
409 * If battery_time is too large, it likely means that there is
410 * no charging or discharging going on. So just display a "null"
414 copyXPMArea(83, 106, 41, 9, 15, 7);
416 } else if (my_cur_info
.battery_time
>= 0) {
419 time_left
= (my_cur_info
.using_minutes
) ? my_cur_info
.battery_time
: my_cur_info
.battery_time
/ 60;
422 time_left
= (my_cur_info
.using_minutes
) ? my_cur_info
.battery_time
/ 60 : my_cur_info
.battery_time
/ 3600;
425 hour_left
= time_left
/ 60;
426 min_left
= time_left
% 60;
428 copyXPMArea( (hour_left
/ 10) * 7 + 5, 93, 7, 9, 21, 7); /* Show 10's (hour) */
429 copyXPMArea((hour_left
% 10) * 7 + 5, 93, 7, 9, 29, 7); /* Show 1's (hour) */
430 copyXPMArea(76, 93, 2, 9, 38, 7); /* colon */
431 copyXPMArea((min_left
/ 10) * 7 + 5, 93, 7, 9, 42, 7); /* Show 10's (min) */
432 copyXPMArea((min_left
% 10) * 7 + 5, 93, 7, 9, 50, 7); /* Show 1's (min) */
441 * Do Battery Percentage.
443 copyXPMArea(76, 81, 19, 7, 7, 34); /* Show Default % */
444 copyXPMArea(66, 31, 49, 9, 7, 21); /* Show Default Meter */
445 if (my_cur_info
.battery_percentage
== 100){
446 copyXPMArea(15, 81, 1, 7, 7, 34); /* If 100%, show 100% */
447 copyXPMArea( 5, 81, 6, 7, 9, 34);
448 copyXPMArea( 5, 81, 6, 7, 15, 34);
449 copyXPMArea(64, 81, 7, 7, 21, 34); /* Show '%' */
450 copyXPMArea(66, 42, 49, 9, 7, 21); /* Show Meter */
453 if (my_cur_info
.battery_percentage
>= 10)
454 copyXPMArea((my_cur_info
.battery_percentage
/ 10) * 6 + 4, 81, 6, 7, 9, 34); /* Show 10's */
455 copyXPMArea((my_cur_info
.battery_percentage
% 10) * 6 + 4, 81, 6, 7, 15, 34); /* Show 1's */
456 copyXPMArea(64, 81, 7, 7, 21, 34); /* Show '%' */
461 k
= my_cur_info
.battery_percentage
* 49 / 100;
462 copyXPMArea(66, 42, k
, 9, 7, 21);
464 copyXPMArea(66+k
-1, 52, 1, 9, 7+k
-1, 21);
466 copyXPMArea(66+k
, 52, 1, 9, 7+k
, 21);
474 * Update the counter. When it hits nMax, we will
475 * process /proc/apm information again.
485 * This controls the 3 LEDs
492 if (( (int)(my_cur_info
.battery_status
) == 2)
493 ||( (int)(my_cur_info
.battery_percentage
) <= CriticalLevel
)){
496 * Battery Status: Critical.
497 * Blink the red led on/off...
499 if (Toggle
||(BlinkRate
== 0.0)){
500 if (Beep
) XBell(display
, Volume
);
502 copyXPMArea(95, 68, 4, 4, 24, 51);
505 copyXPMArea(75, 68, 4, 4, 24, 51);
507 copyXPMArea(81, 68, 4, 4, 30, 51); /* turn off yellow */
508 copyXPMArea(87, 68, 4, 4, 36, 51); /* turn off green */
510 } else if (( (int)(my_cur_info
.battery_status
) == 1)
511 ||( (int)(my_cur_info
.battery_percentage
) <= LowLevel
)){
514 * Battery Status: Low.
516 copyXPMArea(75, 68, 4, 4, 24, 51); /* turn off red */
517 copyXPMArea(101, 68, 4, 4, 30, 51); /* turn ON yellow */
518 copyXPMArea(87, 68, 4, 4, 36, 51); /* turn off green */
520 } else if (( (int)( my_cur_info
.battery_status
) == 0)
521 ||( (int)(my_cur_info
.battery_percentage
) > LowLevel
)){
524 * Battery Status: High.
526 copyXPMArea(75, 68, 4, 4, 24, 51); /* turn off red */
527 copyXPMArea(81, 68, 4, 4, 30, 51); /* turn off yellow */
528 copyXPMArea(107, 68, 4, 4, 36, 51); /* turn ON green */
536 * Update the counter.
546 * This controls Critical Alerts
549 if (( (int)(my_cur_info
.battery_status
) == 2)
550 ||( (int)(my_cur_info
.battery_percentage
) <= CriticalLevel
)){
555 fp
= popen("wall", "w");
556 fprintf(fp
, "Battery is critical!. Percent: %d\n", (int)(my_cur_info
.battery_percentage
));
562 * Update the counter.
568 } else if (( (int)(my_cur_info
.battery_status
) == 1)
569 ||( (int)(my_cur_info
.battery_percentage
) <= LowLevel
)){
574 fp
= popen("wall", "w");
575 fprintf(fp
, "Battery is low. Percent: %d\n", (int)(my_cur_info
.battery_percentage
));
581 * Update the counter.
593 * Process any pending X events.
595 while(XPending(display
)){
596 XNextEvent(display
, &event
);
602 pressEvent(&event
.xbutton
);
615 * Redraw and wait for next update
646 * This routine handles button presses. Pressing the 'S' button
647 * invokes 'apm -S' to place the machine into standby mode. And
648 * pressing the 'Z' buton invokes 'apm -s' to place the machine
651 * Note: in order for users other than root to be able to run
652 * 'apm -s' and 'apm -S', you need to make apm suid (i.e.
653 * run 'chmod +s /usr/bin/apm' as root). This will allow
654 * 'normal' users to execute apm with root privilages.
657 void pressEvent(XButtonEvent
*xev
){
662 if(x
>=5 && y
>=48 && x
<=17 && y
<=58){
667 * Draw button as 'pushed'. Redraw window to show it.
668 * Call 'apm -S' to standby. Sleep for 2 seconds so that
669 * the button doesnt immediately redraw back to unpressed
670 * before the 'apm -S' takes effect.
672 copyXPMArea(5, 106, 13, 11, 5, 48);
679 } else if (x
>=46 && y
>=48 && x
<=58 && y
<=58){
684 * Draw button as 'pushed'. Redraw window to show it.
685 * Call 'apm -s' to suspend. Sleep for 2 seconds so that
686 * the button doesnt immediately redraw back to unpressed
687 * before the 'apm -s' takes effect.
689 copyXPMArea(21, 106, 13, 11, 46, 48);
709 * - Check to see if /proc/apm exists...
715 if (access(APMDEV
, R_OK
))
718 * Cannot find /proc/apm
733 * - Read in the information found in /proc/apm...
737 int apm_read(struct my_apm_info
*i
){
745 * Open /proc/apm for reading
747 if (!(str
= fopen(APMDEV
, "r")))
753 * Scan in the information....
755 fgets(buffer
, sizeof(buffer
) - 1, str
);
756 buffer
[sizeof(buffer
) - 1] = '\0';
757 sscanf(buffer
, "%s %d.%d %x %x %x %x %d%% %d %s\n",
758 (char *)i
->driver_version
,
759 &i
->apm_version_major
,
760 &i
->apm_version_minor
,
765 &i
->battery_percentage
,
770 i
->using_minutes
= !strncmp(units
, "min", 3) ? 1 : 0;
777 if (i
->driver_version
[0] == 'B') {
778 strcpy((char *)i
->driver_version
, "pre-0.7");
779 i
->apm_version_major
= 0;
780 i
->apm_version_minor
= 0;
782 i
->ac_line_status
= 0xff;
783 i
->battery_status
= 0xff;
784 i
->battery_flags
= 0xff;
785 i
->battery_percentage
= -1;
786 i
->battery_time
= -1;
787 i
->using_minutes
= 1;
792 sscanf(buffer
, "BIOS version: %d.%d", &i
->apm_version_major
, &i
->apm_version_minor
);
794 fgets(buffer
, sizeof(buffer
) - 1, str
);
795 sscanf(buffer
, "Flags: 0x%02x", &i
->apm_flags
);
797 if (i
->apm_flags
& APM_32_BIT_SUPPORT
) {
799 fgets(buffer
, sizeof(buffer
) - 1, str
);
800 fgets(buffer
, sizeof(buffer
) - 1, str
);
802 if (buffer
[0] != 'P') {
804 if (!strncmp(buffer
+4, "off line", 8)) i
->ac_line_status
= 0;
805 else if (!strncmp(buffer
+4, "on line", 7)) i
->ac_line_status
= 1;
806 else if (!strncmp(buffer
+4, "on back", 7)) i
->ac_line_status
= 2;
808 fgets(buffer
, sizeof(buffer
) - 1, str
);
809 if (!strncmp(buffer
+16, "high", 4)) i
->battery_status
= 0;
810 else if (!strncmp(buffer
+16, "low", 3)) i
->battery_status
= 1;
811 else if (!strncmp(buffer
+16, "crit", 4)) i
->battery_status
= 2;
812 else if (!strncmp(buffer
+16, "charg", 5)) i
->battery_status
= 3;
814 fgets(buffer
, sizeof(buffer
) - 1, str
);
815 if (strncmp(buffer
+14, "unknown", 7)) i
->battery_percentage
= atoi(buffer
+ 14);
817 if (i
->apm_version_major
>= 1 && i
->apm_version_minor
>= 1) {
818 fgets(buffer
, sizeof(buffer
) - 1, str
);
819 sscanf(buffer
, "Battery flag: 0x%02x", &i
->battery_flags
);
820 fgets(buffer
, sizeof(buffer
) - 1, str
);
821 if (strncmp(buffer
+14, "unknown", 7)) i
->battery_time
= atoi(buffer
+ 14);
832 * Take care of battery percentages > 100%
834 if (i
->battery_percentage
> 100) i
->battery_percentage
= -1;
842 int apm_read(apm_info_t temp_info
) {
846 if ( (fd
= open(APMDEV
, O_RDWR
)) < 0){
850 } else if ( ioctl(fd
, APMIO_GETINFO
, temp_info
) == -1 ) {
867 int apm_read(struct my_apm_info
*i
) {
872 memset(i
,0,sizeof(*i
));
873 if ((fd
= open(APMDEV
,O_RDONLY
)) < 0) {
878 if (ioctl(fd
,BATT_STATUS
,&info
) < 0) return(1);
882 i
->battery_percentage
= info
.capacity
;
883 i
->battery_time
= info
.discharge_time
;
884 i
->using_minutes
= 0;
886 /* convert to internal status:
893 switch(info
.status
) {
894 case EMPTY
: /* Battery has (effectively) no capacity */
895 i
->battery_status
= 2;
897 case LOW_CAPACITY
: /* Battery has less than 25% capacity */
898 i
->battery_status
= 1;
900 case MED_CAPACITY
: /* Battery has less than 50% capacity */
901 i
->battery_status
= 1;
903 case HIGH_CAPACITY
: /* Battery has less than 75% capacity */
904 case FULL_CAPACITY
: /* Battery has more than 75% capacity */
905 i
->battery_status
= 0;
908 i
->battery_status
= 2;
912 switch(info
.charge
) {
913 case DISCHARGE
: /* Battery is discharging (i.e. in use) */
914 i
->ac_line_status
= 0;
916 case FULL_CHARGE
: /* Battery is charging at its fastest rate */
917 case TRICKLE_CHARGE
: /* Battery is charging at a slower rate */
919 i
->ac_line_status
= 1;
923 if (i
->battery_percentage
> 100) i
->battery_percentage
= 100;
925 /* Not sure what else we can fill in right now.
926 * Relevant information is:
928 * info.id_string = type of battery (internal, external, etc)
929 * info.total = total capacity (mWhrs)
942 void ParseCMDLine(int argc
, char *argv
[]) {
947 for (i
= 1; i
< argc
; i
++) {
950 if (cmdline
[0] == '-') {
957 LAlertRate
= atof(argv
[++i
]);
958 CAlertRate
= atof(argv
[++i
]);
961 BlinkRate
= atof(argv
[++i
]);
964 CriticalLevel
= atoi(argv
[++i
]);
967 LowLevel
= atoi(argv
[++i
]);
970 UseLowColorPixmap
= 1;
974 Volume
= atoi(argv
[++i
]);
977 printf("\nwmapm version: %s\n", WMAPM_VERSION
);
979 printf("\t-display <display>\tUse alternate display.\n");
980 printf("\t-l\t\t\tUse a low-color pixmap to conserve colors on 8-bit displays.\n");
981 printf("\t-L <LowLevel>\t\tDefine level at which yellow LED turns on.\n");
982 printf("\t \t\tCriticalLevel takes precedence if LowLevel<CriticalLevel.\n");
983 printf("\t-C <CriticalLevel>\tDefine level at which red LED turns on.\n");
984 printf("\t-b <BlinkRate>\t\tBlink rate for red LED. (0 for no blinking.)\n");
985 printf("\t-B <Volume>\t\tBeep at Critical Level. Volume is between -100%% to 100%%.\n");
986 printf("\t-A <T1 T2>\t\tSend messages to users terminals when Low and critical.\n");
987 printf("\t \t\tT1 is seconds between messages when Low. T2 is seconds between messages when Critical.\n");
988 printf("\t-h\t\t\tDisplay help screen.\n\n");