1
0
mirror of https://github.com/lvgl/lvgl.git synced 2025-01-28 07:03:00 +08:00

feat(drivers): add libinput/xkb driver (#5486)

This commit is contained in:
Johannes Marbach 2024-01-29 13:14:37 +01:00 committed by GitHub
parent c835e888b1
commit b45ef5ccf9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 1210 additions and 2 deletions

View File

@ -95,7 +95,7 @@ jobs:
install: |
apt-get update -y
apt-get install build-essential ccache libgcc-10-dev python3 libpng-dev ruby-full gcovr cmake libjpeg62-turbo-dev libfreetype6-dev libasan6 pngquant python3-pip -q -y
apt-get install build-essential ccache libgcc-10-dev python3 libpng-dev ruby-full gcovr cmake libjpeg62-turbo-dev libfreetype6-dev libasan6 pngquant python3-pip libinput-dev libxkbcommon-dev pkg-config -q -y
pip install pypng lz4
/usr/sbin/update-ccache-symlinks
echo 'export PATH="/usr/lib/ccache:$PATH"' | tee -a ~/.bashrc

14
Kconfig
View File

@ -1489,6 +1489,20 @@ menu "LVGL configuration"
bool "Use evdev input driver"
default n
config LV_USE_LIBINPUT
bool "Use libinput input driver"
default n
config LV_LIBINPUT_BSD
bool "Use the BSD variant of the libinput input driver"
depends on LV_USE_LIBINPUT
default n
config LV_LIBINPUT_XKB
bool "Enable full keyboard support via XKB"
depends on LV_USE_LIBINPUT
default n
config LV_USE_ST7735
bool "Use ST7735 LCD driver"
default n

View File

@ -7,5 +7,6 @@ Drivers
display/index
touchpad/index
libinput
X11
windows

View File

@ -0,0 +1,87 @@
===============
Libinput Driver
===============
Overview
--------
Libinput is an input stack for processes that need to provide events from commonly used input devices. That includes mice, keyboards, touchpads,
touchscreens and graphics tablets. Libinput handles device-specific quirks and provides an easy-to-use API to receive events from devices.
Prerequisites
-------------
You have the development version of libinput installed (usually ``libinput-dev``). If your input device requires quirks, make sure they are
installed as well (usually in ``/usr/share/libinput/*.quirks``). To test if your device is set up correctly for use with libinput, you can
run ``libinput list-devices``.
.. code:: console
$ sudo libinput list-devices
...
Device: ETPS/2 Elantech Touchpad
Kernel: /dev/input/event5
Group: 10
Seat: seat0, default
Size: 102x74mm
Capabilities: pointer gesture
Tap-to-click: disabled
Tap-and-drag: enabled
...
If your device doesn't show up, you may have to configure udev and the appropriate udev rules to connect it.
Additionally, if you want full keyboard support, including letters and modifiers, you'll need the development version of libxkbcommon
installed (usually ``libxkbcommon-dev``).
Configuring the driver
----------------------
Enable the libinput driver support in lv_conf.h, by cmake compiler define or by KConfig.
.. code:: c
#define LV_USE_LIBINPUT 1
Full keyboard support needs to be enabled separately.
.. code:: c
#define LV_LIBINPUT_XKB 1
#define LV_LIBINPUT_XKB_KEY_MAP { .rules = NULL, .model = "pc101", .layout = "us", .variant = NULL, .options = NULL }
To find the right key map values, you may use the ``setxkbmap -query`` command.
Usage
-----
To set up an input device via the libinput driver, all you need to do is call ``lv_libinput_create`` with the respective device type
(``LV_INDEV_TYPE_POINTER`` or ``LV_INDEV_TYPE_KEYPAD``) and device node path (e.g. ``/dev/input/event5``).
.. code:: c
lv_indev_t *indev = lv_libinput_create(LV_INDEV_TYPE_POINTER, "/dev/input/event5");
Note that touchscreens are treated as (absolute) pointer devices by the libinput driver and require ``LV_INDEV_TYPE_POINTER``.
Depending on your system, the device node paths might not be stable across reboots. If this is the case, you can use ``lv_libinput_find_dev``
to find the first device that has a specific capability.
.. code:: c
char *path = lv_libinput_find_dev(LV_LIBINPUT_CAPABILITY_TOUCH, true);
The second argument controls whether or not all devices are rescanned. If you have many devices connected this can get quite slow.
Therefore, you should only specify ``true`` on the first call when calling this method multiple times in a row. If you want to find
all devices that have a specific capability, use ``lv_libinput_find_devs``.
If you want to connect a keyboard device to a textarea, create a dedicated input group and set it on both the indev and textarea.
.. code:: c
lv_obj_t *textarea = lv_textarea_create(...);
...
lv_group_t *keyboard_input_group = lv_group_create();
lv_indev_set_group(indev, keyboard_input_group);
lv_group_add_obj(keyboard_input_group, textarea);

View File

@ -886,6 +886,20 @@
/*Driver for evdev input devices*/
#define LV_USE_EVDEV 0
/*Driver for libinput input devices*/
#define LV_USE_LIBINPUT 0
#if LV_USE_LIBINPUT
#define LV_LIBINPUT_BSD 0
/*Full keyboard support*/
#define LV_LIBINPUT_XKB 0
#if LV_LIBINPUT_XKB
/*"setxkbmap -query" can help find the right values for your keyboard*/
#define LV_LIBINPUT_XKB_KEY_MAP { .rules = NULL, .model = "pc101", .layout = "us", .variant = NULL, .options = NULL }
#endif
#endif
/*Drivers for LCD devices connected via SPI/parallel port*/
#define LV_USE_ST7735 0
#define LV_USE_ST7789 0

View File

@ -6,5 +6,5 @@
#
# Note: This script is run by the CI workflows.
sudo apt update
sudo apt install gcc python3 libpng-dev ruby-full gcovr cmake libjpeg-turbo8-dev libfreetype6-dev pngquant
sudo apt install gcc python3 libpng-dev ruby-full gcovr cmake libjpeg-turbo8-dev libfreetype6-dev pngquant libinput-dev libxkbcommon-dev pkg-config
pip3 install pypng lz4

View File

@ -0,0 +1,664 @@
/**
* @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;
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 */

View File

@ -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 <poll.h>
#include <pthread.h>
#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 */

View File

@ -0,0 +1,172 @@
/**
* @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 <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*********************
* 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;
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 */

View File

@ -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 <stdbool.h>
#include <xkbcommon/xkbcommon.h>
/*********************
* 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 */

View File

@ -32,6 +32,7 @@ extern "C" {
#include "nuttx/lv_nuttx_libuv.h"
#include "evdev/lv_evdev.h"
#include "libinput/lv_libinput.h"
#include "windows/lv_windows_input.h"
#include "windows/lv_windows_display.h"

View File

@ -2906,6 +2906,44 @@
#endif
#endif
/*Driver for libinput input devices*/
#ifndef LV_USE_LIBINPUT
#ifdef CONFIG_LV_USE_LIBINPUT
#define LV_USE_LIBINPUT CONFIG_LV_USE_LIBINPUT
#else
#define LV_USE_LIBINPUT 0
#endif
#endif
#if LV_USE_LIBINPUT
#ifndef LV_LIBINPUT_BSD
#ifdef CONFIG_LV_LIBINPUT_BSD
#define LV_LIBINPUT_BSD CONFIG_LV_LIBINPUT_BSD
#else
#define LV_LIBINPUT_BSD 0
#endif
#endif
/*Full keyboard support*/
#ifndef LV_LIBINPUT_XKB
#ifdef CONFIG_LV_LIBINPUT_XKB
#define LV_LIBINPUT_XKB CONFIG_LV_LIBINPUT_XKB
#else
#define LV_LIBINPUT_XKB 0
#endif
#endif
#if LV_LIBINPUT_XKB
/*"setxkbmap -query" can help find the right values for your keyboard*/
#ifndef LV_LIBINPUT_XKB_KEY_MAP
#ifdef CONFIG_LV_LIBINPUT_XKB_KEY_MAP
#define LV_LIBINPUT_XKB_KEY_MAP CONFIG_LV_LIBINPUT_XKB_KEY_MAP
#else
#define LV_LIBINPUT_XKB_KEY_MAP { .rules = NULL, .model = "pc101", .layout = "us", .variant = NULL, .options = NULL }
#endif
#endif
#endif
#endif
/*Drivers for LCD devices connected via SPI/parallel port*/
#ifndef LV_USE_ST7735
#ifdef CONFIG_LV_USE_ST7735

View File

@ -253,6 +253,14 @@ include_directories(${PNG_INCLUDE_DIR})
find_package(Freetype REQUIRED)
include_directories(${FREETYPE_INCLUDE_DIRS})
# libinput is required for the libinput device driver test case
find_package(Libinput REQUIRED)
include_directories(${LIBINPUT_INCLUDE_DIRS})
# libxkbcommon is required for the libinput device driver test case
find_package(PkgConfig)
pkg_check_modules(xkbcommon REQUIRED xkbcommon)
# disable test targets for build only tests
if (ENABLE_TESTS)
file( GLOB_RECURSE TEST_CASE_FILES src/test_cases/*.c )
@ -288,6 +296,7 @@ foreach( test_case_fname ${TEST_CASE_FILES} )
lvgl_thorvg
${PNG_LIBRARIES}
${FREETYPE_LIBRARIES}
${LIBINPUT_LIBRARIES}
${JPEG_LIBRARIES}
m
${TEST_LIBS})

View File

@ -106,6 +106,8 @@
#define LV_USE_ST7735 1
#define LV_USE_ST7789 1
#define LV_USE_ST7796 1
#define LV_USE_LIBINPUT 1
#define LV_LIBINPUT_XKB 1
#define LV_USE_FREETYPE 1
#define LV_FREETYPE_CACHE_SIZE 768