]> git.scottworley.com Git - batteryviewer/blame - batteryviewer.c
License
[batteryviewer] / batteryviewer.c
CommitLineData
769fc186
SW
1/* batteryviewer: Display battery metrics
2 * Copyright (C) 2023 Scott Worley <scottworley@scottworley.com>
3
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, version 3.
7
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <https://www.gnu.org/licenses/>.
15*/
16
ef0963b8
SW
17// For vasprintf
18#define _GNU_SOURCE
19
5a945372 20#include "chart.h"
0f9e308a
SW
21#include <ctype.h>
22#include <errno.h>
eda1f8c1 23#include <glob.h>
79ac753b 24#include <gtk/gtk.h>
0f9e308a 25#include <math.h>
ef0963b8 26#include <stdarg.h>
0f9e308a
SW
27#include <stdio.h>
28#include <stdlib.h>
eda1f8c1 29#include <string.h>
0f9e308a 30
ef0963b8
SW
31char *sasprintf(const char *fmt, ...) {
32 char *result;
33 va_list args;
34 va_start(args, fmt);
35 if (vasprintf(&result, fmt, args) == -1) {
36 fprintf(stderr, "Can't allocate memory!?\n");
37 abort();
38 }
39 va_end(args);
40 return result;
41}
42
fc1a2773 43struct State {
7281755a
SW
44 char *voltage_filename;
45 char *current_filename;
fc1a2773
SW
46 BVChart *voltage;
47 BVChart *current;
48};
49
0f9e308a
SW
50static float fatof(const char *filename) {
51 FILE *f = fopen(filename, "r");
52 if (f == NULL) {
53 return NAN;
54 }
55
56 const int bufsize = 1024;
57 char buf[bufsize];
58 const int read = fread(buf, sizeof(char), bufsize, f);
59 if (read == 0 || read == bufsize || ferror(f)) {
60 fclose(f);
61 return NAN;
62 }
63 fclose(f);
64
65 char *end;
66 errno = 0;
67 float val = strtof(buf, &end);
68 int parsed = end - buf;
69 gboolean parsed_all = parsed == read;
70 gboolean parsed_all_but_space = parsed == read - 1 && isspace(*end);
71 gboolean parse_ok = parsed_all || parsed_all_but_space;
72 if (errno != 0 || parsed == 0 || !parse_ok) {
73 return NAN;
74 }
75 return val;
76}
77
7281755a 78static gboolean collect_data(struct State *state) {
51a39b46 79 float now = g_get_monotonic_time() / 1e6;
cf9a7d38
SW
80 float voltage = fatof(state->voltage_filename);
81 float current = fatof(state->current_filename);
dc8fff78
SW
82 if (!isnan(voltage)) {
83 bv_chart_add_point(state->voltage, now, voltage);
84 gtk_widget_queue_draw(GTK_WIDGET(state->voltage));
85 }
86 if (!isnan(current)) {
87 bv_chart_add_point(state->current, now, current);
88 gtk_widget_queue_draw(GTK_WIDGET(state->current));
89 }
6f50819e
SW
90 return TRUE;
91}
92
fc1a2773 93static void activate(GtkApplication *app, gpointer user_data) {
79ac753b 94
cecfa926 95 GtkWidget *window = gtk_application_window_new(app);
a8d35c4c 96 gtk_window_set_title(GTK_WINDOW(window), "BatteryViewer");
79ac753b
SW
97 gtk_window_set_default_size(GTK_WINDOW(window), 200, 200);
98
1bd5b327
SW
99 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 1);
100 gtk_container_add(GTK_CONTAINER(window), box);
101
fc1a2773
SW
102 struct State *state = (struct State *)user_data;
103 state->voltage = BV_CHART(bv_chart_new());
1bd5b327
SW
104 gboolean expand = TRUE;
105 gboolean fill = TRUE;
106 guint padding = 0;
fc1a2773
SW
107 gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(state->voltage), expand, fill,
108 padding);
1bd5b327 109
fc1a2773 110 state->current = BV_CHART(bv_chart_new());
fc1a2773
SW
111 gtk_box_pack_end(GTK_BOX(box), GTK_WIDGET(state->current), expand, fill,
112 padding);
79ac753b 113
a9a80e4e 114 gtk_widget_show_all(window);
6f50819e
SW
115
116 g_timeout_add_seconds(1, (GSourceFunc)collect_data, user_data);
79ac753b
SW
117}
118
eda1f8c1
SW
119char *find_battery_dir() {
120 glob_t globbuf = {.gl_offs = 0};
121 char pattern[] = "/sys/class/power_supply/*[Bb][Aa][Tt]*";
122 if (glob(pattern, GLOB_ERR, NULL, &globbuf) != 0 || globbuf.gl_pathc == 0) {
123 fprintf(stderr, "Could not find battery dir %s\n", pattern);
124 exit(1);
125 }
126 if (globbuf.gl_pathc != 1) {
127 // TODO: Filter for directories that have {voltage,current}_now files.
128 // TODO: Support multiple batteries
129 fprintf(stderr, "Multiple batteries found. Arbitrarily choosing %s\n",
130 globbuf.gl_pathv[0]);
131 }
132 char *dir = strdup(globbuf.gl_pathv[0]);
133 globfree(&globbuf);
134 return dir;
135}
136
79ac753b 137int main(int argc, char **argv) {
eda1f8c1 138 char *battery_dir = find_battery_dir();
7281755a 139 struct State state = {
ef0963b8
SW
140 .voltage_filename = sasprintf("%s/voltage_now", battery_dir),
141 .current_filename = sasprintf("%s/current_now", battery_dir),
7281755a 142 };
79ac753b 143
0a969968
SW
144 GtkApplication *app = gtk_application_new(
145 "com.scottworley.batteryviewer",
146 // G_APPLICATION_FLAGS_NONE is deprecated, but
147 // G_APPLICATION_DEFAULT_FLAGS isn't available on Stable Debian yet.
148 G_APPLICATION_FLAGS_NONE);
fc1a2773 149 g_signal_connect(app, "activate", G_CALLBACK(activate), &state);
cecfa926 150 int status = g_application_run(G_APPLICATION(app), argc, argv);
79ac753b
SW
151 g_object_unref(app);
152
153 return status;
154}