/** * @file libinput.c * */ /********************* * INCLUDES *********************/ #include "libinput_drv.h" #if USE_LIBINPUT || USE_BSD_LIBINPUT #include #include #include #include #include #include #include #include #if USE_BSD_LIBINPUT #include #else #include #endif /********************* * DEFINES *********************/ /********************** * TYPEDEFS **********************/ struct input_device { libinput_capability capabilities; char *path; }; /********************** * STATIC PROTOTYPES **********************/ static bool rescan_devices(void); static bool add_scanned_device(char *path, libinput_capability capabilities); static void reset_scanned_devices(void); static void read_pointer(libinput_drv_state_t *state, struct libinput_event *event); static void read_keypad(libinput_drv_state_t *state, struct libinput_event *event); static int open_restricted(const char *path, int flags, void *user_data); static void close_restricted(int fd, void *user_data); /********************** * STATIC VARIABLES **********************/ static struct input_device *devices = NULL; static size_t num_devices = 0; static libinput_drv_state_t default_state = { .most_recent_touch_point = { .x = 0, .y = 0 } }; static const int timeout = 0; // do not block static const nfds_t nfds = 1; static const struct libinput_interface interface = { .open_restricted = open_restricted, .close_restricted = close_restricted, }; /********************** * MACROS **********************/ /********************** * GLOBAL FUNCTIONS **********************/ /** * Determine the capabilities of a specific libinput device. * @param device the libinput device to query * @return the supported input capabilities */ libinput_capability libinput_query_capability(struct libinput_device *device) { libinput_capability capability = LIBINPUT_CAPABILITY_NONE; if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_KEYBOARD) && (libinput_device_keyboard_has_key(device, KEY_ENTER) || libinput_device_keyboard_has_key(device, KEY_KPENTER))) { capability |= LIBINPUT_CAPABILITY_KEYBOARD; } if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_POINTER)) { capability |= LIBINPUT_CAPABILITY_POINTER; } if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TOUCH)) { capability |= LIBINPUT_CAPABILITY_TOUCH; } return capability; } /** * Find connected input device with specific capabilities * @param capabilities required device capabilities * @param force_rescan erase the device cache (if any) and rescan the file system for available devices * @return device node path (e.g. /dev/input/event0) for the first matching device or NULL if no device was found. * The pointer is safe to use until the next forceful device search. */ char *libinput_find_dev(libinput_capability capabilities, bool force_rescan) { char *path = NULL; libinput_find_devs(capabilities, &path, 1, force_rescan); return path; } /** * Find connected input devices with specific capabilities * @param capabilities required device capabilities * @param devices pre-allocated array to store the found device node paths (e.g. /dev/input/event0). The pointers are * safe to use until the next forceful device search. * @param count maximum number of devices to find (the devices array should be at least this long) * @param force_rescan erase the device cache (if any) and rescan the file system for available devices * @return number of devices that were found */ size_t libinput_find_devs(libinput_capability capabilities, char **found, size_t count, bool force_rescan) { if ((!devices || force_rescan) && !rescan_devices()) { return 0; } size_t num_found = 0; for (size_t i = 0; i < num_devices && num_found < count; ++i) { if (devices[i].capabilities & capabilities) { found[num_found] = devices[i].path; num_found++; } } return num_found; } /** * Reconfigure the device file for libinput using the default driver state. Use this function if you only want * to connect a single device. * @param dev_name input device node path (e.g. /dev/input/event0) * @return true: the device file set complete * false: the device file doesn't exist current system */ bool libinput_set_file(char* dev_name) { return libinput_set_file_state(&default_state, dev_name); } /** * Reconfigure the device file for libinput using a specific driver state. Use this function if you want to * connect multiple devices. * @param state the driver state to configure * @param dev_name input device node path (e.g. /dev/input/event0) * @return true: the device file set complete * false: the device file doesn't exist current system */ bool libinput_set_file_state(libinput_drv_state_t *state, char* dev_name) { // This check *should* not be necessary, yet applications crashes even on NULL handles. // citing libinput.h:libinput_path_remove_device: // > If no matching device exists, this function does nothing. if (state->libinput_device) { libinput_path_remove_device(state->libinput_device); state->libinput_device = libinput_device_unref(state->libinput_device); } state->libinput_device = libinput_path_add_device(state->libinput_context, dev_name); if(!state->libinput_device) { perror("unable to add device to libinput context:"); return false; } state->libinput_device = libinput_device_ref(state->libinput_device); if(!state->libinput_device) { perror("unable to reference device within libinput context:"); return false; } state->button = LV_INDEV_STATE_REL; state->key_val = 0; return true; } /** * Prepare for reading input via libinput using the default driver state. Use this function if you only want * to connect a single device. */ void libinput_init(void) { libinput_init_state(&default_state, LIBINPUT_NAME); } /** * Prepare for reading input via libinput using the a specific driver state. Use this function if you want to * connect multiple devices. * @param state driver state to initialize * @param path input device node path (e.g. /dev/input/event0) */ void libinput_init_state(libinput_drv_state_t *state, char* path) { state->libinput_device = NULL; state->libinput_context = libinput_path_create_context(&interface, NULL); if(path == NULL || !libinput_set_file_state(state, path)) { fprintf(stderr, "unable to add device \"%s\" to libinput context: %s\n", path ? path : "NULL", strerror(errno)); return; } state->fd = libinput_get_fd(state->libinput_context); /* prepare poll */ state->fds[0].fd = state->fd; state->fds[0].events = POLLIN; state->fds[0].revents = 0; #if USE_XKB xkb_init_state(&(state->xkb_state)); #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 driver state to de-initialize */ void libinput_deinit_state(libinput_drv_state_t *state) { if (state->libinput_device) { libinput_path_remove_device(state->libinput_device); libinput_device_unref(state->libinput_device); } if (state->libinput_context) { libinput_unref(state->libinput_context); } #if USE_XKB xkb_deinit_state(&(state->xkb_state)); #endif lv_memzero(state, sizeof(libinput_drv_state_t)); } /** * Read available input events via libinput using the default driver state. Use this function if you only want * to connect a single device. * @param indev_drv driver object itself * @param data store the libinput data here */ void libinput_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) { libinput_read_state(&default_state, indev_drv, data); } /** * Read available input events via libinput using a specific driver state. Use this function if you want to * connect multiple devices. * @param state the driver state to use * @param indev_drv driver object itself * @param data store the libinput data here */ void libinput_read_state(libinput_drv_state_t * state, lv_indev_drv_t * indev_drv, lv_indev_data_t * data) { struct libinput_event *event; int rc = 0; rc = poll(state->fds, nfds, timeout); switch (rc){ case -1: perror(NULL); case 0: goto report_most_recent_state; default: break; } libinput_dispatch(state->libinput_context); while((event = libinput_get_event(state->libinput_context)) != NULL) { switch (indev_drv->type) { case LV_INDEV_TYPE_POINTER: read_pointer(state, event); break; case LV_INDEV_TYPE_KEYPAD: read_keypad(state, event); break; default: break; } libinput_event_destroy(event); } report_most_recent_state: data->point.x = state->most_recent_touch_point.x; data->point.y = state->most_recent_touch_point.y; data->state = state->button; data->key = state->key_val; } /********************** * STATIC FUNCTIONS **********************/ /** * rescan all attached evdev devices and store capable ones into the static devices array for quick later filtering * @return true if the operation succeeded */ static bool rescan_devices(void) { reset_scanned_devices(); DIR *dir; struct dirent *ent; if (!(dir = opendir("/dev/input"))) { perror("unable to open directory /dev/input"); return false; } struct libinput *context = libinput_path_create_context(&interface, NULL); while ((ent = readdir(dir))) { if (strncmp(ent->d_name, "event", 5) != 0) { continue; } /* 11 characters for /dev/input/ + length of name + 1 NUL terminator */ char *path = malloc((11 + strlen(ent->d_name) + 1) * sizeof(char)); if (!path) { perror("could not allocate memory for device node path"); libinput_unref(context); reset_scanned_devices(); return false; } strcpy(path, "/dev/input/"); strcat(path, ent->d_name); struct libinput_device *device = libinput_path_add_device(context, path); if(!device) { perror("unable to add device to libinput context"); free(path); continue; } /* The device pointer is guaranteed to be valid until the next libinput_dispatch. Since we're not dispatching events * as part of this function, we don't have to increase its reference count to keep it alive. * https://wayland.freedesktop.org/libinput/doc/latest/api/group__base.html#gaa797496f0150b482a4e01376bd33a47b */ libinput_capability capabilities = libinput_query_capability(device); libinput_path_remove_device(device); if (capabilities == LIBINPUT_CAPABILITY_NONE) { free(path); continue; } if (!add_scanned_device(path, capabilities)) { free(path); libinput_unref(context); reset_scanned_devices(); return false; } } libinput_unref(context); return true; } /** * add a new scanned device to the static devices array, growing its size when necessary * @param path device file path * @param capabilities device input capabilities * @return true if the operation succeeded */ static bool add_scanned_device(char *path, libinput_capability capabilities) { /* Double array size every 2^n elements */ if ((num_devices & (num_devices + 1)) == 0) { struct input_device *tmp = realloc(devices, (2 * num_devices + 1) * sizeof(struct input_device)); if (!tmp) { perror("could not reallocate memory for devices array"); return false; } devices = tmp; } devices[num_devices].path = path; devices[num_devices].capabilities = capabilities; num_devices++; return true; } /** * reset the array of scanned devices and free any dynamically allocated memory */ static void reset_scanned_devices(void) { if (!devices) { return; } for (size_t i = 0; i < num_devices; ++i) { free(devices[i].path); } free(devices); devices = NULL; num_devices = 0; } /** * Handle libinput touch / pointer events * @param state driver state to use * @param event libinput event */ static void read_pointer(libinput_drv_state_t *state, struct libinput_event *event) { struct libinput_event_touch *touch_event = NULL; struct libinput_event_pointer *pointer_event = NULL; enum libinput_event_type type = libinput_event_get_type(event); /* We need to read unrotated display dimensions directly from the driver because libinput won't account * for any rotation inside of LVGL */ lv_disp_drv_t *drv = lv_disp_get_default()->driver; switch (type) { case LIBINPUT_EVENT_TOUCH_MOTION: case LIBINPUT_EVENT_TOUCH_DOWN: touch_event = libinput_event_get_touch_event(event); lv_coord_t x_touch = libinput_event_touch_get_x_transformed(touch_event, drv->physical_hor_res > 0 ? drv->physical_hor_res : drv->hor_res) - drv->offset_x; lv_coord_t y_touch = libinput_event_touch_get_y_transformed(touch_event, drv->physical_ver_res > 0 ? drv->physical_ver_res : drv->ver_res) - drv->offset_y; if (x_touch < 0 || x_touch > drv->hor_res || y_touch < 0 || y_touch > drv->ver_res) { break; /* ignore touches that are out of bounds */ } state->most_recent_touch_point.x = x_touch; state->most_recent_touch_point.y = y_touch; state->button = LV_INDEV_STATE_PR; break; case LIBINPUT_EVENT_TOUCH_UP: state->button = LV_INDEV_STATE_REL; break; case LIBINPUT_EVENT_POINTER_MOTION: pointer_event = libinput_event_get_pointer_event(event); state->most_recent_touch_point.x += libinput_event_pointer_get_dx(pointer_event); state->most_recent_touch_point.y += libinput_event_pointer_get_dy(pointer_event); state->most_recent_touch_point.x = LV_CLAMP(0, state->most_recent_touch_point.x, drv->hor_res - 1); state->most_recent_touch_point.y = LV_CLAMP(0, state->most_recent_touch_point.y, drv->ver_res - 1); break; case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: pointer_event = libinput_event_get_pointer_event(event); lv_coord_t x_pointer = libinput_event_pointer_get_absolute_x_transformed(pointer_event, drv->physical_hor_res > 0 ? drv->physical_hor_res : drv->hor_res) - drv->offset_x; lv_coord_t y_pointer = libinput_event_pointer_get_absolute_y_transformed(pointer_event, drv->physical_ver_res > 0 ? drv->physical_ver_res : drv->ver_res) - drv->offset_y; if (x_pointer < 0 || x_pointer > drv->hor_res || y_pointer < 0 || y_pointer > drv->ver_res) { break; /* ignore pointer events that are out of bounds */ } state->most_recent_touch_point.x = x_pointer; state->most_recent_touch_point.y = y_pointer; break; case LIBINPUT_EVENT_POINTER_BUTTON: pointer_event = libinput_event_get_pointer_event(event); enum libinput_button_state button_state = libinput_event_pointer_get_button_state(pointer_event); state->button = button_state == LIBINPUT_BUTTON_STATE_RELEASED ? LV_INDEV_STATE_REL : LV_INDEV_STATE_PR; break; default: break; } } /** * Handle libinput keyboard events * @param state driver state to use * @param event libinput event */ static void read_keypad(libinput_drv_state_t *state, struct libinput_event *event) { struct libinput_event_keyboard *keyboard_event = NULL; enum libinput_event_type type = libinput_event_get_type(event); switch (type) { case LIBINPUT_EVENT_KEYBOARD_KEY: keyboard_event = libinput_event_get_keyboard_event(event); enum libinput_key_state key_state = libinput_event_keyboard_get_key_state(keyboard_event); uint32_t code = libinput_event_keyboard_get_key(keyboard_event); #if USE_XKB state->key_val = xkb_process_key_state(&(state->xkb_state), code, key_state == LIBINPUT_KEY_STATE_PRESSED); #else switch(code) { case KEY_BACKSPACE: state->key_val = LV_KEY_BACKSPACE; break; case KEY_ENTER: state->key_val = LV_KEY_ENTER; break; case KEY_PREVIOUS: state->key_val = LV_KEY_PREV; break; case KEY_NEXT: state->key_val = LV_KEY_NEXT; break; case KEY_UP: state->key_val = LV_KEY_UP; break; case KEY_LEFT: state->key_val = LV_KEY_LEFT; break; case KEY_RIGHT: state->key_val = LV_KEY_RIGHT; break; case KEY_DOWN: state->key_val = LV_KEY_DOWN; break; case KEY_TAB: state->key_val = LV_KEY_NEXT; break; default: state->key_val = 0; break; } #endif /* USE_XKB */ if (state->key_val != 0) { /* Only record button state when actual output is produced to prevent widgets from refreshing */ state->button = (key_state == LIBINPUT_KEY_STATE_RELEASED) ? LV_INDEV_STATE_REL : LV_INDEV_STATE_PR; } break; default: break; } } static int open_restricted(const char *path, int flags, void *user_data) { LV_UNUSED(user_data); int fd = open(path, flags); return fd < 0 ? -errno : fd; } static void close_restricted(int fd, void *user_data) { LV_UNUSED(user_data); close(fd); } #endif /* USE_LIBINPUT || USE_BSD_LIBINPUT */