--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <signal.h>
+#include <time.h>
+#include <xosd.h>
+#include <X11/Xlib.h>
+
+static xosd *osd;
+static int total_discharge, total_charge, total_full, total_capa;
+
+#define CHECK_PERIOD 10
+#define SHOW_DURATION 2
+#define WARN_THRESHOLD 600
+
+static void scan_batt(void)
+{
+ total_discharge = total_charge = 0;
+ total_full = total_capa = 0;
+
+ const char dir[] = "/proc/acpi/battery";
+ DIR *d = opendir(dir);
+ if (!d)
+ return;
+
+ struct dirent *e;
+ while (e = readdir(d))
+ {
+ if (e->d_name[0] == '.')
+ continue;
+ char n[sizeof(dir) + 1 + strlen(e->d_name) + 6];
+ const char * const names[] = { "state", "info" };
+ int present = 0;
+ int rate = 0;
+ int charging = 0;
+ int remains = 0;
+ int last_full = 0;
+ for (int i=0; i<2; i++)
+ {
+ sprintf(n, "%s/%s/%s", dir, e->d_name, names[i]);
+ FILE *f = fopen(n, "r");
+ if (!f)
+ continue;
+ char line[1024];
+ while (fgets(line, sizeof(line)-1, f))
+ {
+ char *t = strchr(line, '\n');
+ if (t)
+ *t = 0;
+ char *val = strchr(line, ':');
+ if (!val)
+ continue;
+ *val++ = 0;
+ while (*val == ' ' || *val == '\t')
+ *val++ = 0;
+ // printf("<%s> <%s>\n", line, val);
+ if (!strcmp(line, "present"))
+ present = !strcmp(val, "yes");
+ else if (!strcmp(line, "charging state"))
+ charging = !strcmp(val, "charging");
+ else if (!strcmp(line, "present rate"))
+ rate = atol(val);
+ else if (!strcmp(line, "remaining capacity"))
+ remains = atol(val);
+ else if (!strcmp(line, "last full capacity"))
+ last_full = atol(val);
+ }
+ fclose(f);
+ }
+ if (present)
+ {
+ total_full += remains;
+ total_capa += last_full;
+ if (charging)
+ {
+ int ch = last_full*3600 / rate;
+ if (ch > total_charge)
+ total_charge = ch;
+ }
+ else
+ {
+ if (rate <= 0)
+ total_discharge += 359999;
+ else
+ total_discharge += remains*3600 / rate;
+ }
+ }
+ }
+
+ closedir(d);
+}
+
+static void show(void)
+{
+ char status[256];
+ char *p = status;
+ if (total_capa)
+ p += sprintf(p, "%d%%", 100*total_full/total_capa);
+ else
+ p += sprintf(p, "??%%");
+ if (total_discharge)
+ p += sprintf(p, " %d:%02d remains", total_discharge/3600, (total_discharge/60)%60);
+ else if (total_charge)
+ p += sprintf(p, " %d:%02d charging", total_charge/3600, (total_charge/60)%60);
+ else
+ p += sprintf(p, " ????");
+ xosd_display(osd, 0, XOSD_string, status);
+}
+
+static void my_sleep(int sec)
+{
+ struct timespec ts = { .tv_sec = sec };
+ nanosleep(&ts, NULL);
+}
+
+static void sig_show(int sig)
+{
+ alarm(0);
+ scan_batt();
+ show();
+ my_sleep(SHOW_DURATION);
+ xosd_hide(osd);
+ alarm(CHECK_PERIOD);
+}
+
+static void sig_time(int sig)
+{
+ char buf[256];
+ time_t now = time(NULL);
+ struct tm *tm = localtime(&now);
+ if (tm)
+ strftime(buf, sizeof(buf), "%d-%m-%Y %H:%M:%S", tm);
+ else
+ strcpy(buf, "??:??:??");
+ xosd_display(osd, 0, XOSD_string, buf);
+ my_sleep(SHOW_DURATION);
+ xosd_hide(osd);
+}
+
+static void sig_check(int sig)
+{
+ scan_batt();
+ if (total_discharge && total_discharge < WARN_THRESHOLD)
+ {
+ show();
+ my_sleep(SHOW_DURATION);
+ xosd_hide(osd);
+ }
+ alarm(CHECK_PERIOD);
+}
+
+int main(int argc, char **argv)
+{
+ // If daemon mode is requested, fork first.
+ int daemon = (argc > 1 && !strcmp(argv[1], "--daemon"));
+ int watcher = daemon || (argc > 1 && !strcmp(argv[1], "--watch"));
+ if (daemon)
+ {
+ pid_t pid = fork();
+ if (pid < 0)
+ {
+ fprintf(stderr, "batt: Cannot fork: %m\n");
+ return 1;
+ }
+ if (pid > 0)
+ return 0;
+ setsid();
+ }
+
+ // Block SIGALRM and SIGUSR1, because libxosd will start its own thread
+ // and we don't want it to accept these signals later;
+ sigset_t our_signals;
+ sigemptyset(&our_signals);
+ sigaddset(&our_signals, SIGALRM);
+ sigaddset(&our_signals, SIGUSR1);
+ sigaddset(&our_signals, SIGUSR2);
+ sigprocmask(SIG_BLOCK, &our_signals, NULL);
+
+ osd = xosd_create(1);
+ if (!osd)
+ {
+ fprintf(stderr, "batt: Cannot create on-screen display\n");
+ return 1;
+ }
+ xosd_set_font(osd, "-bitstream-bitstream vera sans-bold-r-normal-*-*-320-*-*-p-*-*");
+ xosd_set_outline_offset(osd, 2);
+ xosd_set_outline_colour(osd, "black");
+ xosd_set_pos(osd, XOSD_middle);
+ xosd_set_align(osd, XOSD_center);
+
+ if (!watcher)
+ {
+ scan_batt();
+ show();
+ my_sleep(SHOW_DURATION);
+ xosd_destroy(osd);
+ return 0;
+ }
+
+ // Set up signal handlers from which we do everything asynchronously.
+ struct sigaction sa = {
+ .sa_handler = sig_show,
+ .sa_mask = our_signals,
+ };
+ sigaction(SIGUSR1, &sa, NULL);
+ sa.sa_handler = sig_time;
+ sigaction(SIGUSR2, &sa, NULL);
+ sa.sa_handler = sig_check;
+ sigaction(SIGALRM, &sa, NULL);
+ sigprocmask(SIG_UNBLOCK, &our_signals, NULL);
+ alarm(1);
+
+ // We use this connection only for detecting that the session has ended.
+ // In such cases, we are terminated automatically by XLib.
+ Display *dpy = XOpenDisplay(NULL);
+ if (!dpy)
+ {
+ fprintf(stderr, "batt: Cannot open display\n");
+ return 1;
+ }
+ for (;;)
+ {
+ XEvent ev;
+ XNextEvent(dpy, &ev);
+ }
+
+ return 0;
+}