initial commit
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
compile_commands.json
|
||||
*.o
|
||||
.cache
|
||||
eh
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "lib/log.c"]
|
||||
path = lib/log.c
|
||||
url = https://github.com/rxi/log.c
|
20
Makefile
Normal file
20
Makefile
Normal file
@@ -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)
|
8
config.h
Normal file
8
config.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#include <math.h>
|
||||
|
||||
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);
|
||||
}
|
8
config.mk
Normal file
8
config.mk
Normal file
@@ -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
|
115
include/acpi.c
Normal file
115
include/acpi.c
Normal file
@@ -0,0 +1,115 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#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;
|
||||
}
|
4
include/acpi.h
Normal file
4
include/acpi.h
Normal file
@@ -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]);
|
214
include/led.c
Normal file
214
include/led.c
Normal file
@@ -0,0 +1,214 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/poll.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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);
|
||||
}
|
2
include/led.h
Normal file
2
include/led.h
Normal file
@@ -0,0 +1,2 @@
|
||||
int led_create_thread(char *file);
|
||||
void led_set_rate(double rate);
|
72
include/utils.c
Normal file
72
include/utils.c
Normal file
@@ -0,0 +1,72 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#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;
|
||||
}
|
2
include/utils.h
Normal file
2
include/utils.h
Normal file
@@ -0,0 +1,2 @@
|
||||
char *get_substring(char *s, int pos, int l);
|
||||
char *concat(const char *s1, const char *s2);
|
1
lib/log.c
Submodule
1
lib/log.c
Submodule
Submodule lib/log.c added at f9ea34994b
161
main.c
Normal file
161
main.c
Normal file
@@ -0,0 +1,161 @@
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <sys/poll.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#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);
|
||||
}
|
Reference in New Issue
Block a user