aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Brian Ashworth <bosrsf04@gmail.com>2019-11-03 14:20:05 -0500
committerLibravatar Drew DeVault <sir@cmpwn.com>2019-11-21 10:42:10 -0500
commit5d882cb5fc2d9d9fd68439021e48a90aa2e50e79 (patch)
treec519449aebb2fa15406407eee5af3cd4164e1b62
parentinput_cmd_xkb_file: allow shell path expansion (diff)
downloadsway-5d882cb5fc2d9d9fd68439021e48a90aa2e50e79.tar.gz
sway-5d882cb5fc2d9d9fd68439021e48a90aa2e50e79.tar.zst
sway-5d882cb5fc2d9d9fd68439021e48a90aa2e50e79.zip
Add support for wlr_keyboard_group
A wlr_keyboard_group allows for multiple keyboard devices to be combined into one logical keyboard. This is useful for keyboards that are split into multiple input devices despite appearing as one physical keyboard in the user's mind. This adds support for wlr_keyboard_groups to sway. There are two keyboard groupings currently supported, which can be set on a per-seat basis. The first keyboard grouping is none, which disables all grouping and provides no functional change. The second is keymap, which groups the keyboard devices in the seat by their keymap. With this grouping, the effective layout and repeat info is also synced across keyboard devices in the seat. Device specific bindings will still be executed as normal, but everything else related to key and modifier events will be handled by the keyboard group's keyboard.
-rw-r--r--include/sway/commands.h1
-rw-r--r--include/sway/config.h7
-rw-r--r--include/sway/input/keyboard.h8
-rw-r--r--include/sway/input/seat.h1
-rw-r--r--sway/commands/seat.c1
-rw-r--r--sway/commands/seat/keyboard_grouping.c26
-rw-r--r--sway/config/seat.c5
-rw-r--r--sway/input/input-manager.c2
-rw-r--r--sway/input/keyboard.c272
-rw-r--r--sway/input/seat.c9
-rw-r--r--sway/meson.build1
-rw-r--r--sway/sway-input.5.scd10
12 files changed, 305 insertions, 38 deletions
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;
284sway_cmd seat_cmd_cursor; 284sway_cmd seat_cmd_cursor;
285sway_cmd seat_cmd_fallback; 285sway_cmd seat_cmd_fallback;
286sway_cmd seat_cmd_hide_cursor; 286sway_cmd seat_cmd_hide_cursor;
287sway_cmd seat_cmd_keyboard_grouping;
287sway_cmd seat_cmd_pointer_constraint; 288sway_cmd seat_cmd_pointer_constraint;
288sway_cmd seat_cmd_xcursor_theme; 289sway_cmd seat_cmd_xcursor_theme;
289 290
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 {
176 CONSTRAIN_DISABLE 176 CONSTRAIN_DISABLE
177}; 177};
178 178
179enum seat_keyboard_grouping {
180 KEYBOARD_GROUP_DEFAULT, // the default is currently keymap
181 KEYBOARD_GROUP_NONE,
182 KEYBOARD_GROUP_KEYMAP
183};
184
179/** 185/**
180 * Options for multiseat and other misc device configurations 186 * Options for multiseat and other misc device configurations
181 */ 187 */
@@ -185,6 +191,7 @@ struct seat_config {
185 list_t *attachments; // list of seat_attachment configs 191 list_t *attachments; // list of seat_attachment configs
186 int hide_cursor_timeout; 192 int hide_cursor_timeout;
187 enum seat_config_allow_constrain allow_constrain; 193 enum seat_config_allow_constrain allow_constrain;
194 enum seat_keyboard_grouping keyboard_grouping;
188 struct { 195 struct {
189 char *name; 196 char *name;
190 int size; 197 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 {
67 struct sway_binding *repeat_binding; 67 struct sway_binding *repeat_binding;
68}; 68};
69 69
70struct sway_keyboard_group {
71 struct wlr_keyboard_group *wlr_group;
72 struct sway_seat_device *seat_device;
73 struct wl_listener keyboard_key;
74 struct wl_listener keyboard_modifiers;
75 struct wl_list link; // sway_seat::keyboard_groups
76};
77
70struct xkb_keymap *sway_keyboard_compile_keymap(struct input_config *ic, 78struct xkb_keymap *sway_keyboard_compile_keymap(struct input_config *ic,
71 char **error); 79 char **error);
72 80
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 {
90 struct wl_listener request_set_primary_selection; 90 struct wl_listener request_set_primary_selection;
91 91
92 struct wl_list devices; // sway_seat_device::link 92 struct wl_list devices; // sway_seat_device::link
93 struct wl_list keyboard_groups; // sway_keyboard_group::link
93 94
94 struct wl_list link; // input_manager::seats 95 struct wl_list link; // input_manager::seats
95}; 96};
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[] = {
18 { "attach", seat_cmd_attach }, 18 { "attach", seat_cmd_attach },
19 { "fallback", seat_cmd_fallback }, 19 { "fallback", seat_cmd_fallback },
20 { "hide_cursor", seat_cmd_hide_cursor }, 20 { "hide_cursor", seat_cmd_hide_cursor },
21 { "keyboard_grouping", seat_cmd_keyboard_grouping },
21 { "pointer_constraint", seat_cmd_pointer_constraint }, 22 { "pointer_constraint", seat_cmd_pointer_constraint },
22 { "xcursor_theme", seat_cmd_xcursor_theme }, 23 { "xcursor_theme", seat_cmd_xcursor_theme },
23}; 24};
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 @@
1#include <string.h>
2#include "sway/commands.h"
3#include "sway/config.h"
4#include "stringop.h"
5
6struct cmd_results *seat_cmd_keyboard_grouping(int argc, char **argv) {
7 struct cmd_results *error = NULL;
8 if ((error = checkarg(argc, "keyboard_grouping", EXPECTED_EQUAL_TO, 1))) {
9 return error;
10 }
11 if (!config->handler_context.seat_config) {
12 return cmd_results_new(CMD_INVALID, "No seat defined");
13 }
14
15 struct seat_config *seat_config = config->handler_context.seat_config;
16 if (strcmp(argv[0], "none") == 0) {
17 seat_config->keyboard_grouping = KEYBOARD_GROUP_NONE;
18 } else if (strcmp(argv[0], "keymap") == 0) {
19 seat_config->keyboard_grouping = KEYBOARD_GROUP_KEYMAP;
20 } else {
21 return cmd_results_new(CMD_INVALID,
22 "Expected syntax `keyboard_grouping none|keymap`");
23 }
24
25 return cmd_results_new(CMD_SUCCESS, NULL);
26}
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) {
27 } 27 }
28 seat->hide_cursor_timeout = -1; 28 seat->hide_cursor_timeout = -1;
29 seat->allow_constrain = CONSTRAIN_DEFAULT; 29 seat->allow_constrain = CONSTRAIN_DEFAULT;
30 seat->keyboard_grouping = KEYBOARD_GROUP_DEFAULT;
30 seat->xcursor_theme.name = NULL; 31 seat->xcursor_theme.name = NULL;
31 seat->xcursor_theme.size = 24; 32 seat->xcursor_theme.size = 24;
32 33
@@ -150,6 +151,10 @@ void merge_seat_config(struct seat_config *dest, struct seat_config *source) {
150 dest->allow_constrain = source->allow_constrain; 151 dest->allow_constrain = source->allow_constrain;
151 } 152 }
152 153
154 if (source->keyboard_grouping != KEYBOARD_GROUP_DEFAULT) {
155 dest->keyboard_grouping = source->keyboard_grouping;
156 }
157
153 if (source->xcursor_theme.name != NULL) { 158 if (source->xcursor_theme.name != NULL) {
154 free(dest->xcursor_theme.name); 159 free(dest->xcursor_theme.name);
155 dest->xcursor_theme.name = strdup(source->xcursor_theme.name); 160 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) {
47char *input_device_get_identifier(struct wlr_input_device *device) { 47char *input_device_get_identifier(struct wlr_input_device *device) {
48 int vendor = device->vendor; 48 int vendor = device->vendor;
49 int product = device->product; 49 int product = device->product;
50 char *name = strdup(device->name); 50 char *name = strdup(device->name ? device->name : "");
51 strip_whitespace(name); 51 strip_whitespace(name);
52 52
53 char *p = name; 53 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 @@
3#include <strings.h> 3#include <strings.h>
4#include <wlr/backend/multi.h> 4#include <wlr/backend/multi.h>
5#include <wlr/backend/session.h> 5#include <wlr/backend/session.h>
6#include <wlr/types/wlr_idle.h>
7#include <wlr/interfaces/wlr_keyboard.h> 6#include <wlr/interfaces/wlr_keyboard.h>
7#include <wlr/types/wlr_idle.h>
8#include <wlr/types/wlr_keyboard_group.h>
8#include <xkbcommon/xkbcommon-names.h> 9#include <xkbcommon/xkbcommon-names.h>
9#include "sway/commands.h" 10#include "sway/commands.h"
10#include "sway/desktop/transaction.h" 11#include "sway/desktop/transaction.h"
@@ -150,7 +151,7 @@ static bool update_shortcut_state(struct sway_shortcut_state *state,
150static void get_active_binding(const struct sway_shortcut_state *state, 151static void get_active_binding(const struct sway_shortcut_state *state,
151 list_t *bindings, struct sway_binding **current_binding, 152 list_t *bindings, struct sway_binding **current_binding,
152 uint32_t modifiers, bool release, bool locked, const char *input, 153 uint32_t modifiers, bool release, bool locked, const char *input,
153 xkb_layout_index_t group) { 154 bool exact_input, xkb_layout_index_t group) {
154 for (int i = 0; i < bindings->length; ++i) { 155 for (int i = 0; i < bindings->length; ++i) {
155 struct sway_binding *binding = bindings->items[i]; 156 struct sway_binding *binding = bindings->items[i];
156 bool binding_locked = (binding->flags & BINDING_LOCKED) != 0; 157 bool binding_locked = (binding->flags & BINDING_LOCKED) != 0;
@@ -162,7 +163,7 @@ static void get_active_binding(const struct sway_shortcut_state *state,
162 (binding->group != XKB_LAYOUT_INVALID && 163 (binding->group != XKB_LAYOUT_INVALID &&
163 binding->group != group) || 164 binding->group != group) ||
164 (strcmp(binding->input, input) != 0 && 165 (strcmp(binding->input, input) != 0 &&
165 strcmp(binding->input, "*") != 0)) { 166 (strcmp(binding->input, "*") != 0 || exact_input))) {
166 continue; 167 continue;
167 } 168 }
168 169
@@ -317,16 +318,15 @@ void sway_keyboard_disarm_key_repeat(struct sway_keyboard *keyboard) {
317 } 318 }
318} 319}
319 320
320static void handle_keyboard_key(struct wl_listener *listener, void *data) { 321static void handle_key_event(struct sway_keyboard *keyboard,
321 struct sway_keyboard *keyboard = 322 struct wlr_event_keyboard_key *event) {
322 wl_container_of(listener, keyboard, keyboard_key);
323 struct sway_seat* seat = keyboard->seat_device->sway_seat; 323 struct sway_seat* seat = keyboard->seat_device->sway_seat;
324 struct wlr_seat *wlr_seat = seat->wlr_seat; 324 struct wlr_seat *wlr_seat = seat->wlr_seat;
325 struct wlr_input_device *wlr_device = 325 struct wlr_input_device *wlr_device =
326 keyboard->seat_device->input_device->wlr_device; 326 keyboard->seat_device->input_device->wlr_device;
327 char *device_identifier = input_device_get_identifier(wlr_device); 327 char *device_identifier = input_device_get_identifier(wlr_device);
328 bool exact_identifier = wlr_device->keyboard->group != NULL;
328 wlr_idle_notify_activity(server.idle, wlr_seat); 329 wlr_idle_notify_activity(server.idle, wlr_seat);
329 struct wlr_event_keyboard_key *event = data;
330 bool input_inhibited = seat->exclusive_client != NULL; 330 bool input_inhibited = seat->exclusive_client != NULL;
331 331
332 // Identify new keycode, raw keysym(s), and translated keysym(s) 332 // 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) {
360 } 360 }
361 361
362 bool handled = false; 362 bool handled = false;
363
364 // Identify active release binding 363 // Identify active release binding
365 struct sway_binding *binding_released = NULL; 364 struct sway_binding *binding_released = NULL;
366 get_active_binding(&keyboard->state_keycodes, 365 get_active_binding(&keyboard->state_keycodes,
367 config->current_mode->keycode_bindings, &binding_released, 366 config->current_mode->keycode_bindings, &binding_released,
368 code_modifiers, true, input_inhibited, device_identifier, 367 code_modifiers, true, input_inhibited, device_identifier,
369 keyboard->effective_layout); 368 exact_identifier, keyboard->effective_layout);
370 get_active_binding(&keyboard->state_keysyms_raw, 369 get_active_binding(&keyboard->state_keysyms_raw,
371 config->current_mode->keysym_bindings, &binding_released, 370 config->current_mode->keysym_bindings, &binding_released,
372 raw_modifiers, true, input_inhibited, device_identifier, 371 raw_modifiers, true, input_inhibited, device_identifier,
373 keyboard->effective_layout); 372 exact_identifier, keyboard->effective_layout);
374 get_active_binding(&keyboard->state_keysyms_translated, 373 get_active_binding(&keyboard->state_keysyms_translated,
375 config->current_mode->keysym_bindings, &binding_released, 374 config->current_mode->keysym_bindings, &binding_released,
376 translated_modifiers, true, input_inhibited, device_identifier, 375 translated_modifiers, true, input_inhibited, device_identifier,
377 keyboard->effective_layout); 376 exact_identifier, keyboard->effective_layout);
378 377
379 // Execute stored release binding once no longer active 378 // Execute stored release binding once no longer active
380 if (keyboard->held_binding && binding_released != keyboard->held_binding && 379 if (keyboard->held_binding && binding_released != keyboard->held_binding &&
@@ -395,15 +394,16 @@ static void handle_keyboard_key(struct wl_listener *listener, void *data) {
395 get_active_binding(&keyboard->state_keycodes, 394 get_active_binding(&keyboard->state_keycodes,
396 config->current_mode->keycode_bindings, &binding, 395 config->current_mode->keycode_bindings, &binding,
397 code_modifiers, false, input_inhibited, device_identifier, 396 code_modifiers, false, input_inhibited, device_identifier,
398 keyboard->effective_layout); 397 exact_identifier, keyboard->effective_layout);
399 get_active_binding(&keyboard->state_keysyms_raw, 398 get_active_binding(&keyboard->state_keysyms_raw,
400 config->current_mode->keysym_bindings, &binding, 399 config->current_mode->keysym_bindings, &binding,
401 raw_modifiers, false, input_inhibited, device_identifier, 400 raw_modifiers, false, input_inhibited, device_identifier,
402 keyboard->effective_layout); 401 exact_identifier, keyboard->effective_layout);
403 get_active_binding(&keyboard->state_keysyms_translated, 402 get_active_binding(&keyboard->state_keysyms_translated,
404 config->current_mode->keysym_bindings, &binding, 403 config->current_mode->keysym_bindings, &binding,
405 translated_modifiers, false, input_inhibited, 404 translated_modifiers, false, input_inhibited,
406 device_identifier, keyboard->effective_layout); 405 device_identifier, exact_identifier,
406 keyboard->effective_layout);
407 } 407 }
408 408
409 // Set up (or clear) keyboard repeat for a pressed binding. Since the 409 // 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) {
423 handled = true; 423 handled = true;
424 } 424 }
425 425
426 if (!handled && wlr_device->keyboard->group) {
427 // Only handle device specific bindings for keyboards in a group
428 free(device_identifier);
429 return;
430 }
431
426 // Compositor bindings 432 // Compositor bindings
427 if (!handled && event->state == WLR_KEY_PRESSED) { 433 if (!handled && event->state == WLR_KEY_PRESSED) {
428 handled = keyboard_execute_compositor_binding( 434 handled = keyboard_execute_compositor_binding(
@@ -450,6 +456,19 @@ static void handle_keyboard_key(struct wl_listener *listener, void *data) {
450 free(device_identifier); 456 free(device_identifier);
451} 457}
452 458
459static void handle_keyboard_key(struct wl_listener *listener, void *data) {
460 struct sway_keyboard *keyboard =
461 wl_container_of(listener, keyboard, keyboard_key);
462 handle_key_event(keyboard, data);
463}
464
465static void handle_keyboard_group_key(struct wl_listener *listener,
466 void *data) {
467 struct sway_keyboard_group *sway_group =
468 wl_container_of(listener, sway_group, keyboard_key);
469 handle_key_event(sway_group->seat_device->keyboard, data);
470}
471
453static int handle_keyboard_repeat(void *data) { 472static int handle_keyboard_repeat(void *data) {
454 struct sway_keyboard *keyboard = (struct sway_keyboard *)data; 473 struct sway_keyboard *keyboard = (struct sway_keyboard *)data;
455 struct wlr_keyboard *wlr_device = 474 struct wlr_keyboard *wlr_device =
@@ -491,25 +510,40 @@ static void determine_bar_visibility(uint32_t modifiers) {
491 } 510 }
492} 511}
493 512
494static void handle_keyboard_modifiers(struct wl_listener *listener, 513static void handle_modifier_event(struct sway_keyboard *keyboard) {
495 void *data) {
496 struct sway_keyboard *keyboard =
497 wl_container_of(listener, keyboard, keyboard_modifiers);
498 struct wlr_seat *wlr_seat = keyboard->seat_device->sway_seat->wlr_seat;
499 struct wlr_input_device *wlr_device = 514 struct wlr_input_device *wlr_device =
500 keyboard->seat_device->input_device->wlr_device; 515 keyboard->seat_device->input_device->wlr_device;
501 wlr_seat_set_keyboard(wlr_seat, wlr_device); 516 if (!wlr_device->keyboard->group) {
502 wlr_seat_keyboard_notify_modifiers(wlr_seat, &wlr_device->keyboard->modifiers); 517 struct wlr_seat *wlr_seat = keyboard->seat_device->sway_seat->wlr_seat;
518 wlr_seat_set_keyboard(wlr_seat, wlr_device);
519 wlr_seat_keyboard_notify_modifiers(wlr_seat,
520 &wlr_device->keyboard->modifiers);
503 521
504 uint32_t modifiers = wlr_keyboard_get_modifiers(wlr_device->keyboard); 522 uint32_t modifiers = wlr_keyboard_get_modifiers(wlr_device->keyboard);
505 determine_bar_visibility(modifiers); 523 determine_bar_visibility(modifiers);
524 }
506 525
507 if (wlr_device->keyboard->modifiers.group != keyboard->effective_layout) { 526 if (wlr_device->keyboard->modifiers.group != keyboard->effective_layout &&
527 !wlr_keyboard_group_from_wlr_keyboard(wlr_device->keyboard)) {
508 keyboard->effective_layout = wlr_device->keyboard->modifiers.group; 528 keyboard->effective_layout = wlr_device->keyboard->modifiers.group;
509 ipc_event_input("xkb_layout", keyboard->seat_device->input_device); 529 ipc_event_input("xkb_layout", keyboard->seat_device->input_device);
510 } 530 }
511} 531}
512 532
533static void handle_keyboard_modifiers(struct wl_listener *listener,
534 void *data) {
535 struct sway_keyboard *keyboard =
536 wl_container_of(listener, keyboard, keyboard_modifiers);
537 handle_modifier_event(keyboard);
538}
539
540static void handle_keyboard_group_modifiers(struct wl_listener *listener,
541 void *data) {
542 struct sway_keyboard_group *group =
543 wl_container_of(listener, group, keyboard_modifiers);
544 handle_modifier_event(group->seat_device->keyboard);
545}
546
513struct sway_keyboard *sway_keyboard_create(struct sway_seat *seat, 547struct sway_keyboard *sway_keyboard_create(struct sway_seat *seat,
514 struct sway_seat_device *device) { 548 struct sway_seat_device *device) {
515 struct sway_keyboard *keyboard = 549 struct sway_keyboard *keyboard =
@@ -616,6 +650,163 @@ cleanup:
616 return keymap; 650 return keymap;
617} 651}
618 652
653static bool keymaps_match(struct xkb_keymap *km1, struct xkb_keymap *km2) {
654 char *km1_str = xkb_keymap_get_as_string(km1, XKB_KEYMAP_FORMAT_TEXT_V1);
655 char *km2_str = xkb_keymap_get_as_string(km2, XKB_KEYMAP_FORMAT_TEXT_V1);
656 bool result = strcmp(km1_str, km2_str) == 0;
657 free(km1_str);
658 free(km2_str);
659 return result;
660}
661
662static void sway_keyboard_group_remove(struct sway_keyboard *keyboard) {
663 struct sway_input_device *device = keyboard->seat_device->input_device;
664 struct wlr_keyboard *wlr_keyboard = device->wlr_device->keyboard;
665 struct wlr_keyboard_group *wlr_group = wlr_keyboard->group;
666
667 sway_log(SWAY_DEBUG, "Removing keyboard %s from group %p",
668 device->identifier, wlr_group);
669
670 wlr_keyboard_group_remove_keyboard(wlr_keyboard->group, wlr_keyboard);
671
672 if (wl_list_empty(&wlr_group->devices)) {
673 sway_log(SWAY_DEBUG, "Destroying empty keyboard group %p",
674 wlr_group);
675 struct sway_keyboard_group *sway_group = wlr_group->data;
676 wlr_group->data = NULL;
677 wl_list_remove(&sway_group->link);
678 wl_list_remove(&sway_group->keyboard_key.link);
679 wl_list_remove(&sway_group->keyboard_modifiers.link);
680 free(sway_group->seat_device->keyboard);
681 free(sway_group->seat_device->input_device);
682 free(sway_group->seat_device);
683 free(sway_group);
684 wlr_keyboard_group_destroy(wlr_group);
685 }
686}
687
688static void sway_keyboard_group_remove_invalid(struct sway_keyboard *keyboard) {
689 struct sway_input_device *device = keyboard->seat_device->input_device;
690 struct wlr_keyboard *wlr_keyboard = device->wlr_device->keyboard;
691 if (!wlr_keyboard->group) {
692 return;
693 }
694
695 struct sway_seat *seat = keyboard->seat_device->sway_seat;
696 struct seat_config *sc = seat_get_config(seat);
697 if (!sc) {
698 sc = seat_get_config_by_name("*");
699 }
700
701 switch (sc ? sc->keyboard_grouping : KEYBOARD_GROUP_DEFAULT) {
702 case KEYBOARD_GROUP_NONE:
703 sway_keyboard_group_remove(keyboard);
704 break;
705 case KEYBOARD_GROUP_DEFAULT: /* fallthrough */
706 case KEYBOARD_GROUP_KEYMAP:;
707 struct wlr_keyboard_group *group = wlr_keyboard->group;
708 if (!keymaps_match(keyboard->keymap, group->keyboard.keymap)) {
709 sway_keyboard_group_remove(keyboard);
710 }
711 break;
712 }
713}
714
715static void sway_keyboard_group_add(struct sway_keyboard *keyboard) {
716 struct sway_input_device *device = keyboard->seat_device->input_device;
717 struct wlr_keyboard *wlr_keyboard = device->wlr_device->keyboard;
718 struct sway_seat *seat = keyboard->seat_device->sway_seat;
719 struct seat_config *sc = seat_get_config(seat);
720 if (!sc) {
721 sc = seat_get_config_by_name("*");
722 }
723
724 if (sc && sc->keyboard_grouping == KEYBOARD_GROUP_NONE) {
725 // Keyboard grouping is disabled for the seat
726 return;
727 }
728
729 struct sway_keyboard_group *group;
730 wl_list_for_each(group, &seat->keyboard_groups, link) {
731 switch (sc ? sc->keyboard_grouping : KEYBOARD_GROUP_DEFAULT) {
732 case KEYBOARD_GROUP_NONE:
733 // Nothing to do. This shouldn't even be reached
734 return;
735 case KEYBOARD_GROUP_DEFAULT: /* fallthrough */
736 case KEYBOARD_GROUP_KEYMAP:;
737 struct wlr_keyboard_group *wlr_group = group->wlr_group;
738 if (keymaps_match(keyboard->keymap, wlr_group->keyboard.keymap)) {
739 sway_log(SWAY_DEBUG, "Adding keyboard %s to group %p",
740 device->identifier, wlr_group);
741 wlr_keyboard_group_add_keyboard(wlr_group, wlr_keyboard);
742 return;
743 }
744 break;
745 }
746 }
747
748 struct sway_keyboard_group *sway_group =
749 calloc(1, sizeof(struct sway_keyboard_group));
750 if (!sway_group) {
751 sway_log(SWAY_ERROR, "Failed to allocate sway_keyboard_group");
752 return;
753 }
754
755 sway_group->wlr_group = wlr_keyboard_group_create();
756 if (!sway_group->wlr_group) {
757 sway_log(SWAY_ERROR, "Failed to create keyboard group");
758 goto cleanup;
759 }
760 sway_group->wlr_group->data = sway_group;
761 wlr_keyboard_set_keymap(&sway_group->wlr_group->keyboard, keyboard->keymap);
762 sway_log(SWAY_DEBUG, "Created keyboard group %p", sway_group->wlr_group);
763
764 sway_group->seat_device = calloc(1, sizeof(struct sway_seat_device));
765 if (!sway_group->seat_device) {
766 sway_log(SWAY_ERROR, "Failed to allocate sway_seat_device for group");
767 goto cleanup;
768 }
769 sway_group->seat_device->sway_seat = seat;
770
771 sway_group->seat_device->input_device =
772 calloc(1, sizeof(struct sway_input_device));
773 if (!sway_group->seat_device->input_device) {
774 sway_log(SWAY_ERROR, "Failed to allocate sway_input_device for group");
775 goto cleanup;
776 }
777 sway_group->seat_device->input_device->wlr_device =
778 sway_group->wlr_group->input_device;
779
780 if (!sway_keyboard_create(seat, sway_group->seat_device)) {
781 sway_log(SWAY_ERROR, "Failed to allocate sway_keyboard for group");
782 goto cleanup;
783 }
784
785 sway_log(SWAY_DEBUG, "Adding keyboard %s to group %p",
786 device->identifier, sway_group->wlr_group);
787 wlr_keyboard_group_add_keyboard(sway_group->wlr_group, wlr_keyboard);
788
789 wl_list_insert(&seat->keyboard_groups, &sway_group->link);
790
791 wl_signal_add(&sway_group->wlr_group->keyboard.events.key,
792 &sway_group->keyboard_key);
793 sway_group->keyboard_key.notify = handle_keyboard_group_key;
794
795 wl_signal_add(&sway_group->wlr_group->keyboard.events.modifiers,
796 &sway_group->keyboard_modifiers);
797 sway_group->keyboard_modifiers.notify = handle_keyboard_group_modifiers;
798 return;
799
800cleanup:
801 if (sway_group && sway_group->wlr_group) {
802 wlr_keyboard_group_destroy(sway_group->wlr_group);
803 }
804 free(sway_group->seat_device->keyboard);
805 free(sway_group->seat_device->input_device);
806 free(sway_group->seat_device);
807 free(sway_group);
808}
809
619void sway_keyboard_configure(struct sway_keyboard *keyboard) { 810void sway_keyboard_configure(struct sway_keyboard *keyboard) {
620 struct input_config *input_config = 811 struct input_config *input_config =
621 input_device_get_config(keyboard->seat_device->input_device); 812 input_device_get_config(keyboard->seat_device->input_device);
@@ -633,26 +824,23 @@ void sway_keyboard_configure(struct sway_keyboard *keyboard) {
633 } 824 }
634 } 825 }
635 826
636 bool keymap_changed = false; 827 bool keymap_changed =
828 keyboard->keymap ? !keymaps_match(keyboard->keymap, keymap) : true;
637 bool effective_layout_changed = keyboard->effective_layout != 0; 829 bool effective_layout_changed = keyboard->effective_layout != 0;
638 if (keyboard->keymap) {
639 char *old_keymap_string = xkb_keymap_get_as_string(keyboard->keymap,
640 XKB_KEYMAP_FORMAT_TEXT_V1);
641 char *new_keymap_string = xkb_keymap_get_as_string(keymap,
642 XKB_KEYMAP_FORMAT_TEXT_V1);
643 keymap_changed = strcmp(old_keymap_string, new_keymap_string);
644 free(old_keymap_string);
645 free(new_keymap_string);
646 } else {
647 keymap_changed = true;
648 }
649 830
650 if (keymap_changed || config->reloading) { 831 if (keymap_changed || config->reloading) {
651 xkb_keymap_unref(keyboard->keymap); 832 xkb_keymap_unref(keyboard->keymap);
652 keyboard->keymap = keymap; 833 keyboard->keymap = keymap;
653 keyboard->effective_layout = 0; 834 keyboard->effective_layout = 0;
835
836 sway_keyboard_group_remove_invalid(keyboard);
837
654 wlr_keyboard_set_keymap(wlr_device->keyboard, keyboard->keymap); 838 wlr_keyboard_set_keymap(wlr_device->keyboard, keyboard->keymap);
655 839
840 if (!wlr_device->keyboard->group) {
841 sway_keyboard_group_add(keyboard);
842 }
843
656 xkb_mod_mask_t locked_mods = 0; 844 xkb_mod_mask_t locked_mods = 0;
657 if (input_config && input_config->xkb_numlock > 0) { 845 if (input_config && input_config->xkb_numlock > 0) {
658 xkb_mod_index_t mod_index = xkb_map_mod_get_index(keymap, 846 xkb_mod_index_t mod_index = xkb_map_mod_get_index(keymap,
@@ -679,10 +867,19 @@ void sway_keyboard_configure(struct sway_keyboard *keyboard) {
679 leds |= (1 << i); 867 leds |= (1 << i);
680 } 868 }
681 } 869 }
682 wlr_keyboard_led_update(wlr_device->keyboard, leds); 870 if (wlr_device->keyboard->group) {
871 wlr_keyboard_led_update(
872 &wlr_device->keyboard->group->keyboard, leds);
873 } else {
874 wlr_keyboard_led_update(wlr_device->keyboard, leds);
875 }
683 } 876 }
684 } else { 877 } else {
685 xkb_keymap_unref(keymap); 878 xkb_keymap_unref(keymap);
879 sway_keyboard_group_remove_invalid(keyboard);
880 if (!wlr_device->keyboard->group) {
881 sway_keyboard_group_add(keyboard);
882 }
686 } 883 }
687 884
688 int repeat_rate = 25; 885 int repeat_rate = 25;
@@ -721,6 +918,7 @@ void sway_keyboard_destroy(struct sway_keyboard *keyboard) {
721 if (!keyboard) { 918 if (!keyboard) {
722 return; 919 return;
723 } 920 }
921 sway_keyboard_group_remove(keyboard);
724 if (keyboard->keymap) { 922 if (keyboard->keymap) {
725 xkb_keymap_unref(keyboard->keymap); 923 xkb_keymap_unref(keyboard->keymap);
726 } 924 }
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(
102 return seat_device->keyboard; 102 return seat_device->keyboard;
103 } 103 }
104 } 104 }
105 struct sway_keyboard_group *group;
106 wl_list_for_each(group, &seat->keyboard_groups, link) {
107 struct sway_input_device *input_device =
108 group->seat_device->input_device;
109 if (input_device->wlr_device->keyboard == wlr_keyboard) {
110 return group->seat_device->keyboard;
111 }
112 }
105 return NULL; 113 return NULL;
106} 114}
107 115
@@ -519,6 +527,7 @@ struct sway_seat *seat_create(const char *seat_name) {
519 handle_request_set_primary_selection; 527 handle_request_set_primary_selection;
520 528
521 wl_list_init(&seat->devices); 529 wl_list_init(&seat->devices);
530 wl_list_init(&seat->keyboard_groups);
522 531
523 wl_list_insert(&server.input->seats, &seat->link); 532 wl_list_insert(&server.input->seats, &seat->link);
524 533
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(
91 'commands/seat/cursor.c', 91 'commands/seat/cursor.c',
92 'commands/seat/fallback.c', 92 'commands/seat/fallback.c',
93 'commands/seat/hide_cursor.c', 93 'commands/seat/hide_cursor.c',
94 'commands/seat/keyboard_grouping.c',
94 'commands/seat/pointer_constraint.c', 95 'commands/seat/pointer_constraint.c',
95 'commands/seat/xcursor_theme.c', 96 'commands/seat/xcursor_theme.c',
96 'commands/set.c', 97 '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.
218 disables hiding the cursor. The minimal timeout is 100 and any value less 218 disables hiding the cursor. The minimal timeout is 100 and any value less
219 than that (aside from 0), will be increased to 100. 219 than that (aside from 0), will be increased to 100.
220 220
221*seat* <name> keyboard_grouping none|keymap
222 Set how the keyboards in the seat are grouped together. Currently, there
223 are two options. _none_ will disable all keyboard grouping. This will make
224 it so each keyboard device has its own isolated state. _keymap_ will
225 group the keyboards in the seat by their keymap. This is useful for when
226 the keyboard appears as multiple separate input devices. In this mode,
227 the effective layout and repeat info are also synced between the keyboards
228 in the group. The default is _keymap_. To restore the behavior of older
229 versions of sway, use _none_.
230
221*seat* <name> pointer_constraint enable|disable|escape 231*seat* <name> pointer_constraint enable|disable|escape
222 Enables or disables the ability for clients to capture the cursor (enabled 232 Enables or disables the ability for clients to capture the cursor (enabled
223 by default) for the seat. This is primarily useful for video games. The 233 by default) for the seat. This is primarily useful for video games. The