From 64bd9053a25297f7a442ca831c7da5b44bd33f84 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Wed, 12 Jun 2024 17:54:40 +1000 Subject: Update LVGL to v9.1.0 --- lib/lvgl/src/drivers/libinput/lv_libinput.c | 670 ++++++++++++++++++++++++++++ lib/lvgl/src/drivers/libinput/lv_libinput.h | 134 ++++++ lib/lvgl/src/drivers/libinput/lv_xkb.c | 180 ++++++++ lib/lvgl/src/drivers/libinput/lv_xkb.h | 72 +++ 4 files changed, 1056 insertions(+) create mode 100644 lib/lvgl/src/drivers/libinput/lv_libinput.c create mode 100644 lib/lvgl/src/drivers/libinput/lv_libinput.h create mode 100644 lib/lvgl/src/drivers/libinput/lv_xkb.c create mode 100644 lib/lvgl/src/drivers/libinput/lv_xkb.h (limited to 'lib/lvgl/src/drivers/libinput') diff --git a/lib/lvgl/src/drivers/libinput/lv_libinput.c b/lib/lvgl/src/drivers/libinput/lv_libinput.c new file mode 100644 index 00000000..f207fa47 --- /dev/null +++ b/lib/lvgl/src/drivers/libinput/lv_libinput.c @@ -0,0 +1,670 @@ +/** + * @file lv_libinput.c + * + */ + +/********************* + * INCLUDES + *********************/ + +#include "lv_libinput.h" + +#if LV_USE_LIBINPUT + +#include "../../display/lv_display_private.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if LV_LIBINPUT_BSD + #include +#else + #include +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +struct lv_libinput_device { + lv_libinput_capability capabilities; + char * path; +}; + +/********************** + * STATIC PROTOTYPES + **********************/ + +static bool _rescan_devices(void); +static bool _add_scanned_device(char * path, lv_libinput_capability capabilities); +static void _reset_scanned_devices(void); + +static void * _poll_thread(void * data); + +lv_libinput_event_t * _get_event(lv_libinput_t * state); +bool _event_pending(lv_libinput_t * state); +lv_libinput_event_t * _create_event(lv_libinput_t * state); + +static void _read(lv_indev_t * indev, lv_indev_data_t * data); +static void _read_pointer(lv_libinput_t * state, struct libinput_event * event); +static void _read_keypad(lv_libinput_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 void _delete(lv_libinput_t * dsc); + +/********************** + * STATIC VARIABLES + **********************/ + +static struct lv_libinput_device * devices = NULL; +static size_t num_devices = 0; + +static const int timeout = 100; // ms +static const nfds_t nfds = 1; + +static const struct libinput_interface interface = { + .open_restricted = _open_restricted, + .close_restricted = _close_restricted, +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_libinput_capability lv_libinput_query_capability(struct libinput_device * device) +{ + lv_libinput_capability capability = LV_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 |= LV_LIBINPUT_CAPABILITY_KEYBOARD; + } + if(libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_POINTER)) { + capability |= LV_LIBINPUT_CAPABILITY_POINTER; + } + if(libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TOUCH)) { + capability |= LV_LIBINPUT_CAPABILITY_TOUCH; + } + return capability; +} + +char * lv_libinput_find_dev(lv_libinput_capability capabilities, bool force_rescan) +{ + char * path = NULL; + lv_libinput_find_devs(capabilities, &path, 1, force_rescan); + return path; +} + +size_t lv_libinput_find_devs(lv_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; +} + +lv_indev_t * lv_libinput_create(lv_indev_type_t indev_type, const char * dev_path) +{ + lv_libinput_t * dsc = lv_malloc_zeroed(sizeof(lv_libinput_t)); + LV_ASSERT_MALLOC(dsc); + if(dsc == NULL) return NULL; + + dsc->libinput_context = libinput_path_create_context(&interface, NULL); + if(!dsc->libinput_context) { + LV_LOG_ERROR("libinput_path_create_context failed: %s", strerror(errno)); + _delete(dsc); + return NULL; + } + + dsc->libinput_device = libinput_path_add_device(dsc->libinput_context, dev_path); + if(!dsc->libinput_device) { + _delete(dsc); + return NULL; + } + + dsc->libinput_device = libinput_device_ref(dsc->libinput_device); + if(!dsc->libinput_device) { + _delete(dsc); + return NULL; + } + + dsc->fd = libinput_get_fd(dsc->libinput_context); + + /* Prepare poll */ + dsc->fds[0].fd = dsc->fd; + dsc->fds[0].events = POLLIN; + dsc->fds[0].revents = 0; + +#if LV_LIBINPUT_XKB + struct xkb_rule_names names = LV_LIBINPUT_XKB_KEY_MAP; + lv_xkb_init(&(dsc->xkb), names); +#endif /* LV_LIBINPUT_XKB */ + + /* Create indev */ + lv_indev_t * indev = lv_indev_create(); + if(!indev) { + _delete(dsc); + return NULL; + } + lv_indev_set_type(indev, indev_type); + lv_indev_set_read_cb(indev, _read); + lv_indev_set_driver_data(indev, dsc); + + /* Set up thread & lock */ + pthread_mutex_init(&dsc->event_lock, NULL); + pthread_create(&dsc->worker_thread, NULL, _poll_thread, dsc); + + return indev; +} + +void lv_libinput_delete(lv_indev_t * indev) +{ + _delete(lv_indev_get_driver_data(indev)); + lv_indev_delete(indev); +} + +/********************** + * 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 */ + + lv_libinput_capability capabilities = lv_libinput_query_capability(device); + + libinput_path_remove_device(device); + + if(capabilities == LV_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, lv_libinput_capability capabilities) +{ + /* Double array size every 2^n elements */ + if((num_devices & (num_devices + 1)) == 0) { + struct lv_libinput_device * tmp = realloc(devices, (2 * num_devices + 1) * sizeof(struct lv_libinput_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; +} + +static void * _poll_thread(void * data) +{ + lv_libinput_t * dsc = (lv_libinput_t *)data; + struct libinput_event * event; + int rc = 0; + + LV_LOG_INFO("libinput: poll worker started"); + + while(true) { + rc = poll(dsc->fds, nfds, timeout); + switch(rc) { + case -1: + perror(NULL); + __attribute__((fallthrough)); + case 0: + if(dsc->deinit) { + dsc->deinit = false; /* Signal that we're done */ + return NULL; + } + continue; + default: + break; + } + libinput_dispatch(dsc->libinput_context); + pthread_mutex_lock(&dsc->event_lock); + while((event = libinput_get_event(dsc->libinput_context)) != NULL) { + _read_pointer(dsc, event); + _read_keypad(dsc, event); + libinput_event_destroy(event); + } + pthread_mutex_unlock(&dsc->event_lock); + LV_LOG_INFO("libinput: event read"); + } + + return NULL; +} + +lv_libinput_event_t * _get_event(lv_libinput_t * dsc) +{ + if(dsc->start == dsc->end) { + return NULL; + } + + lv_libinput_event_t * evt = &dsc->points[dsc->start]; + + if(++dsc->start == LV_LIBINPUT_MAX_EVENTS) + dsc->start = 0; + + return evt; +} + +bool _event_pending(lv_libinput_t * dsc) +{ + return dsc->start != dsc->end; +} + +lv_libinput_event_t * _create_event(lv_libinput_t * dsc) +{ + lv_libinput_event_t * evt = &dsc->points[dsc->end]; + + if(++dsc->end == LV_LIBINPUT_MAX_EVENTS) + dsc->end = 0; + + /* We have overflowed the buffer, start overwriting + * old events. + */ + if(dsc->end == dsc->start) { + LV_LOG_INFO("libinput: overflowed event buffer!"); + if(++dsc->start == LV_LIBINPUT_MAX_EVENTS) + dsc->start = 0; + } + + memset(evt, 0, sizeof(lv_libinput_event_t)); + + return evt; +} + +static void _read(lv_indev_t * indev, lv_indev_data_t * data) +{ + lv_libinput_t * dsc = lv_indev_get_driver_data(indev); + LV_ASSERT_NULL(dsc); + + pthread_mutex_lock(&dsc->event_lock); + + lv_libinput_event_t * evt = _get_event(dsc); + + if(!evt) + evt = &dsc->last_event; /* indev expects us to report the most recent state */ + + data->point = evt->point; + data->state = evt->pressed; + data->key = evt->key_val; + data->continue_reading = _event_pending(dsc); + + dsc->last_event = *evt; /* Remember the last event for the next call */ + + pthread_mutex_unlock(&dsc->event_lock); + + if(evt) + LV_LOG_TRACE("libinput_read: (%04d, %04d): %d continue_reading? %d", data->point.x, data->point.y, data->state, + data->continue_reading); +} + +static void _read_pointer(lv_libinput_t * dsc, struct libinput_event * event) +{ + struct libinput_event_touch * touch_event = NULL; + struct libinput_event_pointer * pointer_event = NULL; + lv_libinput_event_t * evt = NULL; + enum libinput_event_type type = libinput_event_get_type(event); + int slot = 0; + + switch(type) { + case LIBINPUT_EVENT_TOUCH_MOTION: + case LIBINPUT_EVENT_TOUCH_DOWN: + case LIBINPUT_EVENT_TOUCH_UP: + touch_event = libinput_event_get_touch_event(event); + break; + case LIBINPUT_EVENT_POINTER_MOTION: + case LIBINPUT_EVENT_POINTER_BUTTON: + case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: + pointer_event = libinput_event_get_pointer_event(event); + break; + default: + return; /* We don't care about this events */ + } + + /* We need to read unrotated display dimensions directly from the driver because libinput won't account + * for any rotation inside of LVGL */ + lv_display_t * disp = lv_display_get_default(); + + /* ignore more than 2 fingers as it will only confuse LVGL */ + if(touch_event && (slot = libinput_event_touch_get_slot(touch_event)) > 1) + return; + + evt = _create_event(dsc); + + const int32_t hor_res = disp->physical_hor_res > 0 ? disp->physical_hor_res : disp->hor_res; + const int32_t ver_res = disp->physical_ver_res > 0 ? disp->physical_ver_res : disp->ver_res; + + switch(type) { + case LIBINPUT_EVENT_TOUCH_MOTION: + case LIBINPUT_EVENT_TOUCH_DOWN: { + lv_point_t point; + point.x = (int32_t)LV_CLAMP(INT32_MIN, libinput_event_touch_get_x_transformed(touch_event, hor_res) - disp->offset_x, + INT32_MAX); + point.y = (int32_t)LV_CLAMP(INT32_MIN, libinput_event_touch_get_y_transformed(touch_event, ver_res) - disp->offset_y, + INT32_MAX); + if(point.x < 0 || point.x > disp->hor_res || point.y < 0 || point.y > disp->ver_res) { + break; /* ignore touches that are out of bounds */ + } + evt->point = point; + evt->pressed = LV_INDEV_STATE_PRESSED; + dsc->slots[slot].point = evt->point; + dsc->slots[slot].pressed = evt->pressed; + break; + } + case LIBINPUT_EVENT_TOUCH_UP: + /* + * We don't support "multitouch", but libinput does. To make fast typing with two thumbs + * on a keyboard feel good, it's necessary to handle two fingers individually. The edge + * case here is if you press a key with one finger and then press a second key with another + * finger. No matter which finger you release, it will count as the second finger releasing + * and ignore the first because LVGL only stores a single (the latest) pressed state. + * + * To work around this, we detect the case where one finger is released while the other is + * still pressed and insert dummy events so that both release events trigger at the correct + * position. + */ + if(slot == 0 && dsc->slots[1].pressed == LV_INDEV_STATE_PRESSED) { + /* The first finger is released while the second finger is still pressed. + * We turn P1 > P2 > R1 > R2 into P1 > P2 > (P1) > R1 > (P2) > R2. + */ + + /* Inject the dummy press event for the first finger */ + lv_libinput_event_t * synth_evt = evt; + synth_evt->pressed = LV_INDEV_STATE_PRESSED; + synth_evt->point = dsc->slots[0].point; + + /* Append the real release event for the first finger */ + evt = _create_event(dsc); + evt->pressed = LV_INDEV_STATE_RELEASED; + evt->point = dsc->slots[0].point; + + /* Inject the dummy press event for the second finger */ + synth_evt = _create_event(dsc); + synth_evt->pressed = LV_INDEV_STATE_PRESSED; + synth_evt->point = dsc->slots[1].point; + } + else if(slot == 1 && dsc->slots[0].pressed == LV_INDEV_STATE_PRESSED) { + /* The second finger is released while the first finger is still pressed. + * We turn P1 > P2 > R2 > R1 into P1 > P2 > R2 > (P1) > R1. + */ + + /* Append the real release event for the second finger */ + evt->pressed = LV_INDEV_STATE_RELEASED; + evt->point = dsc->slots[1].point; + + /* Inject the dummy press event for the first finger */ + lv_libinput_event_t * synth_evt = _create_event(dsc); + synth_evt->pressed = LV_INDEV_STATE_PRESSED; + synth_evt->point = dsc->slots[0].point; + } + else { + evt->pressed = LV_INDEV_STATE_RELEASED; + evt->point = dsc->slots[slot].point; + } + + dsc->slots[slot].pressed = evt->pressed; + break; + case LIBINPUT_EVENT_POINTER_MOTION: + dsc->pointer_position.x = (int32_t)LV_CLAMP(0, dsc->pointer_position.x + libinput_event_pointer_get_dx(pointer_event), + disp->hor_res - 1); + dsc->pointer_position.y = (int32_t)LV_CLAMP(0, dsc->pointer_position.y + libinput_event_pointer_get_dy(pointer_event), + disp->ver_res - 1); + evt->point.x = dsc->pointer_position.x; + evt->point.y = dsc->pointer_position.y; + evt->pressed = dsc->pointer_button_down; + break; + case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: { + lv_point_t point; + point.x = (int32_t)LV_CLAMP(INT32_MIN, libinput_event_pointer_get_absolute_x_transformed(pointer_event, + hor_res) - disp->offset_x, INT32_MAX); + point.y = (int32_t)LV_CLAMP(INT32_MIN, libinput_event_pointer_get_absolute_y_transformed(pointer_event, + ver_res) - disp->offset_y, INT32_MAX); + if(point.x < 0 || point.x > disp->hor_res || point.y < 0 || point.y > disp->ver_res) { + break; /* ignore pointer events that are out of bounds */ + } + evt->point = point; + evt->pressed = dsc->pointer_button_down; + break; + } + case LIBINPUT_EVENT_POINTER_BUTTON: { + enum libinput_button_state button_state = libinput_event_pointer_get_button_state(pointer_event); + dsc->pointer_button_down = button_state == LIBINPUT_BUTTON_STATE_RELEASED ? LV_INDEV_STATE_RELEASED : + LV_INDEV_STATE_PRESSED; + evt->point.x = dsc->pointer_position.x; + evt->point.y = dsc->pointer_position.y; + evt->pressed = dsc->pointer_button_down; + } + default: + break; + } +} + +static void _read_keypad(lv_libinput_t * dsc, struct libinput_event * event) +{ + struct libinput_event_keyboard * keyboard_event = NULL; + enum libinput_event_type type = libinput_event_get_type(event); + lv_libinput_event_t * evt = NULL; + switch(type) { + case LIBINPUT_EVENT_KEYBOARD_KEY: + evt = _create_event(dsc); + 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 LV_LIBINPUT_XKB + evt->key_val = lv_xkb_process_key(&(dsc->xkb), code, key_state == LIBINPUT_KEY_STATE_PRESSED); +#else + switch(code) { + case KEY_BACKSPACE: + evt->key_val = LV_KEY_BACKSPACE; + break; + case KEY_ENTER: + evt->key_val = LV_KEY_ENTER; + break; + case KEY_PREVIOUS: + evt->key_val = LV_KEY_PREV; + break; + case KEY_NEXT: + evt->key_val = LV_KEY_NEXT; + break; + case KEY_UP: + evt->key_val = LV_KEY_UP; + break; + case KEY_LEFT: + evt->key_val = LV_KEY_LEFT; + break; + case KEY_RIGHT: + evt->key_val = LV_KEY_RIGHT; + break; + case KEY_DOWN: + evt->key_val = LV_KEY_DOWN; + break; + case KEY_TAB: + evt->key_val = LV_KEY_NEXT; + break; + case KEY_HOME: + evt->key_val = LV_KEY_HOME; + break; + case KEY_END: + evt->key_val = LV_KEY_END; + break; + default: + evt->key_val = 0; + break; + } +#endif /* LV_LIBINPUT_XKB */ + if(evt->key_val != 0) { + /* Only record button state when actual output is produced to prevent widgets from refreshing */ + evt->pressed = (key_state == LIBINPUT_KEY_STATE_RELEASED) ? LV_INDEV_STATE_RELEASED : LV_INDEV_STATE_PRESSED; + + // just release the key immediatly after it got pressed. + // but don't handle special keys where holding a key makes sense + if(evt->key_val != LV_KEY_BACKSPACE && + evt->key_val != LV_KEY_UP && + evt->key_val != LV_KEY_LEFT && + evt->key_val != LV_KEY_RIGHT && + evt->key_val != LV_KEY_DOWN && + key_state == LIBINPUT_KEY_STATE_PRESSED) { + lv_libinput_event_t * release_evt = _create_event(dsc); + release_evt->pressed = LV_INDEV_STATE_RELEASED; + release_evt->key_val = evt->key_val; + } + } + 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); +} + +static void _delete(lv_libinput_t * dsc) +{ + if(dsc->fd) + dsc->deinit = true; + + /* Give worker thread a whole second to quit */ + for(int i = 0; i < 100; i++) { + if(!dsc->deinit) + break; + usleep(10000); + } + + if(dsc->deinit) { + LV_LOG_ERROR("libinput worker thread did not quit in time, cancelling it"); + pthread_cancel(dsc->worker_thread); + } + + if(dsc->libinput_device) { + libinput_path_remove_device(dsc->libinput_device); + libinput_device_unref(dsc->libinput_device); + } + + if(dsc->libinput_context) { + libinput_unref(dsc->libinput_context); + } + +#if LV_LIBINPUT_XKB + lv_xkb_deinit(&(dsc->xkb)); +#endif /* LV_LIBINPUT_XKB */ + + lv_free(dsc); +} + +#endif /* LV_USE_LIBINPUT */ diff --git a/lib/lvgl/src/drivers/libinput/lv_libinput.h b/lib/lvgl/src/drivers/libinput/lv_libinput.h new file mode 100644 index 00000000..4ec1160a --- /dev/null +++ b/lib/lvgl/src/drivers/libinput/lv_libinput.h @@ -0,0 +1,134 @@ +/** + * @file lv_libinput.h + * + */ + +#ifndef LV_LIBINPUT_H +#define LV_LIBINPUT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +#include "../../indev/lv_indev.h" + +#if LV_USE_LIBINPUT + +#include +#include + +#if LV_LIBINPUT_XKB +#include "lv_xkb.h" +#endif /* LV_LIBINPUT_XKB */ + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +typedef enum { + LV_LIBINPUT_CAPABILITY_NONE = 0, + LV_LIBINPUT_CAPABILITY_KEYBOARD = 1U << 0, + LV_LIBINPUT_CAPABILITY_POINTER = 1U << 1, + LV_LIBINPUT_CAPABILITY_TOUCH = 1U << 2 +} lv_libinput_capability; + +typedef struct { + lv_indev_state_t pressed; + int key_val; + lv_point_t point; +} lv_libinput_event_t; + +#define LV_LIBINPUT_MAX_EVENTS 32 + +typedef struct { + int fd; + struct pollfd fds[1]; + + /* The points array is implemented as a circular LIFO queue */ + lv_libinput_event_t points[LV_LIBINPUT_MAX_EVENTS]; /* Event buffer */ + lv_libinput_event_t slots[2]; /* Realtime state of up to 2 fingers to handle multitouch */ + + /* Pointer devices work a bit differently in libinput which requires us to store their last known state */ + lv_point_t pointer_position; + bool pointer_button_down; + + int start; /* Index of start of event queue */ + int end; /* Index of end of queue*/ + lv_libinput_event_t last_event; /* Report when no new events + * to keep indev state consistent + */ + bool deinit; /* Tell worker thread to quit */ + pthread_mutex_t event_lock; + pthread_t worker_thread; + + struct libinput * libinput_context; + struct libinput_device * libinput_device; + +#if LV_LIBINPUT_XKB + lv_xkb_t xkb; +#endif /* LV_LIBINPUT_XKB */ +} lv_libinput_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Determine the capabilities of a specific libinput device. + * @param device the libinput device to query + * @return the supported input capabilities + */ +lv_libinput_capability lv_libinput_query_capability(struct libinput_device * device); + +/** + * 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 * lv_libinput_find_dev(lv_libinput_capability capabilities, bool force_rescan); + +/** + * 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 lv_libinput_find_devs(lv_libinput_capability capabilities, char ** found, size_t count, bool force_rescan); + +/** + * Create a new libinput input device + * @param type LV_INDEV_TYPE_POINTER or LV_INDEV_TYPE_KEYPAD + * @param dev_path device path, e.g. /dev/input/event0 + * @return pointer to input device or NULL if opening failed + */ +lv_indev_t * lv_libinput_create(lv_indev_type_t indev_type, const char * dev_path); + +/** + * Delete a libinput input devic + * @param indev pointer to input device + */ +void lv_libinput_delete(lv_indev_t * indev); + +/********************** + * MACROS + **********************/ + +#endif /* LV_USE_LIBINPUT */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* LV_LIBINPUT_H */ diff --git a/lib/lvgl/src/drivers/libinput/lv_xkb.c b/lib/lvgl/src/drivers/libinput/lv_xkb.c new file mode 100644 index 00000000..96aeb4c0 --- /dev/null +++ b/lib/lvgl/src/drivers/libinput/lv_xkb.c @@ -0,0 +1,180 @@ +/** + * @file lv_xkb.c + * + */ + +/********************* + * INCLUDES + *********************/ + +#include "lv_xkb.h" + +#if defined(LV_LIBINPUT_XKB) && LV_LIBINPUT_XKB + +#include "../../core/lv_group.h" +#include "../../misc/lv_log.h" + +#include +#include +#include +#include +#include + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +static bool _set_keymap(lv_xkb_t * dsc, struct xkb_rule_names names); + +/********************** + * STATIC VARIABLES + **********************/ + +static struct xkb_context * context = NULL; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +bool lv_xkb_init(lv_xkb_t * dsc, struct xkb_rule_names names) +{ + if(!context) { + context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if(!context) { + LV_LOG_ERROR("xkb_context_new failed: %s", strerror(errno)); + return false; + } + } + + return _set_keymap(dsc, names); +} + +void lv_xkb_deinit(lv_xkb_t * dsc) +{ + if(dsc->state) { + xkb_state_unref(dsc->state); + dsc->state = NULL; + } + + if(dsc->keymap) { + xkb_keymap_unref(dsc->keymap); + dsc->keymap = NULL; + } +} + +uint32_t lv_xkb_process_key(lv_xkb_t * dsc, 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(dsc->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; + case XKB_KEY_Home: + case XKB_KEY_KP_Home: + result = LV_KEY_HOME; + break; + case XKB_KEY_End: + case XKB_KEY_KP_End: + result = LV_KEY_END; + break; + default: + break; + } + + if(result == 0) { + char buffer[4] = { 0, 0, 0, 0 }; + int size = xkb_state_key_get_utf8(dsc->state, keycode, NULL, 0) + 1; + if(size > 1) { + xkb_state_key_get_utf8(dsc->state, keycode, buffer, size); + memcpy(&result, buffer, 4); + } + } + + xkb_state_update_key(dsc->state, keycode, down ? XKB_KEY_DOWN : XKB_KEY_UP); + + return result; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static bool _set_keymap(lv_xkb_t * dsc, struct xkb_rule_names names) +{ + if(dsc->keymap) { + xkb_keymap_unref(dsc->keymap); + dsc->keymap = NULL; + } + + dsc->keymap = xkb_keymap_new_from_names(context, &names, XKB_KEYMAP_COMPILE_NO_FLAGS); + if(!dsc->keymap) { + LV_LOG_ERROR("xkb_keymap_new_from_names failed: %s", strerror(errno)); + return false; + } + + if(dsc->state) { + xkb_state_unref(dsc->state); + dsc->state = NULL; + } + + dsc->state = xkb_state_new(dsc->keymap); + if(!dsc->state) { + LV_LOG_ERROR("xkb_state_new failed: %s", strerror(errno)); + return false; + } + + return true; +} + +#endif /* defined(LV_LIBINPUT_XKB) && LV_LIBINPUT_XKB */ diff --git a/lib/lvgl/src/drivers/libinput/lv_xkb.h b/lib/lvgl/src/drivers/libinput/lv_xkb.h new file mode 100644 index 00000000..d048f33c --- /dev/null +++ b/lib/lvgl/src/drivers/libinput/lv_xkb.h @@ -0,0 +1,72 @@ +/** + * @file lv_xkb.h + * + */ + +#ifndef LV_XKB_H +#define LV_XKB_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +#include "../../lv_conf_internal.h" + +#if defined(LV_LIBINPUT_XKB) && LV_LIBINPUT_XKB + +#include +#include + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + struct xkb_keymap * keymap; + struct xkb_state * state; +} lv_xkb_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Initialise an XKB descriptor. + * @return true if the initialisation was successful + */ +bool lv_xkb_init(lv_xkb_t * dsc, struct xkb_rule_names names); + +/** + * De-initialise an XKB descriptor. + * @param dsc Pointer to descriptor + */ +void lv_xkb_deinit(lv_xkb_t * dsc); + +/** + * Process an evdev scancode using a specific XKB descriptor. + * @param state XKB descriptor 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 lv_xkb_process_key(lv_xkb_t * dsc, uint32_t scancode, bool down); + +/********************** + * MACROS + **********************/ + +#endif /* LV_LIBINPUT_XKB */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* defined(LV_LIBINPUT_XKB) && LV_LIBINPUT_XKB */ -- cgit v1.2.3