/* * Copyright 2017 Markus Hennecke * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define PIDFILE "/run/mpcinputd.pid" volatile sig_atomic_t quit = 0; void * read_file(char *_file) { struct stat st; void *result; int fd; ssize_t n, s, done = 0; if (lstat(_file, &st) != 0) { warn("stat"); return NULL; } if (! (st.st_mode & S_IFREG)) { warnx("%s is no regular file", _file); return NULL; } s = st.st_size; if ((fd = open(_file, O_RDONLY)) == -1) err(1, "open"); if ((result = calloc(1, s)) == NULL) err(1, "malloc"); while (done < s) { n = read(fd, (char *)result + done, s - done); if (n < 0) err(1, "read"); if (n == 0) /* sysfs won't give us the actual length... */ break; done += n; } close(fd); return result; } void chomp(char *_s) { size_t idx = strlen(_s); while (idx && !isprint(_s[idx])) { _s[idx] = '\0'; --idx; } } char * find_input_device(char *_name) { DIR *d; char *result = NULL; struct dirent *ent; int dev_num = -1; if ((d = opendir("/sys/class/input")) == NULL) { warn("opendir"); return NULL; } while ((ent = readdir(d)) != NULL && dev_num == -1) { char *dev_name; if (strncmp("input", ent->d_name, 5 /* strlen("input") */)) { continue; } if (asprintf(&dev_name, "/sys/class/input/%s/name", ent->d_name) < 0) { warn("asprintf"); continue; } char *name = read_file(dev_name); if (name) { chomp(name); if (strcmp(name, _name) == 0) { char *fst_digit = ent->d_name; char *end_digit = NULL; while (*fst_digit && !isdigit(*fst_digit)) ++fst_digit; dev_num = strtol(fst_digit, &end_digit, 10); if (*end_digit != '\0') { warnx("Unable to parse device number"); dev_num = -1; } } } free(name); free(dev_name); } if (dev_num >= 0) { if (asprintf(&result, "/dev/input/event%d", dev_num) == -1) err(1, "asprintf"); } closedir(d); return result; } void sig_handler(int _sig) { switch (_sig) { case SIGINT: case SIGTERM: quit = 1; break; default: break; } } int mpc_command(char *_cmd) { int rc; char *cmd; if (asprintf(&cmd, "mpc %s >/dev/null 2>&1", _cmd) == -1) { warn("asprintf"); return -1; } rc = system(cmd); if (rc != 0) { warnx("cmd '%s' returned with exit code %d", cmd, rc); } free(cmd); return rc; } void drop_privileges(char *_user) { struct passwd *ent; while ((ent = getpwent()) != NULL) { if (strcmp(ent->pw_name, _user) == 0) { if (setgid(ent->pw_gid) == -1) err(1, "setgid"); if (setuid(ent->pw_uid) == -1) err(1, "setuid"); break; } } endpwent(); } void write_pid_file(char *_pidfile) { int fd; struct stat st; if (stat(_pidfile, &st) == -1) { if (errno != ENOENT) err(1, "stat"); } else { errx(1, "pid file already exists"); } if ((fd = creat(_pidfile, 0755)) == -1) err(1, "creat"); FILE *f = fdopen(fd, "a"); pid_t pid = getpid(); fprintf(f, "%d\n", pid); fclose(f); } void handle_input(char *_device) { int flags; struct pollfd poll_fd; int fd = open(_device, O_RDONLY); if (fd == -1) err(1, "open"); flags = fcntl(fd, F_GETFL, 0); if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) err(1, "fcntl"); if (daemon(0, 0) != 0) err(1, "daemon"); write_pid_file(PIDFILE); drop_privileges("nobody"); //signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); poll_fd.fd = fd; poll_fd.events = POLLIN; while (!quit) { struct input_event ev; ssize_t n; int res; res = poll(&poll_fd, 1, -1); if (res < 0) err(1, "poll"); if (res == 0) continue; n = read(fd, &ev, sizeof(ev)); if (n < 0) err(1, "read"); if (n == 0) continue; if (n != sizeof(ev)) errx(1, "read: invalid size"); if (ev.type != EV_MSC || ev.code != EV_MSC) continue; switch (ev.value) { case 0x826f15: printf("Play button pressed\n"); mpc_command("toggle"); break; case 0x826f14: printf("Record button pressed\n"); break; case 0x826f09: printf("Stop button pressed\n"); mpc_command("stop"); break; case 0x826f04: printf("Previous button pressed\n"); mpc_command("prev"); break; case 0x826f0a: printf("Pause button pressed\n"); mpc_command("toggle"); break; case 0x826f06: printf("Next button pressed\n"); mpc_command("next"); break; case 0x826f0e: printf("mute button pressed\n"); mpc_command("pause"); break; default: printf("Key: %08x\n", ev.value); break; } } close(fd); } void usage(void) { extern char *__progname; fprintf(stderr, "usage: %s input_name\n", __progname); exit(1); } int main(int argc, char **argv) { if (argc != 2) usage(); if (geteuid()) errx(1, "need root privileges"); char *device_name = argv[1]; char *dev = find_input_device(device_name); if (dev == NULL) errx(1, "Unable to find input device for %s", device_name); handle_input(dev); return 0; }