/************************************************************************** * * Tint2 : Linux battery * * Copyright (C) 2015 Sebastian Reichel * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version 2 * or any later version as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. **************************************************************************/ #ifdef __linux #include #include "common.h" #include "battery.h" struct psy_battery { /* generic properties */ gchar* name; /* sysfs files */ gchar* path_present; gchar* path_energy_now; gchar* path_energy_full; gchar* path_power_now; gchar* path_status; /* sysfs hints */ gboolean energy_in_uamp; gboolean power_in_uamp; /* values */ gboolean present; gint energy_now; gint energy_full; gint power_now; enum chargestate status; }; #define RETURN_ON_ERROR(err) if(error) { g_error_free(err); return FALSE; } static GList *batteries = NULL; static gboolean power_supply_is_battery(const gchar *entryname) { gchar *path_type = g_build_filename("/sys/class/power_supply", entryname, "type", NULL); GError *error = NULL; gchar *type; gsize typelen; g_file_get_contents(path_type, &type, &typelen, &error); g_free(path_type); RETURN_ON_ERROR(error); if(g_strcmp0(type, "Battery\n")) { g_free(type); return FALSE; } g_free(type); return TRUE; } static gboolean init_linux_battery(struct psy_battery *bat) { const gchar *entryname = bat->name; bat->energy_in_uamp = FALSE; bat->power_in_uamp = FALSE; bat->path_present = g_build_filename("/sys/class/power_supply", entryname, "present", NULL); if (!g_file_test(bat->path_present, G_FILE_TEST_EXISTS)) { goto err0; } bat->path_energy_now = g_build_filename("/sys/class/power_supply", entryname, "energy_now", NULL); if (!g_file_test(bat->path_energy_now, G_FILE_TEST_EXISTS)) { g_free(bat->path_energy_now); bat->path_energy_now = g_build_filename("/sys/class/power_supply", entryname, "charge_now", NULL); bat->energy_in_uamp = TRUE; } if (!g_file_test(bat->path_energy_now, G_FILE_TEST_EXISTS)) { goto err1; } if(!bat->energy_in_uamp) { bat->path_energy_full = g_build_filename("/sys/class/power_supply", entryname, "energy_full", NULL); if (!g_file_test(bat->path_energy_full, G_FILE_TEST_EXISTS)) goto err2; } else { bat->path_energy_full = g_build_filename("/sys/class/power_supply", entryname, "charge_full", NULL); if (!g_file_test(bat->path_energy_full, G_FILE_TEST_EXISTS)) goto err2; } bat->path_power_now = g_build_filename("/sys/class/power_supply", entryname, "power_now", NULL); if (!g_file_test(bat->path_power_now, G_FILE_TEST_EXISTS)) { g_free(bat->path_power_now); bat->path_power_now = g_build_filename("/sys/class/power_supply", entryname, "current_now", NULL); bat->power_in_uamp = TRUE; } if (!g_file_test(bat->path_power_now, G_FILE_TEST_EXISTS)) { goto err3; } bat->path_status = g_build_filename("/sys/class/power_supply", entryname, "status", NULL); if (!g_file_test(bat->path_status, G_FILE_TEST_EXISTS)) { goto err4; } return TRUE; err4: g_free(bat->path_status); err3: g_free(bat->path_power_now); err2: g_free(bat->path_energy_full); err1: g_free(bat->path_energy_now); err0: g_free(bat->path_present); return FALSE; } void free_linux_batteries() { GList *l = batteries; while (l != NULL) { GList *next = l->next; struct psy_battery *bat = l->data; g_free(bat->name); g_free(bat->path_status); g_free(bat->path_power_now); g_free(bat->path_energy_full); g_free(bat->path_energy_now); g_free(bat->path_present); batteries = g_list_delete_link(batteries, l); l = next; } batteries = NULL; } gboolean init_linux_batteries() { GDir *directory = 0; GError *error = NULL; const char *entryname; free_linux_batteries(); directory = g_dir_open("/sys/class/power_supply", 0, &error); RETURN_ON_ERROR(error); while ((entryname = g_dir_read_name(directory))) { if(!power_supply_is_battery(entryname)) continue; struct psy_battery *bat = g_malloc0(sizeof(*bat)); bat->name = g_strdup(entryname); if(init_linux_battery(bat)) { batteries = g_list_append(batteries, bat); fprintf(stdout, "found battery \"%s\"\n", bat->name); } else { g_free(bat); fprintf(stderr, RED "failed to initialize battery \"%s\"\n" RESET, entryname); } } g_dir_close(directory); return batteries != NULL; } static gboolean update_linux_battery(struct psy_battery *bat) { GError *error = NULL; gchar *data; gsize datalen; /* reset values */ bat->present = 0; bat->status = BATTERY_UNKNOWN; bat->energy_now = 0; bat->energy_full = 0; bat->power_now = 0; /* present */ g_file_get_contents(bat->path_present, &data, &datalen, &error); RETURN_ON_ERROR(error); bat->present = (atoi(data) == 1); g_free(data); /* we are done, if battery is not present */ if(!bat->present) return TRUE; /* status */ bat->status = BATTERY_UNKNOWN; g_file_get_contents(bat->path_status, &data, &datalen, &error); RETURN_ON_ERROR(error); if (!g_strcmp0(data, "Charging\n")) { bat->status = BATTERY_CHARGING; } else if (!g_strcmp0(data, "Discharging\n")) { bat->status = BATTERY_DISCHARGING; } else if (!g_strcmp0(data, "Full\n")) { bat->status = BATTERY_FULL; } g_free(data); /* energy now */ g_file_get_contents(bat->path_energy_now, &data, &datalen, &error); RETURN_ON_ERROR(error); bat->energy_now = atoi(data); g_free(data); /* energy full */ g_file_get_contents(bat->path_energy_full, &data, &datalen, &error); RETURN_ON_ERROR(error); bat->energy_full = atoi(data); g_free(data); /* power now */ g_file_get_contents(bat->path_power_now, &data, &datalen, &error); RETURN_ON_ERROR(error); bat->power_now = atoi(data); g_free(data); return TRUE; } void update_linux_batteries(enum chargestate *state, gint64 *energy_now, gint64 *energy_full, int *seconds) { GList *l; gint64 total_energy_now = 0; gint64 total_energy_full = 0; gint64 total_power_now = 0; gboolean charging = FALSE; gboolean discharging = FALSE; gboolean full = FALSE; for (l = batteries; l != NULL; l = l->next) { struct psy_battery *bat = l->data; update_linux_battery(bat); total_energy_now += bat->energy_now; total_energy_full += bat->energy_full; total_power_now += bat->power_now; charging |= (bat->status == BATTERY_CHARGING); discharging |= (bat->status == BATTERY_DISCHARGING); full |= (bat->status == BATTERY_FULL); } /* global energy stats */ *energy_now = total_energy_now; *energy_full = total_energy_full; /* build global state */ *state = BATTERY_UNKNOWN; if (charging && !discharging) *state = BATTERY_CHARGING; else if (!charging && discharging) *state = BATTERY_DISCHARGING; else if (!charging && !discharging && full) *state = BATTERY_FULL; /* calculate seconds */ *seconds = 0; if (total_power_now > 0) { if(*state == BATTERY_CHARGING) *seconds = 3600 * (total_energy_full - total_energy_now) / total_power_now; else if(*state == BATTERY_DISCHARGING) *seconds = 3600 * total_energy_now / total_power_now; } } static gchar* energy_human_readable(struct psy_battery *bat) { gint now = bat->energy_now; gint full = bat->energy_full; gchar unit = bat->energy_in_uamp ? 'A' : 'W'; if (full >= 1000000) { return g_strdup_printf("%d.%d / %d.%d %ch", now / 1000000, (now % 1000000) / 100000, full / 1000000, (full % 1000000) / 100000, unit); } else if (full >= 1000) { return g_strdup_printf("%d.%d / %d.%d m%ch", now / 1000, (now % 1000) / 100, full / 1000, (full % 1000) / 100, unit); } else { return g_strdup_printf("%d / %d µ%ch", now, full, unit); } } static gchar* power_human_readable(struct psy_battery *bat) { gint power = bat->power_now; gchar unit = bat->power_in_uamp ? 'A' : 'W'; if (power >= 1000000) { return g_strdup_printf("%d.%d %c", power / 1000000, (power % 1000000) / 100000, unit); } else if (power >= 1000) { return g_strdup_printf("%d.%d m%c", power / 1000, (power % 1000) / 100, unit); } else if (power > 0) { return g_strdup_printf("%d µ%c", power, unit); } else { return g_strdup_printf("0 %c", unit); } } char* linux_batteries_get_tooltip() { GList *l; GString *tooltip = g_string_new(""); gchar *result; for (l = batteries; l != NULL; l = l->next) { struct psy_battery *bat = l->data; if (tooltip->len) g_string_append_c(tooltip, '\n'); g_string_append_printf(tooltip, "%s\n", bat->name); if (!bat->present) { g_string_append_printf(tooltip, "\tnot connected"); continue; } gchar *power = power_human_readable(bat); gchar *energy = energy_human_readable(bat); gchar *state = (bat->status == BATTERY_UNKNOWN) ? "Level" : chargestate2str(bat->status); guint percentage = 0.5 + ((bat->energy_now <= bat->energy_full ? bat->energy_now : bat->energy_full) * 100.0) / bat->energy_full; g_string_append_printf(tooltip, "\t%s: %s (%u %%)\n\tPower: %s", state, energy, percentage, power); g_free(power); g_free(energy); } result = tooltip->str; g_string_free(tooltip, FALSE); return result; } #endif