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 }