/* LINUX DOCS: https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-power * (C) 2024 Tim Keller * MIT License */ #include #include #include #include #include #define PERIOD 3 // number of seconds before each check #define PSUPATH "/sys/class/power_supply" // number of seconds before each check #define MAX_BATTERIES 8 #define MAX_PSU_NAME_LEN 8 #define MAX_PROP_PATH_LEN 64 // variables static char batteries[MAX_BATTERIES][MAX_PSU_NAME_LEN]; static unsigned int num_batteries; static FILE *fp; static char prop_path[MAX_PROP_PATH_LEN]; // functions void load_installed_batteries() { // search through "power supplies" for "status" property (only available on batteries) DIR *dir, *subdir; struct dirent *entry, *subentry; if ((dir = opendir(PSUPATH)) == NULL) { fprintf(stderr, "failed to see power supplies"); // TODO exit(EXIT_FAILURE); } // power supplies TODO warn user if max batteries exceeded num_batteries = 0; while ((entry = readdir(dir)) != NULL && num_batteries < MAX_BATTERIES) { if (entry->d_name[0] == '.') continue; // skip . and .. if (strlen(entry->d_name) > MAX_PSU_NAME_LEN) { fprintf(stderr, "the power supply name was too long: %s", entry->d_name); exit(EXIT_FAILURE); } // power supplies will always be a directory, no need to check beforehand :) snprintf(prop_path, MAX_PROP_PATH_LEN, PSUPATH "/%s", entry->d_name); if ((subdir = opendir(prop_path)) == NULL) { fprintf(stderr, "failed to see power supplies sub\n"); // TODO exit(EXIT_FAILURE); } // power supply properties while ((subentry = readdir(subdir)) != NULL) { // add to list of batteries if it has the status property if (strcmp(subentry->d_name, "status") == 0) { strncpy(batteries[num_batteries], entry->d_name, MAX_PSU_NAME_LEN); num_batteries++; break; } } closedir(subdir); } closedir(dir); } void notifysend(char *title, char *content, NotifyUrgency urgency) { NotifyNotification *notif; notify_init("lowbat"); notif = notify_notification_new(title, content, NULL); notify_notification_set_timeout(notif, NOTIFY_EXPIRES_NEVER); notify_notification_set_urgency(notif, urgency); notify_notification_show(notif, NULL); } void read_prop(char *psu, char *prop, char *fmt, void *out) { /* this function is only designed to read one var at a time because all bat * properties will only be one var */ snprintf(prop_path, MAX_PROP_PATH_LEN, PSUPATH "/%s/%s", psu, prop); if ((fp = fopen(prop_path, "r")) != NULL) { fscanf(fp, fmt, out); fclose(fp); } else { printf("failed to read fmt '%s' from property %s\n", fmt, prop_path); // TODO fprintf me plz exit(EXIT_FAILURE); } } int get_total_capacity() { unsigned long int energy_full = 0, energy_now = 0, x; int i; for (i = 0; i < num_batteries; i++) { read_prop(batteries[i], "energy_full", "%ld", &x); energy_full += x; read_prop(batteries[i], "energy_now", "%ld", &x); energy_now += x; } return energy_now * 100 / energy_full; } int is_discharging() { char status[12]; // valid values: "Unknown", "Charging", "Discharging", "Not charging", "Full" int i; for (i = 0; i < num_batteries; i++) { read_prop(batteries[i], "status", "%s", &status); if (strcmp(status, "Discharging") != 0) return 0; } return 1; } int main() { int discharging, capacity; int w_lowbat = 0, w_lowbat_crit = 0; // 1 if warned already char msg[32]; load_installed_batteries(); if (!num_batteries) { fprintf(stderr, "no batteries installed in system. exiting...\n"); // TODO exit(EXIT_FAILURE); } while (1) { load_installed_batteries(); discharging = is_discharging(); capacity = get_total_capacity(); if (discharging) { if (capacity <= 5 && !w_lowbat_crit) { sprintf(msg, "%d%% remains", capacity); // TODO snprintf me plz notifysend("Critical Low Battery Warning", msg, NOTIFY_URGENCY_CRITICAL); w_lowbat_crit = w_lowbat = 1; } else if (capacity <= 20 && !w_lowbat) { sprintf(msg, "%d%% remains", capacity); notifysend("Low Battery Warning", msg, NOTIFY_URGENCY_NORMAL); w_lowbat = 1; } } else { w_lowbat_crit = w_lowbat = 0; // clear conditions } printf("\r%s %d%%", discharging ? "Discharging" : "Charging", capacity); fflush(stdout); sleep(PERIOD); } return 0; }