#include "view.h"
#include "workspaces.h"
+struct keysyms {
+ const xkb_keysym_t *syms;
+ int nr_syms;
+};
+
+struct keyinfo {
+ xkb_keycode_t xkb_keycode;
+ struct keysyms translated;
+ struct keysyms raw;
+ uint32_t modifiers;
+ bool is_modifier;
+};
+
static bool should_cancel_cycling_on_next_key_release;
static void
wlr_seat_keyboard_notify_modifiers(seat->seat, &wlr_keyboard->modifiers);
}
-static bool
-handle_keybinding(struct server *server, uint32_t modifiers, xkb_keysym_t sym, xkb_keycode_t code)
+static struct keybind *
+match_keybinding_for_sym(struct server *server, uint32_t modifiers,
+ xkb_keysym_t sym, xkb_keycode_t xkb_keycode)
{
- uint32_t evdev_scancode = code - 8;
-
struct keybind *keybind;
wl_list_for_each(keybind, &rc.keybinds, link) {
if (modifiers ^ keybind->modifiers) {
if (sym == XKB_KEY_NoSymbol) {
/* Use keycodes */
for (size_t i = 0; i < keybind->keycodes_len; i++) {
- /*
- * Update key-state before action_run() because
- * the action might lead to seat_focus() in
- * which case we pass the 'pressed-sent' keys to
- * the new surface.
- */
- if (keybind->keycodes[i] == code) {
- key_state_store_pressed_key_as_bound(evdev_scancode);
- actions_run(NULL, server, &keybind->actions, 0);
- return true;
+ if (keybind->keycodes[i] == xkb_keycode) {
+ return keybind;
}
}
} else {
/* Use syms */
for (size_t i = 0; i < keybind->keysyms_len; i++) {
if (xkb_keysym_to_lower(sym) == keybind->keysyms[i]) {
- key_state_store_pressed_key_as_bound(evdev_scancode);
- actions_run(NULL, server, &keybind->actions, 0);
- return true;
+ return keybind;
}
}
}
}
- return false;
+ return NULL;
+}
+
+/*
+ * When matching against keybinds, we process the input keys in the
+ * following order of precedence:
+ * a. Keycodes (of physical keys) (not if keybind is layoutDependent)
+ * b. Translated keysyms (taking into account modifiers, so if Shift+1
+ * were pressed on a us keyboard, the keysym would be '!')
+ * c. Raw keysyms (ignoring modifiers such as shift, so in the above
+ * example the keysym would just be '1')
+ *
+ * The reasons for this approach are:
+ * 1. To make keybinds keyboard-layout agnostic (by checking keycodes
+ * before keysyms). This means that in a multi-layout situation,
+ * keybinds work regardless of which layout is active at the time
+ * of the key-press.
+ * 2. To support keybinds relating to keysyms that are only available
+ * in a particular layout, for example å, ä and ö.
+ * 3. To support keybinds that are only valid with a modifier, for
+ * example the numpad keys with NumLock enabled: KP_x. These would
+ * only be matched by the translated keysyms.
+ * 4. To support keybinds such as `S-1` (by checking raw keysyms).
+ *
+ * Reason 4 will also be satisfied by matching the keycodes. However,
+ * when a keybind is configured to be layoutDependent we still need
+ * the raw keysym fallback.
+ */
+static struct keybind *
+match_keybinding(struct server *server, struct keyinfo *keyinfo)
+{
+ /* First try keycodes */
+ struct keybind *keybind = match_keybinding_for_sym(server,
+ keyinfo->modifiers, XKB_KEY_NoSymbol, keyinfo->xkb_keycode);
+ if (keybind) {
+ wlr_log(WLR_DEBUG, "keycode matched");
+ return keybind;
+ }
+
+ /* Then fall back to keysyms */
+ for (int i = 0; i < keyinfo->translated.nr_syms; i++) {
+ keybind = match_keybinding_for_sym(server, keyinfo->modifiers,
+ keyinfo->translated.syms[i], keyinfo->xkb_keycode);
+ if (keybind) {
+ wlr_log(WLR_DEBUG, "translated keysym matched");
+ return keybind;
+ }
+ }
+
+ /* And finally test for keysyms without modifier */
+ for (int i = 0; i < keyinfo->raw.nr_syms; i++) {
+ keybind = match_keybinding_for_sym(server, keyinfo->modifiers,
+ keyinfo->raw.syms[i], keyinfo->xkb_keycode);
+ if (keybind) {
+ wlr_log(WLR_DEBUG, "raw keysym matched");
+ return keybind;
+ }
+ }
+
+ return NULL;
}
static bool
}
}
-struct keysyms {
- const xkb_keysym_t *syms;
- int nr_syms;
-};
+static struct keyinfo
+get_keyinfo(struct wlr_keyboard *wlr_keyboard, uint32_t evdev_keycode)
+{
+ struct keyinfo keyinfo = {0};
+
+ /* Translate evdev/libinput keycode -> xkb */
+ keyinfo.xkb_keycode = evdev_keycode + 8;
+
+ /* Get a list of keysyms based on the keymap for this keyboard */
+ keyinfo.translated.nr_syms = xkb_state_key_get_syms(
+ wlr_keyboard->xkb_state, keyinfo.xkb_keycode,
+ &keyinfo.translated.syms);
+
+ /*
+ * Get keysyms from the keyboard as if there was no modifier
+ * translations. For example, get Shift+1 rather than Shift+!
+ * (with US keyboard layout).
+ */
+ xkb_layout_index_t layout_index = xkb_state_key_get_layout(
+ wlr_keyboard->xkb_state, keyinfo.xkb_keycode);
+ keyinfo.raw.nr_syms = xkb_keymap_key_get_syms_by_level(
+ wlr_keyboard->keymap, keyinfo.xkb_keycode, layout_index, 0,
+ &keyinfo.raw.syms);
+
+ /*
+ * keyboard_key_notify() is called before keyboard_key_modifier(),
+ * so 'modifiers' refers to modifiers that were pressed before the
+ * key event in hand. Consequently, we use is_modifier_key() to
+ * find out if the key event being processed is a modifier.
+ *
+ * Sway solves this differently by saving the 'modifiers' state
+ * and checking if it has changed each time we get to the equivalent
+ * of this function. If it has changed, it concludes that the last
+ * key was a modifier and then deletes it from the buffer of pressed
+ * keycodes. For us the equivalent would look something like this:
+ *
+ * static uint32_t last_modifiers;
+ * bool last_key_was_a_modifier = last_modifiers != modifiers;
+ * last_modifiers = modifiers;
+ * if (last_key_was_a_modifier) {
+ * key_state_remove_last_pressed_key(last_pressed_keycode);
+ * }
+ */
+ keyinfo.modifiers = wlr_keyboard_get_modifiers(wlr_keyboard);
+ keyinfo.is_modifier = false;
+ for (int i = 0; i < keyinfo.translated.nr_syms; i++) {
+ keyinfo.is_modifier |=
+ is_modifier_key(keyinfo.translated.syms[i]);
+ }
+
+ return keyinfo;
+}
+
+static bool
+handle_key_release(struct server *server, uint32_t evdev_keycode)
+{
+ /*
+ * Release events for keys that were not bound should always be
+ * forwarded to clients to avoid stuck keys.
+ */
+ if (!key_state_corresponding_press_event_was_bound(evdev_keycode)) {
+ return false;
+ }
+
+ /*
+ * If a user lets go of the modifier (e.g. alt) before the 'normal'
+ * key (e.g. tab) when window-cycling, we do not end the cycling
+ * until both keys have been released. If we end the window-cycling
+ * on release of the modifier only, some XWayland clients such as
+ * hexchat realise that tab is pressed (even though we did not
+ * forward the event) and because we absorb the equivalent release
+ * event it gets stuck on repeat.
+ */
+ if (should_cancel_cycling_on_next_key_release) {
+ end_cycling(server);
+ }
+
+ /*
+ * If a press event was handled by a compositor binding, then do
+ * not forward the corresponding release event to clients.
+ */
+ key_state_bound_key_remove(evdev_keycode);
+ return true;
+}
+
+static bool
+handle_change_vt_key(struct server *server, struct keysyms *translated)
+{
+ for (int i = 0; i < translated->nr_syms; i++) {
+ unsigned int vt =
+ translated->syms[i] - XKB_KEY_XF86Switch_VT_1 + 1;
+ if (vt >= 1 && vt <= 12) {
+ change_vt(server, vt);
+ return true;
+ }
+ }
+ return false;
+}
static void
handle_menu_keys(struct server *server, struct keysyms *syms)
}
}
+static void
+handle_cycle_view_key(struct server *server, struct keyinfo *keyinfo)
+{
+ for (int i = 0; i < keyinfo->translated.nr_syms; i++) {
+ if (keyinfo->translated.syms[i] == XKB_KEY_Escape) {
+ /* cancel view-cycle */
+ osd_preview_restore(server);
+ osd_finish(server);
+ return;
+ }
+ }
+
+ /* cycle to next */
+ bool backwards = keyinfo->modifiers & WLR_MODIFIER_SHIFT;
+ if (!keyinfo->is_modifier) {
+ enum lab_cycle_dir dir = backwards
+ ? LAB_CYCLE_DIR_BACKWARD
+ : LAB_CYCLE_DIR_FORWARD;
+ server->osd_state.cycle_view = desktop_cycle_view(server,
+ server->osd_state.cycle_view, dir);
+ osd_update(server);
+ }
+}
+
static bool
handle_compositor_keybindings(struct keyboard *keyboard,
struct wlr_keyboard_key_event *event)
struct seat *seat = keyboard->base.seat;
struct server *server = seat->server;
struct wlr_keyboard *wlr_keyboard = keyboard->wlr_keyboard;
-
- /* Translate libinput keycode -> xkbcommon */
- uint32_t keycode = event->keycode + 8;
-
- /* Get a list of keysyms based on the keymap for this keyboard */
- struct keysyms translated = { 0 };
- translated.nr_syms = xkb_state_key_get_syms(wlr_keyboard->xkb_state,
- keycode, &translated.syms);
-
- /*
- * Get keysyms from the keyboard as if there was no modifier
- * translations. For example, get Shift+1 rather than Shift+! (with US
- * keyboard layout).
- */
- struct keysyms raw = { 0 };
- xkb_layout_index_t layout_index =
- xkb_state_key_get_layout(wlr_keyboard->xkb_state, keycode);
- raw.nr_syms = xkb_keymap_key_get_syms_by_level(wlr_keyboard->keymap,
- keycode, layout_index, 0, &raw.syms);
-
- /*
- * keyboard_key_notify() is called before keyboard_key_modifier(), so
- * 'modifiers' refers to modifiers that were pressed before the key
- * event in hand. Consequently, we use is_modifier_key() to find out if
- * the key event being processed is a modifier.
- *
- * Sway solves this differently by saving the 'modifiers' state and
- * checking if it has changed each time we get to the equivalent of this
- * function. If it has changed, it concludes that the last key was a
- * modifier and then deletes it from the buffer of pressed keycodes.
- * For us the equivalent would look something like this:
- *
- * static uint32_t last_modifiers;
- * bool last_key_was_a_modifier = last_modifiers != modifiers;
- * last_modifiers = modifiers;
- * if (last_key_was_a_modifier) {
- * key_state_remove_last_pressed_key(last_pressed_keycode);
- * }
- */
-
- bool is_modifier = false;
- uint32_t modifiers = wlr_keyboard_get_modifiers(wlr_keyboard);
-
- for (int i = 0; i < translated.nr_syms; i++) {
- is_modifier |= is_modifier_key(translated.syms[i]);
- }
+ struct keyinfo keyinfo = get_keyinfo(wlr_keyboard, event->keycode);
key_state_set_pressed(event->keycode,
- event->state == WL_KEYBOARD_KEY_STATE_PRESSED, is_modifier);
+ event->state == WL_KEYBOARD_KEY_STATE_PRESSED,
+ keyinfo.is_modifier);
if (event->state == WL_KEYBOARD_KEY_STATE_RELEASED) {
- /*
- * Release events for keys that were not bound should
- * always be forwarded to clients to avoid stuck keys.
- */
- if (!key_state_corresponding_press_event_was_bound(
- event->keycode)) {
- return false;
- }
-
- /*
- * If a user lets go of the modifier (e.g. alt) before
- * the 'normal' key (e.g. tab) when window-cycling, we
- * do not end the cycling until both keys have been
- * released. If we end the window-cycling on release of
- * the modifier only, some XWayland clients such as
- * hexchat realise that tab is pressed (even though we
- * did not forward the event) and because we absorb the
- * equivalent release event it gets stuck on repeat.
- */
- if (should_cancel_cycling_on_next_key_release) {
- end_cycling(server);
- }
-
- /*
- * If a press event was handled by a compositor binding,
- * then do not forward the corresponding release event
- * to clients.
- */
- key_state_bound_key_remove(event->keycode);
- return true;
+ return handle_key_release(server, event->keycode);
}
- /*
- * Note: checks of event->state == WL_KEYBOARD_KEY_STATE_PRESSED
- * below are redundant, but are left in place for now.
- */
- bool handled = false;
-
/* Catch C-A-F1 to C-A-F12 to change tty */
- if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) {
- for (int i = 0; i < translated.nr_syms; i++) {
- unsigned int vt = translated.syms[i] - XKB_KEY_XF86Switch_VT_1 + 1;
- if (vt >= 1 && vt <= 12) {
- change_vt(server, vt);
- /*
- * Don't send any key events to clients when
- * changing tty
- */
- handled = true;
- goto out;
- }
- }
+ if (handle_change_vt_key(server, &keyinfo.translated)) {
+ key_state_store_pressed_key_as_bound(event->keycode);
+ return true;
}
/*
}
if (server->input_mode == LAB_INPUT_STATE_MENU) {
- if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) {
- key_state_store_pressed_key_as_bound(event->keycode);
- handle_menu_keys(server, &translated);
- }
+ key_state_store_pressed_key_as_bound(event->keycode);
+ handle_menu_keys(server, &keyinfo.translated);
return true;
}
if (server->osd_state.cycle_view) {
- if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) {
- for (int i = 0; i < translated.nr_syms; i++) {
- if (translated.syms[i] == XKB_KEY_Escape) {
- /*
- * Cancel view-cycle
- *
- * osd_finish() additionally resets
- * cycle_view to NULL
- */
- osd_preview_restore(server);
- osd_finish(server);
-
- handled = true;
- goto out;
- }
- }
-
- /* cycle to next */
- bool backwards = modifiers & WLR_MODIFIER_SHIFT;
- if (!is_modifier) {
- enum lab_cycle_dir dir = backwards
- ? LAB_CYCLE_DIR_BACKWARD
- : LAB_CYCLE_DIR_FORWARD;
- server->osd_state.cycle_view = desktop_cycle_view(server,
- server->osd_state.cycle_view, dir);
- osd_update(server);
- }
- }
- /* don't send any key events to clients when osd onscreen */
- handled = true;
- goto out;
+ key_state_store_pressed_key_as_bound(event->keycode);
+ handle_cycle_view_key(server, &keyinfo);
+ return true;
}
/*
* Handle compositor keybinds
- *
- * When matching against keybinds, we process the input keys in the
- * following order of precedence:
- * a. Keycodes (of physical keys) (not if keybind is layoutDependent)
- * b. Translated keysyms (taking into account modifiers, so if Shift+1
- * were pressed on a us keyboard, the keysym would be '!')
- * c. Raw keysyms (ignoring modifiers such as shift, so in the above
- * example the keysym would just be '1')
- *
- * The reasons for this approach are:
- * 1. To make keybinds keyboard-layout agnostic (by checking keycodes
- * before keysyms). This means that in a multi-layout situation,
- * keybinds work regardless of which layout is active at the time
- * of the key-press.
- * 2. To support keybinds relating to keysyms that are only available
- * in a particular layout, for example å, ä and ö.
- * 3. To support keybinds that are only valid with a modifier, for
- * example the numpad keys with NumLock enabled: KP_x. These would
- * only be matched by the translated keysyms.
- * 4. To support keybinds such as `S-1` (by checking raw keysyms).
- *
- * Reason 4 will also be satisfied by matching the keycodes. However,
- * when a keybind is configured to be layoutDependent we still need
- * the raw keysym fallback.
*/
-
- if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) {
- /* First try keycodes */
- handled |= handle_keybinding(server, modifiers, XKB_KEY_NoSymbol, keycode);
- if (handled) {
- wlr_log(WLR_DEBUG, "keycodes matched");
- return true;
- }
-
- /* Then fall back to keysyms */
- for (int i = 0; i < translated.nr_syms; i++) {
- handled |= handle_keybinding(server, modifiers,
- translated.syms[i], keycode);
- }
- if (handled) {
- wlr_log(WLR_DEBUG, "translated keysyms matched");
- return true;
- }
-
- /* And finally test for keysyms without modifier */
- for (int i = 0; i < raw.nr_syms; i++) {
- handled |= handle_keybinding(server, modifiers, raw.syms[i], keycode);
- }
- if (handled) {
- wlr_log(WLR_DEBUG, "raw keysyms matched");
- return true;
- }
- }
-
-out:
- if (handled) {
+ struct keybind *keybind = match_keybinding(server, &keyinfo);
+ if (keybind) {
+ /*
+ * Update key-state before action_run() because the action
+ * might lead to seat_focus() in which case we pass the
+ * 'pressed-sent' keys to the new surface.
+ */
key_state_store_pressed_key_as_bound(event->keycode);
+ actions_run(NULL, server, &keybind->actions, 0);
+ return true;
}
- return handled;
+ return false;
}
static int