]>
Commit | Line | Data |
---|---|---|
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 | } |