xbattmon

simple battery monitor for X
git clone git://git.2f30.org/xbattmon.git
Log | Files | Refs | README | LICENSE

xbattmon.c (9252B)


      1 /* See LICENSE file for copyright and license details. */
      2 #include <X11/Xlib.h>
      3 #include <X11/Xatom.h>
      4 #include <X11/Xutil.h>
      5 #ifdef XINERAMA
      6 #include <X11/extensions/Xinerama.h>
      7 #endif /* XINERAMA */
      8 #include <err.h>
      9 #include <errno.h>
     10 #include <limits.h>
     11 #include <poll.h>
     12 #include <stdio.h>
     13 #include <stdlib.h>
     14 #include <string.h>
     15 #include <time.h>
     16 #include <unistd.h>
     17 
     18 #include "arg.h"
     19 #include "util.h"
     20 
     21 #define LEN(x) (sizeof(x) / sizeof(*(x)))
     22 
     23 enum {
     24 	AC_ON,
     25 	AC_OFF
     26 };
     27 
     28 enum {
     29 	COLOR_BAT_CHARGED,
     30 	COLOR_BAT_LEFT2CHARGE,
     31 	COLOR_BAT_DRAINED,
     32 	COLOR_BAT_LEFT2DRAIN
     33 };
     34 
     35 enum {
     36 	BOTTOM,
     37 	TOP,
     38 	LEFT,
     39 	RIGHT
     40 };
     41 
     42 char *argv0;
     43 Display *dpy;
     44 Window winbar;
     45 GC gcbar;
     46 int barx;
     47 int bary;
     48 unsigned int barwidth;
     49 unsigned int barheight;
     50 int state;			/* AC_ON or AC_OFF */
     51 int batcap;			/* 0 if completely discharged or `maxcap' if completely charged */
     52 int timeout;
     53 int blink;
     54 
     55 #include "config.h"
     56 
     57 unsigned long cmap[LEN(colors)];
     58 
     59 void
     60 setsize(void)
     61 {
     62 	unsigned int width, height;
     63 	int screen;
     64 	int scrx, scry;
     65 
     66 #ifdef XINERAMA
     67 	if (XineramaIsActive(dpy)) {
     68 		int n;
     69 		XineramaScreenInfo *info = XineramaQueryScreens(dpy, &n);
     70 		scrx = info[0].x_org;
     71 		scry = info[0].y_org;
     72 		width = info[0].width;
     73 		height = info[0].height;
     74 	} else
     75 #endif /* XINERAMA */
     76 	{
     77 		scrx = scry = 0;
     78 		screen = DefaultScreen(dpy);
     79 		width = DisplayWidth(dpy, screen);
     80 		height = DisplayHeight(dpy, screen);
     81 	}
     82 	if (placement == BOTTOM || placement == TOP) {
     83 		if (thickness > height)
     84 			thickness = height;
     85 	} else {
     86 		if (thickness > width)
     87 			thickness = width;
     88 	}
     89 
     90 	switch (placement) {
     91 	case BOTTOM:
     92 		barx = scrx;
     93 		bary = scry + (height - thickness);
     94 		barwidth = width;
     95 		barheight = thickness;
     96 		break;
     97 	case TOP:
     98 		barx = scrx;
     99 		bary = scry;
    100 		barwidth = width;
    101 		barheight = thickness;
    102 		break;
    103 	case LEFT:
    104 		barx = scrx;
    105 		bary = scry;
    106 		barwidth = thickness;
    107 		barheight = height;
    108 		break;
    109 	case RIGHT:
    110 		barx = scrx + (width - thickness);
    111 		bary = scry;
    112 		barwidth = thickness;
    113 		barheight = height;
    114 		break;
    115 	}
    116 }
    117 
    118 void
    119 setup(void)
    120 {
    121 	XSetWindowAttributes attr;
    122 	XColor color, exact;
    123 	XTextProperty text;
    124 	Atom wintype, wintype_dock;
    125 	static char *name = "xbattmon";
    126 	int r;
    127 	int screen;
    128 	size_t i;
    129 
    130 	dpy = XOpenDisplay(NULL);
    131 	if (!dpy)
    132 		errx(1, "cannot open display");
    133 
    134 	screen = DefaultScreen(dpy);
    135 
    136 	setsize();
    137 
    138 	winbar = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), barx, bary, barwidth,
    139 				     barheight, 0, BlackPixel(dpy, screen),
    140 				     WhitePixel(dpy, screen));
    141 
    142 	attr.override_redirect = True;
    143 	XChangeWindowAttributes(dpy, winbar, CWOverrideRedirect, &attr);
    144 
    145 	XStringListToTextProperty(&name, 1, &text);
    146 	XSetWMName(dpy, winbar, &text);
    147 
    148 	wintype = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False);
    149 	wintype_dock = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DOCK", False);
    150 	XChangeProperty(dpy, winbar, wintype, XA_ATOM, 32,
    151 	    PropModeReplace, (unsigned char *)&wintype_dock, 1);
    152 
    153 	XSelectInput(dpy, RootWindow(dpy, screen), StructureNotifyMask);
    154 	if (raise == 1) {
    155 		XSelectInput(dpy, winbar, ExposureMask | VisibilityChangeMask);
    156 		XMapRaised(dpy, winbar);
    157 	} else {
    158 		XMapWindow(dpy, winbar);
    159 	}
    160 
    161 	gcbar = XCreateGC(dpy, winbar, 0, 0);
    162 
    163 	for (i = 0; i < LEN(colors); i++) {
    164 		r = XAllocNamedColor(dpy, DefaultColormap(dpy, 0),
    165 				     colors[i], &color, &exact);
    166 		if (r == 0)
    167 			errx(1, "cannot allocate color resources");
    168 		cmap[i] = color.pixel;
    169 	}
    170 
    171 	critical = critical * maxcap / 100;
    172 }
    173 
    174 void
    175 redraw(void)
    176 {
    177 	int pos;
    178 	unsigned long done, left;
    179 	static struct timespec oldtp = { 0 };
    180 	struct timespec tp;
    181 	unsigned int delta;
    182 
    183 	if (state == AC_OFF && batcap <= critical) {
    184 		clock_gettime(CLOCK_MONOTONIC, &tp);
    185 		delta = (tp.tv_sec * 1000 + tp.tv_nsec / 1000000)
    186 		    - (oldtp.tv_sec * 1000 + oldtp.tv_nsec / 1000000);
    187 		if (delta < 500) {
    188 			timeout = 500 - delta;
    189 		} else {
    190 			timeout = 500;
    191 			blink = !blink;
    192 			oldtp = tp;
    193 		}
    194 	} else {
    195 		timeout = 500;
    196 		blink = 0;
    197 	}
    198 
    199 	if (placement == BOTTOM || placement == TOP)
    200 		pos = barwidth * batcap / maxcap;
    201 	else
    202 		pos = barheight * batcap / maxcap;
    203 
    204 	if (state == AC_ON) {
    205 		done = cmap[COLOR_BAT_CHARGED];
    206 		left = cmap[COLOR_BAT_LEFT2CHARGE];
    207 	} else {
    208 		done = cmap[!blink ? COLOR_BAT_LEFT2DRAIN : COLOR_BAT_DRAINED];
    209 		left = cmap[COLOR_BAT_DRAINED];
    210 	}
    211 
    212 	if (transparent) {
    213 		if (!blink)
    214 			XMapWindow(dpy, winbar);
    215 		else
    216 			XUnmapWindow(dpy, winbar);
    217 		XSetForeground(dpy, gcbar, done);
    218 		if (placement == BOTTOM || placement == TOP) {
    219 			XMoveResizeWindow(dpy, winbar, barx, bary, pos, thickness);
    220 			XFillRectangle(dpy, winbar, gcbar, 0, 0, pos, thickness);
    221 		} else {
    222 			XMoveResizeWindow(dpy, winbar, barx, bary + (barheight - pos), thickness, pos);
    223 			XFillRectangle(dpy, winbar, gcbar, 0, 0, thickness, barheight);
    224 		}
    225 	} else {
    226 		if (placement == BOTTOM || placement == TOP) {
    227 			XMoveResizeWindow(dpy, winbar, barx, bary, barwidth, thickness);
    228 			XSetForeground(dpy, gcbar, done);
    229 			XFillRectangle(dpy, winbar, gcbar, 0, 0, pos, thickness);
    230 			XSetForeground(dpy, gcbar, left);
    231 			XFillRectangle(dpy, winbar, gcbar, pos, 0, barwidth, thickness);
    232 		} else {
    233 			XMoveResizeWindow(dpy, winbar, barx, bary, thickness, barheight);
    234 			XSetForeground(dpy, gcbar, done);
    235 			XFillRectangle(dpy, winbar, gcbar, 0, barheight - pos, thickness, barheight);
    236 			XSetForeground(dpy, gcbar, left);
    237 			XFillRectangle(dpy, winbar, gcbar, 0, 0, thickness, barheight - pos);
    238 		}
    239 	}
    240 
    241 	XFlush(dpy);
    242 }
    243 
    244 #ifdef __OpenBSD__
    245 #include <sys/ioctl.h>
    246 #include <fcntl.h>
    247 #include <machine/apmvar.h>
    248 
    249 void
    250 pollbat(void)
    251 {
    252 	struct apm_power_info info;
    253 	int r;
    254 	int fd;
    255 
    256 	fd = open(PATH_APM, O_RDONLY);
    257 	if (fd < 0)
    258 		err(1, "open %s", PATH_APM);
    259 	r = ioctl(fd, APM_IOC_GETPOWER, &info);
    260 	if (r < 0)
    261 		err(1, "APM_IOC_GETPOWER %s", PATH_APM);
    262 	close(fd);
    263 
    264 	batcap = info.battery_life;
    265 	if (batcap > maxcap)
    266 		batcap = maxcap;
    267 
    268 	if (info.ac_state == APM_AC_UNKNOWN)
    269 		warnx("unknown AC state");
    270 
    271 	state = info.ac_state == APM_AC_ON ? AC_ON : AC_OFF;
    272 }
    273 #elif __DragonFly__
    274 #include <sys/ioctl.h>
    275 #include <fcntl.h>
    276 #include <machine/apm_bios.h>
    277 
    278 void
    279 pollbat(void)
    280 {
    281 	struct apm_info ai;
    282 	int r;
    283 	int fd;
    284 
    285 	fd = open(PATH_APM, O_RDONLY);
    286 	if (fd < 0)
    287 		err(1, "open %s", PATH_APM);
    288 	r = ioctl(fd, APMIO_GETINFO, &ai);
    289 	if (r < 0)
    290 		err(1, "APMIO_GETINFO %s", PATH_APM);
    291 	close(fd);
    292 
    293 	batcap = ai.ai_batt_life;
    294 	if (batcap > maxcap)
    295 		batcap = maxcap;
    296 	state = ai.ai_acline ? AC_ON : AC_OFF;
    297 }
    298 #elif __linux__
    299 void
    300 pollbat(void)
    301 {
    302 	FILE *fp;
    303 	char tmp[PATH_MAX];
    304 	int total_full = 0, total_now = 0;
    305 	int full, now;
    306 	int acon;
    307 	int i = 0;
    308 
    309 	for (;;) {
    310 		snprintf(tmp, sizeof(tmp), PATH_FMT_BAT_FULL, i);
    311 		fp = fopen(tmp, "r");
    312 		if (!fp) {
    313 			/* warn only if no battery is reachable */
    314 			if (i == 0)
    315 				warn("fopen %s", tmp);
    316 			break;
    317 		}
    318 		fscanf(fp, "%d", &full);
    319 		fclose(fp);
    320 
    321 		snprintf(tmp, sizeof(tmp), PATH_FMT_BAT_NOW, i);
    322 		fp = fopen(tmp, "r");
    323 		if (!fp) {
    324 			warn("fopen %s", tmp);
    325 			break;
    326 		}
    327 		fscanf(fp, "%d", &now);
    328 		fclose(fp);
    329 
    330 		total_full += full / 1000;
    331 		total_now += now / 1000;
    332 
    333 		i++;
    334 	}
    335 
    336 	if (total_full > 0)
    337 		batcap = 100 * total_now / total_full;
    338 	else
    339 		batcap = 0;
    340 
    341 	if (batcap > maxcap)
    342 		batcap = maxcap;
    343 
    344 	fp = fopen(PATH_AC_ONLINE, "r");
    345 	if (!fp)
    346 		err(1, "fopen %s", PATH_AC_ONLINE);
    347 	fscanf(fp, "%d", &acon);
    348 	fclose(fp);
    349 
    350 	state = acon ? AC_ON : AC_OFF;
    351 }
    352 #endif
    353 
    354 Bool
    355 evpredicate()
    356 {
    357 	return True;
    358 }
    359 
    360 void
    361 loop(void)
    362 {
    363 	XEvent ev;
    364 	struct pollfd pfd[1];
    365 	int dpyfd;
    366 
    367 	dpyfd = ConnectionNumber(dpy);
    368 	while (1) {
    369 		pfd[0].fd = dpyfd;
    370 		pfd[0].events = POLLIN;
    371 		switch (poll(pfd, 1, timeout)) {
    372 		case -1:
    373 			if (errno != EINTR)
    374 				err(1, "poll");
    375 			break;
    376 		case 0:
    377 			pollbat();
    378 			redraw();
    379 			break;
    380 		default:
    381 			if ((pfd[0].revents & (POLLERR | POLLHUP | POLLNVAL)))
    382 				errx(1, "bad fd: %d", pfd[0].fd);
    383 			while (XCheckIfEvent(dpy, &ev, evpredicate, NULL)) {
    384 				switch (ev.type) {
    385 				case Expose:
    386 					pollbat();
    387 					redraw();
    388 					break;
    389 				case VisibilityNotify:
    390 					if (ev.xvisibility.state != VisibilityUnobscured)
    391 						XRaiseWindow(dpy, winbar);
    392 					break;
    393 				case ConfigureNotify:
    394 					if (ev.xconfigure.window == DefaultRootWindow(dpy)) {
    395 						setsize();
    396 						redraw();
    397 					}
    398 					break;
    399 				}
    400 			}
    401 			break;
    402 		}
    403 	}
    404 }
    405 
    406 void
    407 usage(void)
    408 {
    409 	fprintf(stderr, "usage: %s [-c capacity] [-p bottom | top | left | right] [-t thickness] [-v]\n"
    410 		" -c\tspecify battery capacity\n"
    411 		" -p\tbar placement\n"
    412 		" -t\tbar thickness\n"
    413 		" -v\tshow version\n",
    414 		argv0);
    415 	exit(1);
    416 }
    417 
    418 int
    419 main(int argc, char *argv[])
    420 {
    421 	char *arg;
    422 	const char *errstr;
    423 
    424 	ARGBEGIN {
    425 	case 'c':
    426 		arg = EARGF(usage());
    427 		maxcap = strtonum(arg, 1, 100, &errstr);
    428 		if (errstr)
    429 			errx(1, "%s: %s", arg, errstr);
    430 		break;
    431 	case 'p':
    432 		arg = EARGF(usage());
    433 		if (strcmp(arg, "bottom") == 0)
    434 			placement = BOTTOM;
    435 		else if (strcmp(arg, "top") == 0)
    436 			placement = TOP;
    437 		else if (strcmp(arg, "left") == 0)
    438 			placement = LEFT;
    439 		else if (strcmp(arg, "right") == 0)
    440 			placement = RIGHT;
    441 		else
    442 			errx(1, "%s: invalid placement", arg);
    443 		break;
    444 	case 't':
    445 		arg = EARGF(usage());
    446 		thickness = strtonum(arg, 1, INT_MAX, &errstr);
    447 		if (errstr)
    448 			errx(1, "%s: %s", arg, errstr);
    449 		break;
    450 	case 'v':
    451 		printf("xbattmon-%s\n", VERSION);
    452 		return 0;
    453 	default:
    454 		usage();
    455 	} ARGEND;
    456 
    457 	if (argc)
    458 		usage();
    459 
    460 	setup();
    461 	pollbat();
    462 	redraw();
    463 	loop();
    464 	return 0;
    465 }