diff --git a/include/sway/config.h b/include/sway/config.h index f1426453..c65d9353 100644 --- a/include/sway/config.h +++ b/include/sway/config.h @@ -53,6 +53,7 @@ struct sway_binding { list_t *keys; // sorted in ascending order list_t *syms; // sorted in ascending order; NULL if BINDING_CODE is not set uint32_t modifiers; + xkb_layout_index_t group; char *command; }; diff --git a/sway/commands/bind.c b/sway/commands/bind.c index 767c2fee..5ec89982 100644 --- a/sway/commands/bind.c +++ b/sway/commands/bind.c @@ -78,6 +78,10 @@ static bool binding_key_compare(struct sway_binding *binding_a, return false; } + if (binding_a->group != binding_b->group) { + return false; + } + if (binding_a->modifiers ^ binding_b->modifiers) { return false; } @@ -337,6 +341,7 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv, } binding->input = strdup("*"); binding->keys = create_list(); + binding->group = XKB_LAYOUT_INVALID; binding->modifiers = 0; binding->flags = 0; binding->type = bindcode ? BINDING_KEYCODE : BINDING_KEYSYM; @@ -387,6 +392,34 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv, list_t *split = split_string(argv[0], "+"); for (int i = 0; i < split->length; ++i) { + // Check for group + if (strncmp(split->items[i], "Group", strlen("Group")) == 0) { + if (binding->group != XKB_LAYOUT_INVALID) { + free_sway_binding(binding); + list_free_items_and_destroy(split); + return cmd_results_new(CMD_FAILURE, + "Only one group can be specified"); + } + char *end; + int group = strtol(split->items[i] + strlen("Group"), &end, 10); + if (group < 1 || group > 4 || end[0] != '\0') { + free_sway_binding(binding); + list_free_items_and_destroy(split); + return cmd_results_new(CMD_FAILURE, "Invalid group"); + } + binding->group = group - 1; + continue; + } else if (strcmp(split->items[i], "Mode_switch") == 0) { + // For full i3 compatibility, Mode_switch is an alias for Group2 + if (binding->group != XKB_LAYOUT_INVALID) { + free_sway_binding(binding); + list_free_items_and_destroy(split); + return cmd_results_new(CMD_FAILURE, + "Only one group can be specified"); + } + binding->group = 1; + } + // Check for a modifier key uint32_t mod; if ((mod = get_modifier_mask_by_name(split->items[i])) > 0) { diff --git a/sway/input/keyboard.c b/sway/input/keyboard.c index aecabbf4..680d1f69 100644 --- a/sway/input/keyboard.c +++ b/sway/input/keyboard.c @@ -143,7 +143,8 @@ static void 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) { + uint32_t modifiers, bool release, bool locked, const char *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; @@ -152,6 +153,8 @@ static void get_active_binding(const struct sway_shortcut_state *state, if (modifiers ^ binding->modifiers || release != binding_release || locked > binding_locked || + (binding->group != XKB_LAYOUT_INVALID && + binding->group != group) || (strcmp(binding->input, input) != 0 && strcmp(binding->input, "*") != 0)) { continue; @@ -186,10 +189,14 @@ static void get_active_binding(const struct sway_shortcut_state *state, bool current_locked = ((*current_binding)->flags & BINDING_LOCKED) != 0; bool current_input = strcmp((*current_binding)->input, input) == 0; + bool current_group_set = + (*current_binding)->group != XKB_LAYOUT_INVALID; bool binding_input = strcmp(binding->input, input) == 0; + bool binding_group_set = binding->group != XKB_LAYOUT_INVALID; if (current_input == binding_input - && current_locked == binding_locked) { + && current_locked == binding_locked + && current_group_set == binding_group_set) { sway_log(SWAY_DEBUG, "Encountered conflicting bindings %d and %d", (*current_binding)->order, binding->order); @@ -200,14 +207,22 @@ static void get_active_binding(const struct sway_shortcut_state *state, continue; // Prefer the correct input } - if (current_input == binding_input && current_locked == locked) { - continue; // Prefer correct lock state for matching inputs + if (current_input == binding_input && + (*current_binding)->group == group) { + continue; // Prefer correct group for matching inputs + } + + if (current_input == binding_input && + current_group_set == binding_group_set && + current_locked == locked) { + continue; // Prefer correct lock state for matching input+group } } *current_binding = binding; if (strcmp((*current_binding)->input, input) == 0 && - (((*current_binding)->flags & BINDING_LOCKED) == locked)) { + (((*current_binding)->flags & BINDING_LOCKED) == locked) && + (*current_binding)->group == group) { return; // If a perfect match is found, quit searching } } @@ -344,13 +359,16 @@ static void handle_keyboard_key(struct wl_listener *listener, void *data) { 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); + code_modifiers, true, input_inhibited, device_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); + raw_modifiers, true, input_inhibited, device_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); + translated_modifiers, true, input_inhibited, device_identifier, + keyboard->effective_layout); // Execute stored release binding once no longer active if (keyboard->held_binding && binding_released != keyboard->held_binding && @@ -370,14 +388,16 @@ static void handle_keyboard_key(struct wl_listener *listener, void *data) { if (event->state == WLR_KEY_PRESSED) { get_active_binding(&keyboard->state_keycodes, config->current_mode->keycode_bindings, &binding, - code_modifiers, false, input_inhibited, device_identifier); + code_modifiers, false, input_inhibited, device_identifier, + keyboard->effective_layout); get_active_binding(&keyboard->state_keysyms_raw, config->current_mode->keysym_bindings, &binding, - raw_modifiers, false, input_inhibited, device_identifier); + raw_modifiers, false, input_inhibited, device_identifier, + keyboard->effective_layout); get_active_binding(&keyboard->state_keysyms_translated, config->current_mode->keysym_bindings, &binding, translated_modifiers, false, input_inhibited, - device_identifier); + device_identifier, keyboard->effective_layout); } // Set up (or clear) keyboard repeat for a pressed binding. Since the diff --git a/sway/sway.5.scd b/sway/sway.5.scd index ac80de3c..3e445e0e 100644 --- a/sway/sway.5.scd +++ b/sway/sway.5.scd @@ -328,14 +328,17 @@ runtime. for_window move container to output -*bindsym* [--whole-window] [--border] [--exclude-titlebar] [--release] [--locked] [--to-code] [--input-device=] [--no-warn] +*bindsym* [--whole-window] [--border] [--exclude-titlebar] [--release] [--locked] \ + [--to-code] [--input-device=] [--no-warn] [Group<1-4>+] \ + Binds _key combo_ to execute the sway command _command_ when pressed. You may use XKB key names here (*xev*(1) is a good tool for discovering these). With the flag _--release_, the command is executed when the key combo is released. If _input-device_ is given, the binding will only be executed for that input device and will be executed instead of any binding that is - generic to all devices. By default, if you overwrite a binding, swaynag - will give you a warning. To silence this, use the _--no-warn_ flag. + generic to all devices. If a group number is given, then the binding will + only be available for that group. By default, if you overwrite a binding, + swaynag will give you a warning. To silence this, use the _--no-warn_ flag. Unless the flag _--locked_ is set, the command will not be run when a screen locking program is active. If there is a matching binding with @@ -356,6 +359,9 @@ runtime. 6=scroll left, 7=scroll right, 8=back, 9=forward). For the latter option, you can find the event names using _libinput debug-events_. + The priority for matching bindings is as follows: input device, group, + and locked state. + _--whole-window_, _--border_, and _--exclude-titlebar_ are mouse-only options which affect the region in which the mouse bindings can be triggered. By default, mouse bindings are only triggered when over the title bar. With the @@ -375,7 +381,8 @@ runtime. bindsym Mod1+Shift+f exec firefox ``` - *bindcode* [--whole-window] [--border] [--exclude-titlebar] [--release] [--locked] [--input-device=] [--no-warn] + *bindcode* [--whole-window] [--border] [--exclude-titlebar] [--release] \ + [--locked] [--input-device=] [--no-warn] [Group<1-4>+] is also available for binding with key/button codes instead of key/button names. *bindswitch* [--locked] [--no-warn] [--reload] :