labwc/src/config/keybind.c

222 lines
5.8 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
#define _POSIX_C_SOURCE 200809L
#include "config/keybind.h"
#include <assert.h>
#include <glib.h>
#include <stdlib.h>
#include <string.h>
#include <wlr/types/wlr_keyboard_group.h>
#include <wlr/util/log.h>
#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);
}