aboutsummaryrefslogtreecommitdiff
path: root/lowbat.c
blob: b56f40b48d51b20c7add49a9e61c82e8cab7d04c (plain)
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
179
180
181
182
/* 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];
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);
				}
				memcpy(batteries[num_batteries], entry->d_name, MAX_PSU_NAME_LEN);
				batteries[num_batteries][MAX_PSU_NAME_LEN-1] = '\0';
				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) {
		if (fscanf(fp, fmt, out) != 1) {
			fprintf(stderr, "failed to scan property %s with format '%s'\n", fmt, prop_path);
			exit(EXIT_FAILURE);
		}
		fclose(fp);
	}
	else {
		fprintf(stderr, "failed to open %s\n", 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;
}