diff --git a/buffyboard/lv_conf.defaults b/buffyboard/lv_conf.defaults index 5937f28..00cf621 100644 --- a/buffyboard/lv_conf.defaults +++ b/buffyboard/lv_conf.defaults @@ -105,7 +105,7 @@ LV_USE_LINUX_FBDEV 1 LV_LINUX_FBDEV_BSD 0 LV_LINUX_FBDEV_RENDER_MODE LV_DISPLAY_RENDER_MODE_PARTIAL -LV_USE_LIBINPUT 1 +LV_USE_LIBINPUT 0 LV_LIBINPUT_BSD 0 LV_LIBINPUT_XKB 0 diff --git a/buffyboard/lv_conf.h b/buffyboard/lv_conf.h index b4242df..6c6570c 100644 --- a/buffyboard/lv_conf.h +++ b/buffyboard/lv_conf.h @@ -1249,7 +1249,7 @@ #define LV_USE_EVDEV 0 /** Driver for libinput input devices */ -#define LV_USE_LIBINPUT 1 +#define LV_USE_LIBINPUT 0 #if LV_USE_LIBINPUT #define LV_LIBINPUT_BSD 0 diff --git a/buffyboard/main.c b/buffyboard/main.c index 97fbbe6..77919fc 100644 --- a/buffyboard/main.c +++ b/buffyboard/main.c @@ -3,7 +3,6 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ - #include "buffyboard.h" #include "command_line.h" #include "config.h" @@ -28,7 +27,6 @@ #include - /** * Static variables */ @@ -36,6 +34,7 @@ bb_cli_opts cli_opts; bb_config_opts conf_opts; +static int fd_active; static lv_obj_t *keyboard = NULL; static sig_atomic_t redraw_requested = false; @@ -148,6 +147,35 @@ static void pop_checked_modifier_keys(void) { } } +static void on_new_terminal() { + if (!redraw_requested && conf_opts.quirks.ignore_unused_terminals) { + lseek(fd_active, 0, SEEK_SET); + + char buffer[8]; + ssize_t size = read(fd_active, buffer, sizeof(buffer) - 1); + if (size <= 0) { + bbx_log(BBX_LOG_LEVEL_WARNING, "Can't read /sys/class/tty/tty0/active"); + return; + } + buffer[size] = 0; + + unsigned int tty; + if (sscanf(buffer, "tty%u", &tty) != 1) { + bbx_log(BBX_LOG_LEVEL_WARNING, "Unexpected value of /sys/class/tty/tty0/active"); + return; + } + + if (!bb_terminal_is_busy(tty)) { + bbx_log(BBX_LOG_LEVEL_VERBOSE, "Terminal %u isn't used, skip automatic update.", tty); + return; + } + } + + redraw_requested = false; + pop_checked_modifier_keys(); + bb_terminal_shrink_current(); + lv_obj_invalidate(keyboard); +} /** * Main @@ -226,9 +254,6 @@ int main(int argc, char *argv[]) { /* Prepare for terminal resizing and reset */ bb_terminal_init(tty_height - 8, hor_res, ver_res); - /* Start input device monitor and auto-connect available devices */ - bbx_indev_start_monitor_and_autoconnect(false, conf_opts.input.pointer, conf_opts.input.touchscreen); - /* Initialise theme */ bbx_theme_apply(bbx_themes_themes[conf_opts.theme.default_id]); lv_theme_apply(lv_screen_active()); @@ -251,8 +276,8 @@ int main(int argc, char *argv[]) { sq2lv_switch_layout(keyboard, SQ2LV_LAYOUT_TERMINAL_US); /* Open the file to track virtual terminals */ - int fd_tty = open("/sys/class/tty/tty0/active", O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (fd_tty < 0) { + fd_active = open("/sys/class/tty/tty0/active", O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (fd_active < 0) { perror("Can't open /sys/class/tty/tty0/active"); return EXIT_FAILURE; } @@ -265,14 +290,22 @@ int main(int argc, char *argv[]) { struct epoll_event event; event.events = EPOLLIN|EPOLLET; - event.data.fd = fd_tty; + event.data.ptr = __extension__ (void*) on_new_terminal; - int r = epoll_ctl(fd_epoll, EPOLL_CTL_ADD, fd_tty, &event); + int r = epoll_ctl(fd_epoll, EPOLL_CTL_ADD, fd_active, &event); if (r == -1) { perror("epoll_ctl() is failed"); return EXIT_FAILURE; } + /* Attach input devices and start monitoring for new ones */ + struct bbx_indev_opts input_config = { + .pointer = conf_opts.input.pointer, + .touchscreen = conf_opts.input.touchscreen + }; + if (bbx_indev_init(fd_epoll, &input_config) == 0) + return EXIT_FAILURE; + /* Set signal handlers */ struct sigaction action; action.sa_handler = signal_handler; @@ -300,39 +333,20 @@ int main(int argc, char *argv[]) { int r = epoll_pwait(fd_epoll, &event, 1, time_till_next, &sigmask); if (r == 0) continue; - if (r < 0) { - if (errno != EINTR) { - perror("epoll_wait() is failed"); - return EXIT_FAILURE; - } - if (!redraw_requested) - continue; - redraw_requested = false; - } else if (conf_opts.quirks.ignore_unused_terminals) { - lseek(fd_tty, 0, SEEK_SET); - - char buffer[8]; - ssize_t size = read(fd_tty, buffer, sizeof(buffer) - 1); - if (size <= 0) { - bbx_log(BBX_LOG_LEVEL_WARNING, "Can't read /sys/class/tty/tty0/active"); - continue; - } - buffer[size] = 0; - - unsigned int tty; - if (sscanf(buffer, "tty%u", &tty) != 1) { - bbx_log(BBX_LOG_LEVEL_WARNING, "Unexpected value of /sys/class/tty/tty0/active"); - continue; - } - - if (!bb_terminal_is_busy(tty)) { - bbx_log(BBX_LOG_LEVEL_VERBOSE, "Terminal %u isn't used, skip automatic update.", tty); - continue; - } + if (r > 0) { + __extension__ void (*handler)() = event.data.ptr; + handler(); + continue; + } + if (errno == EINTR) { + if (redraw_requested) + on_new_terminal(); + continue; } - bb_terminal_shrink_current(); - lv_obj_invalidate(keyboard); + bbx_log(BBX_LOG_LEVEL_ERROR, "epoll_pwait() is failed"); + bb_terminal_reset_all(); + return EXIT_FAILURE; } return 0; diff --git a/buffyboard/meson.build b/buffyboard/meson.build index 0e2b0eb..258ebff 100644 --- a/buffyboard/meson.build +++ b/buffyboard/meson.build @@ -15,10 +15,15 @@ buffyboard_dependencies = [ meson.get_compiler('c').find_library('m', required: false) ] +buffyboard_args = [ + '-DBBX_APP_BUFFYBOARD' +] + executable('buffyboard', include_directories: common_include_dirs, sources: buffyboard_sources + shared_sources + squeek2lvgl_sources + lvgl_sources, dependencies: buffyboard_dependencies, + c_args: buffyboard_args, install: true, install_tag: 'buffyboard' ) diff --git a/buffyboard/terminal.c b/buffyboard/terminal.c index d964d30..16ac16b 100644 --- a/buffyboard/terminal.c +++ b/buffyboard/terminal.c @@ -5,6 +5,8 @@ #include "terminal.h" +#include "../shared/indev.h" + #include #include #include @@ -40,16 +42,17 @@ void bb_terminal_shrink_current() { if (fd < 0) return; - /* KDFONTOP returns EINVAL if we are not in the text mode, - so we can skip this check */ -/* int mode; if (ioctl(fd, KDGETMODE, &mode) != 0) goto end; - if (mode != KD_TEXT) + if (mode == KD_TEXT) { + bbx_indev_resume(); + } else { + bbx_indev_suspend(); goto end; -*/ + } + struct console_font_op cfo = { .op = KD_FONT_OP_GET, .width = UINT_MAX, diff --git a/f0rmz/config.c b/f0rmz/config.c index a0d9d98..955dc2c 100644 --- a/f0rmz/config.c +++ b/f0rmz/config.c @@ -88,6 +88,23 @@ static int parsing_handler(void* user_data, const char* section, const char* key return 1; } } + } else if (strcmp(section, "hardware keyboard") == 0) { + if (strcmp(key, "rules") == 0) { + opts->hw_keyboard.rules = strdup(value); + return 1; + } else if (strcmp(key, "model") == 0) { + opts->hw_keyboard.model = strdup(value); + return 1; + } else if (strcmp(key, "layout") == 0) { + opts->hw_keyboard.layout = strdup(value); + return 1; + } else if (strcmp(key, "variant") == 0) { + opts->hw_keyboard.variant = strdup(value); + return 1; + } else if (strcmp(key, "options") == 0) { + opts->hw_keyboard.options = strdup(value); + return 1; + } } else if (strcmp(section, "quirks") == 0) { if (strcmp(key, "fbdev_force_refresh") == 0) { if (bbx_config_parse_bool(value, &(opts->quirks.fbdev_force_refresh))) { @@ -178,6 +195,11 @@ void f0_config_init_opts(f0_config_opts *opts) { opts->input.keyboard = true; opts->input.pointer = true; opts->input.touchscreen = true; + opts->hw_keyboard.rules = NULL; + opts->hw_keyboard.model = NULL; + opts->hw_keyboard.layout = NULL; + opts->hw_keyboard.variant = NULL; + opts->hw_keyboard.options = NULL; opts->quirks.fbdev_force_refresh = false; opts->quirks.terminal_prevent_graphics_mode = false; opts->quirks.terminal_allow_keyboard_input = false; diff --git a/f0rmz/config.h b/f0rmz/config.h index 3d0700d..9f8dae5 100644 --- a/f0rmz/config.h +++ b/f0rmz/config.h @@ -7,13 +7,13 @@ #define F0_CONFIG_H #include "../shared/backends.h" -#include "../shared/config.h" #include "../shared/themes.h" #include "sq2lv_layouts.h" #include #include +#include /** * General options @@ -122,6 +122,8 @@ typedef struct { f0_config_opts_textarea textarea; /* Options related to input devices */ f0_config_opts_input input; + /* Options to create a keymap for hardware keyboards */ + struct xkb_rule_names hw_keyboard; /* Options related to (normally unneeded) quirks */ f0_config_opts_quirks quirks; /* Intro section */ diff --git a/f0rmz/lv_conf.defaults b/f0rmz/lv_conf.defaults index 4b81a39..b73cf88 100644 --- a/f0rmz/lv_conf.defaults +++ b/f0rmz/lv_conf.defaults @@ -109,9 +109,9 @@ LV_LINUX_FBDEV_RENDER_MODE LV_DISPLAY_RENDER_MODE_PARTIAL LV_USE_LINUX_DRM 0 -LV_USE_LIBINPUT 1 +LV_USE_LIBINPUT 0 LV_LIBINPUT_BSD 0 -LV_LIBINPUT_XKB 1 +LV_LIBINPUT_XKB 0 LV_BUILD_EXAMPLES 0 LV_BUILD_DEMOS 0 diff --git a/f0rmz/lv_conf.h b/f0rmz/lv_conf.h index 94bd31d..40d3a87 100644 --- a/f0rmz/lv_conf.h +++ b/f0rmz/lv_conf.h @@ -1249,13 +1249,13 @@ #define LV_USE_EVDEV 0 /** Driver for libinput input devices */ -#define LV_USE_LIBINPUT 1 +#define LV_USE_LIBINPUT 0 #if LV_USE_LIBINPUT #define LV_LIBINPUT_BSD 0 /** Full keyboard support */ - #define LV_LIBINPUT_XKB 1 + #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 } diff --git a/f0rmz/main.c b/f0rmz/main.c index 6cbe9e3..68354e4 100644 --- a/f0rmz/main.c +++ b/f0rmz/main.c @@ -19,13 +19,15 @@ #include "lvgl/lvgl.h" +#include +#include +#include #include +#include #include #include #include -#include - #define F0_PASSWORD_HIDDEN_DOTS "••••••••" /** @@ -584,9 +586,8 @@ static void show_intro_screen(void) { lv_obj_add_event_cb(btn, get_started_btn_clicked_cb, LV_EVENT_CLICKED, NULL); /* Set up keyboard input for intro screen */ - lv_group_t *intro_input_group = lv_group_create(); - bbx_indev_set_keyboard_input_group(intro_input_group); - lv_group_add_obj(intro_input_group, btn); + lv_group_remove_all_objs(keyboard_input_group); + lv_group_add_obj(keyboard_input_group, btn); lv_obj_add_event_cb(btn, intro_key_cb, LV_EVENT_KEY, NULL); } @@ -734,8 +735,7 @@ static void show_form_screen(void) { keyboard = bbx_keyboard_create(lv_screen_active(), form_textarea, &keyboard_config); /* Configuring routing for physical keyboard input into the textarea */ - lv_group_t *keyboard_input_group = lv_group_create(); - bbx_indev_set_keyboard_input_group(keyboard_input_group); + lv_group_remove_all_objs(keyboard_input_group); lv_group_add_obj(keyboard_input_group, form_textarea); } @@ -831,9 +831,8 @@ static void show_summary_screen(void) { lv_obj_add_event_cb(finish_btn, finish_cb, LV_EVENT_CLICKED, NULL); /* Set up keyboard input for summary screen */ - lv_group_t *summary_input_group = lv_group_create(); - bbx_indev_set_keyboard_input_group(summary_input_group); - lv_group_add_obj(summary_input_group, finish_btn); + lv_group_remove_all_objs(keyboard_input_group); + lv_group_add_obj(keyboard_input_group, finish_btn); } int main(int argc, char *argv[]) { @@ -879,12 +878,23 @@ int main(int argc, char *argv[]) { exit_failure(); } - /* Prepare for routing physical keyboard input into the textarea */ - lv_group_t *keyboard_input_group = lv_group_create(); - bbx_indev_set_keyboard_input_group(keyboard_input_group); + int fd_epoll = epoll_create1(EPOLL_CLOEXEC); + if (fd_epoll == -1) { + bbx_log(BBX_LOG_LEVEL_ERROR, "epoll_create1() is failed"); + exit_failure(); + } - /* Start input device monitor and auto-connect available devices */ - bbx_indev_start_monitor_and_autoconnect(conf_opts.input.keyboard, conf_opts.input.pointer, conf_opts.input.touchscreen); + /* Attach input devices and start monitoring for new ones */ + struct bbx_indev_opts input_config = { + .keymap = &conf_opts.hw_keyboard, + .keyboard = conf_opts.input.keyboard, + .pointer = conf_opts.input.pointer, + .touchscreen = conf_opts.input.touchscreen + }; + if (bbx_indev_init(fd_epoll, &input_config) == 0) + exit_failure(); + + bbx_indev_set_key_power_cb(shutdown); /* Hide the on-screen keyboard by default if a physical keyboard is connected */ if (conf_opts.keyboard.autohide && bbx_indev_is_keyboard_connected()) { @@ -906,7 +916,21 @@ int main(int argc, char *argv[]) { /* Main loop */ while(1) { uint32_t time_till_next = lv_timer_handler(); - usleep(time_till_next * 1000); + + struct epoll_event event; + int r = epoll_wait(fd_epoll, &event, 1, time_till_next); + if (r == 0) + continue; + if (r > 0) { + __extension__ void (*handler)() = event.data.ptr; + handler(); + continue; + } + if (errno == EINTR) + continue; + + bbx_log(BBX_LOG_LEVEL_ERROR, "epoll_wait() is failed"); + exit_failure(); } return 0; diff --git a/f0rmz/meson.build b/f0rmz/meson.build index 80239c8..49b52c3 100644 --- a/f0rmz/meson.build +++ b/f0rmz/meson.build @@ -10,7 +10,6 @@ f0rmz_sources = files( f0rmz_dependencies = [ common_dependencies, - depinih, depxkbcommon ] @@ -26,7 +25,7 @@ endif executable('f0rmz', include_directories: common_include_dirs, - sources: f0rmz_sources + shared_sources + squeek2lvgl_sources + lvgl_sources + header_sources, + sources: f0rmz_sources + shared_sources + shared_sources_ul_f0 + squeek2lvgl_sources + lvgl_sources, dependencies: f0rmz_dependencies, c_args: f0rmz_args, install: true, diff --git a/meson.build b/meson.build index 73e76f9..b871d1b 100644 --- a/meson.build +++ b/meson.build @@ -13,7 +13,7 @@ add_project_arguments( depinih = dependency('inih') deplibinput = dependency('libinput') deplibudev = dependency('libudev') -depxkbcommon = dependency('xkbcommon') # For unl0kr only +depxkbcommon = dependency('xkbcommon') # For unl0kr and f0rmz only if get_option('man') depscdoc = dependency('scdoc', native: true) @@ -22,21 +22,23 @@ endif common_include_dirs = include_directories('.') shared_sources = files( - 'shared/backends.c', 'shared/cursor/cursor.c', - 'shared/cli_common.c', - 'shared/display.c', 'shared/fonts/font_32.c', + 'shared/cli_common.c', 'shared/config.c', 'shared/indev.c', - 'shared/keyboard.c', 'shared/log.c', - 'shared/terminal.c', 'shared/theme.c', 'shared/themes.c' ) -header_sources = files('shared/header.c') +shared_sources_ul_f0 = files( + 'shared/backends.c', + 'shared/display.c', + 'shared/header.c', + 'shared/keyboard.c', + 'shared/terminal.c' +) squeek2lvgl_sources = files( 'squeek2lvgl/sq2lv.c' diff --git a/shared/display.c b/shared/display.c index b6b0ae0..b3ec2cb 100644 --- a/shared/display.c +++ b/shared/display.c @@ -68,16 +68,17 @@ lv_display_t *bbx_display_create(bbx_backends_backend_id_t backend_id, bbx_displ return NULL; } + lv_display_set_physical_resolution(disp, + lv_display_get_horizontal_resolution(disp), + lv_display_get_vertical_resolution(disp)); + /* Apply configuration overrides if provided */ if (config) { /* Set display offset */ lv_display_set_offset(disp, config->x_offset, config->y_offset); /* Override resolution if specified */ - if (config->hor_res > 0 || config->ver_res > 0) { - lv_display_set_physical_resolution(disp, - lv_display_get_horizontal_resolution(disp), - lv_display_get_vertical_resolution(disp)); + if (config->hor_res > 0 && config->ver_res > 0) { lv_display_set_resolution(disp, config->hor_res, config->ver_res); } diff --git a/shared/indev.c b/shared/indev.c index 90576fb..3a68959 100644 --- a/shared/indev.c +++ b/shared/indev.c @@ -3,365 +3,719 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ - #include "indev.h" #include "cursor/cursor.h" #include "log.h" +#include "lvgl/lvgl.h" + +#ifndef BBX_APP_BUFFYBOARD +#include +#endif +#include +#include +#ifndef BBX_APP_BUFFYBOARD +#include +#endif +#include +#include +#include +#include #include #include -#include #include #include +#include -#include +#ifndef BBX_APP_BUFFYBOARD +/* All keyboard devices are connected to this input group */ +lv_group_t *keyboard_input_group; +#endif -#include +enum input_device_type { + BBX_INDEV_NONE, + BBX_INDEV_KEY, + BBX_INDEV_KEYBOARD, + BBX_INDEV_MOUSE, + BBX_INDEV_TOUCHPAD, + BBX_INDEV_TOUCHSCREEN, + BBX_INDEV_OTHER +}; +struct input_device_ext { + /* The second pointer is used to represent the second finger on touchscreens. + This makes typing with thumbs convenient */ + lv_indev_t* pointer1; + lv_indev_t* pointer2; +#ifndef BBX_APP_BUFFYBOARD + lv_indev_t* keypad; + struct xkb_state* xkb_state; +#endif + enum input_device_type type; +}; -/** - * Defines - */ - -#define INPUT_DEVICE_NODE_PREFIX "/dev/input/event" - -#define MAX_KEYBOARD_DEVS 4 -#define MAX_POINTER_DEVS 4 -#define MAX_TOUCHSCREEN_DEVS 1 - +/* The analog of lv_indev_data_t, but without unused fields */ +struct indev_ext { + union { + lv_point_t point; + uint32_t key; + }; + lv_indev_state_t state; +}; /** * Static variables */ -static lv_libinput_capability allowed_capability = LV_LIBINPUT_CAPABILITY_NONE; +static struct libinput *context_libinput; +static struct udev *context_udev; +static struct udev_monitor *monitor; +#ifndef BBX_APP_BUFFYBOARD +static struct xkb_keymap *keymap; +#endif +static lv_obj_t *mouse_cursor; +static bool libinput_is_suspended = false; -static struct udev *context = NULL; -static struct udev_monitor *monitor = NULL; -static int monitor_fd = -1; +#ifndef BBX_APP_BUFFYBOARD +static uint8_t num_keyboards = 0; +#endif +static uint8_t num_mouses = 0; +static uint8_t num_touchpads = 0; -struct input_device { - char *node; - lv_libinput_capability capability; - lv_indev_t *indev; -}; - -static struct input_device **devices = NULL; -static int num_devices = 0; -static int num_connected_devices = 0; - -lv_group_t *keyboard_input_group = NULL; -lv_obj_t *cursor_obj = NULL; - - -/** - * Static prototypes - */ - -/** - * Test whether a device can act as a keyboard device. - * - * @param device the device to test - * @return true if the device has the keyboard capability - */ -static bool is_keyboard_device(struct input_device *device); - -/** - * Test whether a device can act as a pointer device. - * - * @param device the device to test - * @return true if the device has the pointer capability - */ -static bool is_pointer_device(struct input_device *device); - -/** - * Test whether a device can act as a touch device. - * - * @param device the device to test - * @return true if the device has the touch capability - */ -static bool is_touch_device(struct input_device *device); - -/** - * Convert a device capability into a descriptive string. - * - * @param capability input device capability - * @return textual description - */ -static char *capability_to_str(lv_libinput_capability capability); - -/** - * Connect a specific input device using its udev device. - * - * @param device udev device - */ -static void connect_udev_device(struct udev_device *device); - -/** - * Connect a specific input device using its device node. - * - * @param node device node path - */ -static void connect_devnode(const char *node); - -/** - * Disconnect a specific input device using its device node. - * - * @param node device node path - */ -static void disconnect_devnode(const char *node); - -/** - * Disconnect a specific input device using its index in the devices array. - * - * @param idx index in devices array - */ -static void disconnect_idx(int idx); - -/** - * Set up the input group for a keyboard device. - * - * @param device the input device - */ -static void set_keyboard_input_group(struct input_device *device); - -/** - * Set up the mouse cursor image for a pointer device. - * - * @param device the input device - */ -static void set_mouse_cursor(struct input_device *device); +#ifndef BBX_APP_BUFFYBOARD +static void (*on_key_power_cb)() = NULL; +#endif +static struct { +#ifndef BBX_APP_BUFFYBOARD + uint8_t keyboard : 1; +#endif + uint8_t pointer : 1; + uint8_t touchscreen : 1; +} options; /** * Static functions */ -static bool is_keyboard_device(struct input_device *device) { - return (device->capability & LV_LIBINPUT_CAPABILITY_KEYBOARD) != LV_LIBINPUT_CAPABILITY_NONE; +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 bool is_pointer_device(struct input_device *device) { - return (device->capability & LV_LIBINPUT_CAPABILITY_POINTER) != LV_LIBINPUT_CAPABILITY_NONE; +static void close_restricted(int fd, void *user_data) { + LV_UNUSED(user_data); + close(fd); } -static bool is_touch_device(struct input_device *device) { - return (device->capability & LV_LIBINPUT_CAPABILITY_TOUCH) != LV_LIBINPUT_CAPABILITY_NONE; +#ifndef BBX_APP_BUFFYBOARD +static void read_keypad(lv_indev_t *indev, lv_indev_data_t *data) { + struct indev_ext* ext = lv_indev_get_user_data(indev); + + data->key = ext->key; + data->state = ext->state; +} +#endif + +static void read_pointer(lv_indev_t * indev, lv_indev_data_t * data) { + struct indev_ext* ext = lv_indev_get_user_data(indev); + + data->point = ext->point; + data->state = ext->state; } -static char *capability_to_str(lv_libinput_capability capability) { - if (capability == LV_LIBINPUT_CAPABILITY_KEYBOARD) { - return "keyboard"; - } - if (capability == LV_LIBINPUT_CAPABILITY_POINTER) { - return "pointer"; - } - if (capability == LV_LIBINPUT_CAPABILITY_TOUCH) { - return "touch"; - } - return "none"; -} +static enum input_device_type identify_input_device(struct udev_device* device) { + bool is_input = false; + enum input_device_type ret = BBX_INDEV_OTHER; -static void connect_udev_device(struct udev_device *device) { - /* Obtain and verify device node */ - const char *node = udev_device_get_devnode(device); - if (!node || strncmp(node, INPUT_DEVICE_NODE_PREFIX, strlen(INPUT_DEVICE_NODE_PREFIX)) != 0) { - bbx_log(BBX_LOG_LEVEL_VERBOSE, "Ignoring unsupported input device %s", udev_device_get_syspath(device)); - return; + struct udev_list_entry *entry; + struct udev_list_entry *properties = udev_device_get_properties_list_entry(device); + udev_list_entry_foreach(entry, properties) { + const char* name = udev_list_entry_get_name(entry); + const char* value = udev_list_entry_get_value(entry); + + if (strcmp(name, "ID_INPUT") == 0 && strcmp(value, "1") == 0) + is_input = true; + else if (strcmp(name, "ID_INPUT_KEY") == 0 && strcmp(value, "1") == 0) + ret = BBX_INDEV_KEY; + else if (strcmp(name, "ID_INPUT_KEYBOARD") == 0 && strcmp(value, "1") == 0) + ret = BBX_INDEV_KEYBOARD; + else if (strcmp(name, "ID_INPUT_MOUSE") == 0 && strcmp(value, "1") == 0) + ret = BBX_INDEV_MOUSE; + else if (strcmp(name, "ID_INPUT_TOUCHPAD") == 0 && strcmp(value, "1") == 0) + ret = BBX_INDEV_TOUCHPAD; + else if (strcmp(name, "ID_INPUT_TOUCHSCREEN") == 0 && strcmp(value, "1") == 0) + ret = BBX_INDEV_TOUCHSCREEN; } - /* Connect device using its node */ - connect_devnode(node); + if (!is_input) { + bbx_log(BBX_LOG_LEVEL_WARNING, "%s is not an input device", udev_device_get_devnode(device)); + ret = BBX_INDEV_NONE; + } + + return ret; } -static void connect_devnode(const char *node) { - /* Check if the device is already connected */ - for (int i = 0; i < num_connected_devices; ++i) { - if (strcmp(devices[i]->node, node) == 0) { - bbx_log(BBX_LOG_LEVEL_WARNING, "Ignoring already connected input device %s", node); - return; +static void add_device_extension(struct libinput_device* device) { + enum input_device_type type = (enum input_device_type) libinput_device_get_user_data(device); + libinput_device_set_user_data(device, NULL); + if (type == BBX_INDEV_NONE) { + struct udev_device *dev = libinput_device_get_udev_device(device); + if (!dev) { + bbx_log(BBX_LOG_LEVEL_ERROR, "libinput_device_get_udev_device(%s) is failed", libinput_device_get_sysname(device)); + goto failure1; + } + type = identify_input_device(dev); + udev_device_unref(dev); + if (type == BBX_INDEV_NONE) + goto failure1; + } + + struct input_device_ext* ext = malloc(sizeof(struct input_device_ext)); + if (!ext) { + bbx_log(BBX_LOG_LEVEL_ERROR, "Out of memory"); + goto failure1; + } + + ext->pointer1 = NULL; + ext->pointer2 = NULL; +#ifndef BBX_APP_BUFFYBOARD + ext->keypad = NULL; + ext->xkb_state = NULL; +#endif + ext->type = type; + +#ifndef BBX_APP_BUFFYBOARD + if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_KEYBOARD)) { + ext->keypad = lv_indev_create(); + if (!ext->keypad) { + bbx_log(BBX_LOG_LEVEL_ERROR, "Out of memory"); + goto failure2; + } + + struct indev_ext* data = malloc(sizeof(struct indev_ext)); + if (!data) { + bbx_log(BBX_LOG_LEVEL_ERROR, "Out of memory"); + lv_indev_delete(ext->keypad); + goto failure2; + } + + data->key = 0; + data->state = LV_INDEV_STATE_RELEASED; + + lv_indev_set_type(ext->keypad, LV_INDEV_TYPE_KEYPAD); + lv_indev_set_mode(ext->keypad, LV_INDEV_MODE_EVENT); + lv_indev_set_user_data(ext->keypad, data); + lv_indev_set_read_cb(ext->keypad, read_keypad); + lv_indev_set_group(ext->keypad, keyboard_input_group); + + if (ext->type == BBX_INDEV_KEYBOARD) { + ext->xkb_state = xkb_state_new(keymap); + if (!ext->xkb_state) + bbx_log(BBX_LOG_LEVEL_WARNING, "Can't create xkb_state for %s", libinput_device_get_sysname(device)); + } + } +#endif + + if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_POINTER) || + libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TOUCH)) { + ext->pointer1 = lv_indev_create(); + if (!ext->pointer1) { + bbx_log(BBX_LOG_LEVEL_ERROR, "Out of memory"); + goto failure3; + } + + struct indev_ext* data1 = malloc(sizeof(struct indev_ext)); + if (!data1) { + bbx_log(BBX_LOG_LEVEL_ERROR, "Out of memory"); + lv_indev_delete(ext->pointer1); + goto failure3; + } + + lv_display_t* display = lv_indev_get_display(ext->pointer1); + data1->point.x = display->hor_res / 2; + data1->point.y = display->ver_res / 2; + data1->state = LV_INDEV_STATE_RELEASED; + + lv_indev_set_type(ext->pointer1, LV_INDEV_TYPE_POINTER); + lv_indev_set_mode(ext->pointer1, LV_INDEV_MODE_EVENT); + lv_indev_set_user_data(ext->pointer1, data1); + lv_indev_set_read_cb(ext->pointer1, read_pointer); + + if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TOUCH) && + libinput_device_touch_get_touch_count(device) != 1) { + ext->pointer2 = lv_indev_create(); + if (!ext->pointer2) { + bbx_log(BBX_LOG_LEVEL_ERROR, "Out of memory"); + goto failure3; + } + + struct indev_ext* data2 = malloc(sizeof(struct indev_ext)); + if (!data2) { + bbx_log(BBX_LOG_LEVEL_ERROR, "Out of memory"); + lv_indev_delete(ext->pointer2); + goto failure3; + } + + data2->point.x = display->hor_res / 2; + data2->point.y = display->ver_res / 2; + data2->state = LV_INDEV_STATE_RELEASED; + + lv_indev_set_type(ext->pointer2, LV_INDEV_TYPE_POINTER); + lv_indev_set_mode(ext->pointer2, LV_INDEV_MODE_EVENT); + lv_indev_set_user_data(ext->pointer2, data2); + lv_indev_set_read_cb(ext->pointer2, read_pointer); + } + + if (ext->type == BBX_INDEV_MOUSE || ext->type == BBX_INDEV_TOUCHPAD) { + lv_indev_read(ext->pointer1); + lv_indev_set_cursor(ext->pointer1, mouse_cursor); } } - /* Double array size every time it's filled */ - if (num_connected_devices == num_devices) { - /* Re-allocate array */ - struct input_device **tmp = realloc(devices, (2 * num_devices + 1) * sizeof(struct input_device *)); - if (!tmp) { - bbx_log(BBX_LOG_LEVEL_ERROR, "Could not reallocate memory for input device array"); - return; - } - devices = tmp; - - /* Update size counter */ - num_devices = 2 * num_devices + 1; - - /* Fill empty space with zeros */ - lv_memzero(devices + num_connected_devices, (num_devices - num_connected_devices) * sizeof(struct input_device *)); +#ifndef BBX_APP_BUFFYBOARD + if (!ext->pointer1 && !ext->keypad) { +#else + if (!ext->pointer1) { +#endif + bbx_log(BBX_LOG_LEVEL_VERBOSE, "%s does not have required capabilities, ignoring.", + libinput_device_get_sysname(device)); + goto failure2; } - /* Allocate memory for new input device and insert it */ - struct input_device *device = malloc(sizeof(struct input_device)); - lv_memzero(device, sizeof(struct input_device)); - devices[num_connected_devices] = device; + libinput_device_set_user_data(device, ext); - /* Copy the node path so that it can be used beyond the caller's scope */ - device->node = strdup(node); +#ifndef BBX_APP_BUFFYBOARD + if (ext->type == BBX_INDEV_KEYBOARD) + num_keyboards++; + else +#endif + if (ext->type == BBX_INDEV_MOUSE) + num_mouses++; + else if (ext->type == BBX_INDEV_TOUCHPAD) + num_touchpads++; + if (num_mouses != 0 || num_touchpads != 0) + lv_obj_remove_flag(mouse_cursor, LV_OBJ_FLAG_HIDDEN); - /* Initialise the indev and obtain the libinput device */ - device->indev = lv_libinput_create(LV_INDEV_TYPE_NONE, device->node); - if (!device->indev) { - bbx_log(BBX_LOG_LEVEL_WARNING, "Aborting connection of input device %s because libinput failed to connect it", node); - disconnect_idx(num_connected_devices); - return; + bbx_log(BBX_LOG_LEVEL_VERBOSE, "New input device: %s", libinput_device_get_name(device)); + return; + + +failure3: + if (ext->pointer1) { + free(lv_indev_get_user_data(ext->pointer1)); + lv_indev_delete(ext->pointer1); } - lv_libinput_t *dsc = lv_indev_get_driver_data(device->indev); - struct libinput_device *device_libinput = dsc->libinput_device; - if (!device_libinput) { - bbx_log(BBX_LOG_LEVEL_WARNING, "Aborting connection of input device %s because libinput failed to connect it", node); - disconnect_idx(num_connected_devices); - return; + if (ext->pointer2) { + free(lv_indev_get_user_data(ext->pointer2)); + lv_indev_delete(ext->pointer2); } - - /* Obtain device capabilities */ - device->capability = lv_libinput_query_capability(device_libinput); - - /* If the device doesn't have any supported capabilities, exit */ - if ((device->capability & allowed_capability) == LV_LIBINPUT_CAPABILITY_NONE) { - bbx_log(BBX_LOG_LEVEL_WARNING, "Aborting connection of input device %s because it has no allowed capabilities", node); - disconnect_idx(num_connected_devices); - return; +#ifndef BBX_APP_BUFFYBOARD + if (ext->keypad) { + free(lv_indev_get_user_data(ext->keypad)); + lv_indev_delete(ext->keypad); } - - /* - * Set up indev type and related properties - * - * FIXME: Some libinput devices (notably, certain hid-i2c keyboards) - * report both keyboard and pointer capability. Since lvgl expects - * every input device to have only one type, we register those devices - * as keyboards, considering this capability more useful. However - * we must also assume that keyboard+touch devices are touch. - */ - - if (is_touch_device(device)) { - device->indev->type = LV_INDEV_TYPE_POINTER; - device->indev->long_press_repeat_time = USHRT_MAX; - } else if (is_keyboard_device(device)) { - device->indev->type = LV_INDEV_TYPE_KEYPAD; - } else if (is_pointer_device(device)) { - device->indev->type = LV_INDEV_TYPE_POINTER; - device->indev->long_press_repeat_time = USHRT_MAX; + if (ext->xkb_state) { + xkb_state_unref(ext->xkb_state); } - - /* Set the input group for keyboard devices */ - if (is_keyboard_device(device)) { - set_keyboard_input_group(device); - } - - /* Set the mouse cursor for pointer devices */ - if (is_pointer_device(device)) { - set_mouse_cursor(device); - } - - /* Increment connected device count */ - num_connected_devices++; - - bbx_log(BBX_LOG_LEVEL_VERBOSE, "Connected input device %s (%s)", node, capability_to_str(device->capability)); +#endif +failure2: + free(ext); +failure1: + libinput_path_remove_device(device); + return; } -static void disconnect_udev_device(struct udev_device *device) { - /* Obtain and verify device node */ - const char *node = udev_device_get_devnode(device); - if (!node || strncmp(node, INPUT_DEVICE_NODE_PREFIX, strlen(INPUT_DEVICE_NODE_PREFIX)) != 0) { - bbx_log(BBX_LOG_LEVEL_VERBOSE, "Ignoring unsupported input device %s", udev_device_get_syspath(device)); - return; - } +static void on_input_event() { + libinput_dispatch(context_libinput); - /* Disconnect device using its node */ - disconnect_devnode(node); -} + struct libinput_event * event; + while ((event = libinput_get_event(context_libinput))) { + struct libinput_device* device = libinput_event_get_device(event); + struct input_device_ext* ext = libinput_device_get_user_data(device); -static void disconnect_devnode(const char *node) { - /* Find connected device matching the specified node */ - int idx = -1; - for (int i = 0; i < num_connected_devices; ++i) { - if (strcmp(devices[i]->node, node) == 0) { - idx = i; + switch (libinput_event_get_type(event)) { + case LIBINPUT_EVENT_DEVICE_ADDED: { + add_device_extension(device); break; } - } - /* If no matching device was found, exit */ - if (idx < 0) { - bbx_log(BBX_LOG_LEVEL_WARNING, "Ignoring already disconnected input device %s", node); - return; - } + case LIBINPUT_EVENT_DEVICE_REMOVED: { + if (!ext) + break; - /* Disconnect device using its index */ - disconnect_idx(idx); + if (ext->pointer1) { + free(lv_indev_get_user_data(ext->pointer1)); + lv_indev_delete(ext->pointer1); + } + if (ext->pointer2) { + free(lv_indev_get_user_data(ext->pointer2)); + lv_indev_delete(ext->pointer2); + } +#ifndef BBX_APP_BUFFYBOARD + if (ext->keypad) { + free(lv_indev_get_user_data(ext->keypad)); + lv_indev_delete(ext->keypad); + } + if (ext->xkb_state) { + xkb_state_unref(ext->xkb_state); + } - /* Shift subsequent devices forward */ - for (int i = idx + 1; i < num_connected_devices; ++i) { - devices[i - 1] = devices[i]; + if (ext->type == BBX_INDEV_KEYBOARD) { + assert(num_keyboards > 0); + num_keyboards--; + } else +#endif + if (ext->type == BBX_INDEV_MOUSE) { + assert(num_mouses > 0); + num_mouses--; + } else if (ext->type == BBX_INDEV_TOUCHPAD) { + assert(num_touchpads > 0); + num_touchpads--; + } + if (num_mouses == 0 && num_touchpads == 0) + lv_obj_add_flag(mouse_cursor, LV_OBJ_FLAG_HIDDEN); - /* Zero out the last element after shifting it forward */ - if (i == num_connected_devices - 1) { - lv_memzero(devices + i, sizeof(struct input_device *)); + free(ext); + break; } - } - - /* Decrement connected device count */ - --num_connected_devices; - bbx_log(BBX_LOG_LEVEL_VERBOSE, "Disconnected input device %s", node); +#ifndef BBX_APP_BUFFYBOARD + case LIBINPUT_EVENT_KEYBOARD_KEY: { + struct libinput_event_keyboard *kb_event = libinput_event_get_keyboard_event(event); + + struct indev_ext *data = lv_indev_get_user_data(ext->keypad); + uint32_t key = libinput_event_keyboard_get_key(kb_event); + bool pressed = libinput_event_keyboard_get_key_state(kb_event) == LIBINPUT_KEY_STATE_PRESSED; + xkb_keycode_t keycode = key + 8; + bool ignore = false; + + switch (key) { + case KEY_UP: + data->key = LV_KEY_UP; + break; + case KEY_DOWN: + data->key = LV_KEY_DOWN; + break; + case KEY_RIGHT: + data->key = LV_KEY_RIGHT; + break; + case KEY_LEFT: + data->key = LV_KEY_LEFT; + break; + case KEY_ESC: + data->key = LV_KEY_ESC; + break; + case KEY_DELETE: + data->key = LV_KEY_DEL; + break; + case KEY_BACKSPACE: + data->key = LV_KEY_BACKSPACE; + break; + case KEY_ENTER: + data->key = LV_KEY_ENTER; + break; + case KEY_NEXT: + case KEY_TAB: + data->key = LV_KEY_NEXT; + break; + case KEY_PREVIOUS: + data->key = LV_KEY_PREV; + break; + case KEY_HOME: + data->key = LV_KEY_HOME; + break; + case KEY_END: + data->key = LV_KEY_END; + break; + case KEY_POWER: + if (pressed && on_key_power_cb) + on_key_power_cb(); + ignore = true; + break; + default: { + if (!ext->xkb_state) { + ignore = true; + break; + } + char buffer[] = { 0, 0, 0, 0, 0 }; + int size = xkb_state_key_get_utf8(ext->xkb_state, keycode, buffer, sizeof(buffer)); + if (size == 0) { + ignore = true; + break; + } + data->key = *((uint32_t*) buffer); + break; + }} + + if (ext->xkb_state) + xkb_state_update_key(ext->xkb_state, keycode, pressed ? XKB_KEY_DOWN : XKB_KEY_UP); + + if (ignore) + break; + + data->state = pressed ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED; + + lv_indev_read(ext->keypad); + break; + } +#endif + + case LIBINPUT_EVENT_POINTER_MOTION: { + struct libinput_event_pointer *pointer_event = libinput_event_get_pointer_event(event); + + struct indev_ext *data = lv_indev_get_user_data(ext->pointer1); + lv_display_t* display = lv_indev_get_display(ext->pointer1); + + int32_t x = data->point.x + (int32_t) libinput_event_pointer_get_dx(pointer_event); + data->point.x = LV_CLAMP(0, x, display->hor_res - 1); + int32_t y = data->point.y + (int32_t) libinput_event_pointer_get_dy(pointer_event); + data->point.y = LV_CLAMP(0, y, display->ver_res - 1); + + lv_indev_read(ext->pointer1); + break; + } + + case LIBINPUT_EVENT_POINTER_BUTTON: { + struct libinput_event_pointer *pointer_event = libinput_event_get_pointer_event(event); + + struct indev_ext *data = lv_indev_get_user_data(ext->pointer1); + data->state = libinput_event_pointer_get_button_state(pointer_event) == LIBINPUT_BUTTON_STATE_PRESSED ? + LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED; + + lv_indev_read(ext->pointer1); + break; + } + + case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: { + struct libinput_event_pointer *pointer_event = libinput_event_get_pointer_event(event); + + struct indev_ext *data = lv_indev_get_user_data(ext->pointer1); + lv_display_t* display = lv_indev_get_display(ext->pointer1); + + int32_t x = libinput_event_pointer_get_absolute_x_transformed(pointer_event, display->physical_hor_res); + int32_t y = libinput_event_pointer_get_absolute_y_transformed(pointer_event, display->physical_ver_res); + x -= display->offset_x; + y -= display->offset_y; + if (x < 0 || x >= display->hor_res || y < 0 || y >= display->ver_res) + break; + data->point.x = x; + data->point.y = y; + + lv_indev_read(ext->pointer1); + break; + } + + case LIBINPUT_EVENT_TOUCH_MOTION: { + struct libinput_event_touch *touch_event = libinput_event_get_touch_event(event); + int32_t slot = libinput_event_touch_get_slot(touch_event); + + lv_indev_t *pointer; + if (slot <= 0) + pointer = ext->pointer1; + else if (slot == 1) + pointer = ext->pointer2; + else + break; + + struct indev_ext *data = lv_indev_get_user_data(pointer); + lv_display_t* display = lv_indev_get_display(pointer); + + int32_t x = libinput_event_touch_get_x_transformed(touch_event, display->physical_hor_res); + int32_t y = libinput_event_touch_get_y_transformed(touch_event, display->physical_ver_res); + x -= display->offset_x; + y -= display->offset_y; + if (x < 0 || x >= display->hor_res || y < 0 || y >= display->ver_res) + break; + data->point.x = x; + data->point.y = y; + + lv_indev_read(pointer); + break; + } + + case LIBINPUT_EVENT_TOUCH_DOWN: { + struct libinput_event_touch *touch_event = libinput_event_get_touch_event(event); + int32_t slot = libinput_event_touch_get_slot(touch_event); + + lv_indev_t *pointer; + if (slot <= 0) + pointer = ext->pointer1; + else if (slot == 1) + pointer = ext->pointer2; + else + break; + + struct indev_ext *data = lv_indev_get_user_data(pointer); + lv_display_t* display = lv_indev_get_display(pointer); + + int32_t x = libinput_event_touch_get_x_transformed(touch_event, display->physical_hor_res); + int32_t y = libinput_event_touch_get_y_transformed(touch_event, display->physical_ver_res); + x -= display->offset_x; + y -= display->offset_y; + if (x < 0 || x >= display->hor_res || y < 0 || y >= display->ver_res) + break; + data->point.x = x; + data->point.y = y; + data->state = LV_INDEV_STATE_PRESSED; + + lv_indev_read(pointer); + break; + } + + case LIBINPUT_EVENT_TOUCH_UP: + case LIBINPUT_EVENT_TOUCH_CANCEL: { + struct libinput_event_touch *touch_event = libinput_event_get_touch_event(event); + int32_t slot = libinput_event_touch_get_slot(touch_event); + + lv_indev_t *pointer; + if (slot <= 0) + pointer = ext->pointer1; + else if (slot == 1) + pointer = ext->pointer2; + else + break; + + struct indev_ext *data = lv_indev_get_user_data(pointer); + data->state = LV_INDEV_STATE_RELEASED; + + lv_indev_read(pointer); + break; + } + + default: + break; + } + + libinput_event_destroy(event); + } } -static void disconnect_idx(int idx) { - /* Delete LVGL indev */ - if (devices[idx]->indev) { - lv_libinput_delete(devices[idx]->indev); + +static void attach_input_device(struct udev_device* device) { + const char* node = udev_device_get_devnode(device); + + enum input_device_type type = identify_input_device(device); + switch (type) { + case BBX_INDEV_NONE: + return; + case BBX_INDEV_KEY: +#ifndef BBX_APP_BUFFYBOARD + break; +#else + bbx_log(BBX_LOG_LEVEL_VERBOSE, "Key %s is ignored", node); + return; +#endif + case BBX_INDEV_KEYBOARD: +#ifndef BBX_APP_BUFFYBOARD + if (!options.keyboard) { + bbx_log(BBX_LOG_LEVEL_VERBOSE, "Keyboard %s is ignored", node); + return; + } + break; +#else + bbx_log(BBX_LOG_LEVEL_VERBOSE, "Keyboard %s is ignored", node); + return; +#endif + case BBX_INDEV_MOUSE: + if (!options.pointer) { + bbx_log(BBX_LOG_LEVEL_VERBOSE, "Mouse %s is ignored", node); + return; + } + break; + case BBX_INDEV_TOUCHPAD: + if (!options.pointer) { + bbx_log(BBX_LOG_LEVEL_VERBOSE, "Touchpad %s is ignored", node); + return; + } + break; + case BBX_INDEV_TOUCHSCREEN: + if (!options.touchscreen) { + bbx_log(BBX_LOG_LEVEL_VERBOSE, "Touchscreen %s is ignored", node); + return; + } + break; + default: + break; } - /* Free previously copied node path */ - if (devices[idx]->node) { - free(devices[idx]->node); - } - - /* Deallocate memory and zero out freed array element */ - free(devices[idx]); - lv_memzero(devices + idx, sizeof(struct input_device *)); -} - -static void set_keyboard_input_group(struct input_device *device) { - /* Ignore non-keyboard devices */ - if (!is_keyboard_device(device)) { + struct libinput_device* dev = libinput_path_add_device(context_libinput, node); + if (!dev) { + bbx_log(BBX_LOG_LEVEL_WARNING, "libinput can't use %s", node); return; } - /* Apply the input group */ - lv_indev_set_group(device->indev, keyboard_input_group); + static_assert(sizeof(type) <= sizeof(void*)); + libinput_device_set_user_data(dev, (void*) type); + + on_input_event(); /* Process LIBINPUT_EVENT_DEVICE_ADDED immediately */ } -static void set_mouse_cursor(struct input_device *device) { - /* Ignore non-pointer devices */ - if (!is_pointer_device(device)) { +static void attach_input_devices() { + DIR* dir = opendir("/dev/input"); + if (!dir) { + bbx_log(BBX_LOG_LEVEL_ERROR, "Can't open /dev/input: %s", strerror(errno)); return; } - /* Initialise cursor image if needed */ - if (!cursor_obj) { - cursor_obj = lv_image_create(lv_screen_active()); - lv_image_set_src(cursor_obj, &cursor); + struct dirent* entry; + while ((entry = readdir(dir))) { + if (strncmp(entry->d_name, "event", 5) != 0) + continue; + + char node[sizeof("/dev/input") + strlen(entry->d_name) + 1]; + sprintf(node, "/dev/input/%s", entry->d_name); + + struct stat buffer; + int r = stat(node, &buffer); + if (r != 0) { + bbx_log(BBX_LOG_LEVEL_ERROR, "stat(%s) is failed: %s", node, strerror(errno)); + continue; + } + + assert(S_ISCHR(buffer.st_mode)); + + struct udev_device *device = udev_device_new_from_devnum(context_udev, 'c', buffer.st_rdev); + if (!device) { + bbx_log(BBX_LOG_LEVEL_ERROR, "Can't create udev device for %s", node); + continue; + } + + attach_input_device(device); + + udev_device_unref(device); } - /* Apply the cursor image */ - lv_indev_set_cursor(device->indev, cursor_obj); + closedir(dir); + return; } -static void query_device_monitor(lv_timer_t *timer) { - LV_UNUSED(timer); - bbx_indev_query_monitor(); +static void on_input_device_event() { + bool attached = false; + + struct udev_device *device; + while ((device = udev_monitor_receive_device(monitor))) { + const char *action = udev_device_get_action(device); + const char *devnode = udev_device_get_devnode(device); + + if (strcmp(action, "add") != 0) + goto skip; + + if (!devnode || strncmp(devnode, "/dev/input/event", sizeof("/dev/input/event") - 1) != 0) + goto skip; + + attach_input_device(device); + attached = true; +skip: + udev_device_unref(device); + } + + /* Libinput does not store the state, and activates added devices immediately. + So we have to suspend it again. */ + if (attached && libinput_is_suspended) + libinput_suspend(context_libinput); } @@ -369,187 +723,146 @@ static void query_device_monitor(lv_timer_t *timer) { * Public functions */ -void bbx_indev_set_allowed_device_capability(bool keyboard, bool pointer, bool touchscreen) { - allowed_capability = LV_LIBINPUT_CAPABILITY_NONE; - if (keyboard) { - allowed_capability |= LV_LIBINPUT_CAPABILITY_KEYBOARD; - } - if (pointer) { - allowed_capability |= LV_LIBINPUT_CAPABILITY_POINTER; - } - if (touchscreen) { - allowed_capability |= LV_LIBINPUT_CAPABILITY_TOUCH; - } -} +uint8_t bbx_indev_init(int fd_epoll, const struct bbx_indev_opts* opts) { + static const struct libinput_interface interface = { + .open_restricted = open_restricted, + .close_restricted = close_restricted, + }; -void bbx_indev_set_keyboard_input_group(lv_group_t *group) { - /* Store the group */ - keyboard_input_group = group; - - /* Apply the group on all connected keyboard devices */ - for (int i = 0; i < num_connected_devices; ++i) { - if (is_keyboard_device(devices[i])) { - set_keyboard_input_group(devices[i]); - } - } -} - -void bbx_indev_start_monitor_and_autoconnect(bool keyboard, bool pointer, bool touchscreen) { - bbx_indev_set_allowed_device_capability(keyboard, pointer, touchscreen); - bbx_indev_start_monitor(); - lv_timer_create(query_device_monitor, 1000, NULL); - bbx_indev_auto_connect(); -} - -void bbx_indev_auto_connect() { - bbx_log(BBX_LOG_LEVEL_VERBOSE, "Auto-connecting supported input devices"); - - /* Make sure udev context is initialised */ - if (!context) { - context = udev_new(); - if (!context) { - bbx_log(BBX_LOG_LEVEL_WARNING, "Could not create udev context"); - return; - } + context_libinput = libinput_path_create_context(&interface, NULL); + if (!context_libinput) { + bbx_log(BBX_LOG_LEVEL_ERROR, "Could not create libinput context"); + return 0; } - /* Scan for available input devices */ - struct udev_enumerate *enumerate = udev_enumerate_new(context); - udev_enumerate_add_match_subsystem(enumerate, "input"); - udev_enumerate_scan_devices(enumerate); - - /* Prepare for enumerating found devices */ - struct udev_list_entry *first_entry = udev_enumerate_get_list_entry(enumerate); - struct udev_list_entry *entry; - - udev_list_entry_foreach(entry, first_entry) { - /* Obtain system path */ - const char *path = udev_list_entry_get_name(entry); - - /* Create udev device */ - struct udev_device *device = udev_device_new_from_syspath(context, path); - if (!device) { - bbx_log(BBX_LOG_LEVEL_WARNING, "Could not create udev device for %s", path); - continue; - } - - /* Connect device */ - connect_udev_device(device); - - /* Unreference udev device */ - udev_device_unref(device); + context_udev = udev_new(); + if (!context_udev) { + bbx_log(BBX_LOG_LEVEL_ERROR, "Could not create udev context"); + goto failure1; } - /* Unreference enumeration */ - udev_enumerate_unref(enumerate); -} - -void bbx_indev_start_monitor() { - /* Make sure udev context is initialised */ - if (!context) { - context = udev_new(); - if (!context) { - bbx_log(BBX_LOG_LEVEL_WARNING, "Could not create udev context"); - return; - } - } - - /* Check if monitor is already running */ - if (monitor) { - bbx_log(BBX_LOG_LEVEL_WARNING, "Not starting udev monitor because it is already running"); - return; - } - - /* Create new monitor */ - monitor = udev_monitor_new_from_netlink(context, "udev"); + monitor = udev_monitor_new_from_netlink(context_udev, "udev"); if (!monitor) { - bbx_log(BBX_LOG_LEVEL_WARNING, "Could not create udev monitor"); - bbx_indev_stop_monitor(); - return; + bbx_log(BBX_LOG_LEVEL_ERROR, "Could not create udev monitor"); + goto failure2; } - /* Apply input subsystem filter */ - if (udev_monitor_filter_add_match_subsystem_devtype(monitor, "input", NULL) < 0) { - bbx_log(BBX_LOG_LEVEL_WARNING, "Could not add input subsystem filter for udev monitor"); + if (udev_monitor_filter_add_match_subsystem_devtype(monitor, "input", NULL) != 0) + bbx_log(BBX_LOG_LEVEL_WARNING, "Could not add a filter for udev monitor"); + + if (udev_monitor_filter_update(monitor) != 0) + bbx_log(BBX_LOG_LEVEL_WARNING, "Could not update a filter for udev monitor"); + +#ifndef BBX_APP_BUFFYBOARD + struct xkb_context *context_xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (!context_xkb) { + bbx_log(BBX_LOG_LEVEL_ERROR, "Could not create xkb_context"); + goto failure3; } - /* Start monitor */ - if (udev_monitor_enable_receiving(monitor) < 0) { - bbx_log(BBX_LOG_LEVEL_WARNING, "Could not enable udev monitor"); - bbx_indev_stop_monitor(); - return; + keymap = xkb_keymap_new_from_names(context_xkb, opts->keymap, XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!keymap) { + bbx_log(BBX_LOG_LEVEL_ERROR, "Can't compile xkb_rule_names: %s:%s:%s:%s:%s", + opts->keymap->rules, + opts->keymap->model, + opts->keymap->layout, + opts->keymap->variant, + opts->keymap->options); + goto failure4; + } +#endif + + lv_display_t *display = lv_display_get_default(); + assert(display->physical_hor_res > 0); + assert(display->physical_ver_res > 0); + + mouse_cursor = lv_image_create(lv_display_get_layer_sys(display)); + if (!mouse_cursor) { + bbx_log(BBX_LOG_LEVEL_ERROR, "lv_image_create() is failed"); + goto failure5; + } + lv_obj_add_flag(mouse_cursor, LV_OBJ_FLAG_HIDDEN); + lv_image_set_src(mouse_cursor, &cursor); + +#ifndef BBX_APP_BUFFYBOARD + keyboard_input_group = lv_group_create(); + if (!keyboard_input_group) { + bbx_log(BBX_LOG_LEVEL_ERROR, "lv_group_create() is failed"); + goto failure6; } - /* Obtain monitor file descriptor */ - if ((monitor_fd = udev_monitor_get_fd(monitor)) < 0) { - bbx_log(BBX_LOG_LEVEL_WARNING, "Could not acquire file descriptor for udev monitor"); - bbx_indev_stop_monitor(); - return; + options.keyboard = opts->keyboard; +#endif + options.pointer = opts->pointer; + options.touchscreen = opts->touchscreen; + + attach_input_devices(); + + uint8_t ret = 0; + + struct epoll_event event; + event.events = EPOLLIN; + event.data.ptr = __extension__ (void*) on_input_event; + + int r = epoll_ctl(fd_epoll, EPOLL_CTL_ADD, libinput_get_fd(context_libinput), &event); + if (r == -1) { + bbx_log(BBX_LOG_LEVEL_ERROR, "EPOLL_CTL_ADD for libinput context is failed"); + return ret; + } + ret++; + + event.events = EPOLLIN; + event.data.ptr = __extension__ (void*) on_input_device_event; + + r = epoll_ctl(fd_epoll, EPOLL_CTL_ADD, udev_monitor_get_fd(monitor), &event); + if (r == -1) { + bbx_log(BBX_LOG_LEVEL_ERROR, "EPOLL_CTL_ADD for udev_monitor is failed"); + return ret; + } + ret++; + + return ret; + + +failure6: + lv_obj_delete(mouse_cursor); +failure5: +#ifndef BBX_APP_BUFFYBOARD + xkb_keymap_unref(keymap); +failure4: + xkb_context_unref(context_xkb); +#endif +failure3: + udev_monitor_unref(monitor); +failure2: + udev_unref(context_udev); +failure1: + libinput_unref(context_libinput); + return 0; +} + +void bbx_indev_suspend() { + if (!libinput_is_suspended) { + libinput_suspend(context_libinput); + libinput_is_suspended = true; + bbx_log(BBX_LOG_LEVEL_VERBOSE, "libinput is suspended"); } } -void bbx_indev_stop_monitor() { - /* Unreference monitor */ - if (monitor) { - udev_monitor_unref(monitor); - monitor = NULL; - } - - /* Reset monitor file descriptor */ - if (monitor_fd >= 0) { - monitor_fd = -1; - } - - /* Unreference udev context */ - if (context) { - udev_unref(context); - context = NULL; - } -} - -void bbx_indev_query_monitor() { - /* Make sure the monitor is running */ - if (!monitor) { - bbx_log(BBX_LOG_LEVEL_ERROR, "Cannot query udev monitor because it is not running"); - return; - } - - /* Prepare file descriptor set for reading */ - fd_set fds; - FD_ZERO(&fds); - FD_SET(monitor_fd, &fds); - - /* Set up timeval to not block when no updates are available */ - struct timeval tv = { .tv_sec = 0, .tv_usec = 0 }; - - /* Read and process all updates */ - while (select(monitor_fd + 1, &fds, NULL, NULL, &tv) > 0 && FD_ISSET(monitor_fd, &fds)) { - /* Obtain udev device */ - struct udev_device *device = udev_monitor_receive_device(monitor); - if (!device) { - continue; - } - - /* Obtain action */ - const char *action = udev_device_get_action(device); - - /* Connect or disconnect the device */ - if (strcmp(action, "add") == 0) { - connect_udev_device(device); - } else if (strcmp(action, "remove") == 0) { - disconnect_udev_device(device); - } - - /* Unreference udev device */ - udev_device_unref(device); +void bbx_indev_resume() { + if (libinput_is_suspended) { + libinput_resume(context_libinput); + libinput_is_suspended = false; + bbx_log(BBX_LOG_LEVEL_VERBOSE, "libinput is resumed"); } } +#ifndef BBX_APP_BUFFYBOARD bool bbx_indev_is_keyboard_connected() { - for (int i = 0; i < num_connected_devices; ++i) { - if (is_keyboard_device(devices[i])) { - return true; - } - } - return false; + return num_keyboards != 0; } + +void bbx_indev_set_key_power_cb(void (*callback)()) { + on_key_power_cb = callback; +} +#endif diff --git a/shared/indev.h b/shared/indev.h index e0796ae..ece0c66 100644 --- a/shared/indev.h +++ b/shared/indev.h @@ -3,59 +3,50 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ - #ifndef BBX_INDEV_H #define BBX_INDEV_H +#ifndef BBX_APP_BUFFYBOARD #include "lvgl/lvgl.h" +#endif #include +#include + +struct bbx_indev_opts { +#ifndef BBX_APP_BUFFYBOARD + struct xkb_rule_names* keymap; + uint8_t keyboard : 1; +#endif + uint8_t pointer : 1; + uint8_t touchscreen : 1; +}; + +#ifndef BBX_APP_BUFFYBOARD +/* All keyboard devices are added to this input group */ +extern lv_group_t *keyboard_input_group; +#endif /** - * Set the required capabilities for input devices. + * Attach input devices from /dev/input/event* and set up monitoring for new devices. * - * @param keyboard if true, allow connection of keyboard devices - * @param pointer if true, allow connection of pointer devices - * @param touchscreen if true, allow connection of touchscreen devices + * @param fd_epoll epoll descriptor to add monitored events to + * @param opts options for filtering input devices and setting a keyboard keymap + * @return the number of file descriptors added to fd_epoll */ -void bbx_indev_set_allowed_device_capability(bool keyboard, bool pointer, bool touchscreen); +uint8_t bbx_indev_init(int fd_epoll, const struct bbx_indev_opts* opts); /** - * Set the group for receiving input from keyboard devices. - * - * @param group group that should receive input + * Stop input processing. */ -void bbx_indev_set_keyboard_input_group(lv_group_t *group); +void bbx_indev_suspend(); /** - * Start the udev device monitor and auto-connect currently available devices. - * - * @param keyboard if true, allow connection of keyboard devices - * @param pointer if true, allow connection of pointer devices - * @param touchscreen if true, allow connection of touchscreen devices + * Resume input processing. */ -void bbx_indev_start_monitor_and_autoconnect(bool keyboard, bool pointer, bool touchscreen); - -/** - * Auto-connect currently available keyboard, pointer and touchscreen input devices. - */ -void bbx_indev_auto_connect(); - -/** - * Start the udev device monitor. - */ -void bbx_indev_start_monitor(); - -/** - * Stop the udev device monitor. - */ -void bbx_indev_stop_monitor(); - -/** - * Query the udev device monitor and (dis)connect added or removed devices - */ -void bbx_indev_query_monitor(); +void bbx_indev_resume(); +#ifndef BBX_APP_BUFFYBOARD /** * Check if any keyboard devices are connected. * @@ -63,4 +54,10 @@ void bbx_indev_query_monitor(); */ bool bbx_indev_is_keyboard_connected(); +/** + * Set a function that will be called on pressing KEY_POWER. + */ +void bbx_indev_set_key_power_cb(void (*callback)()); +#endif + #endif /* BBX_INDEV_H */ diff --git a/unl0kr/config.c b/unl0kr/config.c index a17c91d..73c5098 100644 --- a/unl0kr/config.c +++ b/unl0kr/config.c @@ -112,6 +112,23 @@ static int parsing_handler(void* user_data, const char* section, const char* key return 1; } } + } else if (strcmp(section, "hardware keyboard") == 0) { + if (strcmp(key, "rules") == 0) { + opts->hw_keyboard.rules = strdup(value); + return 1; + } else if (strcmp(key, "model") == 0) { + opts->hw_keyboard.model = strdup(value); + return 1; + } else if (strcmp(key, "layout") == 0) { + opts->hw_keyboard.layout = strdup(value); + return 1; + } else if (strcmp(key, "variant") == 0) { + opts->hw_keyboard.variant = strdup(value); + return 1; + } else if (strcmp(key, "options") == 0) { + opts->hw_keyboard.options = strdup(value); + return 1; + } } else if (strcmp(section, "quirks") == 0) { if (strcmp(key, "fbdev_force_refresh") == 0) { if (bbx_config_parse_bool(value, &(opts->quirks.fbdev_force_refresh))) { @@ -151,6 +168,11 @@ void ul_config_init_opts(ul_config_opts *opts) { opts->input.keyboard = true; opts->input.pointer = true; opts->input.touchscreen = true; + opts->hw_keyboard.rules = NULL; + opts->hw_keyboard.model = NULL; + opts->hw_keyboard.layout = NULL; + opts->hw_keyboard.variant = NULL; + opts->hw_keyboard.options = NULL; opts->quirks.fbdev_force_refresh = false; opts->quirks.terminal_prevent_graphics_mode = false; opts->quirks.terminal_allow_keyboard_input = false; diff --git a/unl0kr/config.h b/unl0kr/config.h index f5b3055..d32fc70 100644 --- a/unl0kr/config.h +++ b/unl0kr/config.h @@ -15,6 +15,7 @@ #include #include +#include /** * General options @@ -98,6 +99,8 @@ typedef struct { ul_config_opts_theme theme; /* Options related to input devices */ ul_config_opts_input input; + /* Options to create a keymap for hardware keyboards */ + struct xkb_rule_names hw_keyboard; /* Options related to (normally unneeded) quirks */ ul_config_opts_quirks quirks; } ul_config_opts; diff --git a/unl0kr/lv_conf.defaults b/unl0kr/lv_conf.defaults index 4b81a39..b73cf88 100644 --- a/unl0kr/lv_conf.defaults +++ b/unl0kr/lv_conf.defaults @@ -109,9 +109,9 @@ LV_LINUX_FBDEV_RENDER_MODE LV_DISPLAY_RENDER_MODE_PARTIAL LV_USE_LINUX_DRM 0 -LV_USE_LIBINPUT 1 +LV_USE_LIBINPUT 0 LV_LIBINPUT_BSD 0 -LV_LIBINPUT_XKB 1 +LV_LIBINPUT_XKB 0 LV_BUILD_EXAMPLES 0 LV_BUILD_DEMOS 0 diff --git a/unl0kr/lv_conf.h b/unl0kr/lv_conf.h index 94bd31d..40d3a87 100644 --- a/unl0kr/lv_conf.h +++ b/unl0kr/lv_conf.h @@ -1249,13 +1249,13 @@ #define LV_USE_EVDEV 0 /** Driver for libinput input devices */ -#define LV_USE_LIBINPUT 1 +#define LV_USE_LIBINPUT 0 #if LV_USE_LIBINPUT #define LV_LIBINPUT_BSD 0 /** Full keyboard support */ - #define LV_LIBINPUT_XKB 1 + #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 } diff --git a/unl0kr/main.c b/unl0kr/main.c index 5cc21fa..70ebcd1 100644 --- a/unl0kr/main.c +++ b/unl0kr/main.c @@ -21,34 +21,33 @@ #include "lvgl/lvgl.h" +#include +#include +#include #include #include #include #include #include -#include -#include - +ul_cli_opts cli_opts; +ul_config_opts conf_opts; /** * Static variables */ -ul_cli_opts cli_opts; -ul_config_opts conf_opts; +static bool is_alternate_theme = false; +static bool is_password_obscured = true; +static bool is_keyboard_hidden = false; -bool is_alternate_theme = false; -bool is_password_obscured = true; -bool is_keyboard_hidden = false; +static lv_obj_t *container; +static lv_obj_t *keyboard; -lv_obj_t *container = NULL; -lv_obj_t *keyboard = NULL; - -int32_t content_height_with_kb; -int32_t content_height_without_kb; -int32_t content_pad_bottom_with_kb; -int32_t content_pad_bottom_without_kb; +static int32_t content_height_with_kb; +static int32_t content_height_without_kb; +static int32_t content_pad_bottom_with_kb; +static int32_t content_pad_bottom_without_kb; /** * Static prototypes @@ -401,13 +400,6 @@ int main(int argc, char *argv[]) { /* Announce ourselves */ bbx_log(BBX_LOG_LEVEL_VERBOSE, "unl0kr %s", PROJECT_VERSION); - /* Check that we have access to the clock */ - struct timespec ts; - if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1) { - bbx_log(BBX_LOG_LEVEL_ERROR, "Unable to read the clock"); - exit(EXIT_FAILURE); - } - /* Parse config files */ ul_config_init_opts(&conf_opts); ul_config_parse_file("/usr/share/unl0kr/unl0kr.conf", &conf_opts); @@ -444,12 +436,23 @@ int main(int argc, char *argv[]) { exit_failure(); } - /* Prepare for routing physical keyboard input into the textarea */ - lv_group_t *keyboard_input_group = lv_group_create(); - bbx_indev_set_keyboard_input_group(keyboard_input_group); + int fd_epoll = epoll_create1(EPOLL_CLOEXEC); + if (fd_epoll == -1) { + bbx_log(BBX_LOG_LEVEL_ERROR, "epoll_create1() is failed"); + exit_failure(); + } - /* Start input device monitor and auto-connect available devices */ - bbx_indev_start_monitor_and_autoconnect(conf_opts.input.keyboard, conf_opts.input.pointer, conf_opts.input.touchscreen); + /* Attach input devices and start monitoring for new ones */ + struct bbx_indev_opts input_config = { + .keymap = &conf_opts.hw_keyboard, + .keyboard = conf_opts.input.keyboard, + .pointer = conf_opts.input.pointer, + .touchscreen = conf_opts.input.touchscreen + }; + if (bbx_indev_init(fd_epoll, &input_config) == 0) + exit_failure(); + + bbx_indev_set_key_power_cb(shutdown); /* Hide the on-screen keyboard by default if a physical keyboard is connected */ if (conf_opts.keyboard.autohide && bbx_indev_is_keyboard_connected()) { @@ -601,7 +604,20 @@ int main(int argc, char *argv[]) { time_till_next = time_till_shutdown; } - usleep(time_till_next * 1000); + struct epoll_event event; + int r = epoll_wait(fd_epoll, &event, 1, time_till_next); + if (r == 0) + continue; + if (r > 0) { + __extension__ void (*handler)() = event.data.ptr; + handler(); + continue; + } + if (errno == EINTR) + continue; + + bbx_log(BBX_LOG_LEVEL_ERROR, "epoll_wait() is failed"); + exit_failure(); } return 0; diff --git a/unl0kr/meson.build b/unl0kr/meson.build index 6fecd9d..fbffe76 100644 --- a/unl0kr/meson.build +++ b/unl0kr/meson.build @@ -25,7 +25,7 @@ endif executable('unl0kr', include_directories: common_include_dirs, - sources: unl0kr_sources + shared_sources + squeek2lvgl_sources + lvgl_sources + header_sources, + sources: unl0kr_sources + shared_sources + shared_sources_ul_f0 + squeek2lvgl_sources + lvgl_sources, dependencies: unl0kr_dependencies, c_args: unl0kr_args, install: true, diff --git a/unl0kr/unl0kr.conf b/unl0kr/unl0kr.conf index 31307be..70938ca 100644 --- a/unl0kr/unl0kr.conf +++ b/unl0kr/unl0kr.conf @@ -21,6 +21,13 @@ alternate=breezy-dark #pointer=false #touchscreen=false +[hardware keyboard] +#rules= +model=pc101 +layout=us +#variant= +#options= + #[quirks] #fbdev_force_refresh=true #terminal_prevent_graphics_mode=true