From 961e0472f5e3290b083968d1062efc567a579337 Mon Sep 17 00:00:00 2001 From: Squibid Date: Mon, 31 Mar 2025 04:12:41 -0500 Subject: [PATCH] initial commit --- .gitignore | 4 + .gitmodules | 3 + Makefile | 20 +++++ config.h | 8 ++ config.mk | 8 ++ include/acpi.c | 115 ++++++++++++++++++++++++++ include/acpi.h | 4 + include/led.c | 214 ++++++++++++++++++++++++++++++++++++++++++++++++ include/led.h | 2 + include/utils.c | 72 ++++++++++++++++ include/utils.h | 2 + lib/log.c | 1 + main.c | 161 ++++++++++++++++++++++++++++++++++++ 13 files changed, 614 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 Makefile create mode 100644 config.h create mode 100644 config.mk create mode 100644 include/acpi.c create mode 100644 include/acpi.h create mode 100644 include/led.c create mode 100644 include/led.h create mode 100644 include/utils.c create mode 100644 include/utils.h create mode 160000 lib/log.c create mode 100644 main.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7562ef4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +compile_commands.json +*.o +.cache +eh diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f4618ee --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/log.c"] + path = lib/log.c + url = https://github.com/rxi/log.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2d9890a --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +include config.mk + +PROG ?= eh +OBJS = main.o\ + lib/log.c/src/log.o\ + include/led.o\ + include/acpi.o\ + include/utils.o + +all: $(PROG) + +# main build task +.c.h.o: + $(CC) $(CFLAGS) -o $@ $< + +$(PROG): $(OBJS) + $(CC) $(CFLAGS) -o $@ $(OBJS) + +clean: + rm $(PROG) $(OBJS) diff --git a/config.h b/config.h new file mode 100644 index 0000000..dfe86bf --- /dev/null +++ b/config.h @@ -0,0 +1,8 @@ +#include + +static const char *notification_name = "Event Handler"; /* name to use in notifications */ +static const char *battery_dir = "/sys/class/power_supply/BAT1"; /* path to the batteries directory */ +static const int blink_start = 15; /* percentage to start blinking at */ +const double blink_formula(int x) { /* formula used to set the blinking rate, this function takes in one argument: the battery percentage */ + return 1.7 + 18.5 * exp(-0.29 * x); +} diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..b35e790 --- /dev/null +++ b/config.mk @@ -0,0 +1,8 @@ +PKG_CONFIG = pkg-config +CC = cc +VERSION = 0.1 + +# flags and incs +INCLUDES = -I./lib/log.c/src +PKGS = +CFLAGS = -DVERSION=\"$(VERSION)\" $(INCLUDES) -DLOG_USE_COLOR -Wall -Og -g -lm diff --git a/include/acpi.c b/include/acpi.c new file mode 100644 index 0000000..1abdbee --- /dev/null +++ b/include/acpi.c @@ -0,0 +1,115 @@ +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "acpi.h" +#include "utils.h" + +/** + * @brief parse output of acpid + * + * @param str input from acpi socket + * @param out the parsed version of it + * @return 0 on success + */ +int +acpi_parse(char *str, char **out) +{ + int i, section_num, last_section_pos; + size_t section_size; + + if (strlen(str) <= 1) { + log_warn("failed to parse acpid input string: it doesn't exist!"); + return 1; + } + + for (last_section_pos = section_num = i = 0; i <= strlen(str); i++) { + if (str[i] != ' ' && str[i] != '\n' && str[i] != '\0') { + continue; + } + + /* grab correct information of the current section */ + section_size = i - last_section_pos; + + /* copy over the data into the new buffer */ + out[section_num] = get_substring(str, last_section_pos, section_size); + + /* set the last_section_pos for the next section */ + last_section_pos = i + 1; + section_num++; + } + + return 0; +} + +/** + * @brief cleanup the results of acpi_parse + * + * @param out the pointer to the output from acpi_parse + */ +void +acpi_clean_parse(char *out[4]) +{ + int i; + + for (i = 0; i < 4; i++) { + if (out[i]) { + free(out[i]); + } + } +} + +/** + * @brief create a new acpi socket connection + * + * @param socket_file path to the socket file + * @return the socket fd + */ +int +acpi_create_socket(char *socket_file) +{ + int sock_fd; + struct sockaddr_un addr = { 0 }; + + /* create a new socket connection */ + sock_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (sock_fd < 0) { + log_fatal("failed to create a new socket: %s", strerror(errno)); + return 1; + } + + /* setup the socket */ + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, socket_file, strlen(socket_file) + 1); + + /* connect */ + if (connect(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + if (close(sock_fd) < 0) { + log_fatal("failed to close socket close: %s", strerror(errno)); + } + log_fatal("failed to connect to the socket: %s", strerror(errno)); + return 1; + } + + return sock_fd; +} + +/** + * @brief close the socket connection + * + * @param sockfd the sockets fd + * @return 0 on success 1 on error + */ +int +acpi_close_socket(int sockfd) { + if (close(sockfd) < 0) { + log_fatal("failed to close socket: %s", strerror(errno)); + return 1; + } + + return 0; +} diff --git a/include/acpi.h b/include/acpi.h new file mode 100644 index 0000000..399abd7 --- /dev/null +++ b/include/acpi.h @@ -0,0 +1,4 @@ +int acpi_create_socket(char *socket_file); +int acpi_parse(char *str, char **out); +int acpi_close_socket(int sockfd); +void acpi_clean_parse(char *out[4]); diff --git a/include/led.c b/include/led.c new file mode 100644 index 0000000..d414a2d --- /dev/null +++ b/include/led.c @@ -0,0 +1,214 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "led.h" +#include "log.h" + +#define Z() if (data->rate > 0) { \ + usleep(1000000 / data->rate); \ +} + +struct Data { + char *file_name; + FILE *pfile; + double rate; +}; + +/* global data accessed by all threads */ +static struct Data *data; +pthread_t thread; + +/* mutex to lock the thread */ +static pthread_mutex_t mp = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t cv = PTHREAD_COND_INITIALIZER; + +/** + * @brief helper function to set the led rate + * + * @param value set the contents of the led file + * @param d data + */ +static inline void +led_set(char value, struct Data *d) +{ + d->pfile = fopen(d->file_name, "w"); + if (d->pfile == NULL) { + log_warn("failed to open %s fopen: %s", d->file_name, strerror(errno)); + return; + } + + if (fputc(value, d->pfile) == EOF) { + log_warn("failed to write to %s fopen: %s", d->file_name, strerror(errno)); + + /* According to the man page, after erroring doing anything including + * fclose may result in undefined behavior. */ + return; + } + fclose(d->pfile); +} + +/** + * @brief main thread loop + * + * @param args args + */ +static void +*run(void *args) +{ + int err; + struct Data *d = (struct Data *)args; + + for (;;) { + /* turn off the led before doing anything else */ + led_set('0', d); + + /* wait for the condition to change */ + while(d->rate == 0) { + if ((err = pthread_cond_wait(&cv, &mp)) != 0) { + log_fatal("pthread_cond_wait: %s", strerror(err)); + pthread_exit(0); + } + } + + /* exit if we're told to */ + if (d->rate <= -1) { + log_debug("received rate of -1, exiting"); + pthread_exit(0); + } + + Z(); + led_set('1', d); + Z(); + } + + pthread_exit(0); +} + +/** + * @brief run before exiting the program + * this is for cleaning up all the messes we've made + * + * @param signal signal + */ +static void +pre_exit(int signal) +{ + led_set_rate(-1); + pthread_cond_destroy(&cv); + pthread_mutex_destroy(&mp); + + if (data->pfile) { + data->pfile = fopen(data->file_name, "w"); + } + + fputc('0', data->pfile); + + /* make sure to cleanup the allocated data */ + fclose(data->pfile); + free(data->file_name); + free(data); + + exit(0); +} + +/** + * @brief create the led thread + * + * @param file file to send blinking signals to + * @return 0 on success + */ +int +led_create_thread(char *file) +{ + int err; + struct sigaction act; + static bool running; + + /* make sure the led thread cannot be created twice */ + if (running) { + log_warn("led thread has already been created"); + return 1; + } + + /* zero the array, and setup sig handler and exit handler */ + memset(&act, 0, sizeof(act)); + act.sa_handler = &pre_exit; + if (sigaction(SIGINT, &act, NULL) < 0) { + log_fatal("failed to create sigaction: %s, memory is gonna leak", + strerror(errno)); + } + + /* setup data */ + data = calloc(1, sizeof(struct Data)); + data->file_name = malloc(sizeof(char) * (strlen(file) + 1)); + if (data->file_name == NULL) { + log_fatal("failed to create led thread: malloc of led file name failed: %s", + strerror(errno)); + exit(1); + } + + strcpy(data->file_name, file); + data->rate = 0; + + /* attempt to create the thread */ + if ((err = pthread_create(&thread, NULL, run, data)) != 0) { + log_fatal("failed to create led thread: pthread_create: %s", strerror(err)); + return -1; + } + + /* detach the thread to make cleanup nice and easy */ + if ((err = pthread_detach(thread)) != 0) { + log_fatal("failed to create led thread: pthread_detach: %s", strerror(err)); + return -1; + } + + log_info("started LED thread"); + running = true; + + return 0; +} + +/** + * @brief set the led rate + * + * @param r number of blinks per second + */ +void +led_set_rate(double r) +{ + int err; + static bool locked = false; + + /* there's no reason to set the rate if it's already the same */ + if (r == data->rate) { + return; + } + + /* lock the thread, update, and unlock it */ + if (!locked) { + if ((err = pthread_mutex_lock(&mp)) != 0) { + log_fatal("failed to lock the mutex: %s", strerror(errno)); + } + } + + /* set the rate */ + data->rate = r; + log_debug("set rate to: %f", r); + + pthread_cond_signal(&cv); + if (!locked) { + if ((err = pthread_mutex_unlock(&mp)) != 0) { + log_fatal("failed to lock the mutex: %s", strerror(errno)); + } + } + + /* make sure we don't lock the thread in an infinite loop */ + locked = (r != 0); +} diff --git a/include/led.h b/include/led.h new file mode 100644 index 0000000..a42ae6d --- /dev/null +++ b/include/led.h @@ -0,0 +1,2 @@ +int led_create_thread(char *file); +void led_set_rate(double rate); diff --git a/include/utils.c b/include/utils.c new file mode 100644 index 0000000..600f336 --- /dev/null +++ b/include/utils.c @@ -0,0 +1,72 @@ +#include +#include +#include + +#include "utils.h" +#include "log.h" + +/** + * @brief get a substring of a string + * + * @param s string + * @param pos first pos + * @param l length + * @return the substring + */ +char +*get_substring(char *s, int pos, int l) +{ + int i = 0; + char *out; + + if (l <= 0) { + return NULL; + } + + out = malloc((l + 1) * sizeof(char)); + if (out == NULL) { + log_fatal("failed to get substring malloc: %s", strerror(errno)); + exit(1); + } + + /* Copy substring into ss */ + while (i < l) { + out[i] = s[pos + i]; + i++; + } + + /* Null terminate the substring */ + out[i] = '\0'; + + return out; +} + +/** + * @brief concatinate two strings + * + * @param s1 first + * @param s2 second + * @return the concatinated string + */ +char +*concat(const char *s1, const char *s2) +{ + int len1, len2; + char *result; + + len1 = strlen(s1); + len2 = strlen(s2); + + /* attempt to allocate size for the new string */ + result = malloc(len1 + len2 + 1); + if (result == NULL) { + log_fatal("malloc: %s", strerror(errno)); + exit(1); + } + + memcpy(result, s1, len1); + memcpy(result + len1, s2, len2 + 1); + // + 1 copies the null terminator + + return result; +} diff --git a/include/utils.h b/include/utils.h new file mode 100644 index 0000000..a16d9fc --- /dev/null +++ b/include/utils.h @@ -0,0 +1,2 @@ +char *get_substring(char *s, int pos, int l); +char *concat(const char *s1, const char *s2); diff --git a/lib/log.c b/lib/log.c new file mode 160000 index 0000000..f9ea349 --- /dev/null +++ b/lib/log.c @@ -0,0 +1 @@ +Subproject commit f9ea34994bd58ed342d2245cd4110bb5c6790153 diff --git a/main.c b/main.c new file mode 100644 index 0000000..11c2499 --- /dev/null +++ b/main.c @@ -0,0 +1,161 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "include/led.h" +#include "include/acpi.h" +#include "include/utils.h" + +#include "config.h" + +#define SOCKET_FILE "/run/acpid.socket" +#define LED_FILE "/sys/class/leds/input0::capslock/brightness" +#define MAX_BUFLEN 1024 + +char +*battery_percent(void) +{ + char *r, c, *d; + int i; + FILE *f; + + /* open up the battery capacity for reading */ + d = concat(battery_dir, "/capacity"); + f = fopen(d, "r"); + free(d); + + /* create enough space for the battery percentage */ + r = calloc(4, sizeof(char)); + + /* read in the battery percentage */ + i = 0; + while ((c = getc(f)) && i < 4) { + /* make sure to replace the newline with a string terminator */ + if (c == '\n') { + r[i] = '\0'; + break; + } + r[i] = c; + i++; + } + + fclose(f); + return r; +} + +bool +plugged_in(char *fn) +{ + FILE *f; + char buf[1]; + + f = fopen(fn, "r"); + if (read(fileno(f), buf, 1) < 0) { + return -1; + } + fclose(f); + + return buf[0] - '0'; +} + +void +on_battery_event(char *percent, bool plugged) +{ + char *ep; + int res; + double rate; + + if (!plugged) { + /* convert battery percentage to an int */ + res = strtol(percent, &ep, 10); + + if (res <= blink_start) { + rate = blink_formula(res); + if (rate >= 1) { + led_set_rate(rate); + } + } else { + led_set_rate(0); + } + } else { + led_set_rate(0); + } + + free(percent); +} + +int +main(int argc, char *argv[]) +{ + bool plugged = plugged_in("/sys/class/power_supply/ACAD/online"); + char buffer[MAX_BUFLEN], *out[4]; + int sock_fd; + + /* set the logging level */ + log_set_level(LOG_DEBUG); + + /* open up socket address */ + sock_fd = acpi_create_socket(SOCKET_FILE); + + /* create the led thread */ + led_create_thread(LED_FILE); + + /* 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 + */ + on_battery_event(battery_percent(), plugged); + + /* zero buffer */ + memset(out, 0, sizeof(out)); + + for (;;) { + /* read input */ + memset(buffer, 0, MAX_BUFLEN); + if (read(sock_fd, buffer, MAX_BUFLEN) < 0) { + log_fatal("read: %s", strerror(errno)); + return 1; + } + + /* parse */ + if (acpi_parse(buffer, out) != 0) { + continue; + } + + /* rules */ + if (strcmp(out[0], "ac_adapter") == 0) { + char *s = get_substring(out[1], 0, 2); + + if (strcmp(s, "AC") == 0) { + plugged = plugged_in("/sys/class/power_supply/ACAD/online"); + } + + free(s); + } else if (strcmp(out[0], "battery") == 0) { + char *s = get_substring(out[1], 0, 3); + + if (strcmp(s, "PNP") == 0) { + if (strcmp(out[2], "00000080") == 0) { + if (strcmp(out[3], "00000001") == 0) { + on_battery_event(battery_percent(), plugged); + } + } + } + free(s); + } + + acpi_clean_parse(out); + } + + acpi_close_socket(sock_fd); +}