/** * @file lv_libinput.c * */ /********************* * INCLUDES *********************/ #include "../../indev/lv_indev_private.h" #include "lv_libinput_private.h" #if LV_USE_LIBINPUT #include "../../display/lv_display_private.h" #include #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; case KEY_ESC: evt->key_val = LV_KEY_ESC; 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 immediately 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 */