1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
|
/* LINUX DOCS: https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-power
* (C) 2024 Tim Keller <tjk@tjkeller.xyz>
* MIT License
*/
#include <libnotify/notify.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#define PSUPATH "/sys/class/power_supply"
#define PERIOD 3 // number of seconds before each check
#define MAX_BATTERIES 8
#define MAX_PSU_NAME_LEN 8
#define MAX_MSG_LEN 32
#define MAX_PROP_PATH_LEN 64
// variables
static char batteries[MAX_BATTERIES][MAX_PSU_NAME_LEN];
static unsigned int num_batteries;
static FILE *fp;
static char prop_path[MAX_PROP_PATH_LEN];
// functions
void load_installed_batteries() {
// search through "power supplies" for "status" property (only available on batteries)
DIR *dir, *subdir;
struct dirent *entry, *subentry;
if ((dir = opendir(PSUPATH)) == NULL) {
fprintf(stderr, "failed to open power supplies filesystem: %s\n", PSUPATH);
exit(EXIT_FAILURE);
}
// power supplies TODO warn user if max batteries exceeded
num_batteries = 0;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_name[0] == '.')
continue; // skip . and ..
if (strlen(entry->d_name) > MAX_PSU_NAME_LEN) {
fprintf(stderr, "the power supply's name was too long: %s\n", entry->d_name);
exit(EXIT_FAILURE);
}
// power supplies will always be a directory, no need to check beforehand :)
snprintf(prop_path, MAX_PROP_PATH_LEN, PSUPATH "/%.8s", entry->d_name); // 8 is equal to MAX_PSU_NAME_LEN, prevent compiler warning
if ((subdir = opendir(prop_path)) == NULL) {
fprintf(stderr, "failed to open power supply: %s\n", prop_path);
exit(EXIT_FAILURE);
}
// power supply properties
while ((subentry = readdir(subdir)) != NULL) {
// add to list of batteries if it has the status property
if (strcmp(subentry->d_name, "status") == 0) {
if(num_batteries >= MAX_BATTERIES) {
fprintf(stderr, "maximum number of batteries (8) exceeded\n");
exit(EXIT_FAILURE);
}
strncpy(batteries[num_batteries], entry->d_name, MAX_PSU_NAME_LEN);
num_batteries++;
break;
}
}
closedir(subdir);
}
closedir(dir);
}
void notifysend(char *title, char *content, NotifyUrgency urgency) {
NotifyNotification *notif;
notify_init("lowbat");
notif = notify_notification_new(title, content, NULL);
notify_notification_set_timeout(notif, NOTIFY_EXPIRES_NEVER);
notify_notification_set_urgency(notif, urgency);
notify_notification_show(notif, NULL);
}
void read_prop(char *psu, char *prop, char *fmt, void *out) {
/* this function is only designed to read one var at a time because all bat
* properties will only be one var */
snprintf(prop_path, MAX_PROP_PATH_LEN, PSUPATH "/%s/%s", psu, prop);
if ((fp = fopen(prop_path, "r")) != NULL) {
fscanf(fp, fmt, out);
fclose(fp);
}
else {
fprintf(stderr, "failed to read fmt '%s' from property %s\n", fmt, prop_path);
exit(EXIT_FAILURE);
}
}
int get_total_capacity() {
unsigned long int energy_full = 0, energy_now = 0, x;
int i;
if (!num_batteries)
return 0;
for (i = 0; i < num_batteries; i++) {
read_prop(batteries[i], "energy_full", "%ld", &x);
energy_full += x;
read_prop(batteries[i], "energy_now", "%ld", &x);
energy_now += x;
}
return energy_now * 100 / energy_full;
}
int is_discharging() {
char status[12]; // valid values: "Unknown", "Charging", "Discharging", "Not charging", "Full"
int i;
if (!num_batteries)
return 0;
for (i = 0; i < num_batteries; i++) {
read_prop(batteries[i], "status", "%s", &status);
if (strcmp(status, "Discharging") != 0)
return 0;
}
return 1;
}
int main() {
int discharging, capacity, old_discharging = -1, old_capacity = -1;
int warning_level = 0; // 1 if warned already, 2 if warned with critical warning
char msg[MAX_MSG_LEN];
while (1) {
load_installed_batteries();
discharging = is_discharging();
capacity = get_total_capacity();
if (discharging) {
if (capacity <= 5 && warning_level < 2) {
snprintf(msg, MAX_MSG_LEN, "%d%% remains", capacity);
notifysend("Critical Low Battery Warning", msg, NOTIFY_URGENCY_CRITICAL);
warning_level = 2;
} else if (capacity <= 20 && warning_level < 1) {
snprintf(msg, MAX_MSG_LEN, "%d%% remains", capacity);
notifysend("Low Battery Warning", msg, NOTIFY_URGENCY_NORMAL);
warning_level = 1;
}
} else {
warning_level = 0; // clear conditions
}
if (discharging != old_discharging || capacity != old_capacity) {
printf("%s%d%%\n", discharging ? "-" : "+", capacity);
fflush(stdout);
}
old_discharging = discharging;
old_capacity = capacity;
sleep(PERIOD);
}
return 0;
}
|