diff --git a/include/sway/commands.h b/include/sway/commands.h index 45b5b0f4..67665d87 100644 --- a/include/sway/commands.h +++ b/include/sway/commands.h @@ -284,6 +284,7 @@ sway_cmd seat_cmd_attach; sway_cmd seat_cmd_cursor; sway_cmd seat_cmd_fallback; sway_cmd seat_cmd_hide_cursor; +sway_cmd seat_cmd_keyboard_grouping; sway_cmd seat_cmd_pointer_constraint; sway_cmd seat_cmd_xcursor_theme; diff --git a/include/sway/config.h b/include/sway/config.h index 457e0a98..ed542790 100644 --- a/include/sway/config.h +++ b/include/sway/config.h @@ -176,6 +176,12 @@ enum seat_config_allow_constrain { CONSTRAIN_DISABLE }; +enum seat_keyboard_grouping { + KEYBOARD_GROUP_DEFAULT, // the default is currently keymap + KEYBOARD_GROUP_NONE, + KEYBOARD_GROUP_KEYMAP +}; + /** * Options for multiseat and other misc device configurations */ @@ -185,6 +191,7 @@ struct seat_config { list_t *attachments; // list of seat_attachment configs int hide_cursor_timeout; enum seat_config_allow_constrain allow_constrain; + enum seat_keyboard_grouping keyboard_grouping; struct { char *name; int size; diff --git a/include/sway/input/keyboard.h b/include/sway/input/keyboard.h index 4aa0d8f4..72a29ba6 100644 --- a/include/sway/input/keyboard.h +++ b/include/sway/input/keyboard.h @@ -67,6 +67,14 @@ struct sway_keyboard { struct sway_binding *repeat_binding; }; +struct sway_keyboard_group { + struct wlr_keyboard_group *wlr_group; + struct sway_seat_device *seat_device; + struct wl_listener keyboard_key; + struct wl_listener keyboard_modifiers; + struct wl_list link; // sway_seat::keyboard_groups +}; + struct xkb_keymap *sway_keyboard_compile_keymap(struct input_config *ic, char **error); diff --git a/include/sway/input/seat.h b/include/sway/input/seat.h index 24a6fed4..32795b03 100644 --- a/include/sway/input/seat.h +++ b/include/sway/input/seat.h @@ -90,6 +90,7 @@ struct sway_seat { struct wl_listener request_set_primary_selection; struct wl_list devices; // sway_seat_device::link + struct wl_list keyboard_groups; // sway_keyboard_group::link struct wl_list link; // input_manager::seats }; diff --git a/sway/commands/seat.c b/sway/commands/seat.c index 197a405e..a2a3fbc4 100644 --- a/sway/commands/seat.c +++ b/sway/commands/seat.c @@ -18,6 +18,7 @@ static struct cmd_handler seat_handlers[] = { { "attach", seat_cmd_attach }, { "fallback", seat_cmd_fallback }, { "hide_cursor", seat_cmd_hide_cursor }, + { "keyboard_grouping", seat_cmd_keyboard_grouping }, { "pointer_constraint", seat_cmd_pointer_constraint }, { "xcursor_theme", seat_cmd_xcursor_theme }, }; diff --git a/sway/commands/seat/keyboard_grouping.c b/sway/commands/seat/keyboard_grouping.c new file mode 100644 index 00000000..959c6f94 --- /dev/null +++ b/sway/commands/seat/keyboard_grouping.c @@ -0,0 +1,26 @@ +#include +#include "sway/commands.h" +#include "sway/config.h" +#include "stringop.h" + +struct cmd_results *seat_cmd_keyboard_grouping(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "keyboard_grouping", EXPECTED_EQUAL_TO, 1))) { + return error; + } + if (!config->handler_context.seat_config) { + return cmd_results_new(CMD_INVALID, "No seat defined"); + } + + struct seat_config *seat_config = config->handler_context.seat_config; + if (strcmp(argv[0], "none") == 0) { + seat_config->keyboard_grouping = KEYBOARD_GROUP_NONE; + } else if (strcmp(argv[0], "keymap") == 0) { + seat_config->keyboard_grouping = KEYBOARD_GROUP_KEYMAP; + } else { + return cmd_results_new(CMD_INVALID, + "Expected syntax `keyboard_grouping none|keymap`"); + } + + return cmd_results_new(CMD_SUCCESS, NULL); +} diff --git a/sway/config/seat.c b/sway/config/seat.c index d4190cec..d2401162 100644 --- a/sway/config/seat.c +++ b/sway/config/seat.c @@ -27,6 +27,7 @@ struct seat_config *new_seat_config(const char* name) { } seat->hide_cursor_timeout = -1; seat->allow_constrain = CONSTRAIN_DEFAULT; + seat->keyboard_grouping = KEYBOARD_GROUP_DEFAULT; seat->xcursor_theme.name = NULL; seat->xcursor_theme.size = 24; @@ -150,6 +151,10 @@ void merge_seat_config(struct seat_config *dest, struct seat_config *source) { dest->allow_constrain = source->allow_constrain; } + if (source->keyboard_grouping != KEYBOARD_GROUP_DEFAULT) { + dest->keyboard_grouping = source->keyboard_grouping; + } + if (source->xcursor_theme.name != NULL) { free(dest->xcursor_theme.name); dest->xcursor_theme.name = strdup(source->xcursor_theme.name); diff --git a/sway/input/input-manager.c b/sway/input/input-manager.c index 4f9ed891..cfd39bab 100644 --- a/sway/input/input-manager.c +++ b/sway/input/input-manager.c @@ -47,7 +47,7 @@ struct sway_seat *input_manager_get_seat(const char *seat_name, bool create) { char *input_device_get_identifier(struct wlr_input_device *device) { int vendor = device->vendor; int product = device->product; - char *name = strdup(device->name); + char *name = strdup(device->name ? device->name : ""); strip_whitespace(name); char *p = name; diff --git a/sway/input/keyboard.c b/sway/input/keyboard.c index cdc4258d..e925c00d 100644 --- a/sway/input/keyboard.c +++ b/sway/input/keyboard.c @@ -3,8 +3,9 @@ #include #include #include -#include #include +#include +#include #include #include "sway/commands.h" #include "sway/desktop/transaction.h" @@ -150,7 +151,7 @@ static bool update_shortcut_state(struct sway_shortcut_state *state, static void get_active_binding(const struct sway_shortcut_state *state, list_t *bindings, struct sway_binding **current_binding, uint32_t modifiers, bool release, bool locked, const char *input, - xkb_layout_index_t group) { + bool exact_input, xkb_layout_index_t group) { for (int i = 0; i < bindings->length; ++i) { struct sway_binding *binding = bindings->items[i]; bool binding_locked = (binding->flags & BINDING_LOCKED) != 0; @@ -162,7 +163,7 @@ static void get_active_binding(const struct sway_shortcut_state *state, (binding->group != XKB_LAYOUT_INVALID && binding->group != group) || (strcmp(binding->input, input) != 0 && - strcmp(binding->input, "*") != 0)) { + (strcmp(binding->input, "*") != 0 || exact_input))) { continue; } @@ -317,16 +318,15 @@ void sway_keyboard_disarm_key_repeat(struct sway_keyboard *keyboard) { } } -static void handle_keyboard_key(struct wl_listener *listener, void *data) { - struct sway_keyboard *keyboard = - wl_container_of(listener, keyboard, keyboard_key); +static void handle_key_event(struct sway_keyboard *keyboard, + struct wlr_event_keyboard_key *event) { struct sway_seat* seat = keyboard->seat_device->sway_seat; struct wlr_seat *wlr_seat = seat->wlr_seat; struct wlr_input_device *wlr_device = keyboard->seat_device->input_device->wlr_device; char *device_identifier = input_device_get_identifier(wlr_device); + bool exact_identifier = wlr_device->keyboard->group != NULL; wlr_idle_notify_activity(server.idle, wlr_seat); - struct wlr_event_keyboard_key *event = data; bool input_inhibited = seat->exclusive_client != NULL; // Identify new keycode, raw keysym(s), and translated keysym(s) @@ -360,21 +360,20 @@ static void handle_keyboard_key(struct wl_listener *listener, void *data) { } bool handled = false; - // Identify active release binding struct sway_binding *binding_released = NULL; get_active_binding(&keyboard->state_keycodes, config->current_mode->keycode_bindings, &binding_released, code_modifiers, true, input_inhibited, device_identifier, - keyboard->effective_layout); + exact_identifier, keyboard->effective_layout); get_active_binding(&keyboard->state_keysyms_raw, config->current_mode->keysym_bindings, &binding_released, raw_modifiers, true, input_inhibited, device_identifier, - keyboard->effective_layout); + exact_identifier, keyboard->effective_layout); get_active_binding(&keyboard->state_keysyms_translated, config->current_mode->keysym_bindings, &binding_released, translated_modifiers, true, input_inhibited, device_identifier, - keyboard->effective_layout); + exact_identifier, keyboard->effective_layout); // Execute stored release binding once no longer active if (keyboard->held_binding && binding_released != keyboard->held_binding && @@ -395,15 +394,16 @@ static void handle_keyboard_key(struct wl_listener *listener, void *data) { get_active_binding(&keyboard->state_keycodes, config->current_mode->keycode_bindings, &binding, code_modifiers, false, input_inhibited, device_identifier, - keyboard->effective_layout); + exact_identifier, keyboard->effective_layout); get_active_binding(&keyboard->state_keysyms_raw, config->current_mode->keysym_bindings, &binding, raw_modifiers, false, input_inhibited, device_identifier, - keyboard->effective_layout); + exact_identifier, keyboard->effective_layout); get_active_binding(&keyboard->state_keysyms_translated, config->current_mode->keysym_bindings, &binding, translated_modifiers, false, input_inhibited, - device_identifier, keyboard->effective_layout); + device_identifier, exact_identifier, + keyboard->effective_layout); } // Set up (or clear) keyboard repeat for a pressed binding. Since the @@ -423,6 +423,12 @@ static void handle_keyboard_key(struct wl_listener *listener, void *data) { handled = true; } + if (!handled && wlr_device->keyboard->group) { + // Only handle device specific bindings for keyboards in a group + free(device_identifier); + return; + } + // Compositor bindings if (!handled && event->state == WLR_KEY_PRESSED) { handled = keyboard_execute_compositor_binding( @@ -450,6 +456,19 @@ static void handle_keyboard_key(struct wl_listener *listener, void *data) { free(device_identifier); } +static void handle_keyboard_key(struct wl_listener *listener, void *data) { + struct sway_keyboard *keyboard = + wl_container_of(listener, keyboard, keyboard_key); + handle_key_event(keyboard, data); +} + +static void handle_keyboard_group_key(struct wl_listener *listener, + void *data) { + struct sway_keyboard_group *sway_group = + wl_container_of(listener, sway_group, keyboard_key); + handle_key_event(sway_group->seat_device->keyboard, data); +} + static int handle_keyboard_repeat(void *data) { struct sway_keyboard *keyboard = (struct sway_keyboard *)data; struct wlr_keyboard *wlr_device = @@ -491,25 +510,40 @@ static void determine_bar_visibility(uint32_t modifiers) { } } -static void handle_keyboard_modifiers(struct wl_listener *listener, - void *data) { - struct sway_keyboard *keyboard = - wl_container_of(listener, keyboard, keyboard_modifiers); - struct wlr_seat *wlr_seat = keyboard->seat_device->sway_seat->wlr_seat; +static void handle_modifier_event(struct sway_keyboard *keyboard) { struct wlr_input_device *wlr_device = keyboard->seat_device->input_device->wlr_device; - wlr_seat_set_keyboard(wlr_seat, wlr_device); - wlr_seat_keyboard_notify_modifiers(wlr_seat, &wlr_device->keyboard->modifiers); + if (!wlr_device->keyboard->group) { + struct wlr_seat *wlr_seat = keyboard->seat_device->sway_seat->wlr_seat; + wlr_seat_set_keyboard(wlr_seat, wlr_device); + wlr_seat_keyboard_notify_modifiers(wlr_seat, + &wlr_device->keyboard->modifiers); - uint32_t modifiers = wlr_keyboard_get_modifiers(wlr_device->keyboard); - determine_bar_visibility(modifiers); + uint32_t modifiers = wlr_keyboard_get_modifiers(wlr_device->keyboard); + determine_bar_visibility(modifiers); + } - if (wlr_device->keyboard->modifiers.group != keyboard->effective_layout) { + if (wlr_device->keyboard->modifiers.group != keyboard->effective_layout && + !wlr_keyboard_group_from_wlr_keyboard(wlr_device->keyboard)) { keyboard->effective_layout = wlr_device->keyboard->modifiers.group; ipc_event_input("xkb_layout", keyboard->seat_device->input_device); } } +static void handle_keyboard_modifiers(struct wl_listener *listener, + void *data) { + struct sway_keyboard *keyboard = + wl_container_of(listener, keyboard, keyboard_modifiers); + handle_modifier_event(keyboard); +} + +static void handle_keyboard_group_modifiers(struct wl_listener *listener, + void *data) { + struct sway_keyboard_group *group = + wl_container_of(listener, group, keyboard_modifiers); + handle_modifier_event(group->seat_device->keyboard); +} + struct sway_keyboard *sway_keyboard_create(struct sway_seat *seat, struct sway_seat_device *device) { struct sway_keyboard *keyboard = @@ -616,6 +650,163 @@ cleanup: return keymap; } +static bool keymaps_match(struct xkb_keymap *km1, struct xkb_keymap *km2) { + 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 sway_keyboard_group_remove(struct sway_keyboard *keyboard) { + struct sway_input_device *device = keyboard->seat_device->input_device; + struct wlr_keyboard *wlr_keyboard = device->wlr_device->keyboard; + struct wlr_keyboard_group *wlr_group = wlr_keyboard->group; + + sway_log(SWAY_DEBUG, "Removing keyboard %s from group %p", + device->identifier, wlr_group); + + wlr_keyboard_group_remove_keyboard(wlr_keyboard->group, wlr_keyboard); + + if (wl_list_empty(&wlr_group->devices)) { + sway_log(SWAY_DEBUG, "Destroying empty keyboard group %p", + wlr_group); + struct sway_keyboard_group *sway_group = wlr_group->data; + wlr_group->data = NULL; + wl_list_remove(&sway_group->link); + wl_list_remove(&sway_group->keyboard_key.link); + wl_list_remove(&sway_group->keyboard_modifiers.link); + free(sway_group->seat_device->keyboard); + free(sway_group->seat_device->input_device); + free(sway_group->seat_device); + free(sway_group); + wlr_keyboard_group_destroy(wlr_group); + } +} + +static void sway_keyboard_group_remove_invalid(struct sway_keyboard *keyboard) { + struct sway_input_device *device = keyboard->seat_device->input_device; + struct wlr_keyboard *wlr_keyboard = device->wlr_device->keyboard; + if (!wlr_keyboard->group) { + return; + } + + struct sway_seat *seat = keyboard->seat_device->sway_seat; + struct seat_config *sc = seat_get_config(seat); + if (!sc) { + sc = seat_get_config_by_name("*"); + } + + switch (sc ? sc->keyboard_grouping : KEYBOARD_GROUP_DEFAULT) { + case KEYBOARD_GROUP_NONE: + sway_keyboard_group_remove(keyboard); + break; + case KEYBOARD_GROUP_DEFAULT: /* fallthrough */ + case KEYBOARD_GROUP_KEYMAP:; + struct wlr_keyboard_group *group = wlr_keyboard->group; + if (!keymaps_match(keyboard->keymap, group->keyboard.keymap)) { + sway_keyboard_group_remove(keyboard); + } + break; + } +} + +static void sway_keyboard_group_add(struct sway_keyboard *keyboard) { + struct sway_input_device *device = keyboard->seat_device->input_device; + struct wlr_keyboard *wlr_keyboard = device->wlr_device->keyboard; + struct sway_seat *seat = keyboard->seat_device->sway_seat; + struct seat_config *sc = seat_get_config(seat); + if (!sc) { + sc = seat_get_config_by_name("*"); + } + + if (sc && sc->keyboard_grouping == KEYBOARD_GROUP_NONE) { + // Keyboard grouping is disabled for the seat + return; + } + + struct sway_keyboard_group *group; + wl_list_for_each(group, &seat->keyboard_groups, link) { + switch (sc ? sc->keyboard_grouping : KEYBOARD_GROUP_DEFAULT) { + case KEYBOARD_GROUP_NONE: + // Nothing to do. This shouldn't even be reached + return; + case KEYBOARD_GROUP_DEFAULT: /* fallthrough */ + case KEYBOARD_GROUP_KEYMAP:; + struct wlr_keyboard_group *wlr_group = group->wlr_group; + if (keymaps_match(keyboard->keymap, wlr_group->keyboard.keymap)) { + sway_log(SWAY_DEBUG, "Adding keyboard %s to group %p", + device->identifier, wlr_group); + wlr_keyboard_group_add_keyboard(wlr_group, wlr_keyboard); + return; + } + break; + } + } + + struct sway_keyboard_group *sway_group = + calloc(1, sizeof(struct sway_keyboard_group)); + if (!sway_group) { + sway_log(SWAY_ERROR, "Failed to allocate sway_keyboard_group"); + return; + } + + sway_group->wlr_group = wlr_keyboard_group_create(); + if (!sway_group->wlr_group) { + sway_log(SWAY_ERROR, "Failed to create keyboard group"); + goto cleanup; + } + sway_group->wlr_group->data = sway_group; + wlr_keyboard_set_keymap(&sway_group->wlr_group->keyboard, keyboard->keymap); + sway_log(SWAY_DEBUG, "Created keyboard group %p", sway_group->wlr_group); + + sway_group->seat_device = calloc(1, sizeof(struct sway_seat_device)); + if (!sway_group->seat_device) { + sway_log(SWAY_ERROR, "Failed to allocate sway_seat_device for group"); + goto cleanup; + } + sway_group->seat_device->sway_seat = seat; + + sway_group->seat_device->input_device = + calloc(1, sizeof(struct sway_input_device)); + if (!sway_group->seat_device->input_device) { + sway_log(SWAY_ERROR, "Failed to allocate sway_input_device for group"); + goto cleanup; + } + sway_group->seat_device->input_device->wlr_device = + sway_group->wlr_group->input_device; + + if (!sway_keyboard_create(seat, sway_group->seat_device)) { + sway_log(SWAY_ERROR, "Failed to allocate sway_keyboard for group"); + goto cleanup; + } + + sway_log(SWAY_DEBUG, "Adding keyboard %s to group %p", + device->identifier, sway_group->wlr_group); + wlr_keyboard_group_add_keyboard(sway_group->wlr_group, wlr_keyboard); + + wl_list_insert(&seat->keyboard_groups, &sway_group->link); + + wl_signal_add(&sway_group->wlr_group->keyboard.events.key, + &sway_group->keyboard_key); + sway_group->keyboard_key.notify = handle_keyboard_group_key; + + wl_signal_add(&sway_group->wlr_group->keyboard.events.modifiers, + &sway_group->keyboard_modifiers); + sway_group->keyboard_modifiers.notify = handle_keyboard_group_modifiers; + return; + +cleanup: + if (sway_group && sway_group->wlr_group) { + wlr_keyboard_group_destroy(sway_group->wlr_group); + } + free(sway_group->seat_device->keyboard); + free(sway_group->seat_device->input_device); + free(sway_group->seat_device); + free(sway_group); +} + void sway_keyboard_configure(struct sway_keyboard *keyboard) { struct input_config *input_config = input_device_get_config(keyboard->seat_device->input_device); @@ -633,26 +824,23 @@ void sway_keyboard_configure(struct sway_keyboard *keyboard) { } } - bool keymap_changed = false; + bool keymap_changed = + keyboard->keymap ? !keymaps_match(keyboard->keymap, keymap) : true; bool effective_layout_changed = keyboard->effective_layout != 0; - if (keyboard->keymap) { - char *old_keymap_string = xkb_keymap_get_as_string(keyboard->keymap, - XKB_KEYMAP_FORMAT_TEXT_V1); - char *new_keymap_string = xkb_keymap_get_as_string(keymap, - XKB_KEYMAP_FORMAT_TEXT_V1); - keymap_changed = strcmp(old_keymap_string, new_keymap_string); - free(old_keymap_string); - free(new_keymap_string); - } else { - keymap_changed = true; - } if (keymap_changed || config->reloading) { xkb_keymap_unref(keyboard->keymap); keyboard->keymap = keymap; keyboard->effective_layout = 0; + + sway_keyboard_group_remove_invalid(keyboard); + wlr_keyboard_set_keymap(wlr_device->keyboard, keyboard->keymap); + if (!wlr_device->keyboard->group) { + sway_keyboard_group_add(keyboard); + } + xkb_mod_mask_t locked_mods = 0; if (input_config && input_config->xkb_numlock > 0) { xkb_mod_index_t mod_index = xkb_map_mod_get_index(keymap, @@ -679,10 +867,19 @@ void sway_keyboard_configure(struct sway_keyboard *keyboard) { leds |= (1 << i); } } - wlr_keyboard_led_update(wlr_device->keyboard, leds); + if (wlr_device->keyboard->group) { + wlr_keyboard_led_update( + &wlr_device->keyboard->group->keyboard, leds); + } else { + wlr_keyboard_led_update(wlr_device->keyboard, leds); + } } } else { xkb_keymap_unref(keymap); + sway_keyboard_group_remove_invalid(keyboard); + if (!wlr_device->keyboard->group) { + sway_keyboard_group_add(keyboard); + } } int repeat_rate = 25; @@ -721,6 +918,7 @@ void sway_keyboard_destroy(struct sway_keyboard *keyboard) { if (!keyboard) { return; } + sway_keyboard_group_remove(keyboard); if (keyboard->keymap) { xkb_keymap_unref(keyboard->keymap); } diff --git a/sway/input/seat.c b/sway/input/seat.c index f486d5e7..fb3e68ee 100644 --- a/sway/input/seat.c +++ b/sway/input/seat.c @@ -102,6 +102,14 @@ static struct sway_keyboard *sway_keyboard_for_wlr_keyboard( return seat_device->keyboard; } } + struct sway_keyboard_group *group; + wl_list_for_each(group, &seat->keyboard_groups, link) { + struct sway_input_device *input_device = + group->seat_device->input_device; + if (input_device->wlr_device->keyboard == wlr_keyboard) { + return group->seat_device->keyboard; + } + } return NULL; } @@ -519,6 +527,7 @@ struct sway_seat *seat_create(const char *seat_name) { handle_request_set_primary_selection; wl_list_init(&seat->devices); + wl_list_init(&seat->keyboard_groups); wl_list_insert(&server.input->seats, &seat->link); diff --git a/sway/meson.build b/sway/meson.build index e285c09e..5458d3dc 100644 --- a/sway/meson.build +++ b/sway/meson.build @@ -91,6 +91,7 @@ sway_sources = files( 'commands/seat/cursor.c', 'commands/seat/fallback.c', 'commands/seat/hide_cursor.c', + 'commands/seat/keyboard_grouping.c', 'commands/seat/pointer_constraint.c', 'commands/seat/xcursor_theme.c', 'commands/set.c', diff --git a/sway/sway-input.5.scd b/sway/sway-input.5.scd index d0bf648b..5631293c 100644 --- a/sway/sway-input.5.scd +++ b/sway/sway-input.5.scd @@ -218,6 +218,16 @@ correct seat. disables hiding the cursor. The minimal timeout is 100 and any value less than that (aside from 0), will be increased to 100. +*seat* keyboard_grouping none|keymap + Set how the keyboards in the seat are grouped together. Currently, there + are two options. _none_ will disable all keyboard grouping. This will make + it so each keyboard device has its own isolated state. _keymap_ will + group the keyboards in the seat by their keymap. This is useful for when + the keyboard appears as multiple separate input devices. In this mode, + the effective layout and repeat info are also synced between the keyboards + in the group. The default is _keymap_. To restore the behavior of older + versions of sway, use _none_. + *seat* pointer_constraint enable|disable|escape Enables or disables the ability for clients to capture the cursor (enabled by default) for the seat. This is primarily useful for video games. The