wmpager: EWMH support
[dockapps.git] / wmacpi / wmacpi.c
blob36bc574298deffd9fb92c9f891aff13ddebab473
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 /* #define RETARDED_APM */
20 /* #define STUPID_APM */
21 /* see README if you need to #define these or not. No user serviceable
22 * parts below */
24 #define _GNU_SOURCE
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <ctype.h>
30 #include <getopt.h>
31 #include <unistd.h>
32 #include <time.h>
34 #include <X11/X.h>
35 #include <X11/Xlib.h>
36 #include <X11/Xutil.h>
37 #include <X11/extensions/shape.h>
38 #include <X11/xpm.h>
40 #include "wmacpi.h"
42 #if defined(ACPI) && defined(APM)
43 # error Cannot compile with ACPI and APM compiled in. Please select only one.
44 #endif
46 /* main pixmap */
47 #ifdef LOW_COLOR
48 #include "master_low.xpm"
49 #else
50 #include "master.xpm"
51 #endif
53 typedef struct {
54 Display *display; /* X11 display struct */
55 int screen; /* current screen */
56 Window root; /* root window */
57 Window win; /* one window */
58 Window iconwin; /* another one */
59 Pixmap pixmap; /* UI pixmap, window pixmap */
60 Pixmap mask; /* mask pixmap for shape */
61 GC gc; /* main drawing GC */
62 Pixmap text; /* pixmap for text scroller */
63 int tw; /* text width inside text pixmap */
64 int update; /* need to redraw? */
65 int pressed; /* is the button pressed? */
66 DspMode dspmode; /* time remaining or battery timer */
67 Mode blink; /* should we blink the LED? (critical battery) */
68 } Dockapp;
70 /* for debug printing */
71 #ifdef PRO
72 char *state[] = { "AC", "Charging", "High", "Low", "Crit" };
73 #endif
75 /* globals */
76 Dockapp *dockapp;
77 APMInfo *apminfo;
78 int count = 0; /* global timer variable */
79 int noisy_critical = 0; /* ring xbell annoyingly if critical? */
81 /* proto for local stuff */
82 static void new_window(char *name);
83 static int open_display(char *display);
84 static void redraw_window(void);
85 static void render_text(char *string);
86 static void scroll_text(int x, int y, int width, int tw, int reset);
87 static void display_percentage(int percent);
88 static void display_state(void);
89 static void display_time(int minutes);
90 static void blink_button(Mode mode);
92 #define copy_xpm_area(x, y, w, h, dx, dy) \
93 { \
94 XCopyArea(dockapp->display, dockapp->pixmap, dockapp->pixmap, \
95 dockapp->gc, x, y, w, h, dx, dy); \
96 dockapp->update = 1; \
99 static void redraw_window(void)
101 if (dockapp->update) {
102 eprint(1, "redrawing window");
103 XCopyArea(dockapp->display, dockapp->pixmap, dockapp->iconwin,
104 dockapp->gc, 0, 0, 64, 64, 0, 0);
105 XCopyArea(dockapp->display, dockapp->pixmap, dockapp->win,
106 dockapp->gc, 0, 0, 64, 64, 0, 0);
107 dockapp->update = 0;
111 static void new_window(char *name)
113 XpmAttributes attr;
114 Pixel fg, bg;
115 XGCValues gcval;
116 XSizeHints sizehints;
117 XClassHint classhint;
118 XWMHints wmhints;
120 dockapp->screen = DefaultScreen(dockapp->display);
121 dockapp->root = DefaultRootWindow(dockapp->display);
123 sizehints.flags = USSize;
124 sizehints.width = 64;
125 sizehints.height = 64;
127 fg = BlackPixel(dockapp->display, dockapp->screen);
128 bg = WhitePixel(dockapp->display, dockapp->screen);
130 dockapp->win = XCreateSimpleWindow(dockapp->display, dockapp->root,
131 0, 0, sizehints.width,
132 sizehints.height, 1, fg, bg);
133 dockapp->iconwin =
134 XCreateSimpleWindow(dockapp->display, dockapp->win, 0, 0,
135 sizehints.width, sizehints.height, 1, fg, bg);
137 XSetWMNormalHints(dockapp->display, dockapp->win, &sizehints);
138 classhint.res_name = name;
139 classhint.res_class = name;
140 XSetClassHint(dockapp->display, dockapp->win, &classhint);
142 XSelectInput(dockapp->display, dockapp->win,
143 ExposureMask | ButtonPressMask | ButtonReleaseMask |
144 StructureNotifyMask);
145 XSelectInput(dockapp->display, dockapp->iconwin,
146 ExposureMask | ButtonPressMask | ButtonReleaseMask |
147 StructureNotifyMask);
149 XStoreName(dockapp->display, dockapp->win, name);
150 XSetIconName(dockapp->display, dockapp->win, name);
152 gcval.foreground = fg;
153 gcval.background = bg;
154 gcval.graphics_exposures = False;
156 dockapp->gc =
157 XCreateGC(dockapp->display, dockapp->win,
158 GCForeground | GCBackground | GCGraphicsExposures,
159 &gcval);
161 attr.exactColors = 0;
162 attr.alloc_close_colors = 1;
163 attr.closeness = 1L << 15;
164 attr.valuemask = XpmExactColors | XpmAllocCloseColors | XpmCloseness;
165 if (XpmCreatePixmapFromData(dockapp->display, dockapp->win,
166 master_xpm, &dockapp->pixmap,
167 &dockapp->mask, &attr) != XpmSuccess) {
168 fprintf(stderr, "FATAL: Not enough colors for main pixmap!\n");
169 exit(1);
172 /* text area is 318x7, or 53 characters long */
173 dockapp->text = XCreatePixmap(dockapp->display, dockapp->win, 318, 7,
174 DefaultDepth(dockapp->display,
175 dockapp->screen));
176 if (!dockapp->text) {
177 fprintf(stderr, "FATAL: Cannot create text scroll pixmap!\n");
178 exit(1);
181 XShapeCombineMask(dockapp->display, dockapp->win, ShapeBounding, 0, 0,
182 dockapp->mask, ShapeSet);
183 XShapeCombineMask(dockapp->display, dockapp->iconwin, ShapeBounding, 0,
184 0, dockapp->mask, ShapeSet);
186 wmhints.initial_state = WithdrawnState;
187 wmhints.flags = StateHint;
188 wmhints.icon_window = dockapp->iconwin;
189 wmhints.icon_x = sizehints.x;
190 wmhints.icon_y = sizehints.y;
191 wmhints.window_group = dockapp->win;
192 wmhints.flags =
193 StateHint | IconWindowHint | IconPositionHint | WindowGroupHint;
194 XSetWMHints(dockapp->display, dockapp->win, &wmhints);
196 XMapWindow(dockapp->display, dockapp->win);
199 static void render_text(char *string)
201 int i, c, k;
203 if (strlen(string) > 53)
204 return;
206 eprint(1, "rendering: %s", string);
208 /* prepare the text area by clearing it */
209 for (i = 0; i < 54; i++) {
210 XCopyArea(dockapp->display, dockapp->pixmap, dockapp->text,
211 dockapp->gc, 133, 57, 6, 8, i * 6, 0);
213 k = 0;
215 for (i = 0; string[i]; i++) {
216 c = toupper(string[i]);
217 if (c >= 'A' && c <= 'Z') { /* letter */
218 c = c - 'A';
219 XCopyArea(dockapp->display, dockapp->pixmap, dockapp->text,
220 dockapp->gc, c * 6, 67, 6, 7, k, 0);
221 } else if (c >= '0' && c <= '9') { /* number */
222 c = c - '0';
223 XCopyArea(dockapp->display, dockapp->pixmap, dockapp->text,
224 dockapp->gc, c * 6 + 66, 58, 6, 7, k, 0);
225 } else if (c == '.') {
226 XCopyArea(dockapp->display, dockapp->pixmap, dockapp->text,
227 dockapp->gc, 140, 58, 6, 7, k, 0);
228 } else if (c == '-') {
229 XCopyArea(dockapp->display, dockapp->pixmap, dockapp->text,
230 dockapp->gc, 126, 58, 6, 7, k, 0);
232 k += 6;
234 dockapp->tw = k; /* length of text segment */
235 /* re-scroll the message */
236 scroll_text(6, 50, 52, dockapp->tw, 1);
237 /* reset the scroll repeat counter */
238 count = 0;
241 static int open_display(char *display)
243 dockapp->display = XOpenDisplay(display);
244 if (!dockapp->display) {
245 fprintf(stderr, "Unable to open display '%s'\n", display);
246 return 1;
248 return 0;
251 static void scroll_text(int x, int y, int width, int tw, int reset)
253 static int pos, first, stop;
255 if (reset) {
256 pos = 0;
257 first = 0;
258 stop = 0;
259 XCopyArea(dockapp->display, dockapp->pixmap, dockapp->text,
260 dockapp->gc, 0, 0, width, 7, x, y);
263 if (stop) {
264 return;
267 if ((first == 0) && pos == 0) {
268 pos = width;
269 first = 1;
272 if (pos == (0 - tw - 2)) {
273 first = 1;
274 pos = width;
275 stop = 1;
276 return;
278 pos -= 2;
280 eprint(0, "scrolling");
282 if (pos > 0) {
283 copy_xpm_area(66, 9, pos, 7, x, y); /* clear */
284 XCopyArea(dockapp->display, dockapp->text, dockapp->pixmap,
285 dockapp->gc, 0, 0, width - pos, 7, x + pos, y);
286 } else { /* don't need to clear, already in text */
287 XCopyArea(dockapp->display, dockapp->text, dockapp->pixmap,
288 dockapp->gc, abs(pos), 0, width, 7, x, y);
290 dockapp->update = 1;
293 static void display_percentage(int percent)
295 static int op = -1, obar;
296 unsigned int bar;
298 eprint(1, "received: %d\n", percent);
300 if (op == percent)
301 return;
303 if (percent < 0)
304 percent = 0;
305 if (percent > 100)
306 percent = 100;
308 if (percent < 100) { /* 0 - 99 */
309 copy_xpm_area(95, 48, 8, 7, 37, 17);
310 if (percent >= 10)
311 copy_xpm_area((percent / 10) * 6 + 67, 28, 5, 7, 40, 17);
312 copy_xpm_area((percent % 10) * 6 + 67, 28, 5, 7, 46, 17);
313 } else
314 copy_xpm_area(95, 37, 21, 9, 37, 16); /* 100% */
315 op = percent;
317 bar = percent / 1.8518;
319 if (bar == obar)
320 return;
322 copy_xpm_area(66, 0, bar, 8, 5, 5);
323 if (bar < 54)
324 copy_xpm_area(66 + bar, 18, 54 - bar, 8, bar + 5, 5);
325 obar = bar;
328 static void display_time(int minutes)
330 static int ohour = -1, omin = -1;
331 static int counter;
332 int hour, min, tmp;
334 if (minutes == -1) { /* error - blink 00:00 */
335 counter++;
336 if (counter == 5) {
337 copy_xpm_area(80, 76, 31, 11, 7, 32);
338 } else if (counter == 10) {
339 copy_xpm_area(114, 76, 31, 11, 7, 32);
341 if (counter > 10)
342 counter = 0;
343 ohour = omin = -1;
344 return;
347 /* render time on the display */
348 hour = minutes / 60;
349 min = minutes % 60;
351 if (hour == ohour && min == omin)
352 return;
354 eprint(0, "redrawing time");
355 tmp = hour / 10;
356 copy_xpm_area(tmp * 7 + 1, 76, 6, 11, 7, 32);
357 tmp = hour % 10;
358 copy_xpm_area(tmp * 7 + 1, 76, 6, 11, 14, 32);
359 tmp = min / 10;
360 copy_xpm_area(tmp * 7 + 1, 76, 6, 11, 25, 32);
361 tmp = min % 10;
362 copy_xpm_area(tmp * 7 + 1, 76, 6, 11, 32, 32);
363 copy_xpm_area(71, 76, 3, 11, 21, 32);
364 ohour = hour;
365 omin = min;
368 static void display_state(void)
370 static int dopower;
371 static int docharging;
372 static int dobattery;
373 static int docritical;
374 static int counter;
376 switch (apminfo->power) {
377 case POWER:
378 eprint(0, "selected ac power case");
379 if (!dopower) {
380 dopower = 1;
381 docharging = 0;
382 dobattery = 0;
383 dockapp->blink = OFF;
384 copy_xpm_area(67, 38, 12, 7, 6, 17);
385 copy_xpm_area(82, 48, 11, 7, 20, 17);
386 render_text("On AC power");
388 break;
389 case CHARGING:
390 eprint(0, "selected charging case");
391 counter++;
392 if (counter == 10) {
393 copy_xpm_area(67, 38, 12, 7, 6, 17);
394 } else if (counter == 20) {
395 copy_xpm_area(67, 48, 12, 7, 6, 17);
397 if (counter > 20)
398 counter = 0;
399 if (!docharging) {
400 render_text("Battery is charging");
401 /* get rid of battery symbol */
402 copy_xpm_area(82, 48, 12, 7, 20, 17);
403 /* housekeeping */
404 dockapp->blink = OFF;
405 docharging = 1;
406 dopower = 0;
407 dobattery = 0;
409 break;
410 case HIGH:
411 case LOW:
412 case CRIT:
413 eprint(0, "selected battery case");
414 if (!dobattery) {
415 render_text("On Battery");
416 /* display battery symbol */
417 copy_xpm_area(82, 38, 12, 7, 20, 17);
418 /* get rid of AC power symbol */
419 copy_xpm_area(67, 48, 12, 7, 6, 17);
420 dobattery = 1;
421 dopower = 0;
422 docharging = 0;
424 if (apminfo->power == CRIT) {
425 dockapp->blink = BLINK;
426 if (!docritical) {
427 render_text("Battery Critical Low");
428 docritical = 1;
430 } else {
431 if (docritical) {
432 render_text("On Battery");
433 docritical = 0;
435 dockapp->blink = OFF;
437 break;
441 static void blink_button(Mode mode)
443 static int counter;
444 static int clear;
446 if ((mode == OFF) && !clear) {
447 eprint(0, "we are off");
448 copy_xpm_area(136, 38, 3, 3, 44, 30);
449 clear = 1;
450 return;
452 if (mode != BLINK)
453 return;
455 counter++;
457 if (counter == 5) {
458 copy_xpm_area(137, 33, 3, 3, 44, 30);
459 clear = 0;
460 } else if (counter == 10) {
461 copy_xpm_area(136, 38, 3, 3, 44, 30);
462 clear = 0;
463 /* make some noise */
464 if (noisy_critical)
465 XBell(dockapp->display, 100);
467 if (counter > 10)
468 counter = 0;
471 int main(int argc, char **argv)
473 char *display = NULL;
474 char ch;
475 int update = 0;
477 dockapp = calloc(1, sizeof(Dockapp));
478 apminfo = calloc(1, sizeof(APMInfo));
480 dockapp->blink = OFF;
481 apminfo->crit_level = 10;
483 /* see if whatever we want to use is supported */
484 if (power_init()) {
485 /* power_init functions handle printing error messages */
486 exit(1);
489 /* parse command-line options */
490 while ((ch = getopt(argc, argv, "bd:c:h")) != EOF) {
491 switch (ch) {
492 case 'b':
493 noisy_critical = 1;
494 break;
495 case 'c':
496 if (optarg) {
497 apminfo->crit_level = atoi(optarg);
498 if ((apminfo->crit_level < 0) || (apminfo->crit_level > 100)) {
499 fprintf(stderr, "Please use values between 0 and 100%%\n");
500 apminfo->crit_level = 10;
501 fprintf(stderr, "Using default value of 10%%\n");
504 break;
505 case 'd':
506 if (optarg)
507 display = strdup(optarg);
508 break;
509 case 'h':
510 printf("wmacpi - help\t\t[timecop@japan.co.jp]\n\n"
511 "-d display\t\tdisplay on remote display <display>\n"
512 "-b\t\t\tmake noise when battery is critical low (beep)\n"
513 "-c value\t\tset critical low alarm at <value> percent\n"
514 "\t\t\t(default: 10 percent)\n"
515 "-h\t\t\tdisplay this help\n");
516 return 0;
517 break;
522 /* open local or command-line specified display */
523 if (open_display(display))
524 exit(1);
526 /* make new dockapp window */
527 new_window("apm");
529 /* get initial statistics */
530 acquire_info();
532 dockapp->dspmode = REMAIN;
534 /* main loop */
535 while (1) {
536 XEvent event;
537 while (XPending(dockapp->display)) {
538 eprint(0, "X11 activity");
539 XNextEvent(dockapp->display, &event);
540 switch (event.type) {
541 case Expose:
542 /* update */
543 dockapp->update = 1;
544 while (XCheckTypedEvent(dockapp->display, Expose, &event));
545 redraw_window();
546 break;
547 case DestroyNotify:
548 XCloseDisplay(dockapp->display);
549 exit(0);
550 break;
551 case ButtonPress:
552 /* press event */
553 if (event.xbutton.x >= 44 && event.xbutton.x <= 57 &&
554 event.xbutton.y >= 30 && event.xbutton.y <= 43) {
555 eprint(0, "inside button!");
556 dockapp->pressed = 1;
557 copy_xpm_area(118, 38, 15, 15, 44, 30);
559 break;
560 case ButtonRelease:
561 /* release event */
562 if (event.xbutton.x >= 44 && event.xbutton.x <= 57 &&
563 event.xbutton.y >= 30 && event.xbutton.y <= 43 &&
564 dockapp->pressed) {
565 /* handle button press */
566 eprint(0, "release still inside button!");
567 dockapp->pressed = 0;
568 copy_xpm_area(136, 38, 15, 15, 44, 30);
569 if ((apminfo->power != POWER) && (apminfo->power != CHARGING)) {
570 dockapp->dspmode = !dockapp->dspmode;
571 eprint(1, "Mode: %d", dockapp->dspmode);
573 /* end button press handler */
575 if (dockapp->pressed) {
576 copy_xpm_area(136, 38, 15, 15, 44, 30);
577 dockapp->pressed = 0;
579 break;
583 if (update++ == 30) {
584 eprint(1, "polling apm");
585 acquire_info();
586 update = 0;
589 if (count++ == 256) {
590 scroll_text(6, 50, 52, dockapp->tw, 1);
591 count = 0;
594 /* it's okay to test here because display_time will not draw anything
595 * unless there is a change. Also if we switched power states from
596 * battery to charging/etc, we need to exit from "timer" mode */
597 if (dockapp->dspmode == REMAIN || apminfo->power == POWER || apminfo->power == CHARGING) {
598 display_time(apminfo->rtime);
599 } else {
600 display_time((time(NULL) - apminfo->timer) / 60);
603 display_state();
604 blink_button(dockapp->blink);
605 display_percentage(apminfo->percentage);
606 scroll_text(6, 50, 52, dockapp->tw, 0);
608 /* redraw_window, if anything changed - determined inside
609 * redraw_window. */
610 redraw_window();
611 usleep(100000);
613 return 0;
616 /* this handles enabling "on-battery" timer. It only needs to happen once
617 * for each unplug event. Functions from libapm and libacpi call this to
618 * start the timer */
619 void process_plugin_timer(void)
621 static int timer;
623 if ((apminfo->power != POWER) && (apminfo->power != CHARGING) && !timer) {
624 eprint(1, "not AC and not charging, and timer is not started");
625 eprint(1, "starting battery timer");
626 apminfo->timer = time(NULL);
627 timer = 1;
629 if (((apminfo->power == POWER) || (apminfo->power == CHARGING)) && timer) {
630 eprint(1, "disabling battery timer");
631 timer = 0;