]> mj.ucw.cz Git - osdd.git/blob - osd-batt.c
osd-batt: Rewritten to use sysfs instead of /proc/acpi/
[osdd.git] / osd-batt.c
1 /*
2  *      A Simple Battery Status Display via OSD
3  *
4  *      (c) 2007--2012 Martin Mares <mj@ucw.cz>
5  */
6
7 #undef DEBUG
8
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <unistd.h>
13 #include <dirent.h>
14 #include <getopt.h>
15 #include <fcntl.h>
16
17 #include "osd.h"
18
19 static int check_mode;
20 static int check_every;
21 static int warn_threshold = 600;
22
23 static int total_now, total_full, discharge_rate;
24 static int charge_time, discharge_time;
25 static int ac_online;
26 static unsigned int present_mask, charge_mask, discharge_mask;
27
28 static unsigned int last_charge_mask, last_discharge_mask;
29 static int last_ac_online = -1;
30
31 #define MAX_BATTS 4
32 #define BATT_NAME_LEN 32
33 static char batt_names[MAX_BATTS][BATT_NAME_LEN];
34
35 static char sys_dir[256];
36 #define BUFSIZE 256
37
38 static int sys_read(char *buf, char *attribute)
39 {
40   char name[256];
41   snprintf(name, sizeof(name), "%s/%s", sys_dir, attribute);
42
43   int fd = open(name, O_RDONLY);
44   if (fd < 0)
45     return 0;
46
47   int n = read(fd, buf, BUFSIZE);
48   close(fd);
49   if (n < 0)
50     return 0;
51
52   buf[BUFSIZE-1] = 0;
53   char *nl = strchr(buf, '\n');
54   if (nl)
55     *nl = 0;
56   DBG("\t%s=%s\n", attribute, buf);
57   return 1;
58 }
59
60 static int sys_read_int(char *attribute, int default_value)
61 {
62   char buf[BUFSIZE];
63   if (!sys_read(buf, attribute) || !buf[0])
64     return default_value;
65   else
66     return atoi(buf);
67 }
68
69 static void parse_ac(void)
70 {
71   ac_online = sys_read_int("online", 0);
72 }
73
74 static int get_batt_id(char *batt_name)
75 {
76   for (int i=0; i<MAX_BATTS; i++)
77     {
78       if (!strcmp(batt_names[i], batt_name))
79         return i;
80       if (!batt_names[i][0])
81         {
82           snprintf(batt_names[i], BATT_NAME_LEN, "%s", batt_name);
83           return i;
84         }
85     }
86   return MAX_BATTS;
87 }
88
89 static void parse_batt(char *batt_name)
90 {
91   int batt_id = get_batt_id(batt_name);
92   DBG("\t-> id %d\n", batt_id);
93
94   if (!sys_read_int("present", 1))
95     return;
96
97   int charging = sys_read_int("charging", 0);
98   int charge_full = sys_read_int("charge_full", 0);
99   int charge_now = sys_read_int("charge_now", 0);
100   int current_now = sys_read_int("current_now", 0);
101
102   present_mask |= 1 << batt_id;
103   total_now += charge_now;
104   total_full += charge_full;
105   if (charging && current_now > 0)
106     {
107       charge_mask |= 1 << batt_id;
108       int ch = (long long)(charge_full - charge_now)*3600 / current_now;
109       if (ch > charge_time)
110         charge_time = ch;
111     }
112   else if (current_now > 0)
113     {
114       discharge_mask |= 1 << batt_id;
115       discharge_rate += current_now;
116     }
117 }
118
119 static void scan(void)
120 {
121   ac_online = 0;
122   charge_time = discharge_time = 0;
123   total_now = total_full = 0;
124   discharge_rate = 0;
125   present_mask = charge_mask = discharge_mask = 0;
126
127   const char dir[] = "/sys/class/power_supply";
128   DIR *d = opendir(dir);
129   if (!d)
130     return;
131
132   struct dirent *e;
133   while (e = readdir(d))
134     {
135       if (e->d_name[0] == '.')
136         continue;
137       snprintf(sys_dir, sizeof(sys_dir), "%s/%s", dir, e->d_name);
138       DBG("%s\n", sys_dir);
139
140       char type[BUFSIZE];
141       if (!sys_read(type, "type"))
142         continue;
143
144       if (!strcmp(type, "Mains"))
145         parse_ac();
146       else if (!strcmp(type, "Battery"))
147         parse_batt(e->d_name);
148     }
149   if (discharge_rate)
150     discharge_time = (long long) total_now*3600 / discharge_rate;
151   else
152     discharge_time = 1000000;
153
154   closedir(d);
155   DBG("=> Capacity: now=%d full=%d\n", total_now, total_full);
156   DBG("=> Charge: mask=%d time=%d\n", charge_mask, charge_time);
157   DBG("=> Discharge: mask=%d rate=%d time=%d\n", discharge_mask, discharge_rate, discharge_time);
158 }
159
160 static char *batt_mask(char *p, unsigned int mask)
161 {
162   if (present_mask & (present_mask-1))
163     {
164       char *p0 = p;
165       for (int i=0; mask; i++)
166         if (mask & (1 << i))
167           {
168             *p = (p == p0) ? ' ' : '+';
169             p++;
170             p += sprintf(p, "B%d", i);
171             mask &= ~(1 << i);
172           }
173     }
174   return p;
175 }
176
177 static void show(void)
178 {
179   char status[256];
180   char *p = status;
181   if (total_full)
182     p += sprintf(p, "%d%%", (int)((long long)100*total_now/total_full));
183   else
184     p += sprintf(p, "??%%");
185   if (discharge_mask && discharge_time < 1000000)
186     {
187       p += sprintf(p, "  %d:%02d  remains", discharge_time/3600, (discharge_time/60)%60);
188       batt_mask(p, discharge_mask);
189     }
190   else if (charge_mask)
191     {
192       p += sprintf(p, "  %d:%02d  charging", charge_time/3600, (charge_time/60)%60);
193       batt_mask(p, charge_mask);
194     }
195   else if (ac_online)
196     p += sprintf(p, " AC");
197   else
198     p += sprintf(p, " BATT");
199
200   struct osd_msg *msg = osd_new_msg();
201   osd_add_line(msg, NULL, status);
202   osd_send(msg);
203 }
204
205 static void show_if_warn(void)
206 {
207   if (discharge_mask && discharge_time < warn_threshold ||
208       last_ac_online >= 0 && (
209         charge_mask != last_charge_mask ||
210         discharge_mask != last_discharge_mask ||
211         ac_online != last_ac_online))
212     show();
213
214   last_charge_mask = charge_mask;
215   last_discharge_mask = discharge_mask;
216   last_ac_online = ac_online;
217 }
218
219 static void NONRET
220 usage(void)
221 {
222   fprintf(stderr, "\
223 Usage: osd-batt <options>\n\
224 \n\
225 Options:\n\
226 -c, --check\t\tDisplay status only if battery is low\n\
227 -e, --check-every=<sec>\tRun on background and check every <sec> seconds\n\
228 -w, --warn=<sec>\tBattery is low if less than <sec> seconds remain (default: 600)\n\
229 ");
230   exit(1);
231 }
232
233 static const char short_opts[] = "ce:w:";
234
235 static const struct option long_opts[] = {
236   { "check",            no_argument,            NULL,   'c' },
237   { "check-every",      required_argument,      NULL,   'e' },
238   { "warn",             required_argument,      NULL,   'w' },
239   { NULL,               0,                      NULL,   0   },
240 };
241
242 int main(int argc, char **argv)
243 {
244   int opt;
245   while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) >= 0)
246     switch (opt)
247       {
248       case 'c':
249         check_mode++;
250         break;
251       case 'e':
252         check_every = atoi(optarg);
253         break;
254       case 'w':
255         warn_threshold = atoi(optarg);
256         break;
257       default:
258         usage();
259       }
260   if (optind < argc)
261     usage();
262
263   if (check_every)
264     {
265       osd_fork();
266       for (;;)
267         {
268           scan();
269           show_if_warn();
270           osd_wait(check_every);
271         }
272     }
273
274   scan();
275   if (check_mode)
276     show_if_warn();
277   else
278     show();
279
280   return 0;
281 }