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
167
168
169
170
171
172
173
174
175
176
177
178
|
/* 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];
static enum StatusSymbols { UNKNOWN = '?', CHARGING = '+', DISCHARGING = '-', NOT_CHARGING = '=', FULL = '=' };
// 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;
}
char get_status() {
char status[MAX_BATTERIES];
int i;
if (!num_batteries)
return UNKNOWN;
/* store first letter of each battery's status
* valid values: "Unknown", "Charging", "Discharging", "Not charging", "Full" */
// return discharging if any batteries are discharging
for (i = 0; i < num_batteries; i++) {
read_prop(batteries[i], "status", "%c", &status[i]);
if (status[i] == 'D')
return DISCHARGING;
}
// return charging if any batteries are charging
if (strchr(status, 'C') != NULL)
return CHARGING;
// return not charging if any batteries are not charging
if (strchr(status, 'F') != NULL || strchr(status, 'N') != NULL)
return NOT_CHARGING;
// return unknown otherwise
return UNKNOWN;
}
int main() {
char status, old_status = -1;
int capacity, 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();
status = get_status();
capacity = get_total_capacity();
if (status == 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 (status != old_status || capacity != old_capacity) {
printf("%c%d%%\n", status, capacity);
fflush(stdout);
}
old_status = status;
old_capacity = capacity;
sleep(PERIOD);
}
return 0;
}
|