1 /**************************************************************************
2 *
3 * Tint2 : Linux battery
4 *
5 * Copyright (C) 2015 Sebastian Reichel <sre@ring0.de>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License version 2
9 * or any later version as published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 **************************************************************************/
19
20 #ifdef __linux__
21
22 #include <stdlib.h>
23
24 #include "common.h"
25 #include "battery.h"
26 #include "uevent.h"
27
28 enum psy_type {
29 PSY_UNKNOWN,
30 PSY_BATTERY,
31 PSY_MAINS,
32 };
33
34 struct psy_battery {
35 /* generic properties */
36 gchar *name;
37 /* monotonic time, in microseconds */
38 gint64 timestamp;
39 /* sysfs files */
40 gchar *path_present;
41 gchar *path_level_now;
42 gchar *path_level_full;
43 gchar *path_rate_now;
44 gchar *path_status;
45 /* values */
46 gboolean present;
47 gint level_now;
48 gint level_full;
49 gint rate_now;
50 gchar unit;
51 ChargeState status;
52 };
53
54 struct psy_mains {
55 /* generic properties */
56 gchar *name;
57 /* sysfs files */
58 gchar *path_online;
59 /* values */
60 gboolean online;
61 };
62
is_file_non_empty(const char * path)63 static gboolean is_file_non_empty(const char *path)
64 {
65 FILE *f = fopen(path, "r");
66 if (!f)
67 return FALSE;
68 char buffer[1024];
69 size_t count = fread(buffer, 1, sizeof(buffer), f);
70 fclose(f);
71 if (count > 0)
72 return TRUE;
73 else
74 return FALSE;
75 }
76
uevent_battery_update()77 static void uevent_battery_update()
78 {
79 update_battery_tick(NULL);
80 }
81 static struct uevent_notify psy_change = {UEVENT_CHANGE, "power_supply", NULL, uevent_battery_update};
82
uevent_battery_plug()83 static void uevent_battery_plug()
84 {
85 fprintf(stderr, "tint2: reinitialize batteries after HW change\n");
86 reinit_battery();
87 }
88 static struct uevent_notify psy_plug = {UEVENT_ADD | UEVENT_REMOVE, "power_supply", NULL, uevent_battery_plug};
89
90 #define RETURN_ON_ERROR(err) \
91 if (err) { \
92 g_error_free(err); \
93 fprintf(stderr, RED "tint2: %s:%d: errror" RESET "\n", __FILE__, __LINE__); \
94 return FALSE; \
95 }
96
97 static GList *batteries = NULL;
98 static GList *mains = NULL;
99
level_to_percent(gint level_now,gint level_full)100 static guint8 level_to_percent(gint level_now, gint level_full)
101 {
102 return 0.5 + ((level_now <= level_full ? level_now : level_full) * 100.0) / level_full;
103 }
104
power_supply_get_type(const gchar * entryname)105 static enum psy_type power_supply_get_type(const gchar *entryname)
106 {
107 gchar *path_type = g_build_filename(battery_sys_prefix, "/sys/class/power_supply", entryname, "type", NULL);
108 GError *error = NULL;
109 gchar *type;
110 gsize typelen;
111
112 g_file_get_contents(path_type, &type, &typelen, &error);
113 if (error) {
114 fprintf(stderr, RED "tint2: %s:%d: read failed for %s" RESET "\n", __FILE__, __LINE__, path_type);
115 g_free(path_type);
116 g_error_free(error);
117 return PSY_UNKNOWN;
118 }
119 g_free(path_type);
120
121 if (!g_strcmp0(type, "Battery\n")) {
122 g_free(type);
123 return PSY_BATTERY;
124 }
125
126 if (!g_strcmp0(type, "Mains\n")) {
127 g_free(type);
128 return PSY_MAINS;
129 }
130
131 g_free(type);
132
133 return PSY_UNKNOWN;
134 }
135
init_linux_battery(struct psy_battery * bat)136 static gboolean init_linux_battery(struct psy_battery *bat)
137 {
138 const gchar *entryname = bat->name;
139
140 bat->path_present = g_build_filename(battery_sys_prefix, "/sys/class/power_supply", entryname, "present", NULL);
141 if (!is_file_non_empty(bat->path_present)) {
142 fprintf(stderr, RED "tint2: %s:%d: read failed for %s" RESET "\n", __FILE__, __LINE__, bat->path_present);
143 goto err0;
144 }
145
146 bat->path_level_now =
147 g_build_filename(battery_sys_prefix, "/sys/class/power_supply", entryname, "energy_now", NULL);
148 bat->path_level_full =
149 g_build_filename(battery_sys_prefix, "/sys/class/power_supply", entryname, "energy_full", NULL);
150 bat->path_rate_now = g_build_filename(battery_sys_prefix, "/sys/class/power_supply", entryname, "power_now", NULL);
151 bat->unit = 'W';
152
153 if (!is_file_non_empty(bat->path_level_now) ||
154 !is_file_non_empty(bat->path_level_full)) {
155 g_free(bat->path_level_now);
156 g_free(bat->path_level_full);
157 g_free(bat->path_rate_now);
158 bat->path_level_now =
159 g_build_filename(battery_sys_prefix, "/sys/class/power_supply", entryname, "charge_now", NULL);
160 bat->path_level_full =
161 g_build_filename(battery_sys_prefix, "/sys/class/power_supply", entryname, "charge_full", NULL);
162 bat->path_rate_now =
163 g_build_filename(battery_sys_prefix, "/sys/class/power_supply", entryname, "current_now", NULL);
164 bat->unit = 'A';
165 }
166 if (!is_file_non_empty(bat->path_level_now)) {
167 fprintf(stderr, RED "tint2: %s:%d: read failed for %s" RESET "\n", __FILE__, __LINE__, bat->path_level_now);
168 goto err1;
169 }
170 if (!is_file_non_empty(bat->path_level_full)) {
171 fprintf(stderr, RED "tint2: %s:%d: read failed for %s" RESET "\n", __FILE__, __LINE__, bat->path_level_full);
172 goto err1;
173 }
174
175 bat->path_status = g_build_filename(battery_sys_prefix, "/sys/class/power_supply", entryname, "status", NULL);
176 if (!is_file_non_empty(bat->path_status)) {
177 fprintf(stderr, RED "tint2: %s:%d: read failed for %s" RESET "\n", __FILE__, __LINE__, bat->path_status);
178 goto err2;
179 }
180
181 return TRUE;
182
183 err2:
184 g_free(bat->path_status);
185 err1:
186 g_free(bat->path_level_now);
187 g_free(bat->path_level_full);
188 g_free(bat->path_rate_now);
189 err0:
190 g_free(bat->path_present);
191
192 return FALSE;
193 }
194
init_linux_mains(struct psy_mains * ac)195 static gboolean init_linux_mains(struct psy_mains *ac)
196 {
197 const gchar *entryname = ac->name;
198
199 ac->path_online = g_build_filename(battery_sys_prefix, "/sys/class/power_supply", entryname, "online", NULL);
200 if (!is_file_non_empty(ac->path_online)) {
201 fprintf(stderr, RED "tint2: %s:%d: read failed for %s" RESET "\n", __FILE__, __LINE__, ac->path_online);
202 g_free(ac->path_online);
203 return FALSE;
204 }
205
206 return TRUE;
207 }
208
psy_battery_free(gpointer data)209 static void psy_battery_free(gpointer data)
210 {
211 struct psy_battery *bat = data;
212 g_free(bat->name);
213 g_free(bat->path_status);
214 g_free(bat->path_rate_now);
215 g_free(bat->path_level_full);
216 g_free(bat->path_level_now);
217 g_free(bat->path_present);
218 g_free(bat);
219 }
220
psy_mains_free(gpointer data)221 static void psy_mains_free(gpointer data)
222 {
223 struct psy_mains *ac = data;
224 g_free(ac->name);
225 g_free(ac->path_online);
226 g_free(ac);
227 }
228
battery_os_free()229 void battery_os_free()
230 {
231 uevent_unregister_notifier(&psy_change);
232 uevent_unregister_notifier(&psy_plug);
233
234 g_list_free_full(batteries, psy_battery_free);
235 batteries = NULL;
236 g_list_free_full(mains, psy_mains_free);
237 mains = NULL;
238 }
239
add_battery(const char * entryname)240 static void add_battery(const char *entryname)
241 {
242 struct psy_battery *bat = g_malloc0(sizeof(*bat));
243 bat->name = g_strdup(entryname);
244
245 if (init_linux_battery(bat)) {
246 batteries = g_list_append(batteries, bat);
247 fprintf(stderr, GREEN "Found battery \"%s\"" RESET "\n", bat->name);
248 } else {
249 g_free(bat);
250 fprintf(stderr, RED "tint2: Failed to initialize battery \"%s\"" RESET "\n", entryname);
251 }
252 }
253
add_mains(const char * entryname)254 static void add_mains(const char *entryname)
255 {
256 struct psy_mains *ac = g_malloc0(sizeof(*ac));
257 ac->name = g_strdup(entryname);
258
259 if (init_linux_mains(ac)) {
260 mains = g_list_append(mains, ac);
261 fprintf(stderr, GREEN "Found mains \"%s\"" RESET "\n", ac->name);
262 } else {
263 g_free(ac);
264 fprintf(stderr, RED "tint2: Failed to initialize mains \"%s\"" RESET "\n", entryname);
265 }
266 }
267
battery_os_init()268 gboolean battery_os_init()
269 {
270 GDir *directory = 0;
271 GError *error = NULL;
272 const char *entryname;
273
274 battery_os_free();
275
276 gchar *dir_path = g_build_filename(battery_sys_prefix, "/sys/class/power_supply", NULL);
277 directory = g_dir_open(dir_path, 0, &error);
278 g_free(dir_path);
279 RETURN_ON_ERROR(error);
280
281 while ((entryname = g_dir_read_name(directory))) {
282 fprintf(stderr, GREEN "tint2: Found power device %s" RESET "\n", entryname);
283 enum psy_type type = power_supply_get_type(entryname);
284
285 switch (type) {
286 case PSY_BATTERY:
287 add_battery(entryname);
288 break;
289 case PSY_MAINS:
290 add_mains(entryname);
291 break;
292 default:
293 break;
294 }
295 }
296
297 g_dir_close(directory);
298
299 uevent_register_notifier(&psy_change);
300 uevent_register_notifier(&psy_plug);
301
302 return batteries != NULL;
303 }
304
estimate_rate_usage(struct psy_battery * bat,gint old_level_now,gint64 old_timestamp)305 static gint estimate_rate_usage(struct psy_battery *bat, gint old_level_now, gint64 old_timestamp)
306 {
307 gint64 diff_level = ABS(bat->level_now - old_level_now);
308 gint64 diff_time = bat->timestamp - old_timestamp;
309
310 /* µW = (µWh * 3600) / (µs / 1000000) */
311 gint rate = diff_level * 3600 * 1000000 / MAX(1, diff_time);
312
313 return rate;
314 }
315
update_linux_battery(struct psy_battery * bat)316 static gboolean update_linux_battery(struct psy_battery *bat)
317 {
318 GError *error = NULL;
319 gchar *data;
320 gsize datalen;
321
322 gint64 old_timestamp = bat->timestamp;
323 int old_level_now = bat->level_now;
324 gint old_rate_now = bat->rate_now;
325
326 /* reset values */
327 bat->present = 0;
328 bat->status = BATTERY_UNKNOWN;
329 bat->level_now = 0;
330 bat->level_full = 0;
331 bat->rate_now = 0;
332 bat->timestamp = g_get_monotonic_time();
333
334 /* present */
335 g_file_get_contents(bat->path_present, &data, &datalen, &error);
336 RETURN_ON_ERROR(error);
337 bat->present = (atoi(data) == 1);
338 g_free(data);
339
340 /* we are done, if battery is not present */
341 if (!bat->present)
342 return TRUE;
343
344 /* status */
345 bat->status = BATTERY_UNKNOWN;
346 g_file_get_contents(bat->path_status, &data, &datalen, &error);
347 RETURN_ON_ERROR(error);
348 if (!g_strcmp0(data, "Charging\n")) {
349 bat->status = BATTERY_CHARGING;
350 } else if (!g_strcmp0(data, "Discharging\n")) {
351 bat->status = BATTERY_DISCHARGING;
352 } else if (!g_strcmp0(data, "Full\n")) {
353 bat->status = BATTERY_FULL;
354 }
355 g_free(data);
356
357 /* level now */
358 g_file_get_contents(bat->path_level_now, &data, &datalen, &error);
359 RETURN_ON_ERROR(error);
360 bat->level_now = atoi(data);
361 g_free(data);
362
363 /* level full */
364 g_file_get_contents(bat->path_level_full, &data, &datalen, &error);
365 RETURN_ON_ERROR(error);
366 bat->level_full = atoi(data);
367 g_free(data);
368
369 /* rate now */
370 g_file_get_contents(bat->path_rate_now, &data, &datalen, &error);
371 if (g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_NODEV)) {
372 /* some hardware does not support reading current rate consumption */
373 g_error_free(error);
374 bat->rate_now = estimate_rate_usage(bat, old_level_now, old_timestamp);
375 if (bat->rate_now == 0 && bat->status != BATTERY_FULL) {
376 /* If the hardware updates the level slower than our sampling period,
377 * we need to sample more rarely */
378 bat->rate_now = old_rate_now;
379 bat->timestamp = old_timestamp;
380 }
381 } else if (error) {
382 g_error_free(error);
383 return FALSE;
384 } else {
385 bat->rate_now = atoi(data);
386 g_free(data);
387 }
388
389 return TRUE;
390 }
391
update_linux_mains(struct psy_mains * ac)392 static gboolean update_linux_mains(struct psy_mains *ac)
393 {
394 GError *error = NULL;
395 gchar *data;
396 gsize datalen;
397 ac->online = FALSE;
398
399 /* online */
400 g_file_get_contents(ac->path_online, &data, &datalen, &error);
401 RETURN_ON_ERROR(error);
402 ac->online = (atoi(data) == 1);
403 g_free(data);
404
405 return TRUE;
406 }
407
battery_os_update(BatteryState * state)408 int battery_os_update(BatteryState *state)
409 {
410 GList *l;
411
412 gint64 total_level_now = 0;
413 gint64 total_level_full = 0;
414 gint64 total_rate_now = 0;
415 gint seconds = 0;
416
417 gboolean charging = FALSE;
418 gboolean discharging = FALSE;
419 gboolean full = FALSE;
420 gboolean ac_connected = FALSE;
421
422 for (l = batteries; l != NULL; l = l->next) {
423 struct psy_battery *bat = l->data;
424 update_linux_battery(bat);
425
426 total_level_now += bat->level_now;
427 total_level_full += bat->level_full;
428 total_rate_now += bat->rate_now;
429
430 charging |= (bat->status == BATTERY_CHARGING);
431 discharging |= (bat->status == BATTERY_DISCHARGING);
432 full |= (bat->status == BATTERY_FULL);
433 }
434
435 for (l = mains; l != NULL; l = l->next) {
436 struct psy_mains *ac = l->data;
437 update_linux_mains(ac);
438 ac_connected |= (ac->online);
439 }
440
441 /* build global state */
442 if (charging && !discharging)
443 state->state = BATTERY_CHARGING;
444 else if (!charging && discharging)
445 state->state = BATTERY_DISCHARGING;
446 else if (!charging && !discharging && full)
447 state->state = BATTERY_FULL;
448
449 /* calculate seconds */
450 if (total_rate_now > 0) {
451 if (state->state == BATTERY_CHARGING)
452 seconds = 3600 * (total_level_full - total_level_now) / total_rate_now;
453 else if (state->state == BATTERY_DISCHARGING)
454 seconds = 3600 * total_level_now / total_rate_now;
455 seconds = MAX(0, seconds);
456 }
457 battery_state_set_time(state, seconds);
458
459 /* calculate percentage */
460 state->percentage = level_to_percent(total_level_now, total_level_full);
461
462 /* AC state */
463 state->ac_connected = ac_connected;
464
465 if (state->state == BATTERY_UNKNOWN) {
466 if (ac_connected) {
467 if (total_rate_now == 0 && state->percentage >= 90)
468 state->state = BATTERY_FULL;
469 else
470 state->state = BATTERY_CHARGING;
471 } else {
472 state->state = BATTERY_DISCHARGING;
473 }
474 }
475
476 return 0;
477 }
478
level_human_readable(struct psy_battery * bat)479 static gchar *level_human_readable(struct psy_battery *bat)
480 {
481 gint now = bat->level_now;
482 gint full = bat->level_full;
483
484 if (full >= 1000000) {
485 return g_strdup_printf("%d.%d / %d.%d %ch",
486 now / 1000000,
487 (now % 1000000) / 100000,
488 full / 1000000,
489 (full % 1000000) / 100000,
490 bat->unit);
491 } else if (full >= 1000) {
492 return g_strdup_printf("%d.%d / %d.%d m%ch",
493 now / 1000,
494 (now % 1000) / 100,
495 full / 1000,
496 (full % 1000) / 100,
497 bat->unit);
498 } else {
499 return g_strdup_printf("%d / %d µ%ch", now, full, bat->unit);
500 }
501 }
502
rate_human_readable(struct psy_battery * bat)503 static gchar *rate_human_readable(struct psy_battery *bat)
504 {
505 gint rate = bat->rate_now;
506 gchar unit = bat->unit;
507
508 if (rate >= 1000000) {
509 return g_strdup_printf("%d.%d %c", rate / 1000000, (rate % 1000000) / 100000, unit);
510 } else if (rate >= 1000) {
511 return g_strdup_printf("%d.%d m%c", rate / 1000, (rate % 1000) / 100, unit);
512 } else if (rate > 0) {
513 return g_strdup_printf("%d µ%c", rate, unit);
514 } else {
515 return g_strdup_printf("0 %c", unit);
516 }
517 }
518
battery_os_tooltip()519 char *battery_os_tooltip()
520 {
521 GList *l;
522 GString *tooltip = g_string_new("");
523 gchar *result;
524
525 for (l = batteries; l != NULL; l = l->next) {
526 struct psy_battery *bat = l->data;
527
528 if (tooltip->len)
529 g_string_append_c(tooltip, '\n');
530
531 g_string_append_printf(tooltip, "%s\n", bat->name);
532
533 if (!bat->present) {
534 g_string_append_printf(tooltip, "\tnot connected");
535 continue;
536 }
537
538 gchar *rate = rate_human_readable(bat);
539 gchar *level = level_human_readable(bat);
540 gchar *state = (bat->status == BATTERY_UNKNOWN) ? "energy" : chargestate2str(bat->status);
541
542 guint8 percentage = level_to_percent(bat->level_now, bat->level_full);
543
544 g_string_append_printf(tooltip, "\t%s: %s (%u %%)\n\trate: %s", state, level, percentage, rate);
545
546 g_free(rate);
547 g_free(level);
548 }
549
550 for (l = mains; l != NULL; l = l->next) {
551 struct psy_mains *ac = l->data;
552
553 if (tooltip->len)
554 g_string_append_c(tooltip, '\n');
555
556 g_string_append_printf(tooltip, "%s\n", ac->name);
557 g_string_append_printf(tooltip, ac->online ? "\tConnected" : "\tDisconnected");
558 }
559
560 result = tooltip->str;
561 g_string_free(tooltip, FALSE);
562
563 return result;
564 }
565
566 #endif
567