Rewrite shortcut handling code to avoid hardcoded values

The same shortcut algorithm is now used for keycodes,
raw keysyms, and translated keysyms. Pressed keysyms
are now stored in association with the keycodes that
generated them. Modifier keycodes (and associated
keysyms) are identified retroactively by the subsequent
change to the modifier flags.
This commit is contained in:
frsfnrrg 2018-05-31 19:35:17 -04:00
parent f5ed65e633
commit a056419ad7
2 changed files with 168 additions and 265 deletions

View File

@ -3,7 +3,13 @@
#include "sway/input/seat.h" #include "sway/input/seat.h"
#define SWAY_KEYBOARD_PRESSED_KEYSYMS_CAP 32 #define SWAY_KEYBOARD_PRESSED_KEYS_CAP 32
struct sway_shortcut_state {
uint32_t pressed_keys[SWAY_KEYBOARD_PRESSED_KEYS_CAP];
uint32_t pressed_keycodes[SWAY_KEYBOARD_PRESSED_KEYS_CAP];
int last_key_index;
};
struct sway_keyboard { struct sway_keyboard {
struct sway_seat_device *seat_device; struct sway_seat_device *seat_device;
@ -13,11 +19,11 @@ struct sway_keyboard {
struct wl_listener keyboard_key; struct wl_listener keyboard_key;
struct wl_listener keyboard_modifiers; struct wl_listener keyboard_modifiers;
xkb_keysym_t pressed_keysyms_translated[SWAY_KEYBOARD_PRESSED_KEYSYMS_CAP]; struct sway_shortcut_state state_keysyms_translated;
uint32_t modifiers_translated; struct sway_shortcut_state state_keysyms_raw;
struct sway_shortcut_state state_keycodes;
xkb_keysym_t pressed_keysyms_raw[SWAY_KEYBOARD_PRESSED_KEYSYMS_CAP]; struct sway_binding *held_binding;
uint32_t modifiers_raw; uint32_t last_modifiers;
}; };
struct sway_keyboard *sway_keyboard_create(struct sway_seat *seat, struct sway_keyboard *sway_keyboard_create(struct sway_seat *seat,

View File

@ -9,88 +9,93 @@
#include "sway/commands.h" #include "sway/commands.h"
#include "log.h" #include "log.h"
static bool keysym_is_modifier(xkb_keysym_t keysym) { /**
switch (keysym) { * Update the shortcut model state in response to new input
case XKB_KEY_Shift_L: case XKB_KEY_Shift_R: */
case XKB_KEY_Control_L: case XKB_KEY_Control_R: static void update_shortcut_model(struct sway_shortcut_state* state,
case XKB_KEY_Caps_Lock: struct wlr_event_keyboard_key * event,
case XKB_KEY_Shift_Lock: uint32_t new_key,
case XKB_KEY_Meta_L: case XKB_KEY_Meta_R: bool last_key_was_a_modifier) {
case XKB_KEY_Alt_L: case XKB_KEY_Alt_R: if (event->state == WLR_KEY_PRESSED) {
case XKB_KEY_Super_L: case XKB_KEY_Super_R: if (last_key_was_a_modifier && state->last_key_index >= 0) {
case XKB_KEY_Hyper_L: case XKB_KEY_Hyper_R: // Last pressed key before this one was a modifier. We nullify
return true; // the key id but not the keycode (as that is used for erasure
default: // on release)
return false; state->pressed_keys[state->last_key_index] = 0;
} state->last_key_index = -1;
} }
static size_t pressed_keysyms_length(xkb_keysym_t *pressed_keysyms) { // Add current key to set; there may be duplicates
size_t n = 0; for (size_t i = 0; i < SWAY_KEYBOARD_PRESSED_KEYS_CAP; i++) {
for (size_t i = 0; i < SWAY_KEYBOARD_PRESSED_KEYSYMS_CAP; ++i) { if (!state->pressed_keys[i]) {
if (pressed_keysyms[i] != XKB_KEY_NoSymbol) { state->pressed_keys[i] = new_key;
++n; state->pressed_keycodes[i] = event->keycode;
state->last_key_index = i;
break;
} }
} }
return n; } else {
for (size_t i = 0; i < SWAY_KEYBOARD_PRESSED_KEYS_CAP; i++) {
// The same keycode may match multiple keysyms.
if (state->pressed_keycodes[i] == event->keycode) {
state->pressed_keys[i] = 0;
state->pressed_keycodes[i] = 0;
} }
static ssize_t pressed_keysyms_index(xkb_keysym_t *pressed_keysyms,
xkb_keysym_t keysym) {
for (size_t i = 0; i < SWAY_KEYBOARD_PRESSED_KEYSYMS_CAP; ++i) {
if (pressed_keysyms[i] == keysym) {
return i;
}
}
return -1;
}
static void pressed_keysyms_add(xkb_keysym_t *pressed_keysyms,
xkb_keysym_t keysym) {
ssize_t i = pressed_keysyms_index(pressed_keysyms, keysym);
if (i < 0) {
i = pressed_keysyms_index(pressed_keysyms, XKB_KEY_NoSymbol);
if (i >= 0) {
pressed_keysyms[i] = keysym;
} }
} }
} }
static void pressed_keysyms_remove(xkb_keysym_t *pressed_keysyms, /**
xkb_keysym_t keysym) { *
ssize_t i = pressed_keysyms_index(pressed_keysyms, keysym); * Returns a binding which matches the shortcut model state (ignoring the
if (i >= 0) { * `release` flag).
pressed_keysyms[i] = XKB_KEY_NoSymbol; */
static struct sway_binding* check_shortcut_model(
struct sway_shortcut_state* state, list_t* bindings,
uint32_t modifiers, 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) {
struct sway_binding *binding = bindings->items[i];
static void pressed_keysyms_update(xkb_keysym_t *pressed_keysyms, if (modifiers ^ binding->modifiers ||
const xkb_keysym_t *keysyms, size_t keysyms_len, npressed_keys != binding->keys->length ||
enum wlr_key_state state) { locked > binding->locked) {
for (size_t i = 0; i < keysyms_len; ++i) {
if (keysym_is_modifier(keysyms[i])) {
continue; continue;
} }
if (state == WLR_KEY_PRESSED) {
pressed_keysyms_add(pressed_keysyms, keysyms[i]); bool match = true;
} else { // WLR_KEY_RELEASED for (int j = 0; j < binding->keys->length; ++j) {
pressed_keysyms_remove(pressed_keysyms, keysyms[i]); uint32_t key = *(uint32_t*)binding->keys->items[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;
break;
}
} }
static bool binding_matches_key_state(struct sway_binding *binding, if (match) {
enum wlr_key_state key_state) { return binding;
if (key_state == WLR_KEY_PRESSED && !binding->release) {
return true;
} }
if (key_state == WLR_KEY_RELEASED && binding->release) {
return true;
} }
return false; return NULL;
} }
/**
* Execute the command associated to a binding
*/
static void keyboard_execute_command(struct sway_keyboard *keyboard, static void keyboard_execute_command(struct sway_keyboard *keyboard,
struct sway_binding *binding) { struct sway_binding *binding) {
wlr_log(L_DEBUG, "running command for binding: %s", wlr_log(L_DEBUG, "running command for binding: %s",
@ -113,7 +118,7 @@ static void keyboard_execute_command(struct sway_keyboard *keyboard,
* should be propagated to clients. * should be propagated to clients.
*/ */
static bool keyboard_execute_compositor_binding(struct sway_keyboard *keyboard, static bool keyboard_execute_compositor_binding(struct sway_keyboard *keyboard,
xkb_keysym_t *pressed_keysyms, uint32_t modifiers, size_t keysyms_len) { const xkb_keysym_t *pressed_keysyms, uint32_t modifiers, size_t keysyms_len) {
for (size_t i = 0; i < keysyms_len; ++i) { for (size_t i = 0; i < keysyms_len; ++i) {
xkb_keysym_t keysym = pressed_keysyms[i]; xkb_keysym_t keysym = pressed_keysyms[i];
if (keysym >= XKB_KEY_XF86Switch_VT_1 && if (keysym >= XKB_KEY_XF86Switch_VT_1 &&
@ -133,157 +138,6 @@ static bool keyboard_execute_compositor_binding(struct sway_keyboard *keyboard,
return false; return false;
} }
/**
* Execute keyboard bindings bound with `bindysm` for the given keyboard state.
*
* Returns true if the keysym was handled by a binding and false if the event
* should be propagated to clients.
*/
static bool keyboard_execute_bindsym(struct sway_keyboard *keyboard,
xkb_keysym_t *pressed_keysyms, uint32_t modifiers,
enum wlr_key_state key_state, bool locked) {
// configured bindings
int n = pressed_keysyms_length(pressed_keysyms);
list_t *keysym_bindings = config->current_mode->keysym_bindings;
for (int i = 0; i < keysym_bindings->length; ++i) {
struct sway_binding *binding = keysym_bindings->items[i];
if (!binding_matches_key_state(binding, key_state) ||
modifiers ^ binding->modifiers ||
n != binding->keys->length || locked > binding->locked) {
continue;
}
bool match = true;
for (int j = 0; j < binding->keys->length; ++j) {
match =
pressed_keysyms_index(pressed_keysyms,
*(int*)binding->keys->items[j]) >= 0;
if (!match) {
break;
}
}
if (match) {
keyboard_execute_command(keyboard, binding);
return true;
}
}
return false;
}
static bool binding_matches_keycodes(struct wlr_keyboard *keyboard,
struct sway_binding *binding, struct wlr_event_keyboard_key *event, bool locked) {
assert(binding->bindcode);
uint32_t keycode = event->keycode + 8;
if (!binding_matches_key_state(binding, event->state)) {
return false;
}
if (locked > binding->locked) {
return false;
}
uint32_t modifiers = wlr_keyboard_get_modifiers(keyboard);
if (modifiers ^ binding->modifiers) {
return false;
}
// on release, the released key must be in the binding
if (event->state == WLR_KEY_RELEASED) {
bool found = false;
for (int i = 0; i < binding->keys->length; ++i) {
uint32_t binding_keycode = *(uint32_t*)binding->keys->items[i] + 8;
if (binding_keycode == keycode) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
// every keycode in the binding must be present in the pressed keys on the
// keyboard
for (int i = 0; i < binding->keys->length; ++i) {
uint32_t binding_keycode = *(uint32_t*)binding->keys->items[i] + 8;
if (event->state == WLR_KEY_RELEASED && keycode == binding_keycode) {
continue;
}
bool found = false;
for (size_t j = 0; j < keyboard->num_keycodes; ++j) {
xkb_keycode_t keycode = keyboard->keycodes[j] + 8;
if (keycode == binding_keycode) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
// every keycode pressed on the keyboard must be present within the binding
// keys (unless it is a modifier)
for (size_t i = 0; i < keyboard->num_keycodes; ++i) {
xkb_keycode_t keycode = keyboard->keycodes[i] + 8;
bool found = false;
for (int j = 0; j < binding->keys->length; ++j) {
uint32_t binding_keycode = *(uint32_t*)binding->keys->items[j] + 8;
if (binding_keycode == keycode) {
found = true;
break;
}
}
if (!found) {
if (!binding->modifiers) {
return false;
}
// check if it is a modifier, which we know matched from the check
// above
const xkb_keysym_t *keysyms;
int num_keysyms =
xkb_state_key_get_syms(keyboard->xkb_state,
keycode, &keysyms);
if (num_keysyms != 1 || !keysym_is_modifier(keysyms[0])) {
return false;
}
}
}
return true;
}
/**
* Execute keyboard bindings bound with `bindcode` for the given keyboard state.
*
* Returns true if the keysym was handled by a binding and false if the event
* should be propagated to clients.
*/
static bool keyboard_execute_bindcode(struct sway_keyboard *keyboard,
struct wlr_event_keyboard_key *event, bool locked) {
struct wlr_keyboard *wlr_keyboard =
keyboard->seat_device->input_device->wlr_device->keyboard;
list_t *keycode_bindings = config->current_mode->keycode_bindings;
for (int i = 0; i < keycode_bindings->length; ++i) {
struct sway_binding *binding = keycode_bindings->items[i];
if (binding_matches_keycodes(wlr_keyboard, binding, event, locked)) {
keyboard_execute_command(keyboard, binding);
return true;
}
}
return false;
}
/** /**
* Get keysyms and modifiers from the keyboard as xkb sees them. * Get keysyms and modifiers from the keyboard as xkb sees them.
* *
@ -339,61 +193,100 @@ static void handle_keyboard_key(struct wl_listener *listener, void *data) {
struct wlr_event_keyboard_key *event = data; struct wlr_event_keyboard_key *event = data;
bool input_inhibited = keyboard->seat_device->sway_seat->exclusive_client != NULL; bool input_inhibited = keyboard->seat_device->sway_seat->exclusive_client != NULL;
// Identify new keycode, raw keysym(s), and translated keysym(s)
xkb_keycode_t keycode = event->keycode + 8; xkb_keycode_t keycode = event->keycode + 8;
bool handled = false;
// handle keycodes
handled = keyboard_execute_bindcode(keyboard, event, input_inhibited);
// handle translated keysyms
if (!handled && event->state == WLR_KEY_RELEASED) {
handled = keyboard_execute_bindsym(keyboard,
keyboard->pressed_keysyms_translated,
keyboard->modifiers_translated,
event->state, input_inhibited);
}
const xkb_keysym_t *translated_keysyms; const xkb_keysym_t *translated_keysyms;
uint32_t translated_modifiers;
size_t translated_keysyms_len = size_t translated_keysyms_len =
keyboard_keysyms_translated(keyboard, keycode, &translated_keysyms, keyboard_keysyms_translated(keyboard, keycode, &translated_keysyms,
&keyboard->modifiers_translated); &translated_modifiers);
pressed_keysyms_update(keyboard->pressed_keysyms_translated,
translated_keysyms, translated_keysyms_len, event->state); const xkb_keysym_t *raw_keysyms;
if (!handled && event->state == WLR_KEY_PRESSED) { uint32_t raw_modifiers;
handled = keyboard_execute_bindsym(keyboard, size_t raw_keysyms_len =
keyboard->pressed_keysyms_translated, keyboard_keysyms_raw(keyboard, keycode, &raw_keysyms, &raw_modifiers);
keyboard->modifiers_translated,
event->state, input_inhibited); struct wlr_input_device *device =
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 models
update_shortcut_model(&keyboard->state_keycodes, event,
(uint32_t)keycode, last_key_was_a_modifier);
for (size_t i=0;i<translated_keysyms_len;i++) {
update_shortcut_model(&keyboard->state_keysyms_translated,
event, (uint32_t)translated_keysyms[i],
last_key_was_a_modifier);
}
for (size_t i=0;i<raw_keysyms_len;i++) {
update_shortcut_model(&keyboard->state_keysyms_raw,
event, (uint32_t)raw_keysyms[i],
last_key_was_a_modifier);
} }
// Handle raw keysyms // identify which binding should be executed.
if (!handled && event->state == WLR_KEY_RELEASED) { struct sway_binding *binding =
handled = keyboard_execute_bindsym(keyboard, check_shortcut_model(&keyboard->state_keycodes,
keyboard->pressed_keysyms_raw, keyboard->modifiers_raw, config->current_mode->keycode_bindings,
event->state, input_inhibited); code_modifiers, input_inhibited);
for (size_t i=0;i<translated_keysyms_len;i++) {
struct sway_binding *translated_binding =
check_shortcut_model(&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);
}
}
for (size_t i=0;i<raw_keysyms_len;i++) {
struct sway_binding *raw_binding =
check_shortcut_model(&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;
// Execute the identified binding if need be.
if (keyboard->held_binding && binding != keyboard->held_binding &&
event->state == WLR_KEY_RELEASED) {
keyboard_execute_command(keyboard, keyboard->held_binding);
handled = true;
}
if (binding != keyboard->held_binding) {
keyboard->held_binding = NULL;
}
if (binding && event->state == WLR_KEY_PRESSED) {
if (binding->release) {
keyboard->held_binding = binding;
} else {
keyboard_execute_command(keyboard, binding);
handled = true;
} }
const xkb_keysym_t *raw_keysyms;
size_t raw_keysyms_len =
keyboard_keysyms_raw(keyboard, keycode, &raw_keysyms, &keyboard->modifiers_raw);
pressed_keysyms_update(keyboard->pressed_keysyms_raw, raw_keysyms,
raw_keysyms_len, event->state);
if (!handled && event->state == WLR_KEY_PRESSED) {
handled = keyboard_execute_bindsym(keyboard,
keyboard->pressed_keysyms_raw, keyboard->modifiers_raw,
event->state, input_inhibited);
} }
// Compositor bindings // Compositor bindings
if (!handled && event->state == WLR_KEY_PRESSED) { if (!handled && event->state == WLR_KEY_PRESSED) {
handled = handled = keyboard_execute_compositor_binding(
keyboard_execute_compositor_binding(keyboard, keyboard, translated_keysyms, translated_modifiers,
keyboard->pressed_keysyms_translated,
keyboard->modifiers_translated,
translated_keysyms_len); translated_keysyms_len);
} }
if (!handled && event->state == WLR_KEY_PRESSED) { if (!handled && event->state == WLR_KEY_PRESSED) {
handled = handled = keyboard_execute_compositor_binding(
keyboard_execute_compositor_binding(keyboard, keyboard, raw_keysyms, raw_modifiers,
keyboard->pressed_keysyms_raw, keyboard->modifiers_raw,
raw_keysyms_len); raw_keysyms_len);
} }
@ -429,6 +322,10 @@ 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;
} }