From dbec44871d2ab70d473199c4c7382ebef4e69f06 Mon Sep 17 00:00:00 2001 From: Diego Augusto Date: Sat, 15 Oct 2022 13:45:42 -0400 Subject: [PATCH] src: Add support for /dev/input devices Plymouth currently gets keyboard input from the terminal. This isn't ideal, since it means plymouth requires VTs to be enabled in the kernel. Furthermore, most display servers use /dev/input and libxkbcommon for keyboard handling these days. This commit adds similar support to the plymouth core code. Subsequent commits will add support to the render plugins. Some contributions by n3rdopolis and Ray Strode. --- meson.build | 3 + src/libply-splash-core/meson.build | 5 + src/libply-splash-core/ply-device-manager.c | 222 +++++++- src/libply-splash-core/ply-input-device.c | 509 +++++++++++++++++++ src/libply-splash-core/ply-input-device.h | 92 ++++ src/libply-splash-core/ply-renderer-plugin.h | 6 + src/libply-splash-core/ply-renderer.c | 29 +- src/libply-splash-core/ply-renderer.h | 7 + src/libply-splash-core/ply-terminal.c | 37 +- src/libply-splash-core/ply-terminal.h | 4 +- src/libply-splash-graphics/ply-keymap-icon.c | 12 +- 11 files changed, 869 insertions(+), 57 deletions(-) create mode 100644 src/libply-splash-core/ply-input-device.c create mode 100644 src/libply-splash-core/ply-input-device.h diff --git a/meson.build b/meson.build index f5cb0869..bd55c1c8 100644 --- a/meson.build +++ b/meson.build @@ -43,6 +43,9 @@ libpangocairo_dep = dependency('pangocairo', required: get_option('pango')) libfreetype_dep = dependency('freetype2', required: get_option('freetype')) gtk3_dep = dependency('gtk+-3.0', version: '>= 3.14.0', required: get_option('gtk')) libdrm_dep = dependency('libdrm', required: get_option('drm')) +libevdev_dep = dependency('libevdev') +xkbcommon_dep = dependency('xkbcommon') +xkeyboard_config_dep = dependency('xkeyboard-config') if get_option('systemd-integration') systemd_dep = dependency('systemd') diff --git a/src/libply-splash-core/meson.build b/src/libply-splash-core/meson.build index 8914e8ec..69636b13 100644 --- a/src/libply-splash-core/meson.build +++ b/src/libply-splash-core/meson.build @@ -1,6 +1,7 @@ libply_splash_core_sources = files( 'ply-boot-splash.c', 'ply-device-manager.c', + 'ply-input-device.c', 'ply-keyboard.c', 'ply-pixel-buffer.c', 'ply-pixel-display.c', @@ -17,6 +18,9 @@ libply_splash_core_public_deps = [ libply_splash_core_private_deps = [ lm_dep, + libevdev_dep, + xkbcommon_dep, + xkeyboard_config_dep, ] if libudev_dep.found() @@ -49,6 +53,7 @@ libply_splash_core_headers = files( 'ply-boot-splash-plugin.h', 'ply-boot-splash.h', 'ply-device-manager.h', + 'ply-input-device.h', 'ply-keyboard.h', 'ply-pixel-buffer.h', 'ply-pixel-display.h', diff --git a/src/libply-splash-core/ply-device-manager.c b/src/libply-splash-core/ply-device-manager.c index 7451922f..ca2af770 100644 --- a/src/libply-splash-core/ply-device-manager.c +++ b/src/libply-splash-core/ply-device-manager.c @@ -19,6 +19,7 @@ */ #include "config.h" #include "ply-device-manager.h" +#include "ply-renderer.h" #include #include @@ -34,14 +35,19 @@ #include #endif +#include + #include "ply-logger.h" #include "ply-event-loop.h" #include "ply-hashtable.h" #include "ply-list.h" +#include "ply-key-file.h" #include "ply-utils.h" +#include "ply-input-device.h" #define SUBSYSTEM_DRM "drm" #define SUBSYSTEM_FRAME_BUFFER "graphics" +#define SUBSYSTEM_INPUT "input" #ifdef HAVE_UDEV static void create_devices_from_udev (ply_device_manager_t *manager); @@ -60,7 +66,9 @@ struct _ply_device_manager ply_event_loop_t *loop; ply_hashtable_t *terminals; ply_hashtable_t *renderers; + ply_hashtable_t *input_devices; ply_terminal_t *local_console_terminal; + const char *keymap; ply_list_t *keyboards; ply_list_t *text_displays; ply_list_t *pixel_displays; @@ -68,6 +76,9 @@ struct _ply_device_manager struct udev_monitor *udev_monitor; ply_fd_watch_t *fd_watch; + struct xkb_context *xkb_context; + struct xkb_keymap *xkb_keymap; + ply_keyboard_added_handler_t keyboard_added_handler; ply_keyboard_removed_handler_t keyboard_removed_handler; ply_pixel_display_added_handler_t pixel_display_added_handler; @@ -262,6 +273,73 @@ fb_device_has_drm_device (ply_device_manager_t *manager, return has_drm_device; } +static void +on_each_renderer_add_input_device (const char *key, + ply_renderer_t *renderer, + ply_input_device_t *input_device) +{ + ply_trace ("Adding input device '%s' to renderer for output device '%s'", + ply_input_device_get_name (input_device), + ply_renderer_get_device_name (renderer)); + + ply_renderer_add_input_device (renderer, input_device); +} + +static void +add_input_device_to_renderers (ply_device_manager_t *manager, + ply_input_device_t *input_device) +{ + const char *device_path = ply_input_device_get_path (input_device); + if (ply_hashtable_lookup (manager->input_devices, (void *) device_path) != NULL) { + ply_trace ("Input device '%s' already added, skipping...", ply_input_device_get_name (input_device)); + ply_input_device_free (input_device); + return; + } + ply_hashtable_insert (manager->input_devices, (void *) device_path, input_device); + ply_hashtable_foreach (manager->renderers, + (ply_hashtable_foreach_func_t *) + on_each_renderer_add_input_device, + input_device); +} + +static void +on_each_input_device_add_to_renderer (const char *key, + ply_input_device_t *input_device, + ply_renderer_t *renderer) +{ + ply_trace ("Adding input device '%s' to renderer for output device '%s'", + ply_input_device_get_name (input_device), + ply_renderer_get_device_name (renderer)); + + ply_renderer_add_input_device (renderer, input_device); +} + +static void +add_input_devices_to_renderer (ply_device_manager_t *manager, + ply_renderer_t *renderer) +{ + ply_hashtable_foreach (manager->input_devices, + (ply_hashtable_foreach_func_t *) + on_each_input_device_add_to_renderer, + renderer); +} +static void +on_each_input_device_remove_from_renderer (const char *key, + ply_renderer_t *renderer, + ply_input_device_t *input_device) +{ + ply_renderer_remove_input_device (renderer, input_device); +} + +static void +remove_input_device_from_renderers (ply_device_manager_t *manager, + ply_input_device_t *input_device) +{ + const char *device_path = ply_input_device_get_path (input_device); + ply_hashtable_remove (manager->input_devices, (void *) device_path); + ply_hashtable_foreach (manager->renderers, (ply_hashtable_foreach_func_t *) on_each_input_device_remove_from_renderer, input_device); +} + static bool verify_drm_device (struct udev_device *device) { @@ -300,7 +378,7 @@ static bool create_devices_for_udev_device (ply_device_manager_t *manager, struct udev_device *device) { - const char *device_path; + const char *device_path, *device_sysname; bool created = false; bool force_fb = false; @@ -308,6 +386,7 @@ create_devices_for_udev_device (ply_device_manager_t *manager, force_fb = true; device_path = udev_device_get_devnode (device); + device_sysname = udev_device_get_sysname (device); if (device_path != NULL) { const char *subsystem; @@ -331,6 +410,21 @@ create_devices_for_udev_device (ply_device_manager_t *manager, renderer_type = PLY_RENDERER_TYPE_FRAME_BUFFER; else ply_trace ("ignoring, since there's a DRM device associated with it"); + } else if (strcmp (subsystem, SUBSYSTEM_INPUT) == 0) { + if (ply_string_has_prefix (device_sysname, "event")) { + ply_trace ("found input device %s", device_path); + ply_input_device_t *input_device = ply_input_device_open (manager->xkb_context, manager->xkb_keymap, device_path); + if (input_device != NULL) { + ply_input_device_set_disconnect_handler (input_device, (ply_input_device_disconnect_handler_t) remove_input_device_from_renderers, manager); + if (ply_input_device_is_keyboard (input_device)) { + add_input_device_to_renderers (manager, input_device); + } else { + ply_input_device_free (input_device); + } + } + } else { + ply_trace ("Ignoring, since this is a non-evdev device"); + } } if (renderer_type != PLY_RENDERER_TYPE_NONE) { @@ -377,7 +471,7 @@ create_devices_for_subsystem (ply_device_manager_t *manager, udev_list_entry_foreach (entry, udev_enumerate_get_list_entry (matches)){ struct udev_device *device = NULL; - const char *path; + const char *path, *node; path = udev_list_entry_get_name (entry); @@ -395,18 +489,10 @@ create_devices_for_subsystem (ply_device_manager_t *manager, if (udev_device_get_is_initialized (device)) { ply_trace ("device is initialized"); - /* We only care about devices assigned to a (any) devices. Floating - * devices should be ignored. - */ - if (udev_device_has_tag (device, "seat")) { - const char *node; - node = udev_device_get_devnode (device); - if (node != NULL) { - ply_trace ("found node %s", node); - found_device = create_devices_for_udev_device (manager, device); - } - } else { - ply_trace ("device doesn't have a devices tag"); + node = udev_device_get_devnode (device); + if (node != NULL) { + ply_trace ("found node %s", node); + found_device = create_devices_for_udev_device (manager, device); } } else { ply_trace ("it's not initialized"); @@ -580,14 +666,15 @@ watch_for_udev_events (ply_device_manager_t *manager) if (manager->fd_watch != NULL) return; - ply_trace ("watching for udev graphics device add and remove events"); + ply_trace ("watching for udev graphics device and input device add and remove events"); if (manager->udev_monitor == NULL) { manager->udev_monitor = udev_monitor_new_from_netlink (manager->udev_context, "udev"); udev_monitor_filter_add_match_subsystem_devtype (manager->udev_monitor, SUBSYSTEM_DRM, NULL); udev_monitor_filter_add_match_subsystem_devtype (manager->udev_monitor, SUBSYSTEM_FRAME_BUFFER, NULL); - udev_monitor_filter_add_match_tag (manager->udev_monitor, "seat"); + if (!ply_kernel_command_line_has_argument ("plymouth.use-legacy-input")) + udev_monitor_filter_add_match_subsystem_devtype (manager->udev_monitor, SUBSYSTEM_INPUT, NULL); udev_monitor_enable_receiving (manager->udev_monitor); } @@ -631,6 +718,21 @@ free_terminals (ply_device_manager_t *manager) manager); } +static void +free_input_device (char *device, + ply_input_device_t *input_device, + ply_device_manager_t *manager) +{ + ply_hashtable_remove (manager->input_devices, device); + ply_input_device_free (input_device); +} + +static void +free_input_devices (ply_device_manager_t *manager) +{ + ply_hashtable_foreach (manager->input_devices, (ply_hashtable_foreach_func_t *) free_input_device, manager); +} + static ply_terminal_t * get_terminal (ply_device_manager_t *manager, const char *device_name) @@ -657,7 +759,7 @@ get_terminal (ply_device_manager_t *manager, terminal = ply_hashtable_lookup (manager->terminals, full_name); if (terminal == NULL) { - terminal = ply_terminal_new (full_name); + terminal = ply_terminal_new (full_name, manager->keymap); ply_hashtable_insert (manager->terminals, (void *) ply_terminal_get_name (terminal), @@ -686,6 +788,74 @@ free_renderers (ply_device_manager_t *manager) manager); } +static char * +strip_quotes (char *str) +{ + char *old_str; + if (str && str[0] == '"' && str[strlen (str) - 1] == '"') { + old_str = str; + str = strndup (str + 1, strlen (str) - 2); + free (old_str); + } + return str; +} + +static void +parse_vconsole_conf (ply_device_manager_t *manager) +{ + ply_key_file_t *vconsole_conf; + char *keymap = NULL, *xkb_layout = NULL, *xkb_model = NULL, *xkb_variant = NULL, *xkb_options = NULL; + + keymap = ply_kernel_command_line_get_key_value ("rd.vconsole.keymap="); + + if (!keymap) + keymap = ply_kernel_command_line_get_key_value ("vconsole.keymap="); + + vconsole_conf = ply_key_file_new ("/etc/vconsole.conf"); + if (ply_key_file_load_groupless_file (vconsole_conf)) { + /* The values in vconsole.conf might be quoted, strip these */ + if (!keymap) { + keymap = ply_key_file_get_value (vconsole_conf, NULL, "KEYMAP"); + keymap = strip_quotes (keymap); + } + xkb_layout = ply_key_file_get_value (vconsole_conf, NULL, "XKB_LAYOUT"); + xkb_layout = strip_quotes (xkb_layout); + + xkb_model = ply_key_file_get_value (vconsole_conf, NULL, "XKB_MODEL"); + xkb_model = strip_quotes (xkb_model); + + xkb_variant = ply_key_file_get_value (vconsole_conf, NULL, "XKB_VARIANT"); + xkb_variant = strip_quotes (xkb_variant); + + xkb_options = ply_key_file_get_value (vconsole_conf, NULL, "XKB_OPTIONS"); + xkb_options = strip_quotes (xkb_options); + } + ply_key_file_free (vconsole_conf); + + ply_trace ("XKB_KEYMAP: %s KEYMAP: %s", xkb_layout, keymap); + + struct xkb_rule_names xkb_keymap = { + .layout = xkb_layout, + .model = xkb_model, + .variant = xkb_variant, + .options = xkb_options, + }; + manager->xkb_keymap = xkb_keymap_new_from_names (manager->xkb_context, &xkb_keymap, XKB_MAP_COMPILE_NO_FLAGS); + + if (manager->xkb_keymap == NULL) { + ply_trace ("Failed to set xkb keymap of LAYOUT: %s MODEL: %s VARIANT: %s OPTIONS: %s", xkb_layout, xkb_model, xkb_variant, xkb_options); + + manager->xkb_keymap = xkb_keymap_new_from_names (manager->xkb_context, NULL, XKB_MAP_COMPILE_NO_FLAGS); + assert (manager->xkb_keymap != NULL); + } + + free (xkb_layout); + free (xkb_model); + free (xkb_variant); + free (xkb_options); + manager->keymap = keymap; +} + ply_device_manager_t * ply_device_manager_new (const char *default_tty, ply_device_manager_flags_t flags) @@ -694,11 +864,16 @@ ply_device_manager_new (const char *default_tty, manager = calloc (1, sizeof(ply_device_manager_t)); manager->loop = NULL; + manager->xkb_context = xkb_context_new (XKB_CONTEXT_NO_FLAGS); + parse_vconsole_conf (manager); + manager->terminals = ply_hashtable_new (ply_hashtable_string_hash, ply_hashtable_string_compare); manager->renderers = ply_hashtable_new (ply_hashtable_string_hash, ply_hashtable_string_compare); - manager->local_console_terminal = ply_terminal_new (default_tty); + + manager->local_console_terminal = ply_terminal_new (default_tty, manager->keymap); ply_terminal_open (manager->local_console_terminal); + manager->input_devices = ply_hashtable_new (ply_hashtable_string_hash, ply_hashtable_string_compare); manager->keyboards = ply_list_new (); manager->text_displays = ply_list_new (); manager->pixel_displays = ply_list_new (); @@ -731,10 +906,17 @@ ply_device_manager_free (ply_device_manager_t *manager) free_terminals (manager); ply_hashtable_free (manager->terminals); + free ((void *) manager->keymap); free_renderers (manager); ply_hashtable_free (manager->renderers); + free_input_devices (manager); + ply_hashtable_free (manager->input_devices); + + if (manager->xkb_context) + xkb_context_unref (manager->xkb_context); + #ifdef HAVE_UDEV ply_event_loop_stop_watching_for_timeout (manager->loop, (ply_event_loop_timeout_handler_t) @@ -922,6 +1104,8 @@ create_devices_for_terminal_and_renderer_type (ply_device_manager_t *manager, renderer = NULL; return true; } + + add_input_devices_to_renderer (manager, renderer); } } @@ -1027,6 +1211,7 @@ create_devices_from_udev (ply_device_manager_t *manager) ply_trace ("Timeout elapsed, looking for devices from udev"); + create_devices_for_subsystem (manager, SUBSYSTEM_INPUT); create_devices_for_subsystem (manager, SUBSYSTEM_DRM); create_devices_for_subsystem (manager, SUBSYSTEM_FRAME_BUFFER); @@ -1089,6 +1274,7 @@ ply_device_manager_watch_devices (ply_device_manager_t *manager, #ifdef HAVE_UDEV watch_for_udev_events (manager); + create_devices_for_subsystem (manager, SUBSYSTEM_INPUT); create_devices_for_subsystem (manager, SUBSYSTEM_DRM); ply_event_loop_watch_for_timeout (manager->loop, device_timeout, diff --git a/src/libply-splash-core/ply-input-device.c b/src/libply-splash-core/ply-input-device.c new file mode 100644 index 00000000..be473fc5 --- /dev/null +++ b/src/libply-splash-core/ply-input-device.c @@ -0,0 +1,509 @@ +/* ply-input-device.c - evdev input device handling implementation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Written By: Diego Augusto + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "ply-buffer.h" +#include "ply-event-loop.h" +#include "ply-input-device.h" +#include "ply-list.h" +#include "ply-logger.h" +#include "ply-trigger.h" +#include "ply-utils.h" + +/* The docs say this needs to be at least 7, the code enforces this, but also never uses more + * than 5. We'll just do 7. + */ +#define PLY_XKB_KEYSYM_TO_UTF8_BUFFER_SIZE 7 + +struct _ply_input_device +{ + int fd; + char *path; + ply_event_loop_t *loop; + ply_trigger_t *input_trigger; + ply_trigger_t *leds_changed_trigger; + ply_trigger_t *disconnect_trigger; + ply_fd_watch_t *fd_watch; + + struct xkb_context *xkb_context; + struct xkb_keymap *keymap; + struct xkb_state *keyboard_state; + struct xkb_compose_table *compose_table; + struct xkb_compose_state *compose_state; + + struct libevdev *dev; +}; + +static bool +apply_compose_sequence_to_input_buffer (ply_input_device_t *input_device, + xkb_keysym_t input_symbol, + ply_buffer_t *input_buffer) +{ + enum xkb_compose_feed_result compose_feed_result; + enum xkb_compose_status compose_status; + + if (input_device->compose_state == NULL) + return false; + + if (input_symbol == XKB_KEY_NoSymbol) + return false; + + compose_feed_result = xkb_compose_state_feed (input_device->compose_state, input_symbol); + + if (compose_feed_result == XKB_COMPOSE_FEED_IGNORED) + return false; + + assert (compose_feed_result == XKB_COMPOSE_FEED_ACCEPTED); + + compose_status = xkb_compose_state_get_status (input_device->compose_state); + + if (compose_status == XKB_COMPOSE_NOTHING) + return false; + + if (compose_status == XKB_COMPOSE_COMPOSED) { + xkb_keysym_t output_symbol; + ssize_t character_size; + char character_buf[PLY_XKB_KEYSYM_TO_UTF8_BUFFER_SIZE] = ""; + + output_symbol = xkb_compose_state_get_one_sym (input_device->compose_state); + character_size = xkb_keysym_to_utf8 (output_symbol, character_buf, sizeof(character_buf)); + + if (character_size > 0) + ply_buffer_append_bytes (input_buffer, character_buf, character_size); + } else { + /* Either we're mid compose sequence (XKB_COMPOSE_COMPOSING) or the compose sequence has + * been aborted (XKB_COMPOSE_CANCELLED). Either way, we shouldn't append anything to the + * input buffer + */ + } + + return true; +} + +static void +apply_key_to_input_buffer (ply_input_device_t *input_device, + xkb_keysym_t symbol, + int keycode, + ply_buffer_t *input_buffer) +{ + ssize_t character_size; + bool was_compose_sequence; + + was_compose_sequence = apply_compose_sequence_to_input_buffer (input_device, symbol, input_buffer); + + if (was_compose_sequence) + return; + + switch (symbol) { + case XKB_KEY_Escape: + ply_buffer_append_bytes (input_buffer, "\033", 1); + break; + case XKB_KEY_Return: + ply_buffer_append_bytes (input_buffer, "\n", 1); + break; + case XKB_KEY_BackSpace: + ply_buffer_append_bytes (input_buffer, "\177", 1); + break; + case XKB_KEY_NoSymbol: + break; + default: + character_size = xkb_state_key_get_utf8 (input_device->keyboard_state, keycode, NULL, 0); + + if (character_size > 0) { + char character_buf[character_size + 1]; + + character_size = xkb_state_key_get_utf8 (input_device->keyboard_state, keycode, character_buf, sizeof(character_buf)); + + assert (character_size + 1 == sizeof(character_buf)); + + ply_buffer_append_bytes (input_buffer, character_buf, character_size); + } + break; + } +} + +static void +on_input (ply_input_device_t *input_device) +{ + struct input_event ev; + int rc; + ply_buffer_t *input_buffer = ply_buffer_new (); + + for (;;) { + ply_key_direction_t key_state; + enum xkb_key_direction xkb_key_direction; + xkb_keycode_t keycode; + xkb_keysym_t symbol; + enum xkb_state_component updated_state; + + rc = libevdev_next_event (input_device->dev, LIBEVDEV_READ_FLAG_NORMAL, &ev); + if (rc != LIBEVDEV_READ_STATUS_SUCCESS) + break; + + if (!libevdev_event_is_type (&ev, EV_KEY)) + continue; + + /* According to `https://docs.kernel.org/input/event-codes.html#ev-key`: + * if ev.value = 2, then the key is being held down. libxkbcommon doesn't appear to define this + * if ev.value = 1, then key was pressed down + * if ev.value = 0, then key was released up + */ + switch (ev.value) { + case 0: + key_state = PLY_KEY_UP; + xkb_key_direction = XKB_KEY_UP; + break; + + case 1: + key_state = PLY_KEY_DOWN; + xkb_key_direction = XKB_KEY_DOWN; + break; + + case 2: + key_state = PLY_KEY_HELD; + xkb_key_direction = XKB_KEY_UP; + break; + } + + /* According to + * `https://xkbcommon.org/doc/current/xkbcommon_8h.html#ac29aee92124c08d1953910ab28ee1997` + * A xkb keycode = linux evdev code + 8 + */ + keycode = (xkb_keycode_t) (ev.code + 8); + + symbol = xkb_state_key_get_one_sym (input_device->keyboard_state, keycode); + + updated_state = xkb_state_update_key (input_device->keyboard_state, keycode, xkb_key_direction); + + if ((updated_state & XKB_STATE_LEDS) != 0) + ply_trigger_pull (input_device->leds_changed_trigger, input_device); + + /* If the key is repeating, or is being pressed down */ + if (key_state == PLY_KEY_HELD || key_state == PLY_KEY_DOWN) + apply_key_to_input_buffer (input_device, symbol, keycode, input_buffer); + } + if (rc != -EAGAIN) { + ply_error ("There was an error reading events for device '%s': %s", + input_device->path, strerror (-rc)); + goto error; + } + if (ply_buffer_get_size (input_buffer) != 0) { + ply_trigger_pull (input_device->input_trigger, ply_buffer_get_bytes (input_buffer)); + } + +error: + ply_buffer_free (input_buffer); +} + +static void +on_disconnect (ply_input_device_t *input_device) +{ + ply_trace ("Input disconnected: %s (%s)", libevdev_get_name (input_device->dev), + input_device->path); + ply_trigger_pull (input_device->disconnect_trigger, input_device); + + ply_input_device_free (input_device); +} + +void +ply_input_device_set_disconnect_handler (ply_input_device_t *input_device, + ply_input_device_disconnect_handler_t callback, + void *user_data) +{ + ply_trigger_add_handler (input_device->disconnect_trigger, (ply_trigger_handler_t) callback, user_data); +} + +ply_input_device_t * +ply_input_device_open (struct xkb_context *xkb_context, + struct xkb_keymap *xkb_keymap, + const char *path) +{ + int error; + const char *locale; + + /* Look up the preferred locale, falling back to "C" as default */ + if (!(locale = getenv ("LC_ALL"))) + if (!(locale = getenv ("LC_CTYPE"))) + if (!(locale = getenv ("LANG"))) + locale = "C"; + + ply_input_device_t *input_device = calloc (1, sizeof(ply_input_device_t)); + + if (input_device == NULL) { + ply_error ("Out of memory"); + return NULL; + } + + input_device->disconnect_trigger = ply_trigger_new (NULL); + input_device->path = strdup (path); + input_device->input_trigger = ply_trigger_new (NULL); + ply_trigger_set_instance (input_device->input_trigger, input_device); + + input_device->leds_changed_trigger = ply_trigger_new (NULL); + input_device->loop = ply_event_loop_get_default (); + + input_device->fd = open (path, O_RDWR | O_NONBLOCK); + + if (input_device->fd < 0) { + ply_error ("Failed to open input device \"%s\"", path); + goto error; + } + input_device->dev = libevdev_new (); + error = libevdev_set_fd (input_device->dev, input_device->fd); + if (error != 0) { + ply_error ("Failed to set fd for device \"%s\": %s", path, strerror (-error)); + goto error; + } + + input_device->fd_watch = ply_event_loop_watch_fd ( + input_device->loop, input_device->fd, PLY_EVENT_LOOP_FD_STATUS_HAS_DATA, + (ply_event_handler_t) on_input, (ply_event_handler_t) on_disconnect, + input_device); + + input_device->keymap = xkb_keymap_ref (xkb_keymap); + input_device->keyboard_state = xkb_state_new (input_device->keymap); + if (input_device->keyboard_state == NULL) { + ply_error ("Failed to initialize input device \"%s\" keyboard_state", path); + goto error; + } + input_device->compose_table = xkb_compose_table_new_from_locale (xkb_context, locale, XKB_COMPOSE_COMPILE_NO_FLAGS); + if (input_device->compose_table) + input_device->compose_state = xkb_compose_state_new (input_device->compose_table, XKB_COMPOSE_STATE_NO_FLAGS); + + return input_device; + +error: + ply_input_device_free (input_device); + return NULL; +} + +void +ply_input_device_watch_for_input (ply_input_device_t *input_device, + ply_input_device_input_handler_t input_callback, + ply_input_device_leds_changed_handler_t leds_changed_callback, + void *user_data) +{ + ply_trigger_add_instance_handler (input_device->input_trigger, (ply_trigger_instance_handler_t) input_callback, user_data); + ply_trigger_add_handler (input_device->leds_changed_trigger, (ply_trigger_handler_t) leds_changed_callback, user_data); +} + +void +ply_input_device_stop_watching_for_input (ply_input_device_t *input_device, + ply_input_device_input_handler_t input_callback, + ply_input_device_leds_changed_handler_t leds_changed_callback, + void *user_data) +{ + ply_trigger_remove_instance_handler (input_device->input_trigger, (ply_trigger_instance_handler_t) input_callback, user_data); + ply_trigger_remove_handler (input_device->leds_changed_trigger, (ply_trigger_handler_t) leds_changed_callback, user_data); +} + +int +ply_input_device_is_keyboard (ply_input_device_t *input_device) +{ + return libevdev_has_event_type (input_device->dev, EV_KEY); +} + +int +ply_input_device_is_keyboard_with_leds (ply_input_device_t *input_device) +{ + return (libevdev_has_event_type (input_device->dev, EV_KEY)) && + (libevdev_has_event_type (input_device->dev, EV_LED)); +} + +const char * +ply_input_device_get_name (ply_input_device_t *input_device) +{ + return libevdev_get_name (input_device->dev); +} + +const char * +ply_input_device_get_path (ply_input_device_t *input_device) +{ + return input_device->path; +} + +/* + * from libinput's evdev_device_led_update and Weston's weston_keyboard_set_locks + */ +void +ply_input_device_set_state (ply_input_device_t *input_device, + ply_xkb_keyboard_state_t *xkb_state) +{ + static struct + { + ply_led_t ply_led; + int evdev; + xkb_mod_mask_t status; + } map[] = { + { PLY_LED_NUM_LOCK, LED_NUML, false }, + { PLY_LED_CAPS_LOCK, LED_CAPSL, false }, + { PLY_LED_SCROLL_LOCK, LED_SCROLLL, false }, + }; + struct input_event ev[PLY_NUMBER_OF_ELEMENTS (map) + 1]; + xkb_mod_mask_t mods_depressed, mods_latched, mods_locked, group; + unsigned int i; + + mods_depressed = xkb_state_serialize_mods (input_device->keyboard_state, + XKB_STATE_DEPRESSED); + mods_latched = xkb_state_serialize_mods (input_device->keyboard_state, + XKB_STATE_LATCHED); + mods_locked = xkb_state_serialize_mods (input_device->keyboard_state, + XKB_STATE_LOCKED); + group = xkb_state_serialize_group (input_device->keyboard_state, + XKB_STATE_EFFECTIVE); + + if (mods_depressed == xkb_state->mods_depressed && + mods_latched == xkb_state->mods_latched && + mods_locked == xkb_state->mods_locked && + group == xkb_state->group) + return; + + mods_depressed = xkb_state->mods_depressed; + mods_latched = xkb_state->mods_latched; + mods_locked = xkb_state->mods_locked; + group = xkb_state->group; + + xkb_state_update_mask (input_device->keyboard_state, + mods_depressed, + mods_latched, + mods_locked, + 0, + 0, + group); + + map[LED_NUML].status = xkb_state_led_name_is_active (input_device->keyboard_state, XKB_LED_NAME_NUM); + map[LED_CAPSL].status = xkb_state_led_name_is_active (input_device->keyboard_state, XKB_LED_NAME_CAPS); + map[LED_SCROLLL].status = xkb_state_led_name_is_active (input_device->keyboard_state, XKB_LED_NAME_SCROLL); + + memset (ev, 0, sizeof(ev)); + for (i = 0; i < PLY_NUMBER_OF_ELEMENTS (map); i++) { + ev[i].type = EV_LED; + ev[i].code = map[i].evdev; + ev[i].value = map[i].status; + } + ev[i].type = EV_SYN; + ev[i].code = SYN_REPORT; + + ply_write (input_device->fd, ev, sizeof(ev)); +} + +ply_xkb_keyboard_state_t +*ply_input_device_get_state (ply_input_device_t *input_device) +{ + ply_xkb_keyboard_state_t *xkb_state = calloc (1, sizeof(ply_xkb_keyboard_state_t)); + + xkb_state->mods_depressed = xkb_state_serialize_mods (input_device->keyboard_state, + XKB_STATE_DEPRESSED); + xkb_state->mods_latched = xkb_state_serialize_mods (input_device->keyboard_state, + XKB_STATE_LATCHED); + xkb_state->mods_locked = xkb_state_serialize_mods (input_device->keyboard_state, + XKB_STATE_LOCKED); + xkb_state->group = xkb_state_serialize_group (input_device->keyboard_state, + XKB_STATE_EFFECTIVE); + + return xkb_state; +} + +bool +ply_input_device_get_capslock_state (ply_input_device_t *input_device) +{ + return xkb_state_led_name_is_active (input_device->keyboard_state, XKB_LED_NAME_CAPS); +} + +const char * +ply_input_device_get_keymap (ply_input_device_t *input_device) +{ + xkb_layout_index_t num_indices = xkb_keymap_num_layouts (input_device->keymap); + ply_trace ("xkb layout has %d groups", num_indices); + if (num_indices == 0) { + return NULL; + } + /* According to xkbcommon docs: + * (https://xkbcommon.org/doc/current/xkbcommon_8h.html#ad37512642806c55955e1cd5a30efcc39) + * + * Each layout is not required to have a name, and the names are not + * guaranteed to be unique (though they are usually provided and + * unique). Therefore, it is not safe to use the name as a unique + * identifier for a layout. Layout names are case-sensitive. + * + * Layout names are specified in the layout's definition, for example "English + * (US)". These are different from the (conventionally) short names + * which are used to locate the layout, for example "us" or "us(intl)". + * These names are not present in a compiled keymap. + * + * This string shouldn't be used as a unique indentifier for a keymap + */ + return xkb_keymap_layout_get_name (input_device->keymap, num_indices - 1); +} + +int +ply_input_device_get_fd (ply_input_device_t *input_device) +{ + return input_device->fd; +} + +void +ply_input_device_free (ply_input_device_t *input_device) +{ + if (input_device == NULL) + return; + + if (input_device->xkb_context) + xkb_context_unref (input_device->xkb_context); + + if (input_device->keyboard_state) + xkb_state_unref (input_device->keyboard_state); + + if (input_device->keymap) + xkb_keymap_unref (input_device->keymap); + + if (input_device->compose_state) + xkb_compose_state_unref (input_device->compose_state); + + if (input_device->compose_table) + xkb_compose_table_unref (input_device->compose_table); + + if (input_device->dev) + libevdev_free (input_device->dev); + + ply_trigger_free (input_device->input_trigger); + ply_trigger_free (input_device->leds_changed_trigger); + ply_trigger_free (input_device->disconnect_trigger); + + free (input_device->path); + + ply_event_loop_stop_watching_fd (input_device->loop, input_device->fd_watch); + + close (input_device->fd); + + free (input_device); +} diff --git a/src/libply-splash-core/ply-input-device.h b/src/libply-splash-core/ply-input-device.h new file mode 100644 index 00000000..ecc88982 --- /dev/null +++ b/src/libply-splash-core/ply-input-device.h @@ -0,0 +1,92 @@ +/* ply-input-device.h - evdev input device handling + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Written By: Diego Augusto + */ +#ifndef PLY_INPUT_DEVICE_H +#define PLY_INPUT_DEVICE_H + +#include "ply-buffer.h" +#include +#include + +typedef enum +{ + PLY_LED_NUM_LOCK = (1 << 0), + PLY_LED_CAPS_LOCK = (1 << 1), + PLY_LED_SCROLL_LOCK = (1 << 2) +} ply_led_t; + +typedef enum +{ + PLY_KEY_UP, + PLY_KEY_DOWN, + PLY_KEY_HELD, +} ply_key_direction_t; + +typedef struct _ply_input_device ply_input_device_t; +typedef void (*ply_input_device_input_handler_t) (void *user_data, + ply_input_device_t *input_device, + const char *buf); +typedef void (*ply_input_device_leds_changed_handler_t) (void *user_data, + ply_input_device_t *input_device); +typedef void (*ply_input_device_disconnect_handler_t) (void *user_data, + ply_input_device_t *input_device); +typedef struct +{ + xkb_mod_mask_t mods_depressed; + xkb_mod_mask_t mods_latched; + xkb_mod_mask_t mods_locked; + xkb_mod_mask_t group; +} ply_xkb_keyboard_state_t; + +#ifndef PLY_HIDE_FUNCTION_DECLARATIONS + +ply_input_device_t *ply_input_device_open (struct xkb_context *xkb_context, + struct xkb_keymap *xkb_keymap, + const char *path); +void ply_input_device_free (ply_input_device_t *input_device); +void ply_input_device_watch_for_input (ply_input_device_t *input_device, + ply_input_device_input_handler_t input_callback, + ply_input_device_leds_changed_handler_t led_callback, + void *user_data); + +void ply_input_device_stop_watching_for_input (ply_input_device_t *input_device, + ply_input_device_input_handler_t input_callback, + ply_input_device_leds_changed_handler_t led_callback, + void *user_data); + + +ply_xkb_keyboard_state_t *ply_input_device_get_state (ply_input_device_t *input_device); +void ply_input_device_set_state (ply_input_device_t *input_device, + ply_xkb_keyboard_state_t *xkb_state); + +void ply_input_device_set_disconnect_handler (ply_input_device_t *input_device, + ply_input_device_disconnect_handler_t callback, + void *user_data); +int ply_input_device_get_fd (ply_input_device_t *input_device); +int ply_input_device_is_keyboard (ply_input_device_t *input_device); +int ply_input_device_is_keyboard_with_leds (ply_input_device_t *input_device); +const char *ply_input_device_get_name (ply_input_device_t *input_device); +bool ply_input_device_get_capslock_state (ply_input_device_t *input_device); +/* The value shouldn't be used as a unique indentifier for a keymap */ +const char *ply_input_device_get_keymap (ply_input_device_t *input_device); +const char *ply_input_device_get_path (ply_input_device_t *input_device); + +#endif //PLY_HIDE_FUNCTION_DECLARATIONS + +#endif //PLY_INPUT_DEVICE_H diff --git a/src/libply-splash-core/ply-renderer-plugin.h b/src/libply-splash-core/ply-renderer-plugin.h index 6817dac1..483ad27c 100644 --- a/src/libply-splash-core/ply-renderer-plugin.h +++ b/src/libply-splash-core/ply-renderer-plugin.h @@ -26,6 +26,7 @@ #include #include +#include "ply-input-device.h" #include "ply-terminal.h" #include "ply-event-loop.h" #include "ply-list.h" @@ -76,6 +77,11 @@ typedef struct int *scale); bool (*get_capslock_state)(ply_renderer_backend_t *backend); const char * (*get_keymap)(ply_renderer_backend_t *backend); + + void (*add_input_device)(ply_renderer_backend_t *backend, + ply_input_device_t *input_device); + void (*remove_input_device)(ply_renderer_backend_t *backend, + ply_input_device_t *input_device); } ply_renderer_plugin_interface_t; #endif /* PLY_RENDERER_PLUGIN_H */ diff --git a/src/libply-splash-core/ply-renderer.c b/src/libply-splash-core/ply-renderer.c index edebc030..75492885 100644 --- a/src/libply-splash-core/ply-renderer.c +++ b/src/libply-splash-core/ply-renderer.c @@ -368,6 +368,34 @@ ply_renderer_flush_head (ply_renderer_t *renderer, renderer->plugin_interface->flush_head (renderer->backend, head); } +void +ply_renderer_add_input_device (ply_renderer_t *renderer, + ply_input_device_t *input_device) +{ + assert (renderer != NULL); + assert (renderer->plugin_interface != NULL); + assert (input_device != NULL); + + if (!renderer->plugin_interface->add_input_device) + return; + + renderer->plugin_interface->add_input_device (renderer->backend, input_device); +} + +void +ply_renderer_remove_input_device (ply_renderer_t *renderer, + ply_input_device_t *input_device) +{ + assert (renderer != NULL); + assert (renderer->plugin_interface != NULL); + assert (input_device != NULL); + + if (!renderer->plugin_interface->remove_input_device) + return; + + renderer->plugin_interface->remove_input_device (renderer->backend, input_device); +} + ply_renderer_input_source_t * ply_renderer_get_input_source (ply_renderer_t *renderer) { @@ -452,4 +480,3 @@ ply_renderer_get_keymap (ply_renderer_t *renderer) return renderer->plugin_interface->get_keymap (renderer->backend); } - diff --git a/src/libply-splash-core/ply-renderer.h b/src/libply-splash-core/ply-renderer.h index 0b4e0323..5fbf819d 100644 --- a/src/libply-splash-core/ply-renderer.h +++ b/src/libply-splash-core/ply-renderer.h @@ -30,6 +30,7 @@ #include "ply-pixel-buffer.h" #include "ply-terminal.h" #include "ply-utils.h" +#include "ply-input-device.h" typedef struct _ply_renderer ply_renderer_t; typedef struct _ply_renderer_head ply_renderer_head_t; @@ -68,6 +69,12 @@ ply_pixel_buffer_t *ply_renderer_get_buffer_for_head (ply_renderer_t *rende void ply_renderer_flush_head (ply_renderer_t *renderer, ply_renderer_head_t *head); +void ply_renderer_add_input_device (ply_renderer_t *renderer, + ply_input_device_t *input_device); + +void ply_renderer_remove_input_device (ply_renderer_t *renderer, + ply_input_device_t *input_device); + ply_renderer_input_source_t *ply_renderer_get_input_source (ply_renderer_t *renderer); bool ply_renderer_open_input_source (ply_renderer_t *renderer, ply_renderer_input_source_t *input_source); diff --git a/src/libply-splash-core/ply-terminal.c b/src/libply-splash-core/ply-terminal.c index 51771244..eee23c74 100644 --- a/src/libply-splash-core/ply-terminal.c +++ b/src/libply-splash-core/ply-terminal.c @@ -80,7 +80,7 @@ struct _ply_terminal struct termios original_locked_term_attributes; char *name; - char *keymap; + const char *keymap; int fd; int vt_number; int initial_vt_number; @@ -117,37 +117,9 @@ typedef enum static ply_terminal_open_result_t ply_terminal_open_device (ply_terminal_t *terminal); -static char * -ply_terminal_parse_keymap_conf (ply_terminal_t *terminal) -{ - ply_key_file_t *vconsole_conf; - char *keymap, *old_keymap; - - keymap = ply_kernel_command_line_get_key_value ("rd.vconsole.keymap="); - if (keymap) - return keymap; - - keymap = ply_kernel_command_line_get_key_value ("vconsole.keymap="); - if (keymap) - return keymap; - - vconsole_conf = ply_key_file_new ("/etc/vconsole.conf"); - if (ply_key_file_load_groupless_file (vconsole_conf)) - keymap = ply_key_file_get_value (vconsole_conf, NULL, "KEYMAP"); - ply_key_file_free (vconsole_conf); - - /* The keymap name in vconsole.conf might be quoted, strip these */ - if (keymap && keymap[0] == '"' && keymap[strlen (keymap) - 1] == '"') { - old_keymap = keymap; - keymap = strndup (keymap + 1, strlen (keymap) - 2); - free (old_keymap); - } - - return keymap; -} - ply_terminal_t * -ply_terminal_new (const char *device_name) +ply_terminal_new (const char *device_name, + const char *keymap) { ply_terminal_t *terminal; @@ -167,7 +139,7 @@ ply_terminal_new (const char *device_name) terminal->fd = -1; terminal->vt_number = -1; terminal->initial_vt_number = -1; - terminal->keymap = ply_terminal_parse_keymap_conf (terminal); + terminal->keymap = keymap; if (terminal->keymap) ply_trace ("terminal %s keymap: %s", terminal->name, terminal->keymap); @@ -879,7 +851,6 @@ ply_terminal_free (ply_terminal_t *terminal) free_vt_change_closures (terminal); free_input_closures (terminal); - free (terminal->keymap); free (terminal->name); free (terminal); } diff --git a/src/libply-splash-core/ply-terminal.h b/src/libply-splash-core/ply-terminal.h index a2685f5c..14d75d13 100644 --- a/src/libply-splash-core/ply-terminal.h +++ b/src/libply-splash-core/ply-terminal.h @@ -55,7 +55,8 @@ typedef enum } ply_terminal_mode_t; #ifndef PLY_HIDE_FUNCTION_DECLARATIONS -ply_terminal_t *ply_terminal_new (const char *device_name); +ply_terminal_t *ply_terminal_new (const char *device_name, + const char *keymap); void ply_terminal_free (ply_terminal_t *terminal); @@ -121,4 +122,3 @@ void ply_terminal_flush_input (ply_terminal_t *terminal); #endif #endif /* PLY_TERMINAL_H */ - diff --git a/src/libply-splash-graphics/ply-keymap-icon.c b/src/libply-splash-graphics/ply-keymap-icon.c index 94cf5b34..4455f032 100644 --- a/src/libply-splash-graphics/ply-keymap-icon.c +++ b/src/libply-splash-graphics/ply-keymap-icon.c @@ -82,7 +82,7 @@ ply_keymap_icon_fill_keymap_info (ply_keymap_icon_t *keymap_icon) { const char *keymap_with_variant; ply_renderer_t *renderer; - char *keymap; + char *keymap, *compare_keymap; int i; keymap_icon->keymap_offset = -1; @@ -95,7 +95,13 @@ ply_keymap_icon_fill_keymap_info (ply_keymap_icon_t *keymap_icon) keymap = ply_keymap_normalize_keymap (keymap_with_variant); for (i = 0; ply_keymap_metadata[i].name; i++) { - if (strcmp (ply_keymap_metadata[i].name, keymap) == 0) { + if (ply_keymap_metadata[i].type == PLY_LAYOUT_TERMINAL) { + compare_keymap = strdup (keymap); + } else if (ply_keymap_metadata[i].type == PLY_LAYOUT_XKB) { + compare_keymap = strdup (keymap_with_variant); + } + + if (strcmp (ply_keymap_metadata[i].name, compare_keymap) == 0) { keymap_icon->keymap_offset = ply_keymap_metadata[i].offset; keymap_icon->keymap_width = ply_keymap_metadata[i].width; break; @@ -106,6 +112,7 @@ ply_keymap_icon_fill_keymap_info (ply_keymap_icon_t *keymap_icon) ply_trace ("Error no pre-rendered text for '%s' keymap", keymap); free (keymap); + free (compare_keymap); } ply_keymap_icon_t * @@ -273,4 +280,3 @@ ply_keymap_icon_get_height (ply_keymap_icon_t *keymap_icon) { return keymap_icon->height; } - -- 2.47.3