diff --git a/include/sway/config.h b/include/sway/config.h index 5f02e44fa..fdd65efd1 100644 --- a/include/sway/config.h +++ b/include/sway/config.h @@ -40,6 +40,7 @@ enum binding_flags { BINDING_TITLEBAR=16, // mouse only; trigger on container titlebar BINDING_CODE=32, // keyboard only; convert keysyms into keycodes BINDING_RELOAD=64, // switch only; (re)trigger binding on reload + BINDING_INHIBITED=128, // keyboard only: ignore shortcut inhibitor }; /** diff --git a/include/sway/input/input-manager.h b/include/sway/input/input-manager.h index a5392c6b9..410d17a8e 100644 --- a/include/sway/input/input-manager.h +++ b/include/sway/input/input-manager.h @@ -2,6 +2,7 @@ #define _SWAY_INPUT_INPUT_MANAGER_H #include #include +#include #include #include #include "sway/server.h" @@ -20,12 +21,14 @@ struct sway_input_manager { struct wl_list seats; struct wlr_input_inhibit_manager *inhibit; + struct wlr_keyboard_shortcuts_inhibit_manager_v1 *keyboard_shortcuts_inhibit; struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard; struct wlr_virtual_pointer_manager_v1 *virtual_pointer; struct wl_listener new_input; struct wl_listener inhibit_activate; struct wl_listener inhibit_deactivate; + struct wl_listener keyboard_shortcuts_inhibit_new_inhibitor; struct wl_listener virtual_keyboard_new; struct wl_listener virtual_pointer_new; }; diff --git a/include/sway/input/seat.h b/include/sway/input/seat.h index 9c3028c5d..49b9d09b0 100644 --- a/include/sway/input/seat.h +++ b/include/sway/input/seat.h @@ -1,6 +1,7 @@ #ifndef _SWAY_INPUT_SEAT_H #define _SWAY_INPUT_SEAT_H +#include #include #include #include @@ -94,6 +95,8 @@ struct sway_seat { struct wl_list devices; // sway_seat_device::link struct wl_list keyboard_groups; // sway_keyboard_group::link + struct wl_list keyboard_shortcuts_inhibitors; + // sway_keyboard_shortcuts_inhibitor::link struct wl_list link; // input_manager::seats }; @@ -104,6 +107,14 @@ struct sway_pointer_constraint { struct wl_listener destroy; }; +struct sway_keyboard_shortcuts_inhibitor { + struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor; + + struct wl_listener destroy; + + struct wl_list link; // sway_seat::keyboard_shortcuts_inhibitors +}; + struct sway_seat *seat_create(const char *seat_name); void seat_destroy(struct sway_seat *seat); @@ -269,4 +280,11 @@ void seatop_render(struct sway_seat *seat, struct sway_output *output, bool seatop_allows_set_cursor(struct sway_seat *seat); +/** + * Returns the keyboard shortcuts inhibitor that applies to the currently + * focused surface of a seat or NULL if none exists. + */ +struct sway_keyboard_shortcuts_inhibitor * +keyboard_shortcuts_inhibitor_get_for_focused_surface(const struct sway_seat *seat); + #endif diff --git a/sway/commands/bind.c b/sway/commands/bind.c index 5ec899826..0ddee742f 100644 --- a/sway/commands/bind.c +++ b/sway/commands/bind.c @@ -72,7 +72,8 @@ static bool binding_key_compare(struct sway_binding *binding_a, } uint32_t conflict_generating_flags = BINDING_RELEASE | BINDING_BORDER - | BINDING_CONTENTS | BINDING_TITLEBAR | BINDING_LOCKED; + | BINDING_CONTENTS | BINDING_TITLEBAR | BINDING_LOCKED + | BINDING_INHIBITED; if ((binding_a->flags & conflict_generating_flags) != (binding_b->flags & conflict_generating_flags)) { return false; @@ -354,6 +355,8 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv, binding->flags |= BINDING_RELEASE; } else if (strcmp("--locked", argv[0]) == 0) { binding->flags |= BINDING_LOCKED; + } else if (strcmp("--inhibited", argv[0]) == 0) { + binding->flags |= BINDING_INHIBITED; } else if (strcmp("--whole-window", argv[0]) == 0) { binding->flags |= BINDING_BORDER | BINDING_CONTENTS | BINDING_TITLEBAR; } else if (strcmp("--border", argv[0]) == 0) { diff --git a/sway/input/input-manager.c b/sway/input/input-manager.c index 4cc07fe64..af0f5afa6 100644 --- a/sway/input/input-manager.c +++ b/sway/input/input-manager.c @@ -296,6 +296,46 @@ static void handle_inhibit_deactivate(struct wl_listener *listener, void *data) } } +static void handle_keyboard_shortcuts_inhibitor_destroy( + struct wl_listener *listener, void *data) { + struct sway_keyboard_shortcuts_inhibitor *sway_inhibitor = + wl_container_of(listener, sway_inhibitor, destroy); + + sway_log(SWAY_DEBUG, "Removing keyboard shortcuts inhibitor"); + + // sway_seat::keyboard_shortcuts_inhibitors + wl_list_remove(&sway_inhibitor->link); + wl_list_remove(&sway_inhibitor->destroy.link); + free(sway_inhibitor); +} + +static void handle_keyboard_shortcuts_inhibit_new_inhibitor( + struct wl_listener *listener, void *data) { + struct sway_input_manager *input_manager = + wl_container_of(listener, input_manager, + keyboard_shortcuts_inhibit_new_inhibitor); + struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor = data; + + sway_log(SWAY_DEBUG, "Adding keyboard shortcuts inhibitor"); + + struct sway_keyboard_shortcuts_inhibitor *sway_inhibitor = + calloc(1, sizeof(struct sway_keyboard_shortcuts_inhibitor)); + if (!sway_assert(sway_inhibitor, "could not allocate keyboard " + "shortcuts inhibitor")) { + return; + } + sway_inhibitor->inhibitor = inhibitor; + + sway_inhibitor->destroy.notify = handle_keyboard_shortcuts_inhibitor_destroy; + wl_signal_add(&inhibitor->events.destroy, &sway_inhibitor->destroy); + + // attach inhibitor to the seat it applies to + struct sway_seat *seat = inhibitor->seat->data; + wl_list_insert(&seat->keyboard_shortcuts_inhibitors, &sway_inhibitor->link); + + wlr_keyboard_shortcuts_inhibitor_v1_activate(inhibitor); +} + void handle_virtual_keyboard(struct wl_listener *listener, void *data) { struct sway_input_manager *input_manager = wl_container_of(listener, input_manager, virtual_keyboard_new); @@ -397,6 +437,13 @@ struct sway_input_manager *input_manager_create(struct sway_server *server) { wl_signal_add(&input->inhibit->events.deactivate, &input->inhibit_deactivate); + input->keyboard_shortcuts_inhibit = + wlr_keyboard_shortcuts_inhibit_v1_create(server->wl_display); + input->keyboard_shortcuts_inhibit_new_inhibitor.notify = + handle_keyboard_shortcuts_inhibit_new_inhibitor; + wl_signal_add(&input->keyboard_shortcuts_inhibit->events.new_inhibitor, + &input->keyboard_shortcuts_inhibit_new_inhibitor); + return input; } diff --git a/sway/input/keyboard.c b/sway/input/keyboard.c index 2cfcd1265..9c5f190ec 100644 --- a/sway/input/keyboard.c +++ b/sway/input/keyboard.c @@ -150,16 +150,18 @@ 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, - bool exact_input, xkb_layout_index_t group) { + uint32_t modifiers, bool release, bool locked, bool inhibited, + const char *input, 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; + bool binding_inhibited = (binding->flags & BINDING_INHIBITED) != 0; bool binding_release = binding->flags & BINDING_RELEASE; if (modifiers ^ binding->modifiers || release != binding_release || locked > binding_locked || + inhibited > binding_inhibited || (binding->group != XKB_LAYOUT_INVALID && binding->group != group) || (strcmp(binding->input, input) != 0 && @@ -195,6 +197,8 @@ static void get_active_binding(const struct sway_shortcut_state *state, bool current_locked = ((*current_binding)->flags & BINDING_LOCKED) != 0; + bool current_inhibited = + ((*current_binding)->flags & BINDING_INHIBITED) != 0; bool current_input = strcmp((*current_binding)->input, input) == 0; bool current_group_set = (*current_binding)->group != XKB_LAYOUT_INVALID; @@ -203,6 +207,7 @@ static void get_active_binding(const struct sway_shortcut_state *state, if (current_input == binding_input && current_locked == binding_locked + && current_inhibited == binding_inhibited && current_group_set == binding_group_set) { sway_log(SWAY_DEBUG, "Encountered conflicting bindings %d and %d", @@ -224,11 +229,21 @@ static void get_active_binding(const struct sway_shortcut_state *state, current_locked == locked) { continue; // Prefer correct lock state for matching input+group } + + if (current_input == binding_input && + current_group_set == binding_group_set && + current_locked == binding_locked && + current_inhibited == inhibited) { + // Prefer correct inhibition state for matching + // input+group+locked + continue; + } } *current_binding = binding; if (strcmp((*current_binding)->input, input) == 0 && (((*current_binding)->flags & BINDING_LOCKED) == locked) && + (((*current_binding)->flags & BINDING_INHIBITED) == inhibited) && (*current_binding)->group == group) { return; // If a perfect match is found, quit searching } @@ -328,6 +343,9 @@ static void handle_key_event(struct sway_keyboard *keyboard, bool exact_identifier = wlr_device->keyboard->group != NULL; seat_idle_notify_activity(seat, IDLE_SOURCE_KEYBOARD); bool input_inhibited = seat->exclusive_client != NULL; + struct sway_keyboard_shortcuts_inhibitor *sway_inhibitor = + keyboard_shortcuts_inhibitor_get_for_focused_surface(seat); + bool shortcuts_inhibited = sway_inhibitor && sway_inhibitor->inhibitor->active; // Identify new keycode, raw keysym(s), and translated keysym(s) xkb_keycode_t keycode = event->keycode + 8; @@ -364,15 +382,18 @@ static void handle_key_event(struct sway_keyboard *keyboard, 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, + shortcuts_inhibited, device_identifier, 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, + raw_modifiers, true, input_inhibited, + shortcuts_inhibited, device_identifier, 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, + translated_modifiers, true, input_inhibited, + shortcuts_inhibited, device_identifier, exact_identifier, keyboard->effective_layout); // Execute stored release binding once no longer active @@ -393,17 +414,19 @@ static void handle_key_event(struct sway_keyboard *keyboard, 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, + shortcuts_inhibited, device_identifier, 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, + raw_modifiers, false, input_inhibited, + shortcuts_inhibited, device_identifier, 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, exact_identifier, - keyboard->effective_layout); + shortcuts_inhibited, device_identifier, + exact_identifier, keyboard->effective_layout); } // Set up (or clear) keyboard repeat for a pressed binding. Since the diff --git a/sway/input/seat.c b/sway/input/seat.c index 371de56e5..6739c1635 100644 --- a/sway/input/seat.c +++ b/sway/input/seat.c @@ -557,6 +557,7 @@ struct sway_seat *seat_create(const char *seat_name) { handle_request_set_primary_selection; wl_list_init(&seat->keyboard_groups); + wl_list_init(&seat->keyboard_shortcuts_inhibitors); wl_list_insert(&server.input->seats, &seat->link); @@ -1473,3 +1474,18 @@ void seatop_render(struct sway_seat *seat, struct sway_output *output, bool seatop_allows_set_cursor(struct sway_seat *seat) { return seat->seatop_impl->allow_set_cursor; } + +struct sway_keyboard_shortcuts_inhibitor * +keyboard_shortcuts_inhibitor_get_for_focused_surface( + const struct sway_seat *seat) { + struct wlr_surface *focused_surface = + seat->wlr_seat->keyboard_state.focused_surface; + struct sway_keyboard_shortcuts_inhibitor *sway_inhibitor = NULL; + wl_list_for_each(sway_inhibitor, &seat->keyboard_shortcuts_inhibitors, link) { + if (sway_inhibitor->inhibitor->surface == focused_surface) { + return sway_inhibitor; + } + } + + return NULL; +} diff --git a/sway/sway.5.scd b/sway/sway.5.scd index b8b838ab4..a2c74e705 100644 --- a/sway/sway.5.scd +++ b/sway/sway.5.scd @@ -374,6 +374,14 @@ runtime. and one has both _--input-device_ and _--locked_ and the other has neither, the former will be preferred even when unlocked. + Unless the flag _--inhibited_ is set, the command will not be run when + a keyboard shortcuts inhibitor is active for the currently focused + window. Such inhibitors are usually requested by remote desktop and + virtualization software to enable the user to send keyboard shortcuts + to the remote or virtual session. The _--inhibited_ flag allows to + define bindings which will be exempt from pass-through to such + software. The same preference logic as for _--locked_ applies. + Bindings to keysyms are layout-dependent. This can be changed with the _--to-code_ flag. In this case, the keysyms will be translated into the corresponding keycodes in the first configured layout.