xbattmon

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

xbattmon.c (9363B)


      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 int fd;
    250 
    251 void
    252 openbat(void)
    253 {
    254 	fd = open(PATH_APM, O_RDONLY|O_CLOEXEC);
    255 	if (fd < 0)
    256 		err(1, "open %s", PATH_APM);
    257 }
    258 
    259 void
    260 pollbat(void)
    261 {
    262 	struct apm_power_info info;
    263 	int r;
    264 
    265 	r = ioctl(fd, APM_IOC_GETPOWER, &info);
    266 	if (r < 0)
    267 		err(1, "APM_IOC_GETPOWER %s", PATH_APM);
    268 
    269 	batcap = info.battery_life;
    270 	if (batcap > maxcap)
    271 		batcap = maxcap;
    272 
    273 	if (info.ac_state == APM_AC_UNKNOWN)
    274 		warnx("unknown AC state");
    275 
    276 	state = info.ac_state == APM_AC_ON ? AC_ON : AC_OFF;
    277 }
    278 #elif __DragonFly__
    279 #include <sys/ioctl.h>
    280 #include <fcntl.h>
    281 #include <machine/apm_bios.h>
    282 
    283 void
    284 pollbat(void)
    285 {
    286 	struct apm_info ai;
    287 	int r;
    288 	int fd;
    289 
    290 	fd = open(PATH_APM, O_RDONLY);
    291 	if (fd < 0)
    292 		err(1, "open %s", PATH_APM);
    293 	r = ioctl(fd, APMIO_GETINFO, &ai);
    294 	if (r < 0)
    295 		err(1, "APMIO_GETINFO %s", PATH_APM);
    296 	close(fd);
    297 
    298 	batcap = ai.ai_batt_life;
    299 	if (batcap > maxcap)
    300 		batcap = maxcap;
    301 	state = ai.ai_acline ? AC_ON : AC_OFF;
    302 }
    303 #elif __linux__
    304 void
    305 pollbat(void)
    306 {
    307 	FILE *fp;
    308 	char tmp[PATH_MAX];
    309 	int total_full = 0, total_now = 0;
    310 	int full, now;
    311 	int acon;
    312 	int i = 0;
    313 
    314 	for (;;) {
    315 		snprintf(tmp, sizeof(tmp), PATH_FMT_BAT_FULL, i);
    316 		fp = fopen(tmp, "r");
    317 		if (!fp) {
    318 			/* warn only if no battery is reachable */
    319 			if (i == 0)
    320 				warn("fopen %s", tmp);
    321 			break;
    322 		}
    323 		fscanf(fp, "%d", &full);
    324 		fclose(fp);
    325 
    326 		snprintf(tmp, sizeof(tmp), PATH_FMT_BAT_NOW, i);
    327 		fp = fopen(tmp, "r");
    328 		if (!fp) {
    329 			warn("fopen %s", tmp);
    330 			break;
    331 		}
    332 		fscanf(fp, "%d", &now);
    333 		fclose(fp);
    334 
    335 		total_full += full / 1000;
    336 		total_now += now / 1000;
    337 
    338 		i++;
    339 	}
    340 
    341 	if (total_full > 0)
    342 		batcap = 100 * total_now / total_full;
    343 	else
    344 		batcap = 0;
    345 
    346 	if (batcap > maxcap)
    347 		batcap = maxcap;
    348 
    349 	fp = fopen(PATH_AC_ONLINE, "r");
    350 	if (!fp)
    351 		err(1, "fopen %s", PATH_AC_ONLINE);
    352 	fscanf(fp, "%d", &acon);
    353 	fclose(fp);
    354 
    355 	state = acon ? AC_ON : AC_OFF;
    356 }
    357 #endif
    358 
    359 Bool
    360 evpredicate()
    361 {
    362 	return True;
    363 }
    364 
    365 void
    366 loop(void)
    367 {
    368 	XEvent ev;
    369 	struct pollfd pfd[1];
    370 	int dpyfd;
    371 
    372 	dpyfd = ConnectionNumber(dpy);
    373 	while (1) {
    374 		pfd[0].fd = dpyfd;
    375 		pfd[0].events = POLLIN;
    376 		switch (poll(pfd, 1, timeout)) {
    377 		case -1:
    378 			if (errno != EINTR)
    379 				err(1, "poll");
    380 			break;
    381 		case 0:
    382 			pollbat();
    383 			redraw();
    384 			break;
    385 		default:
    386 			if ((pfd[0].revents & (POLLERR | POLLHUP | POLLNVAL)))
    387 				errx(1, "bad fd: %d", pfd[0].fd);
    388 			while (XCheckIfEvent(dpy, &ev, evpredicate, NULL)) {
    389 				switch (ev.type) {
    390 				case Expose:
    391 					pollbat();
    392 					redraw();
    393 					break;
    394 				case VisibilityNotify:
    395 					if (ev.xvisibility.state != VisibilityUnobscured)
    396 						XRaiseWindow(dpy, winbar);
    397 					break;
    398 				case ConfigureNotify:
    399 					if (ev.xconfigure.window == DefaultRootWindow(dpy)) {
    400 						setsize();
    401 						redraw();
    402 					}
    403 					break;
    404 				}
    405 			}
    406 			break;
    407 		}
    408 	}
    409 }
    410 
    411 void
    412 usage(void)
    413 {
    414 	fprintf(stderr, "usage: %s [-c capacity] [-p bottom | top | left | right] [-t thickness] [-v]\n"
    415 		" -c\tspecify battery capacity\n"
    416 		" -p\tbar placement\n"
    417 		" -t\tbar thickness\n"
    418 		" -v\tshow version\n",
    419 		argv0);
    420 	exit(1);
    421 }
    422 
    423 int
    424 main(int argc, char *argv[])
    425 {
    426 	char *arg;
    427 	const char *errstr;
    428 
    429 	ARGBEGIN {
    430 	case 'c':
    431 		arg = EARGF(usage());
    432 		maxcap = strtonum(arg, 1, 100, &errstr);
    433 		if (errstr)
    434 			errx(1, "%s: %s", arg, errstr);
    435 		break;
    436 	case 'p':
    437 		arg = EARGF(usage());
    438 		if (strcmp(arg, "bottom") == 0)
    439 			placement = BOTTOM;
    440 		else if (strcmp(arg, "top") == 0)
    441 			placement = TOP;
    442 		else if (strcmp(arg, "left") == 0)
    443 			placement = LEFT;
    444 		else if (strcmp(arg, "right") == 0)
    445 			placement = RIGHT;
    446 		else
    447 			errx(1, "%s: invalid placement", arg);
    448 		break;
    449 	case 't':
    450 		arg = EARGF(usage());
    451 		thickness = strtonum(arg, 1, INT_MAX, &errstr);
    452 		if (errstr)
    453 			errx(1, "%s: %s", arg, errstr);
    454 		break;
    455 	case 'v':
    456 		printf("xbattmon-%s\n", VERSION);
    457 		return 0;
    458 	default:
    459 		usage();
    460 	} ARGEND;
    461 
    462 	if (argc)
    463 		usage();
    464 
    465 	setup();
    466 #ifdef __OpenBSD__
    467 	openbat();
    468 	if (unveil(NULL, NULL) == -1)
    469 		err(1, "unveil");
    470 #endif
    471 	pollbat();
    472 	redraw();
    473 	loop();
    474 	return 0;
    475 }