input/keyboard: extend bindsym --to-code to work with duplicate matches

This modifies `get_active_binding` to treat bindsym --to-codes
separately: per each keysym `i` in `binding->keys`, look through the list of
matching keycodes in `binding->translations[i]`.

Another solution is to take the cartesian product of all syms and make a
binding per each product. This makes retranslation more difficult though
because the dups from the old layout have to be cleared out before
translating to the new `xkb_layout`. Whether by retaining a parent
binding, searching through all bindings for matching `binding->command`,
or some other ref count structure, that would introduce more complexity
than modifying get_active binding to account for dups. Notice this
requires no changes to the existing retranslation logic.
This commit is contained in:
Furkan Sahin 2024-09-23 16:07:01 -05:00
parent 78fa4e9856
commit 4d0d553338
3 changed files with 73 additions and 66 deletions

View file

@ -62,7 +62,7 @@ struct sway_binding {
char *input;
uint32_t flags;
list_t *keys; // sorted in ascending order
list_t *syms; // sorted in ascending order; NULL if BINDING_CODE is not set
list_t **translations; // translations[i] = all keycodes for keysym keys[i]
uint32_t modifiers;
xkb_layout_index_t group;
char *command;

View file

@ -14,7 +14,6 @@
#include "list.h"
#include "log.h"
#include "stringop.h"
#include "util.h"
int binding_order = 0;
@ -22,9 +21,12 @@ void free_sway_binding(struct sway_binding *binding) {
if (!binding) {
return;
}
if (binding->translations) {
for (int i = 0; i < binding->keys->length; i++) {
list_free_items_and_destroy(binding->translations[i]);
}
}
list_free_items_and_destroy(binding->keys);
list_free_items_and_destroy(binding->syms);
free(binding->input);
free(binding->command);
free(binding);
@ -653,97 +655,81 @@ void seat_execute_command(struct sway_seat *seat, struct sway_binding *binding)
* and the total count of matches.
*/
struct keycode_matches {
xkb_keysym_t keysym;
xkb_keycode_t keycode;
int count;
xkb_keysym_t *keysym;
list_t *keys;
int error;
};
/**
* Iterate through keycodes in the keymap to find ones matching
* the specified keysym.
*/
static void find_keycode(struct xkb_keymap *keymap,
static void add_matching_keycodes(struct xkb_keymap *keymap,
xkb_keycode_t keycode, void *data) {
xkb_keysym_t keysym = xkb_state_key_get_one_sym(
config->keysym_translation_state, keycode);
struct keycode_matches *matches = data;
if (keysym == XKB_KEY_NoSymbol) {
return;
}
struct keycode_matches *matches = data;
if (matches->keysym == keysym) {
matches->keycode = keycode;
matches->count++;
if (*matches->keysym == keysym) {
xkb_keycode_t *new_keycode = malloc(sizeof(keycode));
if (!new_keycode) {
sway_log(SWAY_ERROR, "Unable to allocate memory for keysym");
matches->error++;
return;
}
*new_keycode = keycode;
char buffer[64] = {0};
xkb_keysym_get_name(keysym, buffer, sizeof(buffer));
sway_log(SWAY_DEBUG, "Translated keysym [%d (%s)] -> keycode [%d (%s)]",
keysym, buffer,
*new_keycode, xkb_keymap_key_get_name(keymap, *new_keycode));
list_add(matches->keys, new_keycode);
}
}
/**
* Return the keycode for the specified keysym.
*/
static struct keycode_matches get_keycode_for_keysym(xkb_keysym_t keysym) {
struct keycode_matches matches = {
.keysym = keysym,
.keycode = XKB_KEYCODE_INVALID,
.count = 0,
};
xkb_keymap_key_for_each(
xkb_state_get_keymap(config->keysym_translation_state),
find_keycode, &matches);
return matches;
}
bool translate_binding(struct sway_binding *binding) {
if ((binding->flags & BINDING_CODE) == 0) {
return true;
}
int keys_len = binding->keys->length;
switch (binding->type) {
// a bindsym to translate
case BINDING_KEYSYM:
binding->syms = binding->keys;
binding->keys = create_list();
break;
// a bindsym to re-translate
case BINDING_KEYCODE:
list_free_items_and_destroy(binding->keys);
binding->keys = create_list();
break;
default:
return true;
// Clean out for retranslation
if (binding->type == BINDING_KEYCODE) {
for (int i = 0; i < keys_len; i++) {
list_free_items_and_destroy(binding->translations[i]);
}
}
for (int i = 0; i < binding->syms->length; ++i) {
xkb_keysym_t *keysym = binding->syms->items[i];
struct keycode_matches matches = get_keycode_for_keysym(*keysym);
// Begin translation
binding->translations = malloc(keys_len * sizeof(*binding->keys));
for (int i = 0; i < keys_len; ++i) {
struct keycode_matches matches = {
.keysym = (xkb_keysym_t*)binding->keys->items[i],
.keys = binding->translations[i] = create_list(),
.error = 0,
};
if (matches.count != 1) {
sway_log(SWAY_INFO, "Unable to convert keysym %" PRIu32 " into"
" a single keycode (found %d matches)",
*keysym, matches.count);
xkb_keymap_key_for_each(
xkb_state_get_keymap(config->keysym_translation_state),
add_matching_keycodes, &matches);
if (matches.error) {
sway_log(SWAY_INFO, "Unable to convert keysym %" PRIu32 " into", *matches.keysym);
goto error;
}
xkb_keycode_t *keycode = malloc(sizeof(xkb_keycode_t));
if (!keycode) {
sway_log(SWAY_ERROR, "Unable to allocate memory for a keycode");
goto error;
}
*keycode = matches.keycode;
list_add(binding->keys, keycode);
}
list_qsort(binding->keys, key_qsort_cmp);
binding->type = BINDING_KEYCODE;
return true;
error:
list_free_items_and_destroy(binding->keys);
for (int i = 0; i < keys_len; i++) {
list_free_items_and_destroy(binding->translations[i]);
}
binding->type = BINDING_KEYSYM;
binding->keys = binding->syms;
binding->syms = NULL;
return false;
}

View file

@ -180,10 +180,31 @@ static void get_active_binding(const struct sway_shortcut_state *state,
if (state->npressed == (size_t)binding->keys->length) {
match = true;
for (size_t j = 0; j < state->npressed; j++) {
uint32_t key = *(uint32_t *)binding->keys->items[j];
if (key != state->pressed_keys[j]) {
match = false;
break;
uint32_t key;
// If translated bindsym, keys are syms not keycodes.
// keysym j mapped to keycodes translations[j]
if (binding->type & BINDING_CODE) {
bool dup_match = false;
list_t *duplicate_keys = binding->translations[j];
for (int k = 0; k < duplicate_keys->length; k++) {
key = *(uint32_t *)duplicate_keys->items[k];
if (key == state->pressed_keys[j]) {
dup_match = true;
break;
}
}
if (!dup_match) {
match = false;
break;
}
}
else {
key = *(uint32_t *)binding->keys->items[j];
if (key != state->pressed_keys[j]) {
match = false;
break;
}
}
}
} else if (binding->keys->length == 1) {