Fix keyboard shortcut handling inconsistencies

* Ensure that modifier keys are identified even when the next key does
  not produce a keysym. This requires that modifier change tracking
  be done for each sway_shortcut_state.

* Permit regular and --release shortcuts on the same key combination.
  Distinct bindings are identified for press and release cases; note
  that the release binding needs to be identified for both key press
  and key release events.

* Maintain ascending sort order for the shortcut state list, and keep
  track of the number of pressed key ids, for simpler (and hence
  faster) searching of the list of key bindings.

* Move binding duplicate detection into get_active_binding to avoid
  duplicating error messages.
master
frsfnrrg 7 years ago
parent b23cd827cf
commit ca061ba8bf

@ -21,7 +21,9 @@ struct sway_shortcut_state {
* including duplicates when a keycode generates multiple key ids. * including duplicates when a keycode generates multiple key ids.
*/ */
uint32_t pressed_keycodes[SWAY_KEYBOARD_PRESSED_KEYS_CAP]; uint32_t pressed_keycodes[SWAY_KEYBOARD_PRESSED_KEYS_CAP];
int last_key_index; uint32_t last_keycode;
uint32_t last_raw_modifiers;
size_t npressed;
}; };
struct sway_keyboard { struct sway_keyboard {
@ -36,7 +38,6 @@ struct sway_keyboard {
struct sway_shortcut_state state_keysyms_raw; struct sway_shortcut_state state_keysyms_raw;
struct sway_shortcut_state state_keycodes; struct sway_shortcut_state state_keycodes;
struct sway_binding *held_binding; struct sway_binding *held_binding;
uint32_t last_modifiers;
}; };
struct sway_keyboard *sway_keyboard_create(struct sway_seat *seat, struct sway_keyboard *sway_keyboard_create(struct sway_seat *seat,

@ -9,86 +9,112 @@
#include "sway/commands.h" #include "sway/commands.h"
#include "log.h" #include "log.h"
/**
* Remove all key ids associated to a keycode from the list of pressed keys
*/
static void state_erase_key(struct sway_shortcut_state *state,
uint32_t keycode) {
size_t j = 0;
for (size_t i = 0; i < state->npressed; ++i) {
if (i > j) {
state->pressed_keys[j] = state->pressed_keys[i];
state->pressed_keycodes[j] = state->pressed_keycodes[i];
}
if (state->pressed_keycodes[i] != keycode) {
++j;
}
}
while(state->npressed > j) {
--state->npressed;
state->pressed_keys[state->npressed] = 0;
state->pressed_keycodes[state->npressed] = 0;
}
}
/**
* Add a key id (with associated keycode) to the list of pressed keys,
* if the list is not full.
*/
static void state_add_key(struct sway_shortcut_state *state,
uint32_t keycode, uint32_t key_id) {
if (state->npressed >= SWAY_KEYBOARD_PRESSED_KEYS_CAP) {
return;
}
size_t i = 0;
while (i < state->npressed && state->pressed_keys[i] < key_id) {
++i;
}
size_t j = state->npressed;
while (j > i) {
state->pressed_keys[j] = state->pressed_keys[j - 1];
state->pressed_keycodes[j] = state->pressed_keycodes[j - 1];
--j;
}
state->pressed_keys[i] = key_id;
state->pressed_keycodes[i] = keycode;
state->npressed++;
}
/** /**
* Update the shortcut model state in response to new input * Update the shortcut model state in response to new input
*/ */
static void update_shortcut_state(struct sway_shortcut_state *state, static void update_shortcut_state(struct sway_shortcut_state *state,
struct wlr_event_keyboard_key *event, uint32_t new_key, struct wlr_event_keyboard_key *event, uint32_t new_key,
bool last_key_was_a_modifier) { uint32_t raw_modifiers) {
bool last_key_was_a_modifier = raw_modifiers != state->last_raw_modifiers;
state->last_raw_modifiers = raw_modifiers;
if (event->state == WLR_KEY_PRESSED) { if (event->state == WLR_KEY_PRESSED) {
if (last_key_was_a_modifier && state->last_key_index >= 0) { if (last_key_was_a_modifier && state->last_keycode) {
// Last pressed key before this one was a modifier // Last pressed key before this one was a modifier
state->pressed_keycodes[state->last_key_index] = 0; state_erase_key(state, state->last_keycode);
state->pressed_keys[state->last_key_index] = 0;
state->last_key_index = -1;
} }
// Add current key to set; there may be duplicates // Add current key to set; there may be duplicates
for (size_t i = 0; i < SWAY_KEYBOARD_PRESSED_KEYS_CAP; ++i) { state_add_key(state, event->keycode, new_key);
if (!state->pressed_keys[i]) { state->last_keycode = event->keycode;
state->pressed_keys[i] = new_key;
state->pressed_keycodes[i] = event->keycode;
state->last_key_index = i;
break;
}
}
} else { } else {
for (size_t i = 0; i < SWAY_KEYBOARD_PRESSED_KEYS_CAP; ++i) { state_erase_key(state, event->keycode);
// The same keycode may match multiple keysyms.
if (state->pressed_keycodes[i] == event->keycode) {
state->pressed_keys[i] = 0;
state->pressed_keycodes[i] = 0;
}
}
} }
} }
/** /**
* * If one exists, finds a binding which matches the shortcut model state,
* Returns a binding which matches the shortcut model state (ignoring the * current modifiers, release state, and locked state.
* `release` flag).
*/ */
static struct sway_binding *get_active_binding( static void get_active_binding(const struct sway_shortcut_state *state,
struct sway_shortcut_state *state, list_t *bindings, list_t *bindings, struct sway_binding **current_binding,
uint32_t modifiers, bool locked) { uint32_t modifiers, bool release, bool locked) {
int npressed_keys = 0;
for (size_t i = 0; i < SWAY_KEYBOARD_PRESSED_KEYS_CAP; ++i) {
if (state->pressed_keys[i]) {
++npressed_keys;
}
}
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];
if (modifiers ^ binding->modifiers || if (modifiers ^ binding->modifiers ||
npressed_keys != binding->keys->length || state->npressed != (size_t)binding->keys->length ||
locked > binding->locked) { locked > binding->locked ||
release != binding->release) {
continue; continue;
} }
bool match = true; bool match = true;
for (int j = 0; j < binding->keys->length; ++j) { for (size_t j = 0; j < state->npressed; j++) {
uint32_t key = *(uint32_t *)binding->keys->items[j]; uint32_t key = *(uint32_t *)binding->keys->items[j];
if (key != state->pressed_keys[j]) {
bool key_found = false;
for (int k = 0; k < SWAY_KEYBOARD_PRESSED_KEYS_CAP; ++k) {
if (state->pressed_keys[k] == key) {
key_found = true;
break;
}
}
if (!key_found) {
match = false; match = false;
break; break;
} }
} }
if (!match) {
continue;
}
if (match) { if (*current_binding && *current_binding != binding) {
return binding; wlr_log(L_DEBUG, "encountered duplicate bindings %d and %d",
(*current_binding)->order, binding->order);
} else {
*current_binding = binding;
} }
return;
} }
return NULL;
} }
/** /**
@ -204,69 +230,65 @@ static void handle_keyboard_key(struct wl_listener *listener, void *data) {
size_t raw_keysyms_len = size_t raw_keysyms_len =
keyboard_keysyms_raw(keyboard, keycode, &raw_keysyms, &raw_modifiers); keyboard_keysyms_raw(keyboard, keycode, &raw_keysyms, &raw_modifiers);
struct wlr_input_device *device = uint32_t code_modifiers = wlr_keyboard_get_modifiers(wlr_device->keyboard);
keyboard->seat_device->input_device->wlr_device;
uint32_t code_modifiers = wlr_keyboard_get_modifiers(device->keyboard);
bool last_key_was_a_modifier = code_modifiers != keyboard->last_modifiers;
keyboard->last_modifiers = code_modifiers;
// Update shortcut model state // Update shortcut model state
update_shortcut_state(&keyboard->state_keycodes, event, update_shortcut_state(&keyboard->state_keycodes, event,
(uint32_t)keycode, last_key_was_a_modifier); (uint32_t)keycode, code_modifiers);
for (size_t i = 0; i < translated_keysyms_len; ++i) { for (size_t i = 0; i < translated_keysyms_len; ++i) {
update_shortcut_state(&keyboard->state_keysyms_translated, update_shortcut_state(&keyboard->state_keysyms_translated,
event, (uint32_t)translated_keysyms[i], event, (uint32_t)translated_keysyms[i],
last_key_was_a_modifier && i == 0); code_modifiers);
} }
for (size_t i = 0; i < raw_keysyms_len; ++i) { for (size_t i = 0; i < raw_keysyms_len; ++i) {
update_shortcut_state(&keyboard->state_keysyms_raw, update_shortcut_state(&keyboard->state_keysyms_raw,
event, (uint32_t)raw_keysyms[i], event, (uint32_t)raw_keysyms[i],
last_key_was_a_modifier && i == 0); code_modifiers);
} }
// identify which binding should be executed.
struct sway_binding *binding = get_active_binding(
&keyboard->state_keycodes,
config->current_mode->keycode_bindings,
code_modifiers, input_inhibited);
struct sway_binding *translated_binding = get_active_binding(
&keyboard->state_keysyms_translated,
config->current_mode->keysym_bindings,
translated_modifiers, input_inhibited);
if (translated_binding && !binding) {
binding = translated_binding;
} else if (binding && translated_binding && binding != translated_binding) {
wlr_log(L_DEBUG, "encountered duplicate bindings %d and %d",
binding->order, translated_binding->order);
}
struct sway_binding *raw_binding = get_active_binding(
&keyboard->state_keysyms_raw,
config->current_mode->keysym_bindings,
raw_modifiers, input_inhibited);
if (raw_binding && !binding) {
binding = raw_binding;
} else if (binding && raw_binding && binding != raw_binding) {
wlr_log(L_DEBUG, "encountered duplicate bindings %d and %d",
binding->order, raw_binding->order);
}
bool handled = false; bool handled = false;
// Execute the identified binding if need be. // Identify active release binding
if (keyboard->held_binding && binding != keyboard->held_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);
get_active_binding(&keyboard->state_keysyms_translated,
config->current_mode->keysym_bindings, &binding_released,
translated_modifiers, true, input_inhibited);
get_active_binding(&keyboard->state_keysyms_raw,
config->current_mode->keysym_bindings, &binding_released,
raw_modifiers, true, input_inhibited);
// Execute stored release binding once no longer active
if (keyboard->held_binding && binding_released != keyboard->held_binding &&
event->state == WLR_KEY_RELEASED) { event->state == WLR_KEY_RELEASED) {
keyboard_execute_command(keyboard, keyboard->held_binding); keyboard_execute_command(keyboard, keyboard->held_binding);
handled = true; handled = true;
} }
if (binding != keyboard->held_binding) { if (binding_released != keyboard->held_binding) {
keyboard->held_binding = NULL; keyboard->held_binding = NULL;
} }
if (binding && event->state == WLR_KEY_PRESSED) { if (binding_released && event->state == WLR_KEY_PRESSED) {
if (binding->release) { keyboard->held_binding = binding_released;
keyboard->held_binding = binding; }
} else {
keyboard_execute_command(keyboard, binding); // Identify and execute active pressed binding
if (event->state == WLR_KEY_PRESSED) {
struct sway_binding *binding_pressed = NULL;
get_active_binding(&keyboard->state_keycodes,
config->current_mode->keycode_bindings, &binding_pressed,
code_modifiers, false, input_inhibited);
get_active_binding(&keyboard->state_keysyms_translated,
config->current_mode->keysym_bindings, &binding_pressed,
translated_modifiers, false, input_inhibited);
get_active_binding(&keyboard->state_keysyms_raw,
config->current_mode->keysym_bindings, &binding_pressed,
raw_modifiers, false, input_inhibited);
if (binding_pressed) {
keyboard_execute_command(keyboard, binding_pressed);
handled = true; handled = true;
} }
} }
@ -315,10 +337,6 @@ struct sway_keyboard *sway_keyboard_create(struct sway_seat *seat,
wl_list_init(&keyboard->keyboard_key.link); wl_list_init(&keyboard->keyboard_key.link);
wl_list_init(&keyboard->keyboard_modifiers.link); wl_list_init(&keyboard->keyboard_modifiers.link);
keyboard->state_keycodes.last_key_index = -1;
keyboard->state_keysyms_raw.last_key_index = -1;
keyboard->state_keysyms_translated.last_key_index = -1;
return keyboard; return keyboard;
} }

Loading…
Cancel
Save