// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include "config/keybind.h" #include #include #include #include #include #include #include "common/list.h" #include "common/mem.h" #include "config/rcxml.h" #include "labwc.h" uint32_t parse_modifier(const char *symname) { /* Mod2 == NumLock */ if (!strcmp(symname, "S")) { return WLR_MODIFIER_SHIFT; } else if (!strcmp(symname, "C")) { return WLR_MODIFIER_CTRL; } else if (!strcmp(symname, "A") || !strcmp(symname, "Mod1")) { return WLR_MODIFIER_ALT; } else if (!strcmp(symname, "W") || !strcmp(symname, "Mod4")) { return WLR_MODIFIER_LOGO; } else if (!strcmp(symname, "M") || !strcmp(symname, "Mod5")) { return WLR_MODIFIER_MOD5; } else if (!strcmp(symname, "H") || !strcmp(symname, "Mod3")) { return WLR_MODIFIER_MOD3; } else { return 0; } } bool keybind_the_same(struct keybind *a, struct keybind *b) { assert(a && b); if (a->modifiers != b->modifiers || a->keysyms_len != b->keysyms_len) { return false; } for (size_t i = 0; i < a->keysyms_len; i++) { if (a->keysyms[i] != b->keysyms[i]) { return false; } } return true; } bool keybind_contains_keycode(struct keybind *keybind, xkb_keycode_t keycode) { assert(keybind); for (size_t i = 0; i < keybind->keycodes_len; i++) { if (keybind->keycodes[i] == keycode) { return true; } } return false; } bool keybind_contains_keysym(struct keybind *keybind, xkb_keysym_t keysym) { assert(keybind); for (size_t i = 0; i < keybind->keysyms_len; i++) { if (keybind->keysyms[i] == keysym) { return true; } } return false; } static bool keybind_contains_any_keysym(struct keybind *keybind, const xkb_keysym_t *syms, int nr_syms) { for (int i = 0; i < nr_syms; i++) { if (keybind_contains_keysym(keybind, syms[i])) { return true; } } return false; } static void update_keycodes_iter(struct xkb_keymap *keymap, xkb_keycode_t key, void *data) { struct keybind *keybind; const xkb_keysym_t *syms; xkb_layout_index_t layout = *(xkb_layout_index_t *)data; int nr_syms = xkb_keymap_key_get_syms_by_level(keymap, key, layout, 0, &syms); if (!nr_syms) { return; } wl_list_for_each(keybind, &rc.keybinds, link) { if (keybind->keycodes_layout >= 0 && (xkb_layout_index_t)keybind->keycodes_layout != layout) { /* Prevent storing keycodes from multiple layouts */ continue; } if (keybind->use_syms_only) { continue; } if (keybind_contains_any_keysym(keybind, syms, nr_syms)) { if (keybind_contains_keycode(keybind, key)) { /* Prevent storing the same keycode twice */ continue; } if (keybind->keycodes_len == MAX_KEYCODES) { wlr_log(WLR_ERROR, "Already stored %lu keycodes for keybind", keybind->keycodes_len); continue; } keybind->keycodes[keybind->keycodes_len++] = key; keybind->keycodes_layout = layout; } } } void keybind_update_keycodes(struct server *server) { struct xkb_state *state = server->seat.keyboard_group->keyboard.xkb_state; struct xkb_keymap *keymap = xkb_state_get_keymap(state); struct keybind *keybind; wl_list_for_each(keybind, &rc.keybinds, link) { keybind->keycodes_len = 0; keybind->keycodes_layout = -1; } xkb_layout_index_t layouts = xkb_keymap_num_layouts(keymap); for (xkb_layout_index_t i = 0; i < layouts; i++) { wlr_log(WLR_DEBUG, "Found layout %s", xkb_keymap_layout_get_name(keymap, i)); xkb_keymap_key_for_each(keymap, update_keycodes_iter, &i); } } struct keybind * keybind_create(const char *keybind) { xkb_keysym_t sym; struct keybind *k = znew(*k); xkb_keysym_t keysyms[MAX_KEYSYMS]; gchar **symnames = g_strsplit(keybind, "-", -1); for (size_t i = 0; symnames[i]; i++) { const char *symname = symnames[i]; /* * Since "-" is used as a separator, a keybind string like "W--" * becomes "W", "", "". This means that it is impossible to bind * an action to the "-" key in this way. * We detect empty ""s outputted by g_strsplit and treat them as * literal "-"s. */ if (!symname[0]) { /* * You might have noticed that in the "W--" example, the * output is "W", "", ""; which turns into "W", "-", * "-". In order to avoid such duplications, we perform * a lookahead on the tokens to treat that edge-case. */ if (symnames[i+1] && !symnames[i+1][0]) { continue; } symname = "-"; } uint32_t modifier = parse_modifier(symname); if (modifier != 0) { k->modifiers |= modifier; } else { sym = xkb_keysym_from_name(symname, XKB_KEYSYM_CASE_INSENSITIVE); if (sym == XKB_KEY_NoSymbol && g_utf8_strlen(symname, -1) == 1) { /* * xkb_keysym_from_name() only handles a legacy set of single * characters. Thus we try to get the unicode codepoint here * and try a direct translation instead. * * This allows using keybinds like 'W-รถ' and similar. */ gunichar codepoint = g_utf8_get_char_validated(symname, -1); if (codepoint != (gunichar)-1) { sym = xkb_utf32_to_keysym(codepoint); } } sym = xkb_keysym_to_lower(sym); if (sym == XKB_KEY_NoSymbol) { wlr_log(WLR_ERROR, "unknown keybind (%s)", symname); free(k); k = NULL; break; } keysyms[k->keysyms_len] = sym; k->keysyms_len++; if (k->keysyms_len == MAX_KEYSYMS) { wlr_log(WLR_ERROR, "There are a lot of fingers involved. " "We stopped counting at %u.", MAX_KEYSYMS); wlr_log(WLR_ERROR, "Offending keybind was %s", keybind); break; } } } g_strfreev(symnames); if (!k) { return NULL; } wl_list_append(&rc.keybinds, &k->link); k->keysyms = xmalloc(k->keysyms_len * sizeof(xkb_keysym_t)); memcpy(k->keysyms, keysyms, k->keysyms_len * sizeof(xkb_keysym_t)); wl_list_init(&k->actions); return k; } void keybind_destroy(struct keybind *keybind) { assert(wl_list_empty(&keybind->actions)); zfree(keybind->keysyms); zfree(keybind); }