/** * @file xkb.c * */ /********************* * INCLUDES *********************/ #include "xkb.h" #if USE_XKB #include #include #include #include #include /********************* * DEFINES *********************/ /********************** * TYPEDEFS **********************/ /********************** * STATIC PROTOTYPES **********************/ /********************** * STATIC VARIABLES **********************/ static struct xkb_context *context = NULL; static xkb_drv_state_t default_state = { .keymap = NULL, .state = NULL }; /********************** * MACROS **********************/ /********************** * GLOBAL FUNCTIONS **********************/ /** * Initialise the XKB system using the default driver state. Use this function if you only want * to connect a single device. * @return true if the initialisation was successful */ bool xkb_init(void) { return xkb_init_state(&default_state); } /** * Initialise the XKB system using a specific driver state. Use this function if you want to * connect multiple devices. * @param state XKB driver state to use * @return true if the initialisation was successful */ bool xkb_init_state(xkb_drv_state_t *state) { if (!context) { context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (!context) { perror("could not create new XKB context"); return false; } } #ifdef XKB_KEY_MAP struct xkb_rule_names names = XKB_KEY_MAP; return xkb_set_keymap_state(state, names); #else return false; /* Keymap needs to be set manually using xkb_set_keymap_state to complete initialisation */ #endif } /** * De-initialise a previously initialised driver state and free any dynamically allocated memory. Use this function if you want to * reuse an existing driver state. * @param state XKB driver state to use */ void xkb_deinit_state(xkb_drv_state_t *state) { if (state->state) { xkb_state_unref(state->state); state->state = NULL; } if (state->keymap) { xkb_keymap_unref(state->keymap); state->keymap = NULL; } } /** * Set a new keymap to be used for processing future key events using the default driver state. Use * this function if you only want to connect a single device. * @param names XKB rule names structure (use NULL components for default values) * @return true if creating the keymap and associated state succeeded */ bool xkb_set_keymap(struct xkb_rule_names names) { return xkb_set_keymap_state(&default_state, names); } /** * Set a new keymap to be used for processing future key events using a specific driver state. Use * this function if you want to connect multiple devices. * @param state XKB driver state to use * @param names XKB rule names structure (use NULL components for default values) * @return true if creating the keymap and associated state succeeded */ bool xkb_set_keymap_state(xkb_drv_state_t *state, struct xkb_rule_names names) { if (state->keymap) { xkb_keymap_unref(state->keymap); state->keymap = NULL; } state->keymap = xkb_keymap_new_from_names(context, &names, XKB_KEYMAP_COMPILE_NO_FLAGS); if (!state->keymap) { perror("could not create XKB keymap"); return false; } if (state->state) { xkb_state_unref(state->state); state->state = NULL; } state->state = xkb_state_new(state->keymap); if (!state->state) { perror("could not create XKB state"); return false; } return true; } /** * Process an evdev scancode using the default driver state. Use this function if you only want to * connect a single device. * @param scancode evdev scancode to process * @param down true if the key was pressed, false if it was releases * @return the (first) UTF-8 character produced by the event or 0 if no output was produced */ uint32_t xkb_process_key(uint32_t scancode, bool down) { return xkb_process_key_state(&default_state, scancode, down); } /** * Process an evdev scancode using a specific driver state. Use this function if you want to connect * multiple devices. * @param state XKB driver state to use * @param scancode evdev scancode to process * @param down true if the key was pressed, false if it was releases * @return the (first) UTF-8 character produced by the event or 0 if no output was produced */ uint32_t xkb_process_key_state(xkb_drv_state_t *state, uint32_t scancode, bool down) { /* Offset the evdev scancode by 8, see https://xkbcommon.org/doc/current/xkbcommon_8h.html#ac29aee92124c08d1953910ab28ee1997 */ xkb_keycode_t keycode = scancode + 8; uint32_t result = 0; switch (xkb_state_key_get_one_sym(state->state, keycode)) { case XKB_KEY_BackSpace: result = LV_KEY_BACKSPACE; break; case XKB_KEY_Return: case XKB_KEY_KP_Enter: result = LV_KEY_ENTER; break; case XKB_KEY_Prior: case XKB_KEY_KP_Prior: result = LV_KEY_PREV; break; case XKB_KEY_Next: case XKB_KEY_KP_Next: result = LV_KEY_NEXT; break; case XKB_KEY_Up: case XKB_KEY_KP_Up: result = LV_KEY_UP; break; case XKB_KEY_Left: case XKB_KEY_KP_Left: result = LV_KEY_LEFT; break; case XKB_KEY_Right: case XKB_KEY_KP_Right: result = LV_KEY_RIGHT; break; case XKB_KEY_Down: case XKB_KEY_KP_Down: result = LV_KEY_DOWN; break; case XKB_KEY_Tab: case XKB_KEY_KP_Tab: result = LV_KEY_NEXT; break; case XKB_KEY_ISO_Left_Tab: /* Sent on SHIFT + TAB */ result = LV_KEY_PREV; break; default: break; } if (result == 0) { char buffer[4] = { 0, 0, 0, 0 }; int size = xkb_state_key_get_utf8(state->state, keycode, NULL, 0) + 1; if (size > 1) { xkb_state_key_get_utf8(state->state, keycode, buffer, size); memcpy(&result, buffer, 4); } } xkb_state_update_key(state->state, keycode, down ? XKB_KEY_DOWN : XKB_KEY_UP); return result; } /********************** * STATIC FUNCTIONS **********************/ #endif /* USE_XKB */