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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
31 #include <X11/Xutil.h>
32 #include <X11/extensions/shape.h>
38 #define WMACPI_VER "1.99r2"
42 #include "master_low.xpm"
48 Display
*display
; /* X11 display struct */
49 int screen
; /* current screen */
50 Window root
; /* root window */
51 Window win
; /* one window */
52 Window iconwin
; /* another one */
53 Pixmap pixmap
; /* UI pixmap, window pixmap */
54 Pixmap mask
; /* mask pixmap for shape */
55 GC gc
; /* main drawing GC */
56 Pixmap text
; /* pixmap for text scroller */
57 int tw
; /* text width inside text pixmap */
58 int update
; /* need to redraw? */
59 int blink
; /* should we blink the LED? (critical battery) */
60 int bell
; /* bell on critical low, or not? */
66 int count
= 0; /* global timer variable */
67 /* extern int verbosity; */
69 /* Time for scroll updates */
70 #define DEFAULT_UPDATE 150
71 static int update_timeout
= DEFAULT_UPDATE
;
73 /* proto for local stuff */
74 static void new_window(char *name
);
75 static int open_display(char *display
);
76 static void redraw_window(void);
77 static void render_text(char *string
);
78 static void scroll_text(int x
, int y
, int width
, int tw
, int reset
);
79 static void display_percentage(int percent
);
80 static void display_time(int minutes
);
82 #define copy_xpm_area(x, y, w, h, dx, dy) \
84 XCopyArea(dockapp->display, dockapp->pixmap, dockapp->pixmap, \
85 dockapp->gc, 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, 13, 31, 11, 7, 32);
125 static void redraw_window(void)
127 if (dockapp
->update
) {
128 XCopyArea(dockapp
->display
, dockapp
->pixmap
, dockapp
->iconwin
,
129 dockapp
->gc
, 0, 0, 64, 64, 0, 0);
130 XCopyArea(dockapp
->display
, dockapp
->pixmap
, dockapp
->win
,
131 dockapp
->gc
, 0, 0, 64, 64, 0, 0);
136 static void new_window(char *name
)
141 XSizeHints sizehints
;
142 XClassHint classhint
;
145 dockapp
->screen
= DefaultScreen(dockapp
->display
);
146 dockapp
->root
= DefaultRootWindow(dockapp
->display
);
148 sizehints
.flags
= USSize
| USPosition
;
149 sizehints
.width
= 64;
150 sizehints
.height
= 64;
152 fg
= BlackPixel(dockapp
->display
, dockapp
->screen
);
153 bg
= WhitePixel(dockapp
->display
, dockapp
->screen
);
155 dockapp
->win
= XCreateSimpleWindow(dockapp
->display
, dockapp
->root
,
156 0, 0, sizehints
.width
,
157 sizehints
.height
, 1, fg
, bg
);
159 XCreateSimpleWindow(dockapp
->display
, dockapp
->win
, 0, 0,
160 sizehints
.width
, sizehints
.height
, 1, fg
, bg
);
162 XSetWMNormalHints(dockapp
->display
, dockapp
->win
, &sizehints
);
163 classhint
.res_name
= name
;
164 classhint
.res_class
= name
;
165 XSetClassHint(dockapp
->display
, dockapp
->win
, &classhint
);
167 XSelectInput(dockapp
->display
, dockapp
->win
,
168 ExposureMask
| ButtonPressMask
| ButtonReleaseMask
|
169 StructureNotifyMask
);
170 XSelectInput(dockapp
->display
, dockapp
->iconwin
,
171 ExposureMask
| ButtonPressMask
| ButtonReleaseMask
|
172 StructureNotifyMask
);
174 XStoreName(dockapp
->display
, dockapp
->win
, name
);
175 XSetIconName(dockapp
->display
, dockapp
->win
, name
);
177 gcval
.foreground
= fg
;
178 gcval
.background
= bg
;
179 gcval
.graphics_exposures
= False
;
182 XCreateGC(dockapp
->display
, dockapp
->win
,
183 GCForeground
| GCBackground
| GCGraphicsExposures
,
186 attr
.exactColors
= 0;
187 attr
.alloc_close_colors
= 1;
188 attr
.closeness
= 1L << 15;
189 attr
.valuemask
= XpmExactColors
| XpmAllocCloseColors
| XpmCloseness
;
190 if (XpmCreatePixmapFromData(dockapp
->display
, dockapp
->win
,
191 master_xpm
, &dockapp
->pixmap
,
192 &dockapp
->mask
, &attr
) != XpmSuccess
) {
193 pfatal("FATAL: Not enough colors for main pixmap!\n");
197 /* text area is 318x7, or 53 characters long */
198 dockapp
->text
= XCreatePixmap(dockapp
->display
, dockapp
->win
, 318, 7,
199 DefaultDepth(dockapp
->display
,
201 if (!dockapp
->text
) {
202 pfatal("FATAL: Cannot create text scroll pixmap!\n");
206 XShapeCombineMask(dockapp
->display
, dockapp
->win
, ShapeBounding
, 0, 0,
207 dockapp
->mask
, ShapeSet
);
208 XShapeCombineMask(dockapp
->display
, dockapp
->iconwin
, ShapeBounding
, 0,
209 0, dockapp
->mask
, ShapeSet
);
211 wmhints
.initial_state
= WithdrawnState
;
212 wmhints
.flags
= StateHint
;
213 wmhints
.icon_window
= dockapp
->iconwin
;
214 wmhints
.icon_x
= sizehints
.x
;
215 wmhints
.icon_y
= sizehints
.y
;
216 wmhints
.window_group
= dockapp
->win
;
218 StateHint
| IconWindowHint
| IconPositionHint
| WindowGroupHint
;
219 XSetWMHints(dockapp
->display
, dockapp
->win
, &wmhints
);
221 XMapWindow(dockapp
->display
, dockapp
->win
);
224 static void render_text(char *string
)
228 if (strlen(string
) > 53)
231 /* prepare the text area by clearing it */
232 for (i
= 0; i
< 54; i
++) {
233 XCopyArea(dockapp
->display
, dockapp
->pixmap
, dockapp
->text
,
234 dockapp
->gc
, 133, 57, 6, 8, i
* 6, 0);
238 for (i
= 0; string
[i
]; i
++) {
239 c
= toupper(string
[i
]);
240 if (c
>= 'A' && c
<= 'Z') { /* letter */
242 XCopyArea(dockapp
->display
, dockapp
->pixmap
, dockapp
->text
,
243 dockapp
->gc
, c
* 6, 67, 6, 7, k
, 0);
244 } else if (c
>= '0' && c
<= '9') { /* number */
246 XCopyArea(dockapp
->display
, dockapp
->pixmap
, dockapp
->text
,
247 dockapp
->gc
, c
* 6 + 66, 58, 6, 7, k
, 0);
248 } else if (c
== '.') {
249 XCopyArea(dockapp
->display
, dockapp
->pixmap
, dockapp
->text
,
250 dockapp
->gc
, 140, 58, 6, 7, k
, 0);
251 } else if (c
== '-') {
252 XCopyArea(dockapp
->display
, dockapp
->pixmap
, dockapp
->text
,
253 dockapp
->gc
, 126, 58, 6, 7, k
, 0);
257 dockapp
->tw
= k
; /* length of text segment */
258 /* re-scroll the message */
259 scroll_text(6, 50, 52, dockapp
->tw
, 1);
260 /* reset the scroll repeat counter */
264 static int open_display(char *display
)
266 dockapp
->display
= XOpenDisplay(display
);
267 if (!dockapp
->display
) {
268 perr("Unable to open display '%s'\n", display
);
274 static void scroll_text(int x
, int y
, int width
, int tw
, int reset
)
276 static int pos
, first
, stop
;
282 XCopyArea(dockapp
->display
, dockapp
->pixmap
, dockapp
->text
,
283 dockapp
->gc
, 0, 0, width
, 7, x
, y
);
290 if ((first
== 0) && pos
== 0) {
295 if (pos
== (0 - tw
- 2)) {
304 copy_xpm_area(66, 9, pos
, 7, x
, y
); /* clear */
305 XCopyArea(dockapp
->display
, dockapp
->text
, dockapp
->pixmap
,
306 dockapp
->gc
, 0, 0, width
- pos
, 7, x
+ pos
, y
);
307 } else { /* don't need to clear, already in text */
308 XCopyArea(dockapp
->display
, dockapp
->text
, dockapp
->pixmap
,
309 dockapp
->gc
, abs(pos
), 0, width
, 7, x
, y
);
314 static void display_percentage(int percent
)
317 static unsigned int obar
;
331 if (percent
< 100) { /* 0 - 99 */
332 copy_xpm_area(95, 48, 8, 7, 37, 17);
334 copy_xpm_area((percent
/ 10) * 6 + 67, 28, 5, 7, 40, 17);
335 copy_xpm_area((percent
% 10) * 6 + 67, 28, 5, 7, 46, 17);
337 copy_xpm_area(95, 37, 21, 9, 37, 16); /* 100% */
340 bar
= percent
/ 1.8518;
345 copy_xpm_area(66, 0, bar
, 8, 5, 5);
347 copy_xpm_area(66 + bar
, 18, 54 - bar
, 8, bar
+ 5, 5);
351 static void display_time(int minutes
)
353 static int ohour
= -1, omin
= -1;
356 if (minutes
<= 0) { /* error - clear the display */
357 invalid_time_display();
362 /* render time on the display */
364 /* our display area only fits %2d:%2d, so we need to make sure
365 * what we're displaying will fit in those constraints. I don't
366 * think we're likely to see any batteries that do more than
367 * 100 hours any time soon, so it's fairly safe. */
374 if (hour
== ohour
&& min
== omin
)
378 copy_xpm_area(tmp
* 7 + 1, 76, 6, 11, 7, 32);
380 copy_xpm_area(tmp
* 7 + 1, 76, 6, 11, 14, 32);
382 copy_xpm_area(tmp
* 7 + 1, 76, 6, 11, 25, 32);
384 copy_xpm_area(tmp
* 7 + 1, 76, 6, 11, 32, 32);
385 copy_xpm_area(71, 76, 3, 11, 21, 32);
391 * The reworked state handling stuff.
394 /* set the current state of the power panel */
401 static void really_blink_power_glyph(void)
403 static int counter
= 0;
406 display_power_glyph();
407 else if (counter
== 20)
409 else if (counter
> 30)
414 static void blink_power_glyph(void)
417 really_blink_power_glyph();
420 static void really_blink_battery_glyph(void)
422 static int counter
= 0;
425 display_battery_glyph();
426 else if (counter
== 20)
427 kill_battery_glyph();
428 else if (counter
> 30)
433 static void blink_battery_glyph(void)
436 really_blink_battery_glyph();
439 static void set_power_panel(void)
441 enum panel_states power
= PS_NULL
;
442 battery_t
*binfo
= globals
->binfo
;
443 adapter_t
*ap
= &globals
->adapter
;
445 if (ap
->power
== AC
) {
446 if (power
!= PS_AC
) {
448 kill_battery_glyph();
449 display_power_glyph();
451 } else if (ap
->power
== BATT
) {
452 if (power
!= PS_BATT
) {
455 display_battery_glyph();
459 if (binfo
->charge_state
== CHARGE
)
462 if (binfo
->state
== CRIT
)
463 blink_battery_glyph();
465 if (binfo
->state
== HARD_CRIT
) {
466 really_blink_battery_glyph();
467 /* we only do this here because it'd be obnoxious to
468 * do it anywhere else. */
470 XBell(dockapp
->display
, 100);
476 * The message that needs to be displayed needs to be decided
477 * according to a heirarchy: a message like not present needs to take
478 * precedence over a global thing like the current power status, and
479 * something like a low battery warning should take precedence over
480 * the "on battery" message. Likewise, a battery charging message
481 * needs to take precedence over the on ac power message. The other
482 * question is how much of a precedence local messages should take
483 * over global ones . . .
485 * So, there are three possible sets of messages: not present, on-line
486 * and off-line messages. We need to decide which of those sets is
487 * appropriate right now, and then decide within them.
490 M_NP
, /* not present */
491 M_AC
, /* on ac power */
492 M_CH
, /* battery charging */
493 M_BATT
, /* on battery */
494 M_LB
, /* low battery */
495 M_CB
, /* critical low battery */
496 M_HCB
, /* battery reported critical capacity state */
497 M_NULL
, /* empty starting state */
500 static void set_message(void)
502 static enum messages state
= M_NULL
;
503 battery_t
*binfo
= globals
->binfo
;
504 adapter_t
*ap
= &globals
->adapter
;
506 /* battery not present case */
507 if (!binfo
->present
) {
510 render_text("not present");
512 } else if (ap
->power
== AC
) {
513 if (binfo
->charge_state
== CHARGE
) {
516 update_timeout
= DEFAULT_UPDATE
;
517 render_text("battery charging");
522 update_timeout
= DEFAULT_UPDATE
;
523 render_text("on ac power");
527 if (binfo
->state
== CRIT
) {
531 render_text("critical low battery");
533 } else if (binfo
->state
== HARD_CRIT
) {
534 if (state
!= M_HCB
) {
537 render_text("hard critical low battery");
539 } else if (binfo
->state
== LOW
) {
542 update_timeout
= 100;
543 render_text("low battery");
546 if (state
!= M_BATT
) {
548 update_timeout
= DEFAULT_UPDATE
;
549 render_text("on battery");
555 void set_time_display(void)
557 battery_t
*binfo
= &batteries
[battery_no
];
559 if (binfo
->charge_state
== CHARGE
)
560 display_time(binfo
->charge_time
);
561 else if (binfo
->charge_state
== DISCHARGE
)
562 display_time(globals
->rtime
);
564 invalid_time_display();
568 * This should really be fixed so that it can handle more than two batteries.
573 copy_xpm_area(118, 38, 15, 15, 44, 30);
578 copy_xpm_area(136, 38, 15, 15, 44, 30);
581 void set_batt_id_area(int bno
)
593 void usage(char *name
)
595 printf("%s - help\t\t[simon@dreamcraft.com.au]\n\n"
596 "-d display\t\tdisplay on remote display <display>\n"
597 "-b\t\t\tmake noise when battery is critical low (beep)\n"
598 "-c value\t\tset critical low alarm at <value> percent\n"
599 "\t\t\t(default: 10 percent)\n"
600 "-m <battery number>\tbattery number to monitor\n"
601 "-s <sample rate>\trate at which to sample battery status\n"
602 "\t\t\tdefault 100 (once every three seconds)\n"
603 "-n\t\t\tdo not blink\n"
604 "-w\t\t\trun in command line mode\n"
605 "-a <samples>\t\tsamples to average over (cli mode only)\n"
606 "-v\t\t\tincrease verbosity\n"
607 "\t\t\tcan be used multiple times to increase verbosity further\n"
608 "-h\t\t\tdisplay this help\n",
612 void print_version(void)
614 printf("wmacpi version %s\n", WMACPI_VER
);
615 printf(" Using libacpi version %s\n", LIBACPI_VER
);
618 void cli_wmacpi(int samples
)
620 int i
, j
, sleep_time
= 0;
624 printf("%d\n", samples
);
626 sleep_time
= 1000000/samples
;
628 /* we want to acquire samples over some period of time, so . . . */
629 for(i
= 0; i
< samples
+ 2; i
++) {
630 for(j
= 0; j
< batt_count
; j
++)
631 acquire_batt_info(j
);
632 acquire_global_info();
636 ap
= &globals
->adapter
;
637 if(ap
->power
== AC
) {
638 printf("On AC Power");
639 for(i
= 0; i
< batt_count
; i
++) {
640 binfo
= &batteries
[i
];
641 if(binfo
->present
&& (binfo
->charge_state
== CHARGE
)) {
642 printf("; Battery %s charging", binfo
->name
);
643 printf(", currently at %2d%%", binfo
->percentage
);
644 if(binfo
->charge_time
>= 0)
645 printf(", %2d:%02d remaining",
646 binfo
->charge_time
/60,
647 binfo
->charge_time
%60);
651 } else if(ap
->power
== BATT
) {
652 printf("On Battery");
653 for(i
= 0; i
< batt_count
; i
++) {
654 binfo
= &batteries
[i
];
655 if(binfo
->present
&& (binfo
->percentage
>= 0))
656 printf(", Battery %s at %d%%", binfo
->name
,
659 if(globals
->rtime
>= 0)
660 printf("; %d:%02d remaining", globals
->rtime
/60,
667 int main(int argc
, char **argv
)
669 char *display
= NULL
;
672 int cli
= 0, samples
= 1;
673 int samplerate
= 100;
676 dockapp
= calloc(1, sizeof(Dockapp
));
677 globals
= calloc(1, sizeof(global_t
));
681 globals
->crit_level
= 10;
684 /* parse command-line options */
685 while ((ch
= getopt(argc
, argv
, "d:c:m:s:a:hnwbvV")) != EOF
) {
689 globals
->crit_level
= atoi(optarg
);
690 if ((globals
->crit_level
< 0) || (globals
->crit_level
> 100)) {
691 fprintf(stderr
, "Please use values between 0 and 100%%\n");
692 globals
->crit_level
= 10;
693 fprintf(stderr
, "Using default value of 10%%\n");
699 display
= strdup(optarg
);
703 battery_no
= atoi(optarg
);
704 if (battery_no
>= MAXBATT
) {
705 fprintf(stderr
, "Please specify a battery number below %d\n",
709 if (battery_no
> batt_count
) {
710 fprintf(stderr
, "Battery %d does not appear to be installed\n",
714 fprintf(stderr
, "Monitoring battery %d\n", battery_no
);
719 samplerate
= atoi(optarg
);
720 if (samplerate
== 0) samplerate
= 1;
721 if (samplerate
> 3000) samplerate
= 3000;
744 samples
= atoi(optarg
);
745 if(samples
> 1000 || samples
<= 0) {
746 fprintf(stderr
, "Please specify a reasonable number of samples\n");
761 /* see if whatever we want to use is supported */
763 /* power_init functions handle printing error messages */
766 /* check for cli mode */
774 /* open local or command-line specified display */
775 if (open_display(display
))
778 /* make new dockapp window */
779 /* Don't even /think/ of asking me why, but if I set the window name to
780 * "acpi", the app refuses to dock properly - it's just plain /weird/.
781 * So, wmacpi it is . . . */
782 new_window("wmacpi");
784 /* get initial statistics */
786 binfo
= &batteries
[battery_no
];
787 globals
->binfo
= binfo
;
788 pinfo("monitoring battery %s\n", binfo
->name
);
789 clear_time_display();
792 set_batt_id_area(battery_no
);
797 while (XPending(dockapp
->display
)) {
798 XNextEvent(dockapp
->display
, &event
);
799 switch (event
.type
) {
803 while (XCheckTypedEvent(dockapp
->display
, Expose
, &event
));
807 XCloseDisplay(dockapp
->display
);
813 /* cycle through the known batteries. */
815 battery_no
= battery_no
% batt_count
;
816 globals
->binfo
= &batteries
[battery_no
];
817 binfo
= globals
->binfo
;
818 pinfo("changing to monitor battery %d\n", battery_no
+ 1);
819 set_batt_id_area(battery_no
);
824 /* XXX: some laptops have problems with sampling the battery
825 * regularly - apparently, the BIOS disables interrupts while
826 * reading from the battery, which is generally on a slow bus
827 * and is a slow device, so you get significant periods without
828 * interrupts. This causes interactivity to suffer . . .
830 * My proposed workaround is to allow the user to set the sample
831 * rate - it defaults to ten, but can be set lower (or higher).
833 * The only problem with this is that we need to sample less
834 * frequently, while still allowing the app to update normally.
835 * That means calling redraw_window() and all the set_*() functions
836 * normally, but only calling acquire_all_info() every so often.
837 * As it stands, we only call acquire_all_info() once every three
838 * seconds (once every thirty updates) . . . I'm not entirely sure
839 * /how/ this could cause interactivity problems, but hey . . .
841 * So, given the base rate of once every three seconds, we want to
842 * change this test to . . . */
843 if (update
++ == (3000/samplerate
)) {
848 if (count
++ == update_timeout
) {
849 scroll_text(6, 50, 52, dockapp
->tw
, 1);
853 /* the old code had some kind of weird crap with timers and the like.
854 * As far as I can tell, it's meaningless - the time we want to display
855 * is the time calculated from the remaining capacity, as per the
856 * ACPI spec. The only thing I'd change is the handling of a charging
857 * state: my best guess, based on the behaviour I'm seeing with my
858 * Lifebook, is that the present rate value when charging is the rate
859 * at which the batteries are being charged, which would mean I'd just
860 * need to reverse the rtime calculation to be able to work out how
861 * much time remained until the batteries were fully charged . . .
862 * That would be rather useful, though given it would vary rather a lot
863 * it seems likely that it'd be little more than a rough guesstimate. */
867 display_percentage(binfo
->percentage
);
868 scroll_text(6, 50, 52, dockapp
->tw
, 0);
870 /* redraw_window, if anything changed - determined inside