Compare commits

...

5 Commits

Author SHA1 Message Date
8d676a2167 add more info about configuring event-handler 2025-09-29 23:52:40 -04:00
c89bcb4d68 update readme with an example and info on acpid 2025-09-29 23:39:32 -04:00
323cef6e35 fix: do some cleaning up 2025-09-26 17:15:06 -04:00
22fcfcdba9 fix: don't leak memory...
There's still memory being leaked by the wayland stuff
2025-09-26 16:45:31 -04:00
afa4e48757 fix: stop segfaulting on non-existent directories 2025-09-26 16:40:27 -04:00
7 changed files with 81 additions and 28 deletions

View File

@@ -3,13 +3,32 @@ My custom event handler for battery related things. Here's a brief rundown of
all the features: all the features:
When the percentage gets below a specified percentage it will blink the When the percentage gets below a specified percentage it will blink the
specified led at any rate defined by the config file. specified led at any rate defined by the config file. Example:
![blink.mp4](./examples/blink.mp4 "blink.mp4")
If the system is plugged in and a wayland session is running then the event If the system is plugged in and a wayland session is running then the event
handler will place an idle inhibitor on the session to keep the computer from handler will place an idle inhibitor on the session to keep the computer from
going to sleep. The inhibitor is removed when the system is unplugged. going to sleep. The inhibitor is removed when the system is unplugged.
## Default Config > [!WARNING]
> event-handler relies on [acpid](https://wiki.archlinux.org/title/Acpid) to
> monitor battery events ensure you have it installed and running before using
> event-handler
## Configuring
I built event-handler to be used as a service independent of my current user
and such event-handler does not read the configuration file from any default
path. Instead it's up to you to choose where it is and pass the file's full
path into event-handler like so:
```bash
eh -c ./config.toml
```
The config is completely optional, however you will most likely need it as the
default is setup for my framework 13 system running Void Linux. Below are the
defaults:
### Default Config
```toml ```toml
[battery] [battery]
status_path = "/sys/class/power_supply/BAT1" status_path = "/sys/class/power_supply/BAT1"
@@ -25,3 +44,8 @@ timing_formula = "1.7 + 18.5 * exp(-0.29 * p)" # p is the percentage of the batt
[acpi_daemon] [acpi_daemon]
socket_path = "/run/acpid.socket" socket_path = "/run/acpid.socket"
``` ```
> [!NOTE]
> The default formula may or may not work above 15 percent due to led
> shenanigans. If you wish to craft your own formula all relevant notation may
> be found in [this repo](https://github.com/codeplea/tinyexpr).

BIN
examples/blink.mp4 Normal file

Binary file not shown.

View File

@@ -3,6 +3,7 @@
#include <stdbool.h> #include <stdbool.h>
#include <unistd.h> #include <unistd.h>
#include "log.h"
#include "utils.h" #include "utils.h"
#include "config.h" #include "config.h"
@@ -17,6 +18,11 @@ char
/* open up the battery capacity for reading */ /* open up the battery capacity for reading */
d = concat(battery_status_path, "/capacity"); d = concat(battery_status_path, "/capacity");
f = fopen(d, "r"); f = fopen(d, "r");
if (!f) {
log_fatal("battery directory does not exist");
free(d);
return NULL;
}
free(d); free(d);
/* create enough space for the battery percentage */ /* create enough space for the battery percentage */

View File

@@ -49,15 +49,12 @@ led_blink_timing_func(int percent)
{ {
percentage = percent; percentage = percent;
return te_eval(expr); return te_eval(expr);
// return 1.7 + 18.5 * exp(-0.29 * percent);
} }
static void static void
free_eval(int sig) free_eval(int sig)
{ {
te_free(expr); te_free(expr);
exit(sig);
} }
void void
@@ -67,13 +64,13 @@ setup_led_formula(void)
te_variable vars[] = { { "p", &percentage } }; te_variable vars[] = { { "p", &percentage } };
expr = te_compile(led_blink_timing_formula, vars, 1, &err); expr = te_compile(led_blink_timing_formula, vars, 1, &err);
signal(SIGINT, free_eval);
if (!expr) { if (!expr) {
log_fatal("%s", led_blink_timing_formula); log_fatal("%s", led_blink_timing_formula);
log_fatal("%*s^ Error near here", err - 1, ""); log_fatal("%*s^ Error near here", err - 1, "");
exit(1); exit(1);
} }
signal(SIGINT, free_eval);
} }
void void
@@ -86,6 +83,7 @@ parse_config_file(char *path)
f = fopen(path, "r"); f = fopen(path, "r");
if (!f) { if (!f) {
log_fatal("Failed to open config file: %s", path); log_fatal("Failed to open config file: %s", path);
return;
} }
config = toml_parse_file(f, errbuf, sizeof(errbuf)); config = toml_parse_file(f, errbuf, sizeof(errbuf));
@@ -106,4 +104,6 @@ parse_config_file(char *path)
set_int_conf(led_blink_trigger_level, led_blink, "power_source"); set_int_conf(led_blink_trigger_level, led_blink, "power_source");
set_str_conf(led_blink_timing_formula, led_blink, "timing_formula"); set_str_conf(led_blink_timing_formula, led_blink, "timing_formula");
set_str_conf(acpi_daemon_socket_path, acpi_daemon, "socket_path"); set_str_conf(acpi_daemon_socket_path, acpi_daemon, "socket_path");
toml_free(config);
} }

View File

@@ -6,6 +6,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/poll.h> #include <sys/poll.h>
#include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
#include "led.h" #include "led.h"
@@ -114,8 +115,6 @@ pre_exit(int signal)
fclose(data->pfile); fclose(data->pfile);
free(data->file_name); free(data->file_name);
free(data); free(data);
exit(0);
} }
int int
@@ -124,6 +123,13 @@ led_create_thread(char *file)
int err; int err;
struct sigaction act; struct sigaction act;
static bool running; static bool running;
struct stat s;
/* ensure that file exists */
err = stat(file, &s);
if (err < 0 || !S_ISREG(s.st_mode)) {
return 2;
}
/* make sure the led thread cannot be created twice */ /* make sure the led thread cannot be created twice */
if (running) { if (running) {

View File

@@ -23,7 +23,13 @@
#include "config.h" #include "config.h"
#define MAX_BUFLEN 1024 #define MAX_BUFLEN 1024
#define wrap(ok, func) do { \
if (ok) { \
func; \
} \
} while (0)
bool led_ok, wayland_ok;
static struct cag_option options[] = { static struct cag_option options[] = {
{ {
.identifier = 'c', .identifier = 'c',
@@ -51,6 +57,7 @@ on_battery_event(char *percent, bool plugged)
/* convert battery percentage to an int */ /* convert battery percentage to an int */
res = strtol(percent, &ep, 10); res = strtol(percent, &ep, 10);
wrap(led_ok,
if (res <= led_blink_trigger_level) { if (res <= led_blink_trigger_level) {
rate = led_blink_timing_func(res); rate = led_blink_timing_func(res);
if (rate >= 1) { if (rate >= 1) {
@@ -59,11 +66,12 @@ on_battery_event(char *percent, bool plugged)
} else { } else {
led_set_rate(0); led_set_rate(0);
} }
);
wayland_set_idle_lock(false); wrap(wayland_ok, wayland_set_idle_lock(false));
} else { } else {
led_set_rate(0); wrap(led_ok, led_set_rate(0));
wayland_set_idle_lock(true); wrap(wayland_ok, wayland_set_idle_lock(true));
} }
free(percent); free(percent);
@@ -74,9 +82,12 @@ main(int argc, char *argv[])
{ {
cag_option_context context; cag_option_context context;
bool plugged = plugged_in(battery_power_source); bool plugged = plugged_in(battery_power_source);
char buffer[MAX_BUFLEN], *out[4], *config_path; char buffer[MAX_BUFLEN], *out[4], *config_path, *percent;
int sock_fd; int sock_fd;
/* defaults to working */
led_ok = wayland_ok = true;
cag_option_init(&context, options, CAG_ARRAY_SIZE(options), argc, argv); cag_option_init(&context, options, CAG_ARRAY_SIZE(options), argc, argv);
while (cag_option_fetch(&context)) { while (cag_option_fetch(&context)) {
switch (cag_option_get_identifier(&context)) { switch (cag_option_get_identifier(&context)) {
@@ -104,15 +115,22 @@ main(int argc, char *argv[])
sock_fd = acpi_create_socket(acpi_daemon_socket_path); sock_fd = acpi_create_socket(acpi_daemon_socket_path);
/* create the led thread */ /* create the led thread */
led_create_thread(led_brightness_path); if (led_create_thread(led_brightness_path)) {
led_ok = false;
}
/* create the wayland thread */ /* create the wayland thread */
wayland_create_thread(); if (wayland_create_thread()) {
wayland_ok = false;
}
/* run events that need to be run on start to ensure that the current state /* run events that need to be run on start to ensure that the current state
* inside the program reflects that that is outside the program * inside the program reflects that that is outside the program
*/ */
on_battery_event(battery_percent(), plugged); percent = battery_percent();
if (percent == NULL) {
on_battery_event(percent, plugged);
}
/* zero buffer */ /* zero buffer */
memset(out, 0, sizeof(out)); memset(out, 0, sizeof(out));
@@ -145,7 +163,10 @@ main(int argc, char *argv[])
if (strcmp(s, "PNP") == 0) { if (strcmp(s, "PNP") == 0) {
if (strcmp(out[2], "00000080") == 0) { if (strcmp(out[2], "00000080") == 0) {
if (strcmp(out[3], "00000001") == 0) { if (strcmp(out[3], "00000001") == 0) {
on_battery_event(battery_percent(), plugged); percent = battery_percent();
if (percent == NULL) {
on_battery_event(percent, plugged);
}
} }
} }
} }

View File

@@ -118,16 +118,12 @@ pre_exit(int signal)
zwp_idle_inhibit_manager_v1_destroy(data->inhibitmanager); zwp_idle_inhibit_manager_v1_destroy(data->inhibitmanager);
wl_registry_destroy(data->registry); wl_registry_destroy(data->registry);
// wl_display_destroy(data->display);
wl_compositor_destroy(data->compositor); wl_compositor_destroy(data->compositor);
wl_surface_destroy(data->surface); wl_surface_destroy(data->surface);
/* TODO: this causes a segfault
wl_display_destroy(data->display);
*/
/* make sure to cleanup the allocated data */ /* make sure to cleanup the allocated data */
free(data); free(data);
exit(0);
} }
int int