From 3dbb103c424cb72c4d00de1e3bc94dd1eaeceebd Mon Sep 17 00:00:00 2001 From: Niklas Fiekas Date: Fri, 20 Oct 2023 23:57:11 +0200 Subject: [PATCH] feat(dev): port evdev input driver (#4672) --- Kconfig | 4 + lv_conf_template.h | 3 + lvgl.h | 2 + src/dev/evdev/lv_evdev.c | 224 +++++++++++++++++++++++++++++++++++++++ src/dev/evdev/lv_evdev.h | 64 +++++++++++ src/lv_conf_internal.h | 9 ++ 6 files changed, 306 insertions(+) create mode 100644 src/dev/evdev/lv_evdev.c create mode 100644 src/dev/evdev/lv_evdev.h diff --git a/Kconfig b/Kconfig index 47357cb18..4371b8df3 100644 --- a/Kconfig +++ b/Kconfig @@ -1319,6 +1319,10 @@ menu "LVGL configuration" config LV_USE_TFT_ESPI bool "Use TFT_eSPI driver" default n + + config LV_USE_EVDEV + bool "Use evdev input driver" + default n endmenu menu "Examples" diff --git a/lv_conf_template.h b/lv_conf_template.h index f50ed5860..178786a3f 100644 --- a/lv_conf_template.h +++ b/lv_conf_template.h @@ -782,6 +782,9 @@ /*Interface for TFT_eSPI*/ #define LV_USE_TFT_ESPI 0 +/*Driver for evdev input devices*/ +#define LV_USE_EVDEV 0 + /*================== * EXAMPLES *==================*/ diff --git a/lvgl.h b/lvgl.h index 9a35a7787..094f7a259 100644 --- a/lvgl.h +++ b/lvgl.h @@ -123,6 +123,8 @@ extern "C" { #include "src/dev/nuttx/lv_nuttx_touchscreen.h" #include "src/dev/nuttx/lv_nuttx_lcd.h" +#include "src/dev/evdev/lv_evdev.h" + #include "src/core/lv_global.h" /********************* * DEFINES diff --git a/src/dev/evdev/lv_evdev.c b/src/dev/evdev/lv_evdev.c new file mode 100644 index 000000000..069b4ea6b --- /dev/null +++ b/src/dev/evdev/lv_evdev.c @@ -0,0 +1,224 @@ +/** + * @file lv_evdev.c + * + */ + +/********************** + * INCLUDES + **********************/ +#include "lv_evdev.h" +#if LV_USE_EVDEV + +#include +#include +#include +#include /*To detect BSD*/ +#ifdef BSD + #include +#else + #include +#endif /*BSD*/ +#include "../../misc/lv_assert.h" +#include "../../misc/lv_math.h" +#include "../../stdlib/lv_mem.h" +#include "../../stdlib/lv_string.h" +#include "../../display/lv_display.h" + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + /*Device*/ + int fd; + /*Config*/ + bool swap_axes; + int min_x; + int min_y; + int max_x; + int max_y; + /*State*/ + int root_x; + int root_y; + int key; + lv_indev_state_t state; +} lv_evdev_t; + +/********************** + * STATIC FUNCTIONS + **********************/ + +static int _evdev_process_key(uint16_t code) +{ + switch(code) { + case KEY_UP: + return LV_KEY_UP; + case KEY_DOWN: + return LV_KEY_DOWN; + case KEY_RIGHT: + return LV_KEY_RIGHT; + case KEY_LEFT: + return LV_KEY_LEFT; + case KEY_ESC: + return LV_KEY_ESC; + case KEY_DELETE: + return LV_KEY_DEL; + case KEY_BACKSPACE: + return LV_KEY_BACKSPACE; + case KEY_ENTER: + return LV_KEY_ENTER; + case KEY_NEXT: + case KEY_TAB: + return LV_KEY_NEXT; + case KEY_PREVIOUS: + return LV_KEY_PREV; + case KEY_HOME: + return LV_KEY_HOME; + case KEY_END: + return LV_KEY_END; + default: + return 0; + } +} + +static int _evdev_calibrate(int v, int in_min, int in_max, int out_min, int out_max) +{ + if(in_min != in_max) v = (v - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; + return LV_CLAMP(out_min, v, out_max); +} + +static lv_point_t _evdev_process_pointer(lv_indev_t * indev, int x, int y) +{ + lv_display_t * disp = lv_indev_get_disp(indev); + lv_evdev_t * dsc = lv_indev_get_driver_data(indev); + LV_ASSERT_NULL(dsc); + + int swapped_x = dsc->swap_axes ? y : x; + int swapped_y = dsc->swap_axes ? x : y; + + int offset_x = lv_display_get_offset_x(disp); + int offset_y = lv_display_get_offset_y(disp); + int width = lv_display_get_horizontal_resolution(disp); + int height = lv_display_get_vertical_resolution(disp); + + lv_point_t p; + p.x = _evdev_calibrate(swapped_x, dsc->min_x, dsc->max_x, offset_x, offset_x + width - 1); + p.y = _evdev_calibrate(swapped_y, dsc->min_y, dsc->max_y, offset_y, offset_y + height - 1); + return p; +} + +static void _evdev_read(lv_indev_t * indev, lv_indev_data_t * data) +{ + lv_evdev_t * dsc = lv_indev_get_driver_data(indev); + LV_ASSERT_NULL(dsc); + + /*Update dsc with buffered events*/ + struct input_event in = { 0 }; + while(read(dsc->fd, &in, sizeof(in)) > 0) { + if(in.type == EV_REL) { + if(in.code == REL_X) dsc->root_x += in.value; + else if(in.code == REL_Y) dsc->root_y += in.value; + } + else if(in.type == EV_ABS) { + if(in.code == ABS_X || in.code == ABS_MT_POSITION_X) dsc->root_x = in.value; + else if(in.code == ABS_Y || in.code == ABS_MT_POSITION_Y) dsc->root_y = in.value; + else if(in.code == ABS_MT_TRACKING_ID) { + if(in.value == -1) dsc->state = LV_INDEV_STATE_RELEASED; + else if(in.value == 0) dsc->state = LV_INDEV_STATE_PRESSED; + } + } + else if(in.type == EV_KEY) { + if(in.code == BTN_MOUSE || in.code == BTN_TOUCH) { + if(in.value == 0) dsc->state = LV_INDEV_STATE_RELEASED; + else if(in.value == 1) dsc->state = LV_INDEV_STATE_PRESSED; + } + else { + dsc->key = _evdev_process_key(in.code); + if(dsc->key) { + dsc->state = in.value ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED; + data->continue_reading = true; /*Keep following events in buffer for now*/ + break; + } + } + } + } + + /*Process and store in data*/ + switch(lv_indev_get_type(indev)) { + case LV_INDEV_TYPE_KEYPAD: + data->state = dsc->state; + data->key = dsc->key; + break; + case LV_INDEV_TYPE_POINTER: + data->state = dsc->state; + data->point = _evdev_process_pointer(indev, dsc->root_x, dsc->root_y); + break; + default: + break; + } +} + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_indev_t * lv_evdev_create(lv_indev_type_t indev_type, const char * dev_path) +{ + lv_evdev_t * dsc = lv_malloc(sizeof(lv_evdev_t)); + LV_ASSERT_MALLOC(dsc); + if(dsc == NULL) return NULL; + lv_memzero(dsc, sizeof(lv_evdev_t)); + + dsc->fd = open(dev_path, O_RDONLY | O_NOCTTY | O_CLOEXEC); + if(dsc->fd < 0) { + LV_LOG_ERROR("open failed: %s", strerror(errno)); + goto err_after_malloc; + } + + if(fcntl(dsc->fd, F_SETFL, O_NONBLOCK) < 0) { + LV_LOG_ERROR("fcntl failed: %s", strerror(errno)); + goto err_after_open; + } + + lv_indev_t * indev = lv_indev_create(); + if(indev == NULL) goto err_after_open; + lv_indev_set_type(indev, indev_type); + lv_indev_set_read_cb(indev, _evdev_read); + lv_indev_set_driver_data(indev, dsc); + return indev; + +err_after_open: + close(dsc->fd); +err_after_malloc: + lv_free(dsc); + return NULL; +} + +void lv_evdev_set_swap_axes(lv_indev_t * indev, bool swap_axes) +{ + lv_evdev_t * dsc = lv_indev_get_driver_data(indev); + LV_ASSERT_NULL(dsc); + dsc->swap_axes = swap_axes; +} + +void lv_evdev_set_calibration(lv_indev_t * indev, int min_x, int min_y, int max_x, int max_y) +{ + lv_evdev_t * dsc = lv_indev_get_driver_data(indev); + LV_ASSERT_NULL(dsc); + dsc->min_x = min_x; + dsc->min_y = min_y; + dsc->max_x = max_x; + dsc->max_y = max_y; +} + +void lv_evdev_delete(lv_indev_t * indev) +{ + lv_evdev_t * dsc = lv_indev_get_driver_data(indev); + LV_ASSERT_NULL(dsc); + close(dsc->fd); + lv_free(dsc); + + lv_indev_delete(indev); +} + +#endif /*LV_USE_EVDEV*/ diff --git a/src/dev/evdev/lv_evdev.h b/src/dev/evdev/lv_evdev.h new file mode 100644 index 000000000..95ab1544c --- /dev/null +++ b/src/dev/evdev/lv_evdev.h @@ -0,0 +1,64 @@ +/** + * @file lv_evdev.h + * + */ + +#ifndef LV_EVDEV_H +#define LV_EVDEV_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +#include "../../indev/lv_indev.h" + +#if LV_USE_EVDEV + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create evdev 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_evdev_create(lv_indev_type_t indev_type, const char * dev_path); + +/** + * Set whether coordinates of pointer device should be swapped. Defaults to + * false. + * @param indev evdev input device + * @param swap_axes whether to swap x and y axes + */ +void lv_evdev_set_swap_axes(lv_indev_t * indev, bool swap_axes); + +/** + * Configure a coordinate transformation for pointer devices. Applied after + * axis swap, if any. Defaults to apply no transformation. + * @param indev evdev input device + * @param min_x pointer coordinate mapped to min x of display + * @param min_y pointer coordinate mapped to min y of display + * @param max_x pointer coordinate mapped to max x of display + * @param max_y pointer coordinate mapped to max y of display + */ +void lv_evdev_set_calibration(lv_indev_t * indev, int min_x, int min_y, int max_x, int max_y); + +/** + * Remove evdev input device. + * @param indev evdev input device to close and free + */ +void lv_evdev_delete(lv_indev_t * indev); + +#endif /*LV_USE_EVDEV*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_EVDEV_H*/ diff --git a/src/lv_conf_internal.h b/src/lv_conf_internal.h index 922c79a36..36e7f45d4 100644 --- a/src/lv_conf_internal.h +++ b/src/lv_conf_internal.h @@ -2560,6 +2560,15 @@ #endif #endif +/*Driver for evdev input devices*/ +#ifndef LV_USE_EVDEV + #ifdef CONFIG_LV_USE_EVDEV + #define LV_USE_EVDEV CONFIG_LV_USE_EVDEV + #else + #define LV_USE_EVDEV 0 + #endif +#endif + /*================== * EXAMPLES *==================*/