/* Standard stuff */ #include #include #include /* Standard errors */ #include /* Directory and file control */ #include #include #include #include #include /* Polling */ #include /* Signaling */ #include /* Process wait */ #include #define test_bit(yalv, abs_b) ((((char *)abs_b)[yalv/8] & (1< 0) /* Determine dependencies based on platform */ #ifdef __linux__ #define OS linux #include #include #endif #ifdef __FreeBSD__ #define OS bsd #include #endif #ifndef OS #define OS unix #endif struct key_buffer { unsigned short *buf; unsigned int size; }; int term = 0; // exit flag const char ev_root[] = "/dev/input/"; int key_buffer_add (struct key_buffer*, unsigned short); int key_buffer_remove (struct key_buffer*, unsigned short); void int_handler (int signum); void die (const char *, int); void exec_command(const char *); void update_descriptors_list (struct pollfd **, int *); // TODO: use getopts() to parse command line options int main (void) { /* Handle SIGINT */ term = 0; struct sigaction action; memset(&action, 0, sizeof(action)); action.sa_handler = int_handler; sigaction(SIGINT, &action, NULL); int fd_num = 0; struct pollfd *fds = NULL; update_descriptors_list(&fds, &fd_num); if (!fd_num) { fputs("Could not open any device, exiting\n", stderr); exit(-1); } // TODO: watch for events inside /dev/input and reload accordingly // could use the epoll syscall or the inotify API (linux), // event API (openbsd), kqueue syscall (BSD and macos), a separate // process or some polling system inside the main loop to maintain // portability across other *NIX derivatives, could also use libev struct input_event event; struct key_buffer pb = {NULL, 0}; // Pressed keys buffer ssize_t rb; // Read bits #if OS == linux struct epoll_event epoll_read_ev; epoll_read_ev.events = EPOLLIN; int ev_fd = epoll_create(1); if (ev_fd < 0) die("epoll_create", errno); for (int i = 0; i < fd_num; i++) if (epoll_ctl(ev_fd, EPOLL_CTL_ADD, fds[i].fd, &epoll_read_ev) < 0) die("epoll_ctl", errno); #endif /* Prepare for using epoll */ for (;;) { // TODO: better error reporting /* On linux use epoll(2) as it gives better performance */ #if OS == linux static struct epoll_event ev_type; if (epoll_wait(ev_fd, &ev_type, fd_num, -1) == -1 || term) break; // TODO: use and test kqueue(2) for BSD systems /* On other systems use poll(2) to wait por a file dscriptor * to become ready for reading. */ #else // TODO: add unix and bsd cases if (poll(fds, fd_num, -1) != -1 || term) break; #endif static int i; static unsigned int prev_size; prev_size = pb.size; for (i = 0; i < fd_num; i++) { #if OS == linux if (ev_type.events == EPOLLIN) { #else // TODO: add unix and bsd cases if (fds[i].revents == fds[i].events) { #endif rb = read(fds[i].fd, &event, sizeof(struct input_event)); if (rb != sizeof(struct input_event)) continue; /* Ignore touchpad events */ // TODO: make a event blacklist system if ( event.type == EV_KEY && event.code != BTN_TOUCH && event.code != BTN_TOOL_FINGER && event.code != BTN_TOOL_DOUBLETAP && event.code != BTN_TOOL_TRIPLETAP ) { switch (event.value) { /* Key released */ case (0): key_buffer_remove(&pb, event.code); break; /* Key pressed */ case (1): key_buffer_add(&pb, event.code); break; } } } } if (pb.size != prev_size) { printf("Pressed keys: "); for (unsigned int i = 0; i < pb.size; i++) printf("%d ", pb.buf[i]); putchar('\n'); if (pb.size == 2) if (pb.buf[0] == 56 || pb.buf[0] == 31) if (pb.buf[1] == 31 || pb.buf[1] == 56) exec_command("/home/ale/hello"); } } // TODO: better child handling, for now all children receive the same // interrupts as the father so everything should work fine for now wait(NULL); free(pb.buf); if (!term) fputs("An error occured\n", stderr); for (int i = 0; i < fd_num; i++) { if (close(fds[i].fd) == -1) die("close file descriptors", errno); } return 0; } // TODO: optimize functions to preallocate some memory int key_buffer_add (struct key_buffer *pb, unsigned short key) { /* Adds a keycode to the pressed buffer if it is not already present * Returns non zero if the key was not added. */ if (!pb) return 1; if (pb->buf != NULL) { /* Linear search if the key is already buffered */ for (unsigned int i = 0; i < pb->size; i++) if (key == pb->buf[i]) return 1; } unsigned short *b; b = realloc(pb->buf, sizeof(unsigned short) * (pb->size + 1)); if (!b) die("realloc failed in key_buffer_add", errno); pb->buf = b; pb->buf[pb->size++] = key; return 0; } int key_buffer_remove (struct key_buffer *pb, unsigned short key) { /* Removes a keycode from a pressed buffer if it is present returns * non zero in case of failure (key not present or buffer empty). */ if (!pb) return 1; for (unsigned int i = 0; i < pb->size; i++) { if (pb->buf[i] == key) { pb->size--; pb->buf[i] = pb->buf[pb->size]; unsigned short *b; b = realloc(pb->buf, sizeof(unsigned short) * pb->size); /* if realloc failed but the buffer is populated throw an error */ if (!b && pb->size) die("realloc failed in key_buffer_remove: %s", errno); pb->buf = b; return 0; } } return 1; } void int_handler (int signum) { fputs("Received interrupt signal, exiting gracefully...\n", stderr); term = 1; } void die (const char *msg, int err) { fprintf(stderr, "%s: %s", msg != NULL ? msg : "error", err ? strerror(err): "exiting"); exit(err); } void exec_command (const char *path) { switch (fork()) { case -1: die("Could not fork: %s", errno); break; case 0: /* we are the child */ if(execl(path, path, (char *) NULL) != 0) /* execl only returns if an error occured, so we exit * otherwise we duplicate the process */ exit(-1); /* we shouldn't be here */ break; } // TODO: communication between parent and child about process status/errors/etc } void update_descriptors_list (struct pollfd **fds, int *fd_num) { struct dirent *file_ent; char ev_path[sizeof(ev_root) + NAME_MAX + 1]; void *tmp_p; int tmp_fd; unsigned char evtype_b[EV_MAX]; /* Open the event directory */ DIR *ev_dir = opendir(ev_root); if (!ev_dir) die("opendir", errno); (*fd_num) = 0; if ((*fds)) free(fds); for (;;) { if ((file_ent = readdir(ev_dir)) == NULL) break; /* Filter out non character devices */ if (file_ent->d_type != DT_CHR) continue; /* Compose absolute path from relative */ strncpy(ev_path, ev_root, sizeof(ev_root) + NAME_MAX); strncat(ev_path, file_ent->d_name, sizeof(ev_root) + NAME_MAX); /* Open device and check if it can give key events otherwise ignore it */ tmp_fd = open(ev_path, O_RDONLY | O_NONBLOCK); if (tmp_fd < 0) { fprintf(stderr, "Could not open device %s\n", ev_path); continue; } memset(evtype_b, 0, sizeof(evtype_b)); if (ioctl(tmp_fd, EVIOCGBIT(0, EV_MAX), evtype_b) < 0) { fprintf(stderr, "Could not read capabilities of device %s\n", ev_path); close(tmp_fd); continue; } if (!test_bit(EV_KEY, evtype_b)) { fprintf(stderr, "Ignoring device %s\n", ev_path); close(tmp_fd); continue; } tmp_p = realloc((*fds), sizeof(struct pollfd) * ((*fd_num) + 1)); if (!tmp_p) die("realloc file descriptors", errno); (*fds) = tmp_p; (*fds)[(*fd_num)].events = POLLIN; (*fds)[(*fd_num)].fd = tmp_fd; (*fd_num)++; } closedir(ev_dir); }