From f2d3b1000f451003c38933c05a742b76da48aeee Mon Sep 17 00:00:00 2001 From: Brian Ashworth Date: Sun, 3 Nov 2019 14:18:41 -0500 Subject: [PATCH] Introduce wlr_keyboard_group A wlr_keyboard_group allows for multiple keyboard devices to be combined into one logical keyboard. Each keyboard device can only be added to one keyboard group. This helps with the situation where one physical keyboard is exposed as multiple keyboard devices. It is up to the compositors on how they group keyboards together, if at all. Since a wlr_keyboard_group is one logical keyboard, the keys are a set. This means that if a key is pressed on multiple keyboard devices, the key event will only be emitted once, but the internal state will count the number of devices that the key is pressed on. Likewise, the key release will not be emitted until the key is released from all devices. If the compositor wants access to which keys are pressed and released on each keyboard device, the events for those devices can be listened to, as they currently are, in addition to the group keyboard's events. Also, all keyboard devices in the group must share the same keymap. If the keymap's differ, the keyboard device will not be able to be added to the group. Once in the group, if the keymap or effective layout for one keyboard device changes, it will be synced to all keyboard devices in the group. The repeat info and keyboard modifiers are also synced --- include/wlr/types/meson.build | 1 + include/wlr/types/wlr_keyboard.h | 2 + include/wlr/types/wlr_keyboard_group.h | 37 +++ types/meson.build | 1 + types/wlr_keyboard.c | 7 +- types/wlr_keyboard_group.c | 317 +++++++++++++++++++++++++ 6 files changed, 364 insertions(+), 1 deletion(-) create mode 100644 include/wlr/types/wlr_keyboard_group.h create mode 100644 types/wlr_keyboard_group.c diff --git a/include/wlr/types/meson.build b/include/wlr/types/meson.build index 288fd1c5..aee5c6d9 100644 --- a/include/wlr/types/meson.build +++ b/include/wlr/types/meson.build @@ -16,6 +16,7 @@ install_headers( 'wlr_input_inhibitor.h', 'wlr_input_method_v2.h', 'wlr_keyboard.h', + 'wlr_keyboard_group.h', 'wlr_layer_shell_v1.h', 'wlr_linux_dmabuf_v1.h', 'wlr_list.h', diff --git a/include/wlr/types/wlr_keyboard.h b/include/wlr/types/wlr_keyboard.h index e16df7a7..9bd4acd9 100644 --- a/include/wlr/types/wlr_keyboard.h +++ b/include/wlr/types/wlr_keyboard.h @@ -49,6 +49,7 @@ struct wlr_keyboard_modifiers { struct wlr_keyboard { const struct wlr_keyboard_impl *impl; + struct wlr_keyboard_group *group; char *keymap_string; size_t keymap_size; @@ -84,6 +85,7 @@ struct wlr_keyboard { struct wl_signal modifiers; struct wl_signal keymap; struct wl_signal repeat_info; + struct wl_signal destroy; } events; void *data; diff --git a/include/wlr/types/wlr_keyboard_group.h b/include/wlr/types/wlr_keyboard_group.h new file mode 100644 index 00000000..023887f3 --- /dev/null +++ b/include/wlr/types/wlr_keyboard_group.h @@ -0,0 +1,37 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_KEYBOARD_GROUP_H +#define WLR_TYPES_WLR_KEYBOARD_GROUP_H + +#include +#include "wlr/types/wlr_keyboard.h" +#include "wlr/types/wlr_input_device.h" + +struct wlr_keyboard_group { + struct wlr_keyboard keyboard; + struct wlr_input_device *input_device; + struct wl_list devices; // keyboard_group_device::link + struct wl_list keys; // keyboard_group_key::link + void *data; +}; + +struct wlr_keyboard_group *wlr_keyboard_group_create(void); + +struct wlr_keyboard_group *wlr_keyboard_group_from_wlr_keyboard( + struct wlr_keyboard *keyboard); + +bool wlr_keyboard_group_add_keyboard(struct wlr_keyboard_group *group, + struct wlr_keyboard *keyboard); + +void wlr_keyboard_group_remove_keyboard(struct wlr_keyboard_group *group, + struct wlr_keyboard *keyboard); + +void wlr_keyboard_group_destroy(struct wlr_keyboard_group *group); + +#endif diff --git a/types/meson.build b/types/meson.build index 94d37873..6e9c2826 100644 --- a/types/meson.build +++ b/types/meson.build @@ -39,6 +39,7 @@ lib_wlr_types = static_library( 'wlr_input_inhibitor.c', 'wlr_input_method_v2.c', 'wlr_keyboard.c', + 'wlr_keyboard_group.c', 'wlr_layer_shell_v1.c', 'wlr_linux_dmabuf_v1.c', 'wlr_list.c', diff --git a/types/wlr_keyboard.c b/types/wlr_keyboard.c index 1235d0b0..50e09a37 100644 --- a/types/wlr_keyboard.c +++ b/types/wlr_keyboard.c @@ -82,6 +82,8 @@ void wlr_keyboard_notify_modifiers(struct wlr_keyboard *keyboard, if (updated) { wlr_signal_emit_safe(&keyboard->events.modifiers, keyboard); } + + keyboard_led_update(keyboard); } void wlr_keyboard_notify_key(struct wlr_keyboard *keyboard, @@ -98,12 +100,13 @@ void wlr_keyboard_notify_key(struct wlr_keyboard *keyboard, xkb_state_update_key(keyboard->xkb_state, keycode, event->state == WLR_KEY_PRESSED ? XKB_KEY_DOWN : XKB_KEY_UP); } - keyboard_led_update(keyboard); bool updated = keyboard_modifier_update(keyboard); if (updated) { wlr_signal_emit_safe(&keyboard->events.modifiers, keyboard); } + + keyboard_led_update(keyboard); } void wlr_keyboard_init(struct wlr_keyboard *kb, @@ -113,6 +116,7 @@ void wlr_keyboard_init(struct wlr_keyboard *kb, wl_signal_init(&kb->events.modifiers); wl_signal_init(&kb->events.keymap); wl_signal_init(&kb->events.repeat_info); + wl_signal_init(&kb->events.destroy); // Sane defaults kb->repeat_info.rate = 25; @@ -123,6 +127,7 @@ void wlr_keyboard_destroy(struct wlr_keyboard *kb) { if (kb == NULL) { return; } + wlr_signal_emit_safe(&kb->events.destroy, kb); xkb_state_unref(kb->xkb_state); xkb_keymap_unref(kb->keymap); free(kb->keymap_string); diff --git a/types/wlr_keyboard_group.c b/types/wlr_keyboard_group.c new file mode 100644 index 00000000..f7c3c1f6 --- /dev/null +++ b/types/wlr_keyboard_group.c @@ -0,0 +1,317 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include "wlr/interfaces/wlr_keyboard.h" +#include "wlr/types/wlr_keyboard.h" +#include "wlr/types/wlr_keyboard_group.h" +#include "wlr/util/log.h" + +struct keyboard_group_device { + struct wlr_keyboard *keyboard; + struct wl_listener key; + struct wl_listener modifiers; + struct wl_listener keymap; + struct wl_listener repeat_info; + struct wl_listener destroy; + struct wl_list link; // wlr_keyboard_group::devices +}; + +struct keyboard_group_key { + uint32_t keycode; + size_t count; + struct wl_list link; // wlr_keyboard_group::keys +}; + +static void keyboard_set_leds(struct wlr_keyboard *kb, uint32_t leds) { + struct wlr_keyboard_group *group = wlr_keyboard_group_from_wlr_keyboard(kb); + struct keyboard_group_device *device; + wl_list_for_each(device, &group->devices, link) { + wlr_keyboard_led_update(device->keyboard, leds); + } +} + +static void keyboard_destroy(struct wlr_keyboard *kb) { + // Just remove the event listeners. The keyboard will be freed as part of + // the wlr_keyboard_group in wlr_keyboard_group_destroy. + wl_list_remove(&kb->events.key.listener_list); + wl_list_remove(&kb->events.modifiers.listener_list); + wl_list_remove(&kb->events.keymap.listener_list); + wl_list_remove(&kb->events.repeat_info.listener_list); + wl_list_remove(&kb->events.destroy.listener_list); +} + +static const struct wlr_keyboard_impl impl = { + .destroy = keyboard_destroy, + .led_update = keyboard_set_leds +}; + +struct wlr_keyboard_group *wlr_keyboard_group_create(void) { + struct wlr_keyboard_group *group = + calloc(1, sizeof(struct wlr_keyboard_group)); + if (!group) { + wlr_log(WLR_ERROR, "Failed to allocate wlr_keyboard_group"); + return NULL; + } + + group->input_device = calloc(1, sizeof(struct wlr_input_device)); + if (!group->input_device) { + wlr_log(WLR_ERROR, "Failed to allocate wlr_input_device for group"); + free(group); + return NULL; + } + wl_signal_init(&group->input_device->events.destroy); + group->input_device->keyboard = &group->keyboard; + + wlr_keyboard_init(&group->keyboard, &impl); + wl_list_init(&group->devices); + wl_list_init(&group->keys); + + return group; +} + +struct wlr_keyboard_group *wlr_keyboard_group_from_wlr_keyboard( + struct wlr_keyboard *keyboard) { + if (keyboard->impl != &impl) { + return NULL; + } + return (struct wlr_keyboard_group *)keyboard; +} + +static bool keymaps_match(struct xkb_keymap *km1, struct xkb_keymap *km2) { + if (!km1 || !km2) { + return false; + } + char *km1_str = xkb_keymap_get_as_string(km1, XKB_KEYMAP_FORMAT_TEXT_V1); + char *km2_str = xkb_keymap_get_as_string(km2, XKB_KEYMAP_FORMAT_TEXT_V1); + bool result = strcmp(km1_str, km2_str) == 0; + free(km1_str); + free(km2_str); + return result; +} + +static void handle_keyboard_key(struct wl_listener *listener, void *data) { + struct keyboard_group_device *group_device = + wl_container_of(listener, group_device, key); + struct wlr_keyboard_group *group = group_device->keyboard->group; + struct wlr_event_keyboard_key *event = data; + + struct keyboard_group_key *key, *tmp; + wl_list_for_each_safe(key, tmp, &group->keys, link) { + if (key->keycode != event->keycode) { + continue; + } + if (event->state == WLR_KEY_PRESSED) { + key->count++; + return; + } + if (event->state == WLR_KEY_RELEASED) { + key->count--; + if (key->count > 0) { + return; + } + wl_list_remove(&key->link); + free(key); + } + break; + } + + if (event->state == WLR_KEY_PRESSED) { + struct keyboard_group_key *key = + calloc(1, sizeof(struct keyboard_group_key)); + if (!key) { + wlr_log(WLR_ERROR, "Failed to allocate keyboard_group_key"); + return; + } + key->keycode = event->keycode; + key->count = 1; + wl_list_insert(&group->keys, &key->link); + } + + wlr_keyboard_notify_key(&group_device->keyboard->group->keyboard, data); +} + +static void handle_keyboard_modifiers(struct wl_listener *listener, + void *data) { + // Sync the effective layout (group modifier) to all keyboards. The rest of + // the modifiers will be derived from the wlr_keyboard_group's key state + struct keyboard_group_device *group_device = + wl_container_of(listener, group_device, modifiers); + struct wlr_keyboard_modifiers mods = group_device->keyboard->modifiers; + + struct keyboard_group_device *device; + wl_list_for_each(device, &group_device->keyboard->group->devices, link) { + if (mods.depressed != device->keyboard->modifiers.depressed || + mods.latched != device->keyboard->modifiers.latched || + mods.locked != device->keyboard->modifiers.locked || + mods.group != device->keyboard->modifiers.group) { + wlr_keyboard_notify_modifiers(device->keyboard, + mods.depressed, mods.latched, mods.locked, mods.group); + return; + } + } + + wlr_keyboard_notify_modifiers(&group_device->keyboard->group->keyboard, + mods.depressed, mods.latched, mods.locked, mods.group); +} + +static void handle_keyboard_keymap(struct wl_listener *listener, void *data) { + struct keyboard_group_device *group_device = + wl_container_of(listener, group_device, keymap); + struct wlr_keyboard *keyboard = group_device->keyboard; + + if (!keymaps_match(keyboard->group->keyboard.keymap, keyboard->keymap)) { + struct keyboard_group_device *device; + wl_list_for_each(device, &keyboard->group->devices, link) { + if (!keymaps_match(keyboard->keymap, device->keyboard->keymap)) { + wlr_keyboard_set_keymap(device->keyboard, keyboard->keymap); + return; + } + } + } + + wlr_keyboard_set_keymap(&keyboard->group->keyboard, keyboard->keymap); +} + +static void handle_keyboard_repeat_info(struct wl_listener *listener, + void *data) { + struct keyboard_group_device *group_device = + wl_container_of(listener, group_device, repeat_info); + struct wlr_keyboard *keyboard = group_device->keyboard; + + struct keyboard_group_device *device; + wl_list_for_each(device, &keyboard->group->devices, link) { + struct wlr_keyboard *devkb = device->keyboard; + if (devkb->repeat_info.rate != keyboard->repeat_info.rate || + devkb->repeat_info.delay != keyboard->repeat_info.delay) { + wlr_keyboard_set_repeat_info(devkb, keyboard->repeat_info.rate, + keyboard->repeat_info.delay); + return; + } + } + + wlr_keyboard_set_repeat_info(&keyboard->group->keyboard, + keyboard->repeat_info.rate, keyboard->repeat_info.delay); +} + +static void refresh_state(struct keyboard_group_device *device, + enum wlr_key_state state) { + for (size_t i = 0; i < device->keyboard->num_keycodes; i++) { + struct wlr_event_keyboard_key *event = + calloc(1, sizeof(struct wlr_event_keyboard_key)); + if (!event) { + wlr_log(WLR_ERROR, "Failed to allocate wlr_event_keyboard_key"); + continue; // TODO: Handle corrupt state somehow + } + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + event->time_msec = (int64_t)now.tv_sec * 1000 + now.tv_nsec / 1000000; + event->keycode = device->keyboard->keycodes[i]; + event->update_state = true; + event->state = state; + handle_keyboard_key(&device->key, event); + } +} + +static void remove_keyboard_group_device(struct keyboard_group_device *device) { + refresh_state(device, WLR_KEY_RELEASED); + device->keyboard->group = NULL; + wl_list_remove(&device->link); + wl_list_remove(&device->key.link); + wl_list_remove(&device->modifiers.link); + wl_list_remove(&device->keymap.link); + wl_list_remove(&device->repeat_info.link); + wl_list_remove(&device->destroy.link); + free(device); +} + +static void handle_keyboard_destroy(struct wl_listener *listener, void *data) { + struct keyboard_group_device *device = + wl_container_of(listener, device, destroy); + remove_keyboard_group_device(device); +} + +bool wlr_keyboard_group_add_keyboard(struct wlr_keyboard_group *group, + struct wlr_keyboard *keyboard) { + if (keyboard->group) { + wlr_log(WLR_ERROR, "A wlr_keyboard can only belong to one group"); + return false; + } + + if (keyboard->impl == &impl) { + wlr_log(WLR_ERROR, "Cannot add a group's keyboard to a group"); + return false; + } + + if (!keymaps_match(group->keyboard.keymap, keyboard->keymap)) { + wlr_log(WLR_ERROR, "Device keymap does not match keyboard group's"); + return false; + } + + struct keyboard_group_device *device = + calloc(1, sizeof(struct keyboard_group_device)); + if (!device) { + wlr_log(WLR_ERROR, "Failed to allocate keyboard_group_device"); + return false; + } + + device->keyboard = keyboard; + keyboard->group = group; + wl_list_insert(&group->devices, &device->link); + + wl_signal_add(&keyboard->events.key, &device->key); + device->key.notify = handle_keyboard_key; + + wl_signal_add(&keyboard->events.modifiers, &device->modifiers); + device->modifiers.notify = handle_keyboard_modifiers; + + wl_signal_add(&keyboard->events.keymap, &device->keymap); + device->keymap.notify = handle_keyboard_keymap; + + wl_signal_add(&keyboard->events.repeat_info, &device->repeat_info); + device->repeat_info.notify = handle_keyboard_repeat_info; + + wl_signal_add(&keyboard->events.destroy, &device->destroy); + device->destroy.notify = handle_keyboard_destroy; + + struct wlr_keyboard *group_kb = &group->keyboard; + if (keyboard->modifiers.group != group_kb->modifiers.group) { + wlr_keyboard_notify_modifiers(keyboard, keyboard->modifiers.depressed, + keyboard->modifiers.latched, keyboard->modifiers.locked, + group_kb->modifiers.group); + } + if (keyboard->repeat_info.rate != group_kb->repeat_info.rate || + keyboard->repeat_info.delay != group_kb->repeat_info.delay) { + wlr_keyboard_set_repeat_info(keyboard, group_kb->repeat_info.rate, + group_kb->repeat_info.delay); + } + + refresh_state(device, WLR_KEY_PRESSED); + return true; +} + +void wlr_keyboard_group_remove_keyboard(struct wlr_keyboard_group *group, + struct wlr_keyboard *keyboard) { + struct keyboard_group_device *device, *tmp; + wl_list_for_each_safe(device, tmp, &group->devices, link) { + if (device->keyboard == keyboard) { + remove_keyboard_group_device(device); + return; + } + } + wlr_log(WLR_ERROR, "keyboard not found in group"); +} + +void wlr_keyboard_group_destroy(struct wlr_keyboard_group *group) { + struct keyboard_group_device *device, *tmp; + wl_list_for_each_safe(device, tmp, &group->devices, link) { + wlr_keyboard_group_remove_keyboard(group, device->keyboard); + } + wlr_keyboard_destroy(&group->keyboard); + wl_list_remove(&group->input_device->events.destroy.listener_list); + free(group->input_device); + free(group); +}