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? */
61 int scroll
; /* scroll message text? */
67 int count
= 0; /* global timer variable */
68 /* extern int verbosity; */
70 /* Time for scroll updates */
71 #define DEFAULT_UPDATE 150
72 static int update_timeout
= DEFAULT_UPDATE
;
74 /* proto for local stuff */
75 static void new_window(char *name
);
76 static int open_display(char *display
);
77 static void redraw_window(void);
78 static void render_text(char *string
);
79 static void scroll_text(int x
, int y
, int width
, int tw
, int reset
);
80 static void display_percentage(int percent
);
81 static void display_time(int minutes
);
83 #define copy_xpm_area(x, y, w, h, dx, dy) \
85 XCopyArea(dockapp->display, dockapp->pixmap, dockapp->pixmap, \
86 dockapp->gc, 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, 13, 31, 11, 7, 32);
126 static void redraw_window(void)
128 if (dockapp
->update
) {
129 XCopyArea(dockapp
->display
, dockapp
->pixmap
, dockapp
->iconwin
,
130 dockapp
->gc
, 0, 0, 64, 64, 0, 0);
131 XCopyArea(dockapp
->display
, dockapp
->pixmap
, dockapp
->win
,
132 dockapp
->gc
, 0, 0, 64, 64, 0, 0);
137 static void new_window(char *name
)
142 XSizeHints sizehints
;
143 XClassHint classhint
;
146 dockapp
->screen
= DefaultScreen(dockapp
->display
);
147 dockapp
->root
= DefaultRootWindow(dockapp
->display
);
149 sizehints
.flags
= USSize
| USPosition
;
150 sizehints
.width
= 64;
151 sizehints
.height
= 64;
153 fg
= BlackPixel(dockapp
->display
, dockapp
->screen
);
154 bg
= WhitePixel(dockapp
->display
, dockapp
->screen
);
156 dockapp
->win
= XCreateSimpleWindow(dockapp
->display
, dockapp
->root
,
157 0, 0, sizehints
.width
,
158 sizehints
.height
, 1, fg
, bg
);
160 XCreateSimpleWindow(dockapp
->display
, dockapp
->win
, 0, 0,
161 sizehints
.width
, sizehints
.height
, 1, fg
, bg
);
163 XSetWMNormalHints(dockapp
->display
, dockapp
->win
, &sizehints
);
164 classhint
.res_name
= name
;
165 classhint
.res_class
= name
;
166 XSetClassHint(dockapp
->display
, dockapp
->win
, &classhint
);
168 XSelectInput(dockapp
->display
, dockapp
->win
,
169 ExposureMask
| ButtonPressMask
| ButtonReleaseMask
|
170 StructureNotifyMask
);
171 XSelectInput(dockapp
->display
, dockapp
->iconwin
,
172 ExposureMask
| ButtonPressMask
| ButtonReleaseMask
|
173 StructureNotifyMask
);
175 XStoreName(dockapp
->display
, dockapp
->win
, name
);
176 XSetIconName(dockapp
->display
, dockapp
->win
, name
);
178 gcval
.foreground
= fg
;
179 gcval
.background
= bg
;
180 gcval
.graphics_exposures
= False
;
183 XCreateGC(dockapp
->display
, dockapp
->win
,
184 GCForeground
| GCBackground
| GCGraphicsExposures
,
187 attr
.exactColors
= 0;
188 attr
.alloc_close_colors
= 1;
189 attr
.closeness
= 1L << 15;
190 attr
.valuemask
= XpmExactColors
| XpmAllocCloseColors
| XpmCloseness
;
191 if (XpmCreatePixmapFromData(dockapp
->display
, dockapp
->win
,
192 master_xpm
, &dockapp
->pixmap
,
193 &dockapp
->mask
, &attr
) != XpmSuccess
) {
194 pfatal("FATAL: Not enough colors for main pixmap!\n");
198 /* text area is 318x7, or 53 characters long */
199 dockapp
->text
= XCreatePixmap(dockapp
->display
, dockapp
->win
, 318, 7,
200 DefaultDepth(dockapp
->display
,
202 if (!dockapp
->text
) {
203 pfatal("FATAL: Cannot create text scroll pixmap!\n");
207 XShapeCombineMask(dockapp
->display
, dockapp
->win
, ShapeBounding
, 0, 0,
208 dockapp
->mask
, ShapeSet
);
209 XShapeCombineMask(dockapp
->display
, dockapp
->iconwin
, ShapeBounding
, 0,
210 0, dockapp
->mask
, ShapeSet
);
212 wmhints
.initial_state
= WithdrawnState
;
213 wmhints
.flags
= StateHint
;
214 wmhints
.icon_window
= dockapp
->iconwin
;
215 wmhints
.icon_x
= sizehints
.x
;
216 wmhints
.icon_y
= sizehints
.y
;
217 wmhints
.window_group
= dockapp
->win
;
219 StateHint
| IconWindowHint
| IconPositionHint
| WindowGroupHint
;
220 XSetWMHints(dockapp
->display
, dockapp
->win
, &wmhints
);
222 XMapWindow(dockapp
->display
, dockapp
->win
);
225 static void render_text(char *string
)
229 /* drop out immediately if scrolling is disabled - we don't render
230 * any text at all, since there's not much else we could do
231 * sensibly without scrolling. */
232 if (!dockapp
->scroll
)
235 if (strlen(string
) > 53)
238 /* prepare the text area by clearing it */
239 for (i
= 0; i
< 54; i
++) {
240 XCopyArea(dockapp
->display
, dockapp
->pixmap
, dockapp
->text
,
241 dockapp
->gc
, 133, 57, 6, 8, i
* 6, 0);
245 for (i
= 0; string
[i
]; i
++) {
246 c
= toupper(string
[i
]);
247 if (c
>= 'A' && c
<= 'Z') { /* letter */
249 XCopyArea(dockapp
->display
, dockapp
->pixmap
, dockapp
->text
,
250 dockapp
->gc
, c
* 6, 67, 6, 7, k
, 0);
251 } else if (c
>= '0' && c
<= '9') { /* number */
253 XCopyArea(dockapp
->display
, dockapp
->pixmap
, dockapp
->text
,
254 dockapp
->gc
, c
* 6 + 66, 58, 6, 7, k
, 0);
255 } else if (c
== '.') {
256 XCopyArea(dockapp
->display
, dockapp
->pixmap
, dockapp
->text
,
257 dockapp
->gc
, 140, 58, 6, 7, k
, 0);
258 } else if (c
== '-') {
259 XCopyArea(dockapp
->display
, dockapp
->pixmap
, dockapp
->text
,
260 dockapp
->gc
, 126, 58, 6, 7, k
, 0);
264 dockapp
->tw
= k
; /* length of text segment */
265 /* re-scroll the message */
266 scroll_text(6, 50, 52, dockapp
->tw
, 1);
267 /* reset the scroll repeat counter */
271 static int open_display(char *display
)
273 dockapp
->display
= XOpenDisplay(display
);
274 if (!dockapp
->display
) {
275 perr("Unable to open display '%s'\n", display
);
281 static void scroll_text(int x
, int y
, int width
, int tw
, int reset
)
283 static int pos
, first
, stop
;
285 if (!dockapp
->scroll
)
292 XCopyArea(dockapp
->display
, dockapp
->pixmap
, dockapp
->text
,
293 dockapp
->gc
, 0, 0, width
, 7, x
, y
);
300 if ((first
== 0) && pos
== 0) {
305 if (pos
== (0 - tw
- 2)) {
314 copy_xpm_area(66, 9, pos
, 7, x
, y
); /* clear */
315 XCopyArea(dockapp
->display
, dockapp
->text
, dockapp
->pixmap
,
316 dockapp
->gc
, 0, 0, width
- pos
, 7, x
+ pos
, y
);
317 } else { /* don't need to clear, already in text */
318 XCopyArea(dockapp
->display
, dockapp
->text
, dockapp
->pixmap
,
319 dockapp
->gc
, abs(pos
), 0, width
, 7, x
, y
);
324 static void display_percentage(int percent
)
327 static unsigned int obar
;
341 if (percent
< 100) { /* 0 - 99 */
342 copy_xpm_area(95, 48, 8, 7, 37, 17);
344 copy_xpm_area((percent
/ 10) * 6 + 67, 28, 5, 7, 40, 17);
345 copy_xpm_area((percent
% 10) * 6 + 67, 28, 5, 7, 46, 17);
347 copy_xpm_area(95, 37, 21, 9, 37, 16); /* 100% */
350 bar
= percent
/ 1.8518;
355 copy_xpm_area(66, 0, bar
, 8, 5, 5);
357 copy_xpm_area(66 + bar
, 18, 54 - bar
, 8, bar
+ 5, 5);
361 static void display_time(int minutes
)
363 static int ohour
= -1, omin
= -1;
366 if (minutes
<= 0) { /* error - clear the display */
367 invalid_time_display();
372 /* render time on the display */
374 /* our display area only fits %2d:%2d, so we need to make sure
375 * what we're displaying will fit in those constraints. I don't
376 * think we're likely to see any batteries that do more than
377 * 100 hours any time soon, so it's fairly safe. */
384 if (hour
== ohour
&& min
== omin
)
388 copy_xpm_area(tmp
* 7 + 1, 76, 6, 11, 7, 32);
390 copy_xpm_area(tmp
* 7 + 1, 76, 6, 11, 14, 32);
392 copy_xpm_area(tmp
* 7 + 1, 76, 6, 11, 25, 32);
394 copy_xpm_area(tmp
* 7 + 1, 76, 6, 11, 32, 32);
395 copy_xpm_area(71, 76, 3, 11, 21, 32);
401 * The reworked state handling stuff.
404 /* set the current state of the power panel */
411 static void really_blink_power_glyph(void)
413 static int counter
= 0;
416 display_power_glyph();
417 else if (counter
== 20)
419 else if (counter
> 30)
424 static void blink_power_glyph(void)
427 really_blink_power_glyph();
430 static void really_blink_battery_glyph(void)
432 static int counter
= 0;
435 display_battery_glyph();
436 else if (counter
== 20)
437 kill_battery_glyph();
438 else if (counter
> 30)
443 static void blink_battery_glyph(void)
446 really_blink_battery_glyph();
449 static void set_power_panel(void)
451 enum panel_states power
= PS_NULL
;
452 battery_t
*binfo
= globals
->binfo
;
453 adapter_t
*ap
= &globals
->adapter
;
455 if (ap
->power
== AC
) {
456 if (power
!= PS_AC
) {
458 kill_battery_glyph();
459 display_power_glyph();
461 } else if (ap
->power
== BATT
) {
462 if (power
!= PS_BATT
) {
465 display_battery_glyph();
469 if (binfo
->charge_state
== CHARGE
)
472 if (binfo
->state
== CRIT
)
473 blink_battery_glyph();
475 if (binfo
->state
== HARD_CRIT
) {
476 really_blink_battery_glyph();
477 /* we only do this here because it'd be obnoxious to
478 * do it anywhere else. */
480 XBell(dockapp
->display
, 100);
486 * The message that needs to be displayed needs to be decided
487 * according to a heirarchy: a message like not present needs to take
488 * precedence over a global thing like the current power status, and
489 * something like a low battery warning should take precedence over
490 * the "on battery" message. Likewise, a battery charging message
491 * needs to take precedence over the on ac power message. The other
492 * question is how much of a precedence local messages should take
493 * over global ones . . .
495 * So, there are three possible sets of messages: not present, on-line
496 * and off-line messages. We need to decide which of those sets is
497 * appropriate right now, and then decide within them.
500 M_NP
, /* not present */
501 M_AC
, /* on ac power */
502 M_CH
, /* battery charging */
503 M_BATT
, /* on battery */
504 M_LB
, /* low battery */
505 M_CB
, /* critical low battery */
506 M_HCB
, /* battery reported critical capacity state */
507 M_NULL
, /* empty starting state */
510 static void set_message(void)
512 static enum messages state
= M_NULL
;
513 battery_t
*binfo
= globals
->binfo
;
514 adapter_t
*ap
= &globals
->adapter
;
516 /* battery not present case */
517 if (!binfo
->present
) {
520 render_text("not present");
522 } else if (ap
->power
== AC
) {
523 if (binfo
->charge_state
== CHARGE
) {
526 update_timeout
= DEFAULT_UPDATE
;
527 render_text("battery charging");
532 update_timeout
= DEFAULT_UPDATE
;
533 render_text("on ac power");
537 if (binfo
->state
== CRIT
) {
541 render_text("critical low battery");
543 } else if (binfo
->state
== HARD_CRIT
) {
544 if (state
!= M_HCB
) {
547 render_text("hard critical low battery");
549 } else if (binfo
->state
== LOW
) {
552 update_timeout
= 100;
553 render_text("low battery");
556 if (state
!= M_BATT
) {
558 update_timeout
= DEFAULT_UPDATE
;
559 render_text("on battery");
565 void set_time_display(void)
567 battery_t
*binfo
= &batteries
[battery_no
];
569 if (binfo
->charge_state
== CHARGE
)
570 display_time(binfo
->charge_time
);
571 else if (binfo
->charge_state
== DISCHARGE
)
572 display_time(globals
->rtime
);
574 invalid_time_display();
578 * This should really be fixed so that it can handle more than two batteries.
583 copy_xpm_area(118, 38, 15, 15, 44, 30);
588 copy_xpm_area(136, 38, 15, 15, 44, 30);
591 void set_batt_id_area(int bno
)
603 void usage(char *name
)
605 printf("%s - help\t\t[simon@dreamcraft.com.au]\n\n"
606 "-d display\t\tdisplay on remote display <display>\n"
607 "-b\t\t\tmake noise when battery is critical low (beep)\n"
608 "-r\t\t\tdisable scrolling message\n"
609 "-c value\t\tset critical low alarm at <value> percent\n"
610 "\t\t\t(default: 10 percent)\n"
611 "-m <battery number>\tbattery number to monitor\n"
612 "-s <sample rate>\trate at which to sample battery status\n"
613 "\t\t\tdefault 100 (once every three seconds)\n"
614 "-n\t\t\tdo not blink\n"
615 "-w\t\t\trun in command line mode\n"
616 "-a <samples>\t\tsamples to average over (cli mode only)\n"
617 "-v\t\t\tincrease verbosity\n"
618 "\t\t\tcan be used multiple times to increase verbosity further\n"
619 "-h\t\t\tdisplay this help\n",
623 void print_version(void)
625 printf("wmacpi version %s\n", WMACPI_VER
);
626 printf(" Using libacpi version %s\n", LIBACPI_VER
);
629 void cli_wmacpi(int samples
)
631 int i
, j
, sleep_time
= 0;
635 printf("%d\n", samples
);
637 sleep_time
= 1000000/samples
;
639 /* we want to acquire samples over some period of time, so . . . */
640 for(i
= 0; i
< samples
+ 2; i
++) {
641 for(j
= 0; j
< batt_count
; j
++)
642 acquire_batt_info(j
);
643 acquire_global_info();
647 ap
= &globals
->adapter
;
648 if(ap
->power
== AC
) {
649 printf("On AC Power");
650 for(i
= 0; i
< batt_count
; i
++) {
651 binfo
= &batteries
[i
];
652 if(binfo
->present
&& (binfo
->charge_state
== CHARGE
)) {
653 printf("; Battery %s charging", binfo
->name
);
654 printf(", currently at %2d%%", binfo
->percentage
);
655 if(binfo
->charge_time
>= 0)
656 printf(", %2d:%02d remaining",
657 binfo
->charge_time
/60,
658 binfo
->charge_time
%60);
662 } else if(ap
->power
== BATT
) {
663 printf("On Battery");
664 for(i
= 0; i
< batt_count
; i
++) {
665 binfo
= &batteries
[i
];
666 if(binfo
->present
&& (binfo
->percentage
>= 0))
667 printf(", Battery %s at %d%%", binfo
->name
,
670 if(globals
->rtime
>= 0)
671 printf("; %d:%02d remaining", globals
->rtime
/60,
678 int main(int argc
, char **argv
)
680 char *display
= NULL
;
683 int cli
= 0, samples
= 1;
684 int samplerate
= 100;
687 dockapp
= calloc(1, sizeof(Dockapp
));
688 globals
= calloc(1, sizeof(global_t
));
693 globals
->crit_level
= 10;
696 /* parse command-line options */
697 while ((ch
= getopt(argc
, argv
, "d:c:m:s:a:hnwbrvV")) != EOF
) {
701 globals
->crit_level
= atoi(optarg
);
702 if ((globals
->crit_level
< 0) || (globals
->crit_level
> 100)) {
703 fprintf(stderr
, "Please use values between 0 and 100%%\n");
704 globals
->crit_level
= 10;
705 fprintf(stderr
, "Using default value of 10%%\n");
711 display
= strdup(optarg
);
715 battery_no
= atoi(optarg
);
716 if (battery_no
>= MAXBATT
) {
717 fprintf(stderr
, "Please specify a battery number below %d\n",
721 if (battery_no
> batt_count
) {
722 fprintf(stderr
, "Battery %d does not appear to be installed\n",
726 fprintf(stderr
, "Monitoring battery %d\n", battery_no
);
731 samplerate
= atoi(optarg
);
732 if (samplerate
== 0) samplerate
= 1;
733 if (samplerate
> 3000) samplerate
= 3000;
756 samples
= atoi(optarg
);
757 if(samples
> 1000 || samples
<= 0) {
758 fprintf(stderr
, "Please specify a reasonable number of samples\n");
767 printf("disabling scroll\n");
777 /* see if whatever we want to use is supported */
779 /* power_init functions handle printing error messages */
782 /* check for cli mode */
790 /* open local or command-line specified display */
791 if (open_display(display
))
794 /* make new dockapp window */
795 /* Don't even /think/ of asking me why, but if I set the window name to
796 * "acpi", the app refuses to dock properly - it's just plain /weird/.
797 * So, wmacpi it is . . . */
798 new_window("wmacpi");
800 /* get initial statistics */
802 binfo
= &batteries
[battery_no
];
803 globals
->binfo
= binfo
;
804 pinfo("monitoring battery %s\n", binfo
->name
);
805 clear_time_display();
808 set_batt_id_area(battery_no
);
813 while (XPending(dockapp
->display
)) {
814 XNextEvent(dockapp
->display
, &event
);
815 switch (event
.type
) {
819 while (XCheckTypedEvent(dockapp
->display
, Expose
, &event
));
823 XCloseDisplay(dockapp
->display
);
829 /* cycle through the known batteries. */
831 battery_no
= battery_no
% batt_count
;
832 globals
->binfo
= &batteries
[battery_no
];
833 binfo
= globals
->binfo
;
834 pinfo("changing to monitor battery %d\n", battery_no
+ 1);
835 set_batt_id_area(battery_no
);
840 /* XXX: some laptops have problems with sampling the battery
841 * regularly - apparently, the BIOS disables interrupts while
842 * reading from the battery, which is generally on a slow bus
843 * and is a slow device, so you get significant periods without
844 * interrupts. This causes interactivity to suffer . . .
846 * My proposed workaround is to allow the user to set the sample
847 * rate - it defaults to ten, but can be set lower (or higher).
849 * The only problem with this is that we need to sample less
850 * frequently, while still allowing the app to update normally.
851 * That means calling redraw_window() and all the set_*() functions
852 * normally, but only calling acquire_all_info() every so often.
853 * As it stands, we only call acquire_all_info() once every three
854 * seconds (once every thirty updates) . . . I'm not entirely sure
855 * /how/ this could cause interactivity problems, but hey . . .
857 * So, given the base rate of once every three seconds, we want to
858 * change this test to . . . */
859 if (update
++ == (3000/samplerate
)) {
864 if (count
++ == update_timeout
) {
865 scroll_text(6, 50, 52, dockapp
->tw
, 1);
869 /* the old code had some kind of weird crap with timers and the like.
870 * As far as I can tell, it's meaningless - the time we want to display
871 * is the time calculated from the remaining capacity, as per the
872 * ACPI spec. The only thing I'd change is the handling of a charging
873 * state: my best guess, based on the behaviour I'm seeing with my
874 * Lifebook, is that the present rate value when charging is the rate
875 * at which the batteries are being charged, which would mean I'd just
876 * need to reverse the rtime calculation to be able to work out how
877 * much time remained until the batteries were fully charged . . .
878 * That would be rather useful, though given it would vary rather a lot
879 * it seems likely that it'd be little more than a rough guesstimate. */
883 display_percentage(binfo
->percentage
);
884 scroll_text(6, 50, 52, dockapp
->tw
, 0);
886 /* redraw_window, if anything changed - determined inside