Select Git revision
-
Martin Mareš authoredMartin Mareš authored
osd-batt.c 5.95 KiB
/*
* A Simple Battery Status Display via OSD
*
* (c) 2007--2012 Martin Mares <mj@ucw.cz>
*/
#undef DEBUG
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <getopt.h>
#include <fcntl.h>
#include "util.h"
#include "osd.h"
static int check_mode;
static int check_every;
static int warn_threshold = 600;
static int total_now, total_full, discharge_rate;
static int charge_time, discharge_time;
static int ac_online;
static unsigned int present_mask, charge_mask, discharge_mask;
static unsigned int last_charge_mask, last_discharge_mask;
static int last_ac_online = -1;
#define MAX_BATTS 4
#define BATT_NAME_LEN 32
static char batt_names[MAX_BATTS][BATT_NAME_LEN];
static char sys_dir[256];
#define BUFSIZE 256
static int sys_read(char *buf, char *attribute)
{
char name[256];
snprintf(name, sizeof(name), "%s/%s", sys_dir, attribute);
int fd = open(name, O_RDONLY);
if (fd < 0)
return 0;
int n = read(fd, buf, BUFSIZE);
close(fd);
if (n < 0)
return 0;
buf[BUFSIZE-1] = 0;
char *nl = strchr(buf, '\n');
if (nl)
*nl = 0;
DBG("\t%s=%s\n", attribute, buf);
return 1;
}
static int sys_read_int(char *attribute, int default_value)
{
char buf[BUFSIZE];
if (!sys_read(buf, attribute) || !buf[0])
return default_value;
else
return atoi(buf);
}
static void parse_ac(void)
{
ac_online = sys_read_int("online", 0);
}
static int get_batt_id(char *batt_name)
{
for (int i=0; i<MAX_BATTS; i++)
{
if (!strcmp(batt_names[i], batt_name))
return i;
if (!batt_names[i][0])
{
snprintf(batt_names[i], BATT_NAME_LEN, "%s", batt_name);
return i;
}
}
return MAX_BATTS;
}
static void parse_batt(char *batt_name)
{
int batt_id = get_batt_id(batt_name);
DBG("\t-> id %d\n", batt_id);
if (!sys_read_int("present", 1))
return;
char status[BUFSIZE];
int charging = sys_read(status, "status") && !strcmp(status, "Charging");
int charge_full = sys_read_int("charge_full", 0);
int charge_now = sys_read_int("charge_now", 0);
int current_now = sys_read_int("current_now", 0);
present_mask |= 1 << batt_id;
total_now += charge_now;
total_full += charge_full;
if (charging && current_now > 0)
{
charge_mask |= 1 << batt_id;
int ch = (long long)(charge_full - charge_now)*3600 / current_now;
if (ch > charge_time)
charge_time = ch;
}
else if (current_now > 0)
{
discharge_mask |= 1 << batt_id;
discharge_rate += current_now;
}
}
static void scan(void)
{
ac_online = 0;
charge_time = discharge_time = 0;
total_now = total_full = 0;
discharge_rate = 0;
present_mask = charge_mask = discharge_mask = 0;
const char dir[] = "/sys/class/power_supply";
DIR *d = opendir(dir);
if (!d)
return;
struct dirent *e;
while (e = readdir(d))
{
if (e->d_name[0] == '.')
continue;
snprintf(sys_dir, sizeof(sys_dir), "%s/%s", dir, e->d_name);
DBG("%s\n", sys_dir);
char type[BUFSIZE];
if (!sys_read(type, "type"))
continue;
if (!strcmp(type, "Mains"))
parse_ac();
else if (!strcmp(type, "Battery"))
parse_batt(e->d_name);
}
if (discharge_rate)
discharge_time = (long long) total_now*3600 / discharge_rate;
else
discharge_time = 1000000;
closedir(d);
DBG("=> Capacity: now=%d full=%d\n", total_now, total_full);
DBG("=> Charge: mask=%d time=%d\n", charge_mask, charge_time);
DBG("=> Discharge: mask=%d rate=%d time=%d\n", discharge_mask, discharge_rate, discharge_time);
}
static char *batt_mask(char *p, unsigned int mask)
{
if (present_mask & (present_mask-1))
{
char *p0 = p;
for (int i=0; mask; i++)
if (mask & (1 << i))
{
*p = (p == p0) ? ' ' : '+';
p++;
p += sprintf(p, "B%d", i);
mask &= ~(1 << i);
}
}
return p;
}
static void show(void)
{
char status[256];
char *p = status;
if (total_full)
p += sprintf(p, "%d%%", (int)((long long)100*total_now/total_full));
else
p += sprintf(p, "??%%");
if (discharge_mask && discharge_time < 1000000)
{
p += sprintf(p, " %d:%02d remains", discharge_time/3600, (discharge_time/60)%60);
batt_mask(p, discharge_mask);
}
else if (charge_mask)
{
p += sprintf(p, " %d:%02d charging", charge_time/3600, (charge_time/60)%60);
batt_mask(p, charge_mask);
}
else if (ac_online)
p += sprintf(p, " AC");
else
p += sprintf(p, " BATT");
struct osd_msg *msg = osd_new_msg();
osd_add_line(msg, NULL, status);
osd_send(msg);
}
static void show_if_warn(void)
{
if (discharge_mask && discharge_time < warn_threshold ||
last_ac_online >= 0 && (
charge_mask != last_charge_mask ||
discharge_mask != last_discharge_mask ||
ac_online != last_ac_online))
show();
last_charge_mask = charge_mask;
last_discharge_mask = discharge_mask;
last_ac_online = ac_online;
}
static void NONRET
usage(void)
{
fprintf(stderr, "\
Usage: osd-batt <options>\n\
\n\
Options:\n\
-c, --check\t\tDisplay status only if battery is low\n\
-e, --check-every=<sec>\tRun on background and check every <sec> seconds\n\
-w, --warn=<sec>\tBattery is low if less than <sec> seconds remain (default: 600)\n\
");
exit(1);
}
static const char short_opts[] = "ce:w:";
static const struct option long_opts[] = {
{ "check", no_argument, NULL, 'c' },
{ "check-every", required_argument, NULL, 'e' },
{ "warn", required_argument, NULL, 'w' },
{ NULL, 0, NULL, 0 },
};
int main(int argc, char **argv)
{
int opt;
while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) >= 0)
switch (opt)
{
case 'c':
check_mode++;
break;
case 'e':
check_every = atoi(optarg);
break;
case 'w':
warn_threshold = atoi(optarg);
break;
default:
usage();
}
if (optind < argc)
usage();
if (check_every)
{
osd_fork();
for (;;)
{
scan();
show_if_warn();
osd_wait(check_every);
}
}
scan();
if (check_mode)
show_if_warn();
else
show();
return 0;
}