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