#include #include #include #include #include #include #include #include #include #include #include typedef struct { struct xkb_context *xkb_context; struct xkb_keymap *xkb_keymap; struct xkb_state *xkb_state; } key_mapper; typedef enum { MODKEY_ALT = 1 << 0, MODKEY_SUPER = 1 << 1, MODKEY_CTRL = 1 << 2, MODKEY_SHIFT = 1 << 3 } modkeys; typedef struct { uint32_t key; uint32_t modifiers; /* modkeys */ const char *action; } global_hotkey; #define NUM_GLOBAL_HOTKEYS 6 static global_hotkey global_hotkeys[NUM_GLOBAL_HOTKEYS] = { { .key = XKB_KEY_z, .modifiers = MODKEY_ALT, .action = "show_hide" }, { .key = XKB_KEY_F9, .modifiers = MODKEY_ALT, .action = "record" }, { .key = XKB_KEY_F7, .modifiers = MODKEY_ALT, .action = "pause" }, { .key = XKB_KEY_F8, .modifiers = MODKEY_ALT, .action = "stream" }, { .key = XKB_KEY_F10, .modifiers = MODKEY_ALT | MODKEY_SHIFT, .action = "replay_start" }, { .key = XKB_KEY_F10, .modifiers = MODKEY_ALT, .action = "replay_save" } }; 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, "error: failed to open %s, error: %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 bool is_mod_key(xkb_keycode_t xkb_key_code) { return xkb_key_code >= XKB_KEY_Shift_L && xkb_key_code <= XKB_KEY_Hyper_R; } typedef struct { const char *modname; modkeys key; } modname_to_modkey_map; static uint32_t xkb_state_to_modifiers(struct xkb_state *xkb_state) { const modname_to_modkey_map modifier_keys[] = { { .modname = XKB_MOD_NAME_ALT, .key = MODKEY_ALT }, { .modname = XKB_MOD_NAME_LOGO, .key = MODKEY_SUPER }, { .modname = XKB_MOD_NAME_SHIFT, .key = MODKEY_SHIFT }, { .modname = XKB_MOD_NAME_CTRL, .key = MODKEY_CTRL } }; uint32_t modifiers = 0; for(int i = 0; i < 4; ++i) { if(xkb_state_mod_name_is_active(xkb_state, modifier_keys[i].modname, XKB_STATE_MODS_EFFECTIVE) > 0) modifiers |= modifier_keys[i].key; } return modifiers; } #define KEY_CODE_EV_TO_XKB(key) ((key) + 8) static int print_key_event(struct libinput_event *event, key_mapper *mapper) { struct libinput_event_keyboard *keyboard = libinput_event_get_keyboard_event(event); const uint32_t key_code = libinput_event_keyboard_get_key(keyboard); enum libinput_key_state state_code = libinput_event_keyboard_get_key_state(keyboard); const xkb_keycode_t xkb_key_code = KEY_CODE_EV_TO_XKB(key_code); xkb_state_update_key(mapper->xkb_state, xkb_key_code, state_code == LIBINPUT_KEY_STATE_PRESSED ? XKB_KEY_DOWN : XKB_KEY_UP); xkb_keysym_t xkb_key_sym = xkb_state_key_get_one_sym(mapper->xkb_state, xkb_key_code); // char main_key[128]; // main_key[0] = '\0'; // if(xkb_state_mod_name_is_active(mapper->xkb_state, XKB_MOD_NAME_LOGO, XKB_STATE_MODS_EFFECTIVE) > 0) // strcat(main_key, "Super+"); // if(xkb_state_mod_name_is_active(mapper->xkb_state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_EFFECTIVE) > 0) // strcat(main_key, "Ctrl+"); // if(xkb_state_mod_name_is_active(mapper->xkb_state, XKB_MOD_NAME_ALT, XKB_STATE_MODS_EFFECTIVE) > 0 && strcmp(main_key, "Meta") != 0) // strcat(main_key, "Alt+"); // if(xkb_state_mod_name_is_active(mapper->xkb_state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_EFFECTIVE) > 0) // strcat(main_key, "Shift+"); // if(!is_mod_key(xkb_key_sym)) { // char reg_key[64]; // reg_key[0] = '\0'; // xkb_keysym_get_name(xkb_key_sym, reg_key, sizeof(reg_key)); // strcat(main_key, reg_key); // } if(state_code != LIBINPUT_KEY_STATE_PRESSED) return 0; const uint32_t current_modifiers = xkb_state_to_modifiers(mapper->xkb_state); for(int i = 0; i < NUM_GLOBAL_HOTKEYS; ++i) { if(xkb_key_sym == global_hotkeys[i].key && current_modifiers == global_hotkeys[i].modifiers) { puts(global_hotkeys[i].action); fflush(stdout); break; } } return 0; } static int handle_events(struct libinput *libinput, key_mapper *mapper) { int result = -1; struct libinput_event *event; if(libinput_dispatch(libinput) < 0) return result; while((event = libinput_get_event(libinput)) != NULL) { if(libinput_event_get_type(event) == LIBINPUT_EVENT_KEYBOARD_KEY) print_key_event(event, mapper); libinput_event_destroy(event); result = 0; } return result; } static int run_mainloop(struct libinput *libinput, key_mapper *mapper) { struct pollfd fds[2] = { { .fd = libinput_get_fd(libinput), .events = POLLIN, .revents = 0 }, { .fd = STDOUT_FILENO, .events = 0, .revents = 0 } }; if(handle_events(libinput, mapper) != 0) { fprintf(stderr, "error: didn't receive device added events. Is this program not running as root?\n"); return -1; } while(poll(fds, 2, -1) >= 0) { if(fds[0].revents & POLLIN) handle_events(libinput, mapper); if(fds[1].revents & (POLLHUP|POLLERR)) break; } return 0; } static bool mapper_refresh_keymap(key_mapper *mapper) { if(mapper->xkb_keymap != NULL) { xkb_keymap_unref(mapper->xkb_keymap); mapper->xkb_keymap = NULL; } // TODO: struct xkb_rule_names names = { NULL, NULL, NULL,//keymap_is_default(mapper->layout) ? NULL : mapper->layout, NULL,//keymap_is_default(mapper->variant) ? NULL : mapper->variant, NULL }; mapper->xkb_keymap = xkb_keymap_new_from_names(mapper->xkb_context, &names, XKB_KEYMAP_COMPILE_NO_FLAGS); if(mapper->xkb_keymap == NULL) { fprintf(stderr, "error: failed to create XKB keymap.\n"); return false; } if(mapper->xkb_state != NULL) { xkb_state_unref(mapper->xkb_state); mapper->xkb_state = NULL; } mapper->xkb_state = xkb_state_new(mapper->xkb_keymap); if(mapper->xkb_state == NULL) { fprintf(stderr, "error: failed to create XKB state.\n"); return false; } return true; } int main(void) { int result = 0; struct udev *udev = NULL; struct libinput *libinput = NULL; const uid_t user_id = getuid(); if(geteuid() != 0) { if(setuid(0) == -1) { fprintf(stderr, "error: failed to change user to root\n"); return 1; } } udev = udev_new(); if(!udev) { fprintf(stderr, "error: udev_new failed\n"); result = 1; goto done; } libinput = libinput_udev_create_context(&interface, NULL, udev); if(!libinput) { fprintf(stderr, "error: libinput_udev_create_context failed\n"); result = 1; goto done; } if(libinput_udev_assign_seat(libinput, "seat0") != 0) { fprintf(stderr, "error: libinput_udev_assign_seat with seat0 failed\n"); result = 1; goto done; } key_mapper mapper; mapper.xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if(!mapper.xkb_context) { fprintf(stderr, "error: xkb_context_new failed\n"); result = 1; goto done; } if(!mapper_refresh_keymap(&mapper)) { fprintf(stderr, "error: key mapper failed\n"); result = 1; goto done; } if(run_mainloop(libinput, &mapper) < 0) { fprintf(stderr, "error: failed to start main loop\n"); result = 1; goto done; } done: if(libinput) libinput_unref(libinput); if(udev) udev_unref(udev); setuid(user_id); return result; }