bindsym/code: add group support

This adds support for specifying a binding for a specific group. Any
binding without a group listed will be available in all groups. The
priority for matching bindings is as follows: input device, group, and
locked state.

For full compatibility with i3, this also adds Mode_switch as an alias
for Group2. Since i3 only supports this for backwards compatibility
with older versions of i3, it is implemented here, but not documented.
This commit is contained in:
Brian Ashworth 2019-07-26 12:02:18 -04:00 committed by Simon Ser
parent 14562fdbee
commit 8ee054b1b9
4 changed files with 76 additions and 15 deletions

View file

@ -53,6 +53,7 @@ struct sway_binding {
list_t *keys; // sorted in ascending order list_t *keys; // sorted in ascending order
list_t *syms; // sorted in ascending order; NULL if BINDING_CODE is not set list_t *syms; // sorted in ascending order; NULL if BINDING_CODE is not set
uint32_t modifiers; uint32_t modifiers;
xkb_layout_index_t group;
char *command; char *command;
}; };

View file

@ -78,6 +78,10 @@ static bool binding_key_compare(struct sway_binding *binding_a,
return false; return false;
} }
if (binding_a->group != binding_b->group) {
return false;
}
if (binding_a->modifiers ^ binding_b->modifiers) { if (binding_a->modifiers ^ binding_b->modifiers) {
return false; return false;
} }
@ -337,6 +341,7 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv,
} }
binding->input = strdup("*"); binding->input = strdup("*");
binding->keys = create_list(); binding->keys = create_list();
binding->group = XKB_LAYOUT_INVALID;
binding->modifiers = 0; binding->modifiers = 0;
binding->flags = 0; binding->flags = 0;
binding->type = bindcode ? BINDING_KEYCODE : BINDING_KEYSYM; 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], "+"); list_t *split = split_string(argv[0], "+");
for (int i = 0; i < split->length; ++i) { 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 // Check for a modifier key
uint32_t mod; uint32_t mod;
if ((mod = get_modifier_mask_by_name(split->items[i])) > 0) { if ((mod = get_modifier_mask_by_name(split->items[i])) > 0) {

View file

@ -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, static void get_active_binding(const struct sway_shortcut_state *state,
list_t *bindings, struct sway_binding **current_binding, 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) { for (int i = 0; i < bindings->length; ++i) {
struct sway_binding *binding = bindings->items[i]; struct sway_binding *binding = bindings->items[i];
bool binding_locked = (binding->flags & BINDING_LOCKED) != 0; 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 || if (modifiers ^ binding->modifiers ||
release != binding_release || release != binding_release ||
locked > binding_locked || locked > binding_locked ||
(binding->group != XKB_LAYOUT_INVALID &&
binding->group != group) ||
(strcmp(binding->input, input) != 0 && (strcmp(binding->input, input) != 0 &&
strcmp(binding->input, "*") != 0)) { strcmp(binding->input, "*") != 0)) {
continue; continue;
@ -186,10 +189,14 @@ static void get_active_binding(const struct sway_shortcut_state *state,
bool current_locked = bool current_locked =
((*current_binding)->flags & BINDING_LOCKED) != 0; ((*current_binding)->flags & BINDING_LOCKED) != 0;
bool current_input = strcmp((*current_binding)->input, input) == 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_input = strcmp(binding->input, input) == 0;
bool binding_group_set = binding->group != XKB_LAYOUT_INVALID;
if (current_input == binding_input if (current_input == binding_input
&& current_locked == binding_locked) { && current_locked == binding_locked
&& current_group_set == binding_group_set) {
sway_log(SWAY_DEBUG, sway_log(SWAY_DEBUG,
"Encountered conflicting bindings %d and %d", "Encountered conflicting bindings %d and %d",
(*current_binding)->order, binding->order); (*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 continue; // Prefer the correct input
} }
if (current_input == binding_input && current_locked == locked) { if (current_input == binding_input &&
continue; // Prefer correct lock state for matching inputs (*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; *current_binding = binding;
if (strcmp((*current_binding)->input, input) == 0 && 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 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; struct sway_binding *binding_released = NULL;
get_active_binding(&keyboard->state_keycodes, get_active_binding(&keyboard->state_keycodes,
config->current_mode->keycode_bindings, &binding_released, 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, get_active_binding(&keyboard->state_keysyms_raw,
config->current_mode->keysym_bindings, &binding_released, 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, get_active_binding(&keyboard->state_keysyms_translated,
config->current_mode->keysym_bindings, &binding_released, 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 // Execute stored release binding once no longer active
if (keyboard->held_binding && binding_released != keyboard->held_binding && 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) { if (event->state == WLR_KEY_PRESSED) {
get_active_binding(&keyboard->state_keycodes, get_active_binding(&keyboard->state_keycodes,
config->current_mode->keycode_bindings, &binding, 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, get_active_binding(&keyboard->state_keysyms_raw,
config->current_mode->keysym_bindings, &binding, 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, get_active_binding(&keyboard->state_keysyms_translated,
config->current_mode->keysym_bindings, &binding, config->current_mode->keysym_bindings, &binding,
translated_modifiers, false, input_inhibited, translated_modifiers, false, input_inhibited,
device_identifier); device_identifier, keyboard->effective_layout);
} }
// Set up (or clear) keyboard repeat for a pressed binding. Since the // Set up (or clear) keyboard repeat for a pressed binding. Since the

View file

@ -328,14 +328,17 @@ runtime.
for_window <criteria> move container to output <output> for_window <criteria> move container to output <output>
*bindsym* [--whole-window] [--border] [--exclude-titlebar] [--release] [--locked] [--to-code] [--input-device=<device>] [--no-warn] <key combo> <command> *bindsym* [--whole-window] [--border] [--exclude-titlebar] [--release] [--locked] \
[--to-code] [--input-device=<device>] [--no-warn] [Group<1-4>+]<key combo> \
<command>
Binds _key combo_ to execute the sway command _command_ when pressed. You 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). 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 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 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 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 generic to all devices. If a group number is given, then the binding will
will give you a warning. To silence this, use the _--no-warn_ flag. 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 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 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, 6=scroll left, 7=scroll right, 8=back, 9=forward). For the latter option,
you can find the event names using _libinput debug-events_. 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 _--whole-window_, _--border_, and _--exclude-titlebar_ are mouse-only options
which affect the region in which the mouse bindings can be triggered. By 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 default, mouse bindings are only triggered when over the title bar. With the
@ -375,7 +381,8 @@ runtime.
bindsym Mod1+Shift+f exec firefox bindsym Mod1+Shift+f exec firefox
``` ```
*bindcode* [--whole-window] [--border] [--exclude-titlebar] [--release] [--locked] [--input-device=<device>] [--no-warn] <code> <command> *bindcode* [--whole-window] [--border] [--exclude-titlebar] [--release] \
[--locked] [--input-device=<device>] [--no-warn] [Group<1-4>+]<code> <command>
is also available for binding with key/button codes instead of key/button names. is also available for binding with key/button codes instead of key/button names.
*bindswitch* [--locked] [--no-warn] [--reload] <switch>:<state> <command> *bindswitch* [--locked] [--no-warn] [--reload] <switch>:<state> <command>