diff options
author | papush! | 2025-06-28 18:11:22 +0200 |
---|---|---|
committer | papush! | 2025-06-28 18:30:43 +0200 |
commit | ff5e9f826d6e89f5ab1adb09e959afd5d1e17a5f (patch) | |
tree | 4ce18b7b3457f5daf72acd5a3cb87e40472f0a8d /src |
initial commit
Diffstat (limited to 'src')
-rw-r--r-- | src/main.c | 392 |
1 files changed, 392 insertions, 0 deletions
diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..7ecfc15 --- /dev/null +++ b/src/main.c @@ -0,0 +1,392 @@ +#define _DEFAULT_SOURCE +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <poll.h> +#include <unistd.h> +#include <fcntl.h> +#include <dirent.h> +#include <ctype.h> +#include <linux/input.h> +#include <linux/limits.h> +#include <sys/inotify.h> +#include <pwd.h> +#include <termios.h> + +#define NPFDS 4096 + +struct binding +{ + int code; + int value; + char *cmd; +}; + + +struct pollfd pfds[NPFDS] = {0}; +int test_mode = 0; +size_t nbindings = 0; +struct binding *bindings = NULL; + + +void add_device(const char name[static 1]) +{ + if (strncmp(name, "event", strlen("event")) != 0) + { + return; + } + char path[NAME_MAX + 1 + 11] = "/dev/input/"; /* 11 == strlen("/dev/input/") */ + strcat(path, name); + int fd = open(path, O_RDONLY | O_NONBLOCK); + if (fd < 0) + { + fprintf(stderr, "failed to open device: %s: %s\n", path, strerror(errno)); + return; + } + for (size_t i = 1; i < NPFDS; i++) + { + if (pfds[i].fd < 0) + { + pfds[i].fd = fd; + return; + } + } +} + +void process_inotify_watch(int inotify_fd) +{ + char buf[sizeof (struct inotify_event) + NAME_MAX + 1] = {0}; + ssize_t len = read(inotify_fd, buf, sizeof buf); + if (len < 0) + { + fprintf(stderr, "read on inotify fd failed: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + const struct inotify_event *event = NULL; + for (char *ptr = buf; ptr < buf + len; + ptr += sizeof(struct inotify_event) + event->len) + { + event = (const struct inotify_event *) ptr; + if (event->mask & (IN_IGNORED | IN_UNMOUNT | IN_Q_OVERFLOW)) + { + fputs("inotify error (filesystem unmounted?)", stderr); + exit(EXIT_FAILURE); + } + add_device(event->name); + } +} + +void skip_space(char **c) +{ + for (; **c && isspace(**c); (*c)++); +} + +int read_int(char **c, int out[static 1]) +{ + if (!isdigit(**c)) + { + return -1; + } + errno = 0; + char *end = NULL; + *out = strtol(*c, &end, 10); + if (errno) + { + return -1; + } + *c = end; + return 0; +} + +void parse_bindings_file(const char path[static 1]) +{ + size_t cap = 512; + bindings = malloc(sizeof (struct binding) * cap); + FILE *f = fopen(path, "r"); + if (f == NULL) + { + fprintf(stderr, "failed to open bindings file: %s: %s\n", path, + strerror(errno)); + exit(EXIT_FAILURE); + } + + size_t line_nb = 0; + char *line = NULL; + size_t n; + while (getline(&line, &n, f) != -1) + { + line_nb++; + struct binding binding; + char *end = strchr(line, '#'); + if (end == NULL) + { + end = line + strlen(line); + } + *end = '\0'; + char *c = line; + + skip_space(&c); + if (c == end) continue; + + /* code */ + if (read_int(&c, &binding.code) < 0 || c >= end) + { + goto fail; + } + + /* press | release */ + skip_space(&c); + if (strncmp(c, "press", strlen("press")) == 0) + { + c += strlen("press"); + binding.value = 1; + } + else if (strncmp(c, "release", strlen("release")) == 0) + { + c += strlen("release"); + binding.value = 0; + } + else + { + goto fail; + } + + /* cmd */ + skip_space(&c); + if (c >= end) + { + goto fail; + } + binding.cmd = malloc(end - c + 1); + memcpy(binding.cmd, c, end - c + 1); + + if (nbindings >= cap) + { + cap *= 2; + bindings = realloc(bindings, cap * sizeof (struct binding)); + } + bindings[nbindings++] = binding; + } + if (!feof(f)) + { + fprintf(stderr, "failed to read bindings file: %s: %s\n", path, + strerror(errno)); + } + free(line); + fclose(f); + return; + +fail: + fprintf(stderr, "syntax error at line %ld\n", line_nb); + exit(EXIT_FAILURE); +} + +void run_binding(int code, int value) +{ + for (size_t i = 0; i < nbindings; i++) + { + if (bindings[i].code == code && bindings[i].value == value) + { + if (fork() == 0) + { + execl("/bin/sh", "/bin/sh", "-c", bindings[i].cmd); + } + } + } +} + +void process_input_device(int fd) +{ + struct input_event event; + if (read(fd, &event, sizeof event) != sizeof event) + { + fputs("invalid read on input device\n", stderr); + return; + } + if (event.type == EV_KEY && event.value != 2) + { + if (test_mode) + { + printf("%d %s\n", event.code, event.value == 0 ? "release" : "press"); + } + else + { + run_binding(event.code, event.value); + } + } +} + +void add_existing_devices() +{ + DIR *dirp = opendir("/dev/input"); + if (dirp == NULL) + { + fprintf(stderr, "opendir failed: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + errno = 0; + struct dirent *dirent = NULL; + while ((dirent = readdir(dirp)) != NULL) + { + if (dirent->d_type == DT_CHR) + { + add_device(dirent->d_name); + } + } + if (errno != 0) + { + fprintf(stderr, "readdir failed: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } +} + +int run(const char *bindings_file_path) +{ + if (!test_mode) + { + parse_bindings_file(bindings_file_path); + } + int inotify_fd = inotify_init1(IN_CLOEXEC); + if (inotify_fd < 0) + { + fprintf(stderr, "inotify_init1 failed: %s\n", strerror(errno)); + return EXIT_FAILURE; + } + int watch_fd = inotify_add_watch(inotify_fd, "/dev/input/", IN_CREATE); + if (watch_fd < 0) + { + fprintf(stderr, "inotify_add_watch failed: %s\n", strerror(errno)); + return EXIT_FAILURE; + } + for (size_t i = 0; i < NPFDS; i++) + { + pfds[i].fd = -1; + pfds[i].events = POLLIN; + } + pfds[0].fd = inotify_fd; + add_existing_devices(); + for (;;) + { + int ready = poll(pfds, NPFDS, -1); + if (ready < 0) + { + fprintf(stderr, "poll failed: %s\n", strerror(errno)); + return EXIT_FAILURE; + } + if (pfds[0].revents == POLLIN) + { + process_inotify_watch(inotify_fd); + } + else if (pfds[0].revents != 0) + { + fprintf(stderr, "inotify watch error\n"); + return EXIT_FAILURE; + } + for (size_t i = 1; i < NPFDS; i++) + { + if (pfds[i].fd >= 0) + { + if (pfds[i].revents == POLLIN) + { + process_input_device(pfds[i].fd); + } + else if (pfds[i].revents != 0) + { + close(pfds[i].fd); + pfds[i].fd = -1; + } + } + } + } + return EXIT_SUCCESS; +} + +char *get_home_dir() +{ + char *home = getenv("HOME"); + if (home == NULL) + { + errno = 0; + home = getpwuid(getuid())->pw_dir; + if (errno != 0) + { + fprintf(stderr, "could not find home dir: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + } + char *ret = malloc(strlen(home) + 1); + strcpy(ret, home); + return ret; +} + +char *default_bindings_file_path() +{ + char *config_home = getenv("XDG_CONFIG_HOME"); + char *path = NULL; + if (config_home != NULL) + { + path = malloc(strlen(config_home) + 1); + strcpy(path, config_home); + } + else + { + path = get_home_dir(); + path = realloc(path, strlen(path) + strlen("/.config") + 1); + strcat(path, "/.config"); + } + path = realloc(path, strlen(path) + strlen("/shibari/bindings.conf") + 1); + strcat(path, "/shibari/bindings.conf"); + return path; +} + +void usage(const char *argv0) +{ + printf("Usage: %s [-ht] [-c file]\n" + "Reads linux evdev inputs and executes the configured user bindings." + " -h Show this help\n" + " -t Test mode: no binding is run and all inputs are printed\n" + " -c file Use the specified bindings file\n", argv0); +} + +int main(int argc, char *argv[static argc]) +{ + int opt; + char *bindings_file_path = NULL; + while ((opt = getopt(argc, argv, "hc:t")) != -1) + { + switch (opt) + { + case 'h': + usage(argv[0]); + return EXIT_SUCCESS; + case '?': + usage(argv[0]); + return EXIT_FAILURE; + case 't': + test_mode = 1; + break; + case 'c': + bindings_file_path = optarg; + break; + } + } + if (optind != argc) + { + fprintf(stderr, "unrecognized option: %s\n", argv[optind]); + usage(argv[0]); + return EXIT_FAILURE; + } + if (bindings_file_path == NULL && !test_mode) + { + bindings_file_path = default_bindings_file_path(); + } + if (test_mode) + { + struct termios t; + tcgetattr(0, &t); + t.c_lflag &= ~ECHO; + tcsetattr(0, TCSANOW, &t); + } + return run(bindings_file_path); +} |