#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #define MAX_BUFFER_LENGTH 512 enum error_code { NO_ERROR, UDEV_FAILED, LIBINPUT_FAILED, SEAT_FAILED, PERMISSION_FAILED }; struct input_handler_data { struct udev *udev; struct libinput *libinput; }; static void *handle_input(void *user_data) { struct input_handler_data *input_handler_data = user_data; char line[MAX_BUFFER_LENGTH]; while (fgets(line, MAX_BUFFER_LENGTH, stdin) != NULL) { if (strcmp(line, "stop\n") == 0) { libinput_unref(input_handler_data->libinput); udev_unref(input_handler_data->udev); exit(EXIT_SUCCESS); } } return NULL; } static int open_restricted(const char *path, int flags, void *user_data) { (void)user_data; int fd = open(path, flags); if (fd < 0) fprintf(stderr, "Failed to open %s because of %s.\n", path, strerror(errno)); return fd < 0 ? -errno : fd; } static void close_restricted(int fd, void *user_data) { (void)user_data; close(fd); } static const struct libinput_interface interface = { .open_restricted = open_restricted, .close_restricted = close_restricted, }; static int print_key_event(struct libinput_event *event) { struct libinput_event_keyboard *keyboard = libinput_event_get_keyboard_event(event); enum libinput_event_type event_type = libinput_event_get_type(event); uint32_t time_stamp = libinput_event_keyboard_get_time(keyboard); uint32_t key_code = libinput_event_keyboard_get_key(keyboard); const char *key_name = libevdev_event_code_get_name(EV_KEY, key_code); key_name = key_name ? key_name : "null"; enum libinput_key_state state_code = libinput_event_keyboard_get_key_state(keyboard); const char *state_name = state_code == LIBINPUT_KEY_STATE_PRESSED ? "PRESSED" : "RELEASED"; return printf("{" "\"event_name\": \"KEYBOARD_KEY\", " "\"event_type\": %d, " "\"time_stamp\": %d, " "\"key_name\": \"%s\", " "\"key_code\": %d, " "\"state_name\": \"%s\", " "\"state_code\": %d" "}\n", event_type, time_stamp, key_name, key_code, state_name, state_code); } static int print_button_event(struct libinput_event *event) { struct libinput_event_pointer *pointer = libinput_event_get_pointer_event(event); enum libinput_event_type event_type = libinput_event_get_type(event); uint32_t time_stamp = libinput_event_pointer_get_time(pointer); uint32_t button_code = libinput_event_pointer_get_button(pointer); const char *button_name = libevdev_event_code_get_name(EV_KEY, button_code); enum libinput_button_state state_code = libinput_event_pointer_get_button_state(pointer); const char *state_name = state_code == LIBINPUT_BUTTON_STATE_PRESSED ? "PRESSED" : "RELEASED"; return printf("{" "\"event_name\": \"POINTER_BUTTON\", " "\"event_type\": %d, " "\"time_stamp\": %d, " "\"key_name\": \"%s\", " "\"key_code\": %d, " "\"state_name\": \"%s\", " "\"state_code\": %d" "}\n", event_type, time_stamp, button_name, button_code, state_name, state_code); } static int handle_events(struct libinput *libinput) { int result = -1; struct libinput_event *event; if (libinput_dispatch(libinput) < 0) return result; // Please keep printing a line per json. while ((event = libinput_get_event(libinput)) != NULL) { switch (libinput_event_get_type(event)) { // This program only handle key event. case LIBINPUT_EVENT_KEYBOARD_KEY: print_key_event(event); break; // Sorry, mouse button is also a key. case LIBINPUT_EVENT_POINTER_BUTTON: print_button_event(event); break; default: break; } // Do a `fflush(stdout)` here, so when we write to pipes, // the other one can always get a latest result. // If we don't have `fflush(stdout)` here, pipe will save // some lines in buffer and pass them together. fflush(stdout); libinput_event_destroy(event); result = 0; } return result; } static int run_mainloop(struct libinput *libinput) { struct pollfd fd; fd.fd = libinput_get_fd(libinput); fd.events = POLLIN; fd.revents = 0; if (handle_events(libinput) != 0) { fprintf(stderr, "Expected device added events on startup but " "got none. Maybe you don't have the right permissions?" "\n"); return -1; } while (poll(&fd, 1, -1) > -1) handle_events(libinput); return 0; } void print_help(char *program_name) { printf("The backend of Show Me The Key.\n"); printf("Version " PROJECT_VERSION ".\n"); printf("Usage: %s [OPTION…]\n", program_name); printf("Options:\n"); printf("\t-h, --help\tDisplay help then exit.\n"); printf("\t-v, --version\tDisplay version then exit.\n"); printf("Warning: This is the backend and is not designed to run " "by users. You should run the frontend of Show Me The Key, " "and the frontend will run this.\n"); } int main(int argc, char *argv[]) { const struct option long_options[] = { { "version", no_argument, 0, 'v' }, { "help", no_argument, 0, 'h' }, { NULL, 0, NULL, 0 } }; int option_index = 0; int opt = 0; while ((opt = getopt_long(argc, argv, "vh", long_options, &option_index)) != -1) { switch (opt) { case 0: // We don't use this. break; case 'v': printf(PROJECT_VERSION "\n"); return 0; case 'h': print_help(argv[0]); return 0; case '?': // getopt_long already printed an error message. break; default: fprintf(stderr, "%s: Invalid option `-%c`.\n", argv[0], opt); break; } } struct udev *udev = udev_new(); if (udev == NULL) { fprintf(stderr, "Failed to initialize udev.\n"); return UDEV_FAILED; } struct libinput *libinput = libinput_udev_create_context(&interface, NULL, udev); if (!libinput) { fprintf(stderr, "Failed to initialize libinput from udev.\n"); return LIBINPUT_FAILED; } // TODO: Support custom seat. if (libinput_udev_assign_seat(libinput, "seat0") != 0) { fprintf(stderr, "Failed to set seat.\n"); libinput_unref(libinput); udev_unref(udev); return SEAT_FAILED; } // Typically this will be run with pkexec as a subprocess, // and the parent cannot kill it because it is privileged, // so we use another thread to see if it gets "stop\n" from stdin, // it will exit by itself. pthread_t input_handler; struct input_handler_data input_handler_data = { udev, libinput }; pthread_create(&input_handler, NULL, handle_input, &input_handler_data); if (run_mainloop(libinput) < 0) return PERMISSION_FAILED; libinput_unref(libinput); udev_unref(udev); return NO_ERROR; }