config/bindings: Extend bindsym --to-code to additionally

handle the case in which a given keysym maps to more than
one keycode.

Sometimes, a keysym can map to duplicate keycodes and `libxkbcommon`
does not prohibit this. In that case, it makes sense to
find all the keycodes that map to the keysym.

- Merges translate_binding into translate_keysyms, simplify logic
- Changes add_matching_keycodes to find ALL keycodes, not just last
- Removes unncessary list_t* syms. It's only used to alias keys as
  "syms". No other purpose.

This commit also introduces a recursive cartesian product
calculator in order to handle any cases.

`bindsym --to-code (M + .. + N)` s.t.
Keysym M maps to keycodes { M_0 .. M_m }
...
Keysym N maps to keycodes { N_0 .. N_n }
results in || M x .. x N || bindings (cartesian product)
This commit is contained in:
Furkan Sahin 2024-09-14 21:22:21 -05:00
parent f957c7e658
commit 052d905fc7
3 changed files with 111 additions and 98 deletions

View file

@ -62,7 +62,6 @@ 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
uint32_t modifiers; uint32_t modifiers;
xkb_layout_index_t group; xkb_layout_index_t group;
char *command; char *command;

View file

@ -22,9 +22,7 @@ void free_sway_binding(struct sway_binding *binding) {
if (!binding) { if (!binding) {
return; return;
} }
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);
@ -282,6 +280,7 @@ static struct cmd_results *binding_add(struct sway_binding *binding,
const char *keycombo, bool warn) { const char *keycombo, bool warn) {
struct sway_binding *config_binding = binding_upsert(binding, mode_bindings); struct sway_binding *config_binding = binding_upsert(binding, mode_bindings);
binding->order = binding_order++;
if (config_binding) { if (config_binding) {
sway_log(SWAY_INFO, "Overwriting binding '%s' for device '%s' " sway_log(SWAY_INFO, "Overwriting binding '%s' for device '%s' "
"to `%s` from `%s`", keycombo, binding->input, "to `%s` from `%s`", keycombo, binding->input,
@ -467,10 +466,11 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv,
// sort ascending // sort ascending
list_qsort(binding->keys, key_qsort_cmp); list_qsort(binding->keys, key_qsort_cmp);
binding->command = join_args(argv + 1, argc - 1);
// translate keysyms into keycodes // translate keysyms into keycodes
if (!translate_binding(binding)) { if (!translate_binding(binding)) {
sway_log(SWAY_INFO, sway_log(SWAY_INFO,"Unable to translate bindsym into bindcode: %s", argv[0]);
"Unable to translate bindsym into bindcode: %s", argv[0]);
} }
list_t *mode_bindings; list_t *mode_bindings;
@ -481,13 +481,10 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv,
} else { } else {
mode_bindings = config->current_mode->mouse_bindings; mode_bindings = config->current_mode->mouse_bindings;
} }
if (unbind) { if (unbind) {
return binding_remove(binding, mode_bindings, bindtype, argv[0]); return binding_remove(binding, mode_bindings, bindtype, argv[0]);
} }
binding->command = join_args(argv + 1, argc - 1);
binding->order = binding_order++;
return binding_add(binding, mode_bindings, bindtype, argv[0], warn); return binding_add(binding, mode_bindings, bindtype, argv[0], warn);
} }
@ -649,101 +646,141 @@ void seat_execute_command(struct sway_seat *seat, struct sway_binding *binding)
} }
/** /**
* The last found keycode associated with the keysym * All of the matching keycodes for the given keysym
* 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 *keycodes;
int count; bool error;
}; };
/** /**
* Iterate through keycodes in the keymap to find ones matching * Iterate through keycodes in the keymap and add the matching
* the specified keysym. * the specified keysym's list of matched keycodes.
*/ */
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);
if (keysym == XKB_KEY_NoSymbol) { struct keycode_matches *matches = data;
if (*matches->keysym == keysym) {
xkb_keycode_t *new_keycode = malloc(sizeof(xkb_keycode_t));
if (!new_keycode) {
matches->error = true;
return;
}
*new_keycode = keycode;
list_add(matches->keycodes, new_keycode);
}
}
void cartesian_product_helper(list_t** sets, int n, xkb_keycode_t** current_result, int* curr_size, xkb_keycode_t* current_product, int depth) {
// Conquer
if (depth == n) {
current_result[*curr_size] = malloc(n * sizeof(xkb_keycode_t));
for (int i = 0; i < n; ++i) {
current_result[*curr_size][i] = current_product[i];
}
(*curr_size)++;
return; return;
} }
struct keycode_matches *matches = data; // Divide
if (matches->keysym == keysym) { for (int i = 0; i < sets[depth]->length; ++i) {
matches->keycode = keycode; current_product[depth] = *(xkb_keycode_t*)sets[depth]->items[i];
matches->count++; cartesian_product_helper(sets, n, current_result, curr_size, current_product, depth + 1);
} }
} }
/** /**
* Return the keycode for the specified keysym. * Compute the calculate the Cartesian product of `n` sets
*/ */
static struct keycode_matches get_keycode_for_keysym(xkb_keysym_t keysym) { xkb_keycode_t** cartesian_product(list_t** sets, int n) {
struct keycode_matches matches = { int total_combinations = 1;
.keysym = keysym, for (int i = 0; i < n; ++i) {
.keycode = XKB_KEYCODE_INVALID, total_combinations *= sets[i]->length;
.count = 0, }
};
xkb_keymap_key_for_each( // Allocate memory for the current_result
xkb_state_get_keymap(config->keysym_translation_state), int result_size = 0;
find_keycode, &matches); xkb_keycode_t** result = malloc(total_combinations * sizeof(xkb_keycode_t*));
return matches; xkb_keycode_t* current_product = malloc(n * sizeof(xkb_keycode_t));
cartesian_product_helper(sets, n, result, &result_size, current_product, 0);
free(current_product);
return result;
} }
/*
* Convert keysyms to keycodes for --to-code. If any keysym does not have at
* least 1 keycode: halt conversion and unset BINDING_CODE. Assumes
* identify_key() read in binding->keys in keysym format vs keycode format
*/
bool translate_binding(struct sway_binding *binding) { bool translate_binding(struct sway_binding *binding) {
if ((binding->flags & BINDING_CODE) == 0) {
if (binding->type != BINDING_KEYSYM ||
(binding->flags & BINDING_CODE) == 0) {
return true; return true;
} }
switch (binding->type) { int num_syms = binding->keys->length;
// a bindsym to translate list_t ** sym2code = malloc(num_syms * sizeof(list_t*));
case BINDING_KEYSYM: // Collect all keycodes for all keysyms
binding->syms = binding->keys; for (int i = 0; i < num_syms; i++) {
binding->keys = create_list(); struct keycode_matches matches = {
break; .keysym = (xkb_keysym_t*)binding->keys->items[i],
// a bindsym to re-translate .keycodes = create_list(),
case BINDING_KEYCODE: .error = false
list_free_items_and_destroy(binding->keys); };
binding->keys = create_list();
break;
default:
return true;
}
for (int i = 0; i < binding->syms->length; ++i) { xkb_keymap_key_for_each(
xkb_keysym_t *keysym = binding->syms->items[i]; xkb_state_get_keymap(config->keysym_translation_state),
struct keycode_matches matches = get_keycode_for_keysym(*keysym); add_matching_keycodes, &matches);
if (matches.count != 1) { if (matches.error) {
sway_log(SWAY_INFO, "Unable to convert keysym %" PRIu32 " into" sway_log(SWAY_ERROR, "Failed to allocate memory for keycodes while iterating for keysym %" PRIu32, *matches.keysym);
" a single keycode (found %d matches)",
*keysym, matches.count);
goto error; goto error;
} }
xkb_keycode_t *keycode = malloc(sizeof(xkb_keycode_t)); if (matches.keycodes->length == 0) {
if (!keycode) { sway_log(SWAY_INFO, "Unable to convert keysym %" PRIu32 " into"
sway_log(SWAY_ERROR, "Unable to allocate memory for a keycode"); "any keycodes", *matches.keysym);
goto error; goto error;
} }
*keycode = matches.keycode; sym2code[i] = matches.keycodes;
list_add(binding->keys, keycode);
} }
list_qsort(binding->keys, key_qsort_cmp); // If any keycode maps to more than one keysym, use all combinations.
binding->type = BINDING_KEYCODE; xkb_keycode_t** combos = cartesian_product(sym2code, num_syms);
for (int i = 0; i< num_syms; i++) {
struct sway_binding * copy_binding = malloc(sizeof(struct sway_binding));
binding->type = BINDING_KEYCODE;
*copy_binding = *binding;
list_t *keys = create_list();
// copy the keys over
for (int j = 0; j < num_syms; j++) {
xkb_keycode_t * key = malloc(sizeof(xkb_keycode_t));
*key = combos[i][j];
list_add(keys, key);
}
copy_binding->keys = keys;
list_qsort(copy_binding->keys, key_qsort_cmp);
binding_add_translated(copy_binding, config->current_mode->keycode_bindings);
}
return true; return true;
// if any key cannot be translated, the binding revert to keysym binding
error: error:
list_free_items_and_destroy(binding->keys); list_free_items_and_destroy(binding->keys);
for (int i = 0; i < binding->keys->length; i++) {
list_free_items_and_destroy(sym2code[i]);
}
free(sym2code);
binding->type = BINDING_KEYSYM; binding->type = BINDING_KEYSYM;
binding->keys = binding->syms;
binding->syms = NULL;
return false; return false;
} }
@ -752,6 +789,7 @@ void binding_add_translated(struct sway_binding *binding,
struct sway_binding *config_binding = struct sway_binding *config_binding =
binding_upsert(binding, mode_bindings); binding_upsert(binding, mode_bindings);
binding->order = binding_order++;
if (config_binding) { if (config_binding) {
sway_log(SWAY_INFO, "Overwriting binding for device '%s' " sway_log(SWAY_INFO, "Overwriting binding for device '%s' "
"to `%s` from `%s`", binding->input, "to `%s` from `%s`", binding->input,

View file

@ -972,28 +972,6 @@ void config_update_font_height(void) {
} }
} }
static void translate_binding_list(list_t *bindings, list_t *bindsyms,
list_t *bindcodes) {
for (int i = 0; i < bindings->length; ++i) {
struct sway_binding *binding = bindings->items[i];
translate_binding(binding);
switch (binding->type) {
case BINDING_KEYSYM:
binding_add_translated(binding, bindsyms);
break;
case BINDING_KEYCODE:
binding_add_translated(binding, bindcodes);
break;
default:
sway_assert(false, "unexpected translated binding type: %d",
binding->type);
break;
}
}
}
void translate_keysyms(struct input_config *input_config) { void translate_keysyms(struct input_config *input_config) {
keysym_translation_state_destroy(config->keysym_translation_state); keysym_translation_state_destroy(config->keysym_translation_state);
@ -1008,18 +986,16 @@ void translate_keysyms(struct input_config *input_config) {
for (int i = 0; i < config->modes->length; ++i) { for (int i = 0; i < config->modes->length; ++i) {
struct sway_mode *mode = config->modes->items[i]; struct sway_mode *mode = config->modes->items[i];
for (int j = 0; j < mode->keysym_bindings->length; j++){
list_t *bindsyms = create_list(); struct sway_binding* binding = mode->keysym_bindings->items[j];
list_t *bindcodes = create_list(); if(translate_binding(binding)) {
return;
translate_binding_list(mode->keysym_bindings, bindsyms, bindcodes); }
translate_binding_list(mode->keycode_bindings, bindsyms, bindcodes); list_t *mode_bindings = binding->type == BINDING_KEYCODE
? config->current_mode->keycode_bindings
list_free(mode->keysym_bindings); : config->current_mode->keysym_bindings;
list_free(mode->keycode_bindings); binding_add_translated(binding, mode_bindings);
}
mode->keysym_bindings = bindsyms;
mode->keycode_bindings = bindcodes;
} }
sway_log(SWAY_DEBUG, "Translated keysyms using config for device '%s'", sway_log(SWAY_DEBUG, "Translated keysyms using config for device '%s'",