You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
535 lines
17 KiB
C
535 lines
17 KiB
C
2 years ago
|
/**
|
||
|
* @file libinput.c
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
/*********************
|
||
|
* INCLUDES
|
||
|
*********************/
|
||
|
#include "libinput_drv.h"
|
||
|
#if USE_LIBINPUT || USE_BSD_LIBINPUT
|
||
|
|
||
|
#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>
|
||
|
|
||
|
#if USE_BSD_LIBINPUT
|
||
|
#include <dev/evdev/input.h>
|
||
|
#else
|
||
|
#include <linux/input.h>
|
||
|
#endif
|
||
|
|
||
|
/*********************
|
||
|
* DEFINES
|
||
|
*********************/
|
||
|
|
||
|
/**********************
|
||
|
* TYPEDEFS
|
||
|
**********************/
|
||
|
struct input_device {
|
||
|
libinput_capability capabilities;
|
||
|
char *path;
|
||
|
};
|
||
|
|
||
|
/**********************
|
||
|
* STATIC PROTOTYPES
|
||
|
**********************/
|
||
|
static bool rescan_devices(void);
|
||
|
static bool add_scanned_device(char *path, libinput_capability capabilities);
|
||
|
static void reset_scanned_devices(void);
|
||
|
|
||
|
static void read_pointer(libinput_drv_state_t *state, struct libinput_event *event);
|
||
|
static void read_keypad(libinput_drv_state_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 VARIABLES
|
||
|
**********************/
|
||
|
static struct input_device *devices = NULL;
|
||
|
static size_t num_devices = 0;
|
||
|
|
||
|
static libinput_drv_state_t default_state = { .most_recent_touch_point = { .x = 0, .y = 0 } };
|
||
|
|
||
|
static const int timeout = 0; // do not block
|
||
|
static const nfds_t nfds = 1;
|
||
|
|
||
|
static const struct libinput_interface interface = {
|
||
|
.open_restricted = open_restricted,
|
||
|
.close_restricted = close_restricted,
|
||
|
};
|
||
|
|
||
|
/**********************
|
||
|
* MACROS
|
||
|
**********************/
|
||
|
|
||
|
/**********************
|
||
|
* GLOBAL FUNCTIONS
|
||
|
**********************/
|
||
|
|
||
|
/**
|
||
9 months ago
|
* Determine the capabilities of a specific libinput device.
|
||
|
* @param device the libinput device to query
|
||
|
* @return the supported input capabilities
|
||
|
*/
|
||
|
libinput_capability libinput_query_capability(struct libinput_device *device) {
|
||
|
libinput_capability capability = 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 |= LIBINPUT_CAPABILITY_KEYBOARD;
|
||
|
}
|
||
|
if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_POINTER)) {
|
||
|
capability |= LIBINPUT_CAPABILITY_POINTER;
|
||
|
}
|
||
|
if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TOUCH)) {
|
||
|
capability |= LIBINPUT_CAPABILITY_TOUCH;
|
||
|
}
|
||
|
return capability;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Find connected input device with specific capabilities
|
||
2 years ago
|
* @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 *libinput_find_dev(libinput_capability capabilities, bool force_rescan) {
|
||
|
char *path = NULL;
|
||
|
libinput_find_devs(capabilities, &path, 1, force_rescan);
|
||
|
return path;
|
||
|
}
|
||
|
|
||
|
/**
|
||
9 months ago
|
* Find connected input devices with specific capabilities
|
||
2 years ago
|
* @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 libinput_find_devs(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;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Reconfigure the device file for libinput using the default driver state. Use this function if you only want
|
||
|
* to connect a single device.
|
||
|
* @param dev_name input device node path (e.g. /dev/input/event0)
|
||
|
* @return true: the device file set complete
|
||
|
* false: the device file doesn't exist current system
|
||
|
*/
|
||
|
bool libinput_set_file(char* dev_name)
|
||
|
{
|
||
|
return libinput_set_file_state(&default_state, dev_name);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Reconfigure the device file for libinput using a specific driver state. Use this function if you want to
|
||
|
* connect multiple devices.
|
||
|
* @param state the driver state to configure
|
||
|
* @param dev_name input device node path (e.g. /dev/input/event0)
|
||
|
* @return true: the device file set complete
|
||
|
* false: the device file doesn't exist current system
|
||
|
*/
|
||
|
bool libinput_set_file_state(libinput_drv_state_t *state, char* dev_name)
|
||
|
{
|
||
|
// This check *should* not be necessary, yet applications crashes even on NULL handles.
|
||
|
// citing libinput.h:libinput_path_remove_device:
|
||
|
// > If no matching device exists, this function does nothing.
|
||
|
if (state->libinput_device) {
|
||
|
libinput_path_remove_device(state->libinput_device);
|
||
9 months ago
|
state->libinput_device = libinput_device_unref(state->libinput_device);
|
||
2 years ago
|
}
|
||
|
|
||
|
state->libinput_device = libinput_path_add_device(state->libinput_context, dev_name);
|
||
|
if(!state->libinput_device) {
|
||
|
perror("unable to add device to libinput context:");
|
||
|
return false;
|
||
|
}
|
||
|
state->libinput_device = libinput_device_ref(state->libinput_device);
|
||
|
if(!state->libinput_device) {
|
||
|
perror("unable to reference device within libinput context:");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
state->button = LV_INDEV_STATE_REL;
|
||
|
state->key_val = 0;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Prepare for reading input via libinput using the default driver state. Use this function if you only want
|
||
|
* to connect a single device.
|
||
|
*/
|
||
|
void libinput_init(void)
|
||
|
{
|
||
|
libinput_init_state(&default_state, LIBINPUT_NAME);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Prepare for reading input via libinput using the a specific driver state. Use this function if you want to
|
||
|
* connect multiple devices.
|
||
|
* @param state driver state to initialize
|
||
|
* @param path input device node path (e.g. /dev/input/event0)
|
||
|
*/
|
||
|
void libinput_init_state(libinput_drv_state_t *state, char* path)
|
||
|
{
|
||
|
state->libinput_device = NULL;
|
||
|
state->libinput_context = libinput_path_create_context(&interface, NULL);
|
||
|
|
||
|
if(path == NULL || !libinput_set_file_state(state, path)) {
|
||
|
fprintf(stderr, "unable to add device \"%s\" to libinput context: %s\n", path ? path : "NULL", strerror(errno));
|
||
|
return;
|
||
|
}
|
||
|
state->fd = libinput_get_fd(state->libinput_context);
|
||
|
|
||
|
/* prepare poll */
|
||
|
state->fds[0].fd = state->fd;
|
||
|
state->fds[0].events = POLLIN;
|
||
|
state->fds[0].revents = 0;
|
||
|
|
||
|
#if USE_XKB
|
||
|
xkb_init_state(&(state->xkb_state));
|
||
|
#endif
|
||
|
}
|
||
|
|
||
9 months ago
|
/**
|
||
|
* De-initialise a previously initialised driver state and free any dynamically allocated memory. Use this function if you want to
|
||
|
* reuse an existing driver state.
|
||
|
* @param state driver state to de-initialize
|
||
|
*/
|
||
|
void libinput_deinit_state(libinput_drv_state_t *state)
|
||
|
{
|
||
|
if (state->libinput_device) {
|
||
|
libinput_path_remove_device(state->libinput_device);
|
||
|
libinput_device_unref(state->libinput_device);
|
||
|
}
|
||
|
|
||
|
if (state->libinput_context) {
|
||
|
libinput_unref(state->libinput_context);
|
||
|
}
|
||
|
|
||
|
#if USE_XKB
|
||
|
xkb_deinit_state(&(state->xkb_state));
|
||
|
#endif
|
||
|
|
||
|
lv_memzero(state, sizeof(libinput_drv_state_t));
|
||
|
}
|
||
|
|
||
2 years ago
|
/**
|
||
|
* Read available input events via libinput using the default driver state. Use this function if you only want
|
||
|
* to connect a single device.
|
||
|
* @param indev_drv driver object itself
|
||
|
* @param data store the libinput data here
|
||
|
*/
|
||
|
void libinput_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
|
||
|
{
|
||
|
libinput_read_state(&default_state, indev_drv, data);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Read available input events via libinput using a specific driver state. Use this function if you want to
|
||
|
* connect multiple devices.
|
||
|
* @param state the driver state to use
|
||
|
* @param indev_drv driver object itself
|
||
|
* @param data store the libinput data here
|
||
|
*/
|
||
|
void libinput_read_state(libinput_drv_state_t * state, lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
|
||
|
{
|
||
|
struct libinput_event *event;
|
||
|
int rc = 0;
|
||
|
|
||
|
rc = poll(state->fds, nfds, timeout);
|
||
|
switch (rc){
|
||
|
case -1:
|
||
|
perror(NULL);
|
||
|
case 0:
|
||
|
goto report_most_recent_state;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
libinput_dispatch(state->libinput_context);
|
||
|
while((event = libinput_get_event(state->libinput_context)) != NULL) {
|
||
|
switch (indev_drv->type) {
|
||
|
case LV_INDEV_TYPE_POINTER:
|
||
|
read_pointer(state, event);
|
||
|
break;
|
||
|
case LV_INDEV_TYPE_KEYPAD:
|
||
|
read_keypad(state, event);
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
libinput_event_destroy(event);
|
||
|
}
|
||
|
report_most_recent_state:
|
||
|
data->point.x = state->most_recent_touch_point.x;
|
||
|
data->point.y = state->most_recent_touch_point.y;
|
||
|
data->state = state->button;
|
||
|
data->key = state->key_val;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**********************
|
||
|
* 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 */
|
||
|
|
||
9 months ago
|
libinput_capability capabilities = libinput_query_capability(device);
|
||
2 years ago
|
|
||
|
libinput_path_remove_device(device);
|
||
|
|
||
|
if (capabilities == 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, libinput_capability capabilities) {
|
||
|
/* Double array size every 2^n elements */
|
||
|
if ((num_devices & (num_devices + 1)) == 0) {
|
||
|
struct input_device *tmp = realloc(devices, (2 * num_devices + 1) * sizeof(struct input_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;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Handle libinput touch / pointer events
|
||
|
* @param state driver state to use
|
||
|
* @param event libinput event
|
||
|
*/
|
||
|
static void read_pointer(libinput_drv_state_t *state, struct libinput_event *event) {
|
||
|
struct libinput_event_touch *touch_event = NULL;
|
||
|
struct libinput_event_pointer *pointer_event = NULL;
|
||
|
enum libinput_event_type type = libinput_event_get_type(event);
|
||
|
|
||
|
/* We need to read unrotated display dimensions directly from the driver because libinput won't account
|
||
|
* for any rotation inside of LVGL */
|
||
|
lv_disp_drv_t *drv = lv_disp_get_default()->driver;
|
||
|
|
||
|
switch (type) {
|
||
|
case LIBINPUT_EVENT_TOUCH_MOTION:
|
||
|
case LIBINPUT_EVENT_TOUCH_DOWN:
|
||
|
touch_event = libinput_event_get_touch_event(event);
|
||
9 months ago
|
lv_coord_t x_touch = libinput_event_touch_get_x_transformed(touch_event, drv->physical_hor_res > 0 ? drv->physical_hor_res : drv->hor_res) - drv->offset_x;
|
||
|
lv_coord_t y_touch = libinput_event_touch_get_y_transformed(touch_event, drv->physical_ver_res > 0 ? drv->physical_ver_res : drv->ver_res) - drv->offset_y;
|
||
|
if (x_touch < 0 || x_touch > drv->hor_res || y_touch < 0 || y_touch > drv->ver_res) {
|
||
2 years ago
|
break; /* ignore touches that are out of bounds */
|
||
|
}
|
||
9 months ago
|
state->most_recent_touch_point.x = x_touch;
|
||
|
state->most_recent_touch_point.y = y_touch;
|
||
2 years ago
|
state->button = LV_INDEV_STATE_PR;
|
||
|
break;
|
||
|
case LIBINPUT_EVENT_TOUCH_UP:
|
||
|
state->button = LV_INDEV_STATE_REL;
|
||
|
break;
|
||
|
case LIBINPUT_EVENT_POINTER_MOTION:
|
||
|
pointer_event = libinput_event_get_pointer_event(event);
|
||
|
state->most_recent_touch_point.x += libinput_event_pointer_get_dx(pointer_event);
|
||
|
state->most_recent_touch_point.y += libinput_event_pointer_get_dy(pointer_event);
|
||
|
state->most_recent_touch_point.x = LV_CLAMP(0, state->most_recent_touch_point.x, drv->hor_res - 1);
|
||
|
state->most_recent_touch_point.y = LV_CLAMP(0, state->most_recent_touch_point.y, drv->ver_res - 1);
|
||
|
break;
|
||
9 months ago
|
case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE:
|
||
|
pointer_event = libinput_event_get_pointer_event(event);
|
||
|
lv_coord_t x_pointer = libinput_event_pointer_get_absolute_x_transformed(pointer_event, drv->physical_hor_res > 0 ? drv->physical_hor_res : drv->hor_res) - drv->offset_x;
|
||
|
lv_coord_t y_pointer = libinput_event_pointer_get_absolute_y_transformed(pointer_event, drv->physical_ver_res > 0 ? drv->physical_ver_res : drv->ver_res) - drv->offset_y;
|
||
|
if (x_pointer < 0 || x_pointer > drv->hor_res || y_pointer < 0 || y_pointer > drv->ver_res) {
|
||
|
break; /* ignore pointer events that are out of bounds */
|
||
|
}
|
||
|
state->most_recent_touch_point.x = x_pointer;
|
||
|
state->most_recent_touch_point.y = y_pointer;
|
||
|
break;
|
||
2 years ago
|
case LIBINPUT_EVENT_POINTER_BUTTON:
|
||
|
pointer_event = libinput_event_get_pointer_event(event);
|
||
|
enum libinput_button_state button_state = libinput_event_pointer_get_button_state(pointer_event);
|
||
|
state->button = button_state == LIBINPUT_BUTTON_STATE_RELEASED ? LV_INDEV_STATE_REL : LV_INDEV_STATE_PR;
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Handle libinput keyboard events
|
||
|
* @param state driver state to use
|
||
|
* @param event libinput event
|
||
|
*/
|
||
|
static void read_keypad(libinput_drv_state_t *state, struct libinput_event *event) {
|
||
|
struct libinput_event_keyboard *keyboard_event = NULL;
|
||
|
enum libinput_event_type type = libinput_event_get_type(event);
|
||
|
switch (type) {
|
||
|
case LIBINPUT_EVENT_KEYBOARD_KEY:
|
||
|
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 USE_XKB
|
||
|
state->key_val = xkb_process_key_state(&(state->xkb_state), code, key_state == LIBINPUT_KEY_STATE_PRESSED);
|
||
|
#else
|
||
|
switch(code) {
|
||
|
case KEY_BACKSPACE:
|
||
|
state->key_val = LV_KEY_BACKSPACE;
|
||
|
break;
|
||
|
case KEY_ENTER:
|
||
|
state->key_val = LV_KEY_ENTER;
|
||
|
break;
|
||
|
case KEY_PREVIOUS:
|
||
|
state->key_val = LV_KEY_PREV;
|
||
|
break;
|
||
|
case KEY_NEXT:
|
||
|
state->key_val = LV_KEY_NEXT;
|
||
|
break;
|
||
|
case KEY_UP:
|
||
|
state->key_val = LV_KEY_UP;
|
||
|
break;
|
||
|
case KEY_LEFT:
|
||
|
state->key_val = LV_KEY_LEFT;
|
||
|
break;
|
||
|
case KEY_RIGHT:
|
||
|
state->key_val = LV_KEY_RIGHT;
|
||
|
break;
|
||
|
case KEY_DOWN:
|
||
|
state->key_val = LV_KEY_DOWN;
|
||
|
break;
|
||
|
case KEY_TAB:
|
||
|
state->key_val = LV_KEY_NEXT;
|
||
|
break;
|
||
|
default:
|
||
|
state->key_val = 0;
|
||
|
break;
|
||
|
}
|
||
|
#endif /* USE_XKB */
|
||
|
if (state->key_val != 0) {
|
||
|
/* Only record button state when actual output is produced to prevent widgets from refreshing */
|
||
|
state->button = (key_state == LIBINPUT_KEY_STATE_RELEASED) ? LV_INDEV_STATE_REL : LV_INDEV_STATE_PR;
|
||
|
}
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
#endif /* USE_LIBINPUT || USE_BSD_LIBINPUT */
|