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; char *input;
uint32_t flags; uint32_t flags;
list_t *keys; // sorted in ascending order 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; uint32_t modifiers;
xkb_layout_index_t group; xkb_layout_index_t group;
char *command; char *command;

View file

@ -14,7 +14,6 @@
#include "list.h" #include "list.h"
#include "log.h" #include "log.h"
#include "stringop.h" #include "stringop.h"
#include "util.h"
int binding_order = 0; int binding_order = 0;
@ -22,9 +21,12 @@ void free_sway_binding(struct sway_binding *binding) {
if (!binding) { if (!binding) {
return; 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->keys);
list_free_items_and_destroy(binding->syms);
free(binding->input); free(binding->input);
free(binding->command); free(binding->command);
free(binding); free(binding);
@ -653,97 +655,81 @@ void seat_execute_command(struct sway_seat *seat, struct sway_binding *binding)
* and the total count of matches. * and the total count of matches.
*/ */
struct keycode_matches { struct keycode_matches {
xkb_keysym_t keysym; xkb_keysym_t *keysym;
xkb_keycode_t keycode; list_t *keys;
int count; int error;
}; };
/** /**
* Iterate through keycodes in the keymap to find ones matching * Iterate through keycodes in the keymap to find ones matching
* the specified keysym. * 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_keycode_t keycode, void *data) {
xkb_keysym_t keysym = xkb_state_key_get_one_sym( xkb_keysym_t keysym = xkb_state_key_get_one_sym(
config->keysym_translation_state, keycode); config->keysym_translation_state, keycode);
struct keycode_matches *matches = data;
if (keysym == XKB_KEY_NoSymbol) { if (keysym == XKB_KEY_NoSymbol) {
return; return;
} }
struct keycode_matches *matches = data; if (*matches->keysym == keysym) {
if (matches->keysym == keysym) { xkb_keycode_t *new_keycode = malloc(sizeof(keycode));
matches->keycode = keycode; if (!new_keycode) {
matches->count++; 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) { bool translate_binding(struct sway_binding *binding) {
if ((binding->flags & BINDING_CODE) == 0) { if ((binding->flags & BINDING_CODE) == 0) {
return true; return true;
} }
int keys_len = binding->keys->length;
switch (binding->type) { // Clean out for retranslation
// a bindsym to translate if (binding->type == BINDING_KEYCODE) {
case BINDING_KEYSYM: for (int i = 0; i < keys_len; i++) {
binding->syms = binding->keys; list_free_items_and_destroy(binding->translations[i]);
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;
} }
for (int i = 0; i < binding->syms->length; ++i) { // Begin translation
xkb_keysym_t *keysym = binding->syms->items[i]; binding->translations = malloc(keys_len * sizeof(*binding->keys));
struct keycode_matches matches = get_keycode_for_keysym(*keysym); 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) { xkb_keymap_key_for_each(
sway_log(SWAY_INFO, "Unable to convert keysym %" PRIu32 " into" xkb_state_get_keymap(config->keysym_translation_state),
" a single keycode (found %d matches)", add_matching_keycodes, &matches);
*keysym, matches.count);
if (matches.error) {
sway_log(SWAY_INFO, "Unable to convert keysym %" PRIu32 " into", *matches.keysym);
goto error; 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; binding->type = BINDING_KEYCODE;
return true; return true;
error: 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->type = BINDING_KEYSYM;
binding->keys = binding->syms;
binding->syms = NULL;
return false; 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) { if (state->npressed == (size_t)binding->keys->length) {
match = true; match = true;
for (size_t j = 0; j < state->npressed; j++) { for (size_t j = 0; j < state->npressed; j++) {
uint32_t key = *(uint32_t *)binding->keys->items[j]; uint32_t key;
if (key != state->pressed_keys[j]) {
match = false; // If translated bindsym, keys are syms not keycodes.
break; // 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) { } else if (binding->keys->length == 1) {