diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Makefile | 26 | ||||
-rw-r--r-- | lowbat.c | 155 | ||||
-rwxr-xr-x | shlowbat | 26 |
4 files changed, 209 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f46a5a0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +lowbat +lowbat.o diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..180a311 --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +# Define variables +CC = gcc +CFLAGS = -std=c99 -Wall $(shell pkg-config --cflags libnotify) +LDFLAGS = $(shell pkg-config --libs libnotify) +LIBS = -lnotify +TARGET = lowbat +SRCS = lowbat.c +OBJS = $(SRCS:.c=.o) + +# Default target +all: $(TARGET) + +# Build target +$(TARGET): $(OBJS) + $(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS) $(LIBS) + +# Compile source files +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +# Clean up build files +clean: + rm -f $(TARGET) $(OBJS) + +# Phony targets +.PHONY: all clean diff --git a/lowbat.c b/lowbat.c new file mode 100644 index 0000000..14f10a2 --- /dev/null +++ b/lowbat.c @@ -0,0 +1,155 @@ +/* LINUX DOCS: https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-power + * (C) 2024 Tim Keller <tjkeller.xyz> + * MIT License + */ + +#include <libnotify/notify.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <dirent.h> + +#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) { + NotifyNotification *notif; + notify_init("lowbat"); + notif = notify_notification_new(title, content, NULL); + + 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); + + w_lowbat_crit = w_lowbat = 1; + } else if (capacity <= 20 && !w_lowbat) { + sprintf(msg, "%d%% remains", capacity); + notifysend("Low Battery Warning", msg); + + w_lowbat = 1; + } + } else { + w_lowbat_crit = w_lowbat = 0; // clear conditions + } + + printf("\r%d%%", capacity); + fflush(stdout); + sleep(PERIOD); + } + + return 0; +} diff --git a/shlowbat b/shlowbat new file mode 100755 index 0000000..a02ce56 --- /dev/null +++ b/shlowbat @@ -0,0 +1,26 @@ +#!/bin/sh +psupath=/sys/class/power_supply +warning_level=0 # 0 = not warned, 1 = warned, 2 = warned critical + +sum_props() { + for prop in $(cat $psupath/*/$1); do sum=$(( $sum + $prop )); done + echo $sum +} +is_discharging() { cat $psupath/*/status | grep "Discharging" > /dev/null ; } + +while true; do + capacity=$(( $(sum_props energy_now) * 100 / $(sum_props energy_full) )) + + if is_discharging; then + if [ $capacity -le 5 ] && [ $warning_level -lt 2 ]; then + notify_send "Critical Low Battery Warning" "${capacity}% remains" + elif [ $capacity -le 20 ] && [ $warning_level -lt 1 ]; then + notify_send "Low Battery Warning" "${capacity}% remains" + fi + else + warning_level=0 + fi + + printf "\r%d%%" $capacity + sleep 3 +done |