// For vasprintf #define _GNU_SOURCE #include "chart.h" #include #include #include #include #include #include #include #include #include char *sasprintf(const char *fmt, ...) { char *result; va_list args; va_start(args, fmt); if (vasprintf(&result, fmt, args) == -1) { fprintf(stderr, "Can't allocate memory!?\n"); abort(); } va_end(args); return result; } struct State { char *voltage_filename; char *current_filename; BVChart *voltage; BVChart *current; }; static float fatof(const char *filename) { FILE *f = fopen(filename, "r"); if (f == NULL) { return NAN; } const int bufsize = 1024; char buf[bufsize]; const int read = fread(buf, sizeof(char), bufsize, f); if (read == 0 || read == bufsize || ferror(f)) { fclose(f); return NAN; } fclose(f); char *end; errno = 0; float val = strtof(buf, &end); int parsed = end - buf; gboolean parsed_all = parsed == read; gboolean parsed_all_but_space = parsed == read - 1 && isspace(*end); gboolean parse_ok = parsed_all || parsed_all_but_space; if (errno != 0 || parsed == 0 || !parse_ok) { return NAN; } return val; } static gboolean collect_data(struct State *state) { float now = g_get_monotonic_time() / 1e6; float voltage = fatof(state->voltage_filename); float current = fatof(state->current_filename); if (!isnan(voltage)) { bv_chart_add_point(state->voltage, now, voltage); gtk_widget_queue_draw(GTK_WIDGET(state->voltage)); } if (!isnan(current)) { bv_chart_add_point(state->current, now, current); gtk_widget_queue_draw(GTK_WIDGET(state->current)); } return TRUE; } static void activate(GtkApplication *app, gpointer user_data) { GtkWidget *window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "BatteryViewer"); gtk_window_set_default_size(GTK_WINDOW(window), 200, 200); GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 1); gtk_container_add(GTK_CONTAINER(window), box); struct State *state = (struct State *)user_data; state->voltage = BV_CHART(bv_chart_new()); gboolean expand = TRUE; gboolean fill = TRUE; guint padding = 0; gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(state->voltage), expand, fill, padding); state->current = BV_CHART(bv_chart_new()); gtk_box_pack_end(GTK_BOX(box), GTK_WIDGET(state->current), expand, fill, padding); gtk_widget_show_all(window); g_timeout_add_seconds(1, (GSourceFunc)collect_data, user_data); } char *find_battery_dir() { glob_t globbuf = {.gl_offs = 0}; char pattern[] = "/sys/class/power_supply/*[Bb][Aa][Tt]*"; if (glob(pattern, GLOB_ERR, NULL, &globbuf) != 0 || globbuf.gl_pathc == 0) { fprintf(stderr, "Could not find battery dir %s\n", pattern); exit(1); } if (globbuf.gl_pathc != 1) { // TODO: Filter for directories that have {voltage,current}_now files. // TODO: Support multiple batteries fprintf(stderr, "Multiple batteries found. Arbitrarily choosing %s\n", globbuf.gl_pathv[0]); } char *dir = strdup(globbuf.gl_pathv[0]); globfree(&globbuf); return dir; } int main(int argc, char **argv) { char *battery_dir = find_battery_dir(); struct State state = { .voltage_filename = sasprintf("%s/voltage_now", battery_dir), .current_filename = sasprintf("%s/current_now", battery_dir), }; GtkApplication *app = gtk_application_new("com.scottworley.batteryviewer", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), &state); int status = g_application_run(G_APPLICATION(app), argc, argv); g_object_unref(app); return status; }