1 /* batteryviewer: Display battery metrics
2 * Copyright (C) 2023 Scott Worley <scottworley@scottworley.com>
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.
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.
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/>.
31 char *sasprintf(const char *fmt
, ...) {
35 if (vasprintf(&result
, fmt
, args
) == -1) {
36 fprintf(stderr
, "Can't allocate memory!?\n");
44 char *voltage_filename
;
45 char *current_filename
;
50 static float fatof(const char *filename
) {
51 FILE *f
= fopen(filename
, "r");
56 const int bufsize
= 1024;
58 const int read
= fread(buf
, sizeof(char), bufsize
, f
);
59 if (read
== 0 || read
== bufsize
|| ferror(f
)) {
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
) {
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
));
86 if (!isnan(current
)) {
87 bv_chart_add_point(state
->current
, now
, current
);
88 gtk_widget_queue_draw(GTK_WIDGET(state
->current
));
93 static void activate(GtkApplication
*app
, gpointer user_data
) {
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);
99 GtkWidget
*box
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 1);
100 gtk_container_add(GTK_CONTAINER(window
), box
);
102 struct State
*state
= (struct State
*)user_data
;
103 state
->voltage
= BV_CHART(bv_chart_new());
104 gboolean expand
= TRUE
;
105 gboolean fill
= TRUE
;
107 gtk_box_pack_start(GTK_BOX(box
), GTK_WIDGET(state
->voltage
), expand
, fill
,
110 state
->current
= BV_CHART(bv_chart_new());
111 gtk_box_pack_end(GTK_BOX(box
), GTK_WIDGET(state
->current
), expand
, fill
,
114 gtk_widget_show_all(window
);
116 g_timeout_add_seconds(1, (GSourceFunc
)collect_data
, user_data
);
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
);
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]);
132 char *dir
= strdup(globbuf
.gl_pathv
[0]);
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
),
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
);