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); +}