]> git.scottworley.com Git - batteryviewer/blob - batteryviewer.c
License
[batteryviewer] / batteryviewer.c
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
17 // For vasprintf
18 #define _GNU_SOURCE
19
20 #include "chart.h"
21 #include <ctype.h>
22 #include <errno.h>
23 #include <glob.h>
24 #include <gtk/gtk.h>
25 #include <math.h>
26 #include <stdarg.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30
31 char *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
43 struct State {
44 char *voltage_filename;
45 char *current_filename;
46 BVChart *voltage;
47 BVChart *current;
48 };
49
50 static 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
78 static gboolean collect_data(struct State *state) {
79 float now = g_get_monotonic_time() / 1e6;
80 float voltage = fatof(state->voltage_filename);
81 float current = fatof(state->current_filename);
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 }
90 return TRUE;
91 }
92
93 static void activate(GtkApplication *app, gpointer user_data) {
94
95 GtkWidget *window = gtk_application_window_new(app);
96 gtk_window_set_title(GTK_WINDOW(window), "BatteryViewer");
97 gtk_window_set_default_size(GTK_WINDOW(window), 200, 200);
98
99 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 1);
100 gtk_container_add(GTK_CONTAINER(window), box);
101
102 struct State *state = (struct State *)user_data;
103 state->voltage = BV_CHART(bv_chart_new());
104 gboolean expand = TRUE;
105 gboolean fill = TRUE;
106 guint padding = 0;
107 gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(state->voltage), expand, fill,
108 padding);
109
110 state->current = BV_CHART(bv_chart_new());
111 gtk_box_pack_end(GTK_BOX(box), GTK_WIDGET(state->current), expand, fill,
112 padding);
113
114 gtk_widget_show_all(window);
115
116 g_timeout_add_seconds(1, (GSourceFunc)collect_data, user_data);
117 }
118
119 char *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
137 int main(int argc, char **argv) {
138 char *battery_dir = find_battery_dir();
139 struct State state = {
140 .voltage_filename = sasprintf("%s/voltage_now", battery_dir),
141 .current_filename = sasprintf("%s/current_now", battery_dir),
142 };
143
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);
149 g_signal_connect(app, "activate", G_CALLBACK(activate), &state);
150 int status = g_application_run(G_APPLICATION(app), argc, argv);
151 g_object_unref(app);
152
153 return status;
154 }