summaryrefslogtreecommitdiff
path: root/lib/lvgl/src/drivers/libinput/lv_libinput.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/lvgl/src/drivers/libinput/lv_libinput.c')
-rw-r--r--lib/lvgl/src/drivers/libinput/lv_libinput.c670
1 files changed, 670 insertions, 0 deletions
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 <stdio.h>
+#include <unistd.h>
+#include <linux/limits.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <dirent.h>
+#include <libinput.h>
+#include <pthread.h>
+
+#if LV_LIBINPUT_BSD
+ #include <dev/evdev/input.h>
+#else
+ #include <linux/input.h>
+#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 */