diff options
-rw-r--r-- | include/sway/config.h | 30 | ||||
-rw-r--r-- | sway/commands/bind.c | 169 | ||||
-rw-r--r-- | sway/commands/input.c | 25 | ||||
-rw-r--r-- | sway/config.c | 76 |
4 files changed, 274 insertions, 26 deletions
diff --git a/include/sway/config.h b/include/sway/config.h index 86410544..392f6538 100644 --- a/include/sway/config.h +++ b/include/sway/config.h | |||
@@ -24,7 +24,6 @@ struct sway_variable { | |||
24 | char *value; | 24 | char *value; |
25 | }; | 25 | }; |
26 | 26 | ||
27 | |||
28 | enum binding_input_type { | 27 | enum binding_input_type { |
29 | BINDING_KEYCODE, | 28 | BINDING_KEYCODE, |
30 | BINDING_KEYSYM, | 29 | BINDING_KEYSYM, |
@@ -39,6 +38,7 @@ enum binding_flags { | |||
39 | BINDING_BORDER=4, // mouse only; trigger on container border | 38 | BINDING_BORDER=4, // mouse only; trigger on container border |
40 | BINDING_CONTENTS=8, // mouse only; trigger on container contents | 39 | BINDING_CONTENTS=8, // mouse only; trigger on container contents |
41 | BINDING_TITLEBAR=16, // mouse only; trigger on container titlebar | 40 | BINDING_TITLEBAR=16, // mouse only; trigger on container titlebar |
41 | BINDING_CODE=32, // keyboard only; convert keysyms into keycodes | ||
42 | }; | 42 | }; |
43 | 43 | ||
44 | /** | 44 | /** |
@@ -50,6 +50,7 @@ struct sway_binding { | |||
50 | char *input; | 50 | char *input; |
51 | uint32_t flags; | 51 | uint32_t flags; |
52 | list_t *keys; // sorted in ascending order | 52 | list_t *keys; // sorted in ascending order |
53 | list_t *syms; // sorted in ascending order; NULL if BINDING_CODE is not set | ||
53 | uint32_t modifiers; | 54 | uint32_t modifiers; |
54 | char *command; | 55 | char *command; |
55 | }; | 56 | }; |
@@ -407,6 +408,14 @@ enum alignment { | |||
407 | }; | 408 | }; |
408 | 409 | ||
409 | /** | 410 | /** |
411 | * The keysym to keycode translation. | ||
412 | */ | ||
413 | struct keysym_translation_data { | ||
414 | struct xkb_keymap *xkb_keymap; | ||
415 | struct xkb_state *xkb_state; | ||
416 | }; | ||
417 | |||
418 | /** | ||
410 | * The configuration struct. The result of loading a config file. | 419 | * The configuration struct. The result of loading a config file. |
411 | */ | 420 | */ |
412 | struct sway_config { | 421 | struct sway_config { |
@@ -508,6 +517,9 @@ struct sway_config { | |||
508 | list_t *feature_policies; | 517 | list_t *feature_policies; |
509 | list_t *ipc_policies; | 518 | list_t *ipc_policies; |
510 | 519 | ||
520 | // The keysym to keycode translation | ||
521 | struct keysym_translation_data keysym_translation; | ||
522 | |||
511 | // Context for command handlers | 523 | // Context for command handlers |
512 | struct { | 524 | struct { |
513 | struct input_config *input_config; | 525 | struct input_config *input_config; |
@@ -617,12 +629,6 @@ bool spawn_swaybg(void); | |||
617 | 629 | ||
618 | int workspace_output_cmp_workspace(const void *a, const void *b); | 630 | int workspace_output_cmp_workspace(const void *a, const void *b); |
619 | 631 | ||
620 | int sway_binding_cmp(const void *a, const void *b); | ||
621 | |||
622 | int sway_binding_cmp_qsort(const void *a, const void *b); | ||
623 | |||
624 | int sway_binding_cmp_keys(const void *a, const void *b); | ||
625 | |||
626 | void free_sway_binding(struct sway_binding *sb); | 632 | void free_sway_binding(struct sway_binding *sb); |
627 | 633 | ||
628 | void free_switch_binding(struct sway_switch_binding *binding); | 634 | void free_switch_binding(struct sway_switch_binding *binding); |
@@ -651,6 +657,16 @@ void free_workspace_config(struct workspace_config *wsc); | |||
651 | */ | 657 | */ |
652 | void config_update_font_height(bool recalculate); | 658 | void config_update_font_height(bool recalculate); |
653 | 659 | ||
660 | /** | ||
661 | * Convert bindsym into bindcode using the first configured layout. | ||
662 | * Return false in case the conversion is unsuccessful. | ||
663 | */ | ||
664 | bool translate_binding(struct sway_binding *binding); | ||
665 | |||
666 | void translate_keysyms(const char *layout); | ||
667 | |||
668 | void binding_add_translated(struct sway_binding *binding, list_t *bindings); | ||
669 | |||
654 | /* Global config singleton. */ | 670 | /* Global config singleton. */ |
655 | extern struct sway_config *config; | 671 | extern struct sway_config *config; |
656 | 672 | ||
diff --git a/sway/commands/bind.c b/sway/commands/bind.c index dc7e0b19..e5fd4433 100644 --- a/sway/commands/bind.c +++ b/sway/commands/bind.c | |||
@@ -24,6 +24,7 @@ void free_sway_binding(struct sway_binding *binding) { | |||
24 | } | 24 | } |
25 | 25 | ||
26 | list_free_items_and_destroy(binding->keys); | 26 | list_free_items_and_destroy(binding->keys); |
27 | list_free_items_and_destroy(binding->syms); | ||
27 | free(binding->input); | 28 | free(binding->input); |
28 | free(binding->command); | 29 | free(binding->command); |
29 | free(binding); | 30 | free(binding); |
@@ -249,31 +250,41 @@ static struct cmd_results *switch_binding_remove( | |||
249 | switchcombo); | 250 | switchcombo); |
250 | } | 251 | } |
251 | 252 | ||
252 | static struct cmd_results *binding_add(struct sway_binding *binding, | 253 | /** |
253 | list_t *mode_bindings, const char *bindtype, | 254 | * Insert or update the binding. |
254 | const char *keycombo, bool warn) { | 255 | * Return the binding which has been replaced or NULL. |
255 | // overwrite the binding if it already exists | 256 | */ |
256 | bool overwritten = false; | 257 | static struct sway_binding *binding_upsert(struct sway_binding *binding, |
258 | list_t *mode_bindings) { | ||
257 | for (int i = 0; i < mode_bindings->length; ++i) { | 259 | for (int i = 0; i < mode_bindings->length; ++i) { |
258 | struct sway_binding *config_binding = mode_bindings->items[i]; | 260 | struct sway_binding *config_binding = mode_bindings->items[i]; |
259 | if (binding_key_compare(binding, config_binding)) { | 261 | if (binding_key_compare(binding, config_binding)) { |
260 | sway_log(SWAY_INFO, "Overwriting binding '%s' for device '%s' " | ||
261 | "to `%s` from `%s`", keycombo, binding->input, | ||
262 | binding->command, config_binding->command); | ||
263 | if (warn) { | ||
264 | config_add_swaynag_warning("Overwriting binding" | ||
265 | "'%s' for device '%s' to `%s` from `%s`", | ||
266 | keycombo, binding->input, binding->command, | ||
267 | config_binding->command); | ||
268 | } | ||
269 | free_sway_binding(config_binding); | ||
270 | mode_bindings->items[i] = binding; | 262 | mode_bindings->items[i] = binding; |
271 | overwritten = true; | 263 | return config_binding; |
272 | } | 264 | } |
273 | } | 265 | } |
274 | 266 | ||
275 | if (!overwritten) { | 267 | list_add(mode_bindings, binding); |
276 | list_add(mode_bindings, binding); | 268 | return NULL; |
269 | } | ||
270 | |||
271 | static struct cmd_results *binding_add(struct sway_binding *binding, | ||
272 | list_t *mode_bindings, const char *bindtype, | ||
273 | const char *keycombo, bool warn) { | ||
274 | struct sway_binding *config_binding = binding_upsert(binding, mode_bindings); | ||
275 | |||
276 | if (config_binding) { | ||
277 | sway_log(SWAY_INFO, "Overwriting binding '%s' for device '%s' " | ||
278 | "to `%s` from `%s`", keycombo, binding->input, | ||
279 | binding->command, config_binding->command); | ||
280 | if (warn) { | ||
281 | config_add_swaynag_warning("Overwriting binding" | ||
282 | "'%s' for device '%s' to `%s` from `%s`", | ||
283 | keycombo, binding->input, binding->command, | ||
284 | config_binding->command); | ||
285 | } | ||
286 | free_sway_binding(config_binding); | ||
287 | } else { | ||
277 | sway_log(SWAY_DEBUG, "%s - Bound %s to command `%s` for device '%s'", | 288 | sway_log(SWAY_DEBUG, "%s - Bound %s to command `%s` for device '%s'", |
278 | bindtype, keycombo, binding->command, binding->input); | 289 | bindtype, keycombo, binding->command, binding->input); |
279 | } | 290 | } |
@@ -329,7 +340,6 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv, | |||
329 | bool exclude_titlebar = false; | 340 | bool exclude_titlebar = false; |
330 | bool warn = true; | 341 | bool warn = true; |
331 | 342 | ||
332 | // Handle --release and --locked | ||
333 | while (argc > 0) { | 343 | while (argc > 0) { |
334 | if (strcmp("--release", argv[0]) == 0) { | 344 | if (strcmp("--release", argv[0]) == 0) { |
335 | binding->flags |= BINDING_RELEASE; | 345 | binding->flags |= BINDING_RELEASE; |
@@ -339,6 +349,10 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv, | |||
339 | binding->flags |= BINDING_BORDER | BINDING_CONTENTS | BINDING_TITLEBAR; | 349 | binding->flags |= BINDING_BORDER | BINDING_CONTENTS | BINDING_TITLEBAR; |
340 | } else if (strcmp("--border", argv[0]) == 0) { | 350 | } else if (strcmp("--border", argv[0]) == 0) { |
341 | binding->flags |= BINDING_BORDER; | 351 | binding->flags |= BINDING_BORDER; |
352 | } else if (strcmp("--to-code", argv[0]) == 0) { | ||
353 | if (!bindcode) { | ||
354 | binding->flags |= BINDING_CODE; | ||
355 | } | ||
342 | } else if (strcmp("--exclude-titlebar", argv[0]) == 0) { | 356 | } else if (strcmp("--exclude-titlebar", argv[0]) == 0) { |
343 | exclude_titlebar = true; | 357 | exclude_titlebar = true; |
344 | } else if (strncmp("--input-device=", argv[0], | 358 | } else if (strncmp("--input-device=", argv[0], |
@@ -410,6 +424,12 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv, | |||
410 | // sort ascending | 424 | // sort ascending |
411 | list_qsort(binding->keys, key_qsort_cmp); | 425 | list_qsort(binding->keys, key_qsort_cmp); |
412 | 426 | ||
427 | // translate keysyms into keycodes | ||
428 | if (!translate_binding(binding)) { | ||
429 | sway_log(SWAY_INFO, | ||
430 | "Unable to translate bindsym into bindcode: %s", argv[0]); | ||
431 | } | ||
432 | |||
413 | list_t *mode_bindings; | 433 | list_t *mode_bindings; |
414 | if (binding->type == BINDING_KEYCODE) { | 434 | if (binding->type == BINDING_KEYCODE) { |
415 | mode_bindings = config->current_mode->keycode_bindings; | 435 | mode_bindings = config->current_mode->keycode_bindings; |
@@ -566,3 +586,114 @@ void seat_execute_command(struct sway_seat *seat, struct sway_binding *binding) | |||
566 | ipc_event_binding(binding); | 586 | ipc_event_binding(binding); |
567 | } | 587 | } |
568 | } | 588 | } |
589 | |||
590 | /** | ||
591 | * The last found keycode associated with the keysym | ||
592 | * and the total count of matches. | ||
593 | */ | ||
594 | struct keycode_matches { | ||
595 | xkb_keysym_t keysym; | ||
596 | xkb_keycode_t keycode; | ||
597 | int count; | ||
598 | }; | ||
599 | |||
600 | /** | ||
601 | * Iterate through keycodes in the keymap to find ones matching | ||
602 | * the specified keysym. | ||
603 | */ | ||
604 | static void find_keycode(struct xkb_keymap *keymap, | ||
605 | xkb_keycode_t keycode, void *data) { | ||
606 | xkb_keysym_t keysym = xkb_state_key_get_one_sym( | ||
607 | config->keysym_translation.xkb_state, keycode); | ||
608 | |||
609 | if (keysym == XKB_KEY_NoSymbol) { | ||
610 | return; | ||
611 | } | ||
612 | |||
613 | struct keycode_matches *matches = data; | ||
614 | if (matches->keysym == keysym) { | ||
615 | matches->keycode = keycode; | ||
616 | matches->count++; | ||
617 | } | ||
618 | } | ||
619 | |||
620 | /** | ||
621 | * Return the keycode for the specified keysym. | ||
622 | */ | ||
623 | static struct keycode_matches get_keycode_for_keysym(xkb_keysym_t keysym) { | ||
624 | struct keycode_matches matches = { | ||
625 | .keysym = keysym, | ||
626 | .keycode = XKB_KEYCODE_INVALID, | ||
627 | .count = 0, | ||
628 | }; | ||
629 | |||
630 | xkb_keymap_key_for_each(config->keysym_translation.xkb_keymap, | ||
631 | find_keycode, &matches); | ||
632 | return matches; | ||
633 | } | ||
634 | |||
635 | bool translate_binding(struct sway_binding *binding) { | ||
636 | if ((binding->flags & BINDING_CODE) == 0) { | ||
637 | return true; | ||
638 | } | ||
639 | |||
640 | switch (binding->type) { | ||
641 | // a bindsym to translate | ||
642 | case BINDING_KEYSYM: | ||
643 | binding->syms = binding->keys; | ||
644 | binding->keys = create_list(); | ||
645 | break; | ||
646 | // a bindsym to re-translate | ||
647 | case BINDING_KEYCODE: | ||
648 | list_free_items_and_destroy(binding->keys); | ||
649 | binding->keys = create_list(); | ||
650 | break; | ||
651 | default: | ||
652 | return true; | ||
653 | } | ||
654 | |||
655 | for (int i = 0; i < binding->syms->length; ++i) { | ||
656 | xkb_keysym_t *keysym = binding->syms->items[i]; | ||
657 | struct keycode_matches matches = get_keycode_for_keysym(*keysym); | ||
658 | |||
659 | if (matches.count != 1) { | ||
660 | sway_log(SWAY_INFO, "Unable to convert keysym %d into" | ||
661 | " a single keycode (found %d matches)", | ||
662 | *keysym, matches.count); | ||
663 | goto error; | ||
664 | } | ||
665 | |||
666 | xkb_keycode_t *keycode = malloc(sizeof(xkb_keycode_t)); | ||
667 | if (!keycode) { | ||
668 | sway_log(SWAY_ERROR, "Unable to allocate memory for a keycode"); | ||
669 | goto error; | ||
670 | } | ||
671 | |||
672 | *keycode = matches.keycode; | ||
673 | list_add(binding->keys, keycode); | ||
674 | } | ||
675 | |||
676 | list_qsort(binding->keys, key_qsort_cmp); | ||
677 | binding->type = BINDING_KEYCODE; | ||
678 | return true; | ||
679 | |||
680 | error: | ||
681 | list_free_items_and_destroy(binding->keys); | ||
682 | binding->type = BINDING_KEYSYM; | ||
683 | binding->keys = binding->syms; | ||
684 | binding->syms = NULL; | ||
685 | return false; | ||
686 | } | ||
687 | |||
688 | void binding_add_translated(struct sway_binding *binding, | ||
689 | list_t *mode_bindings) { | ||
690 | struct sway_binding *config_binding = | ||
691 | binding_upsert(binding, mode_bindings); | ||
692 | |||
693 | if (config_binding) { | ||
694 | sway_log(SWAY_INFO, "Overwriting binding for device '%s' " | ||
695 | "to `%s` from `%s`", binding->input, | ||
696 | binding->command, config_binding->command); | ||
697 | free_sway_binding(config_binding); | ||
698 | } | ||
699 | } | ||
diff --git a/sway/commands/input.c b/sway/commands/input.c index b72bd76b..903d574f 100644 --- a/sway/commands/input.c +++ b/sway/commands/input.c | |||
@@ -39,6 +39,30 @@ static struct cmd_handler input_config_handlers[] = { | |||
39 | { "xkb_numlock", input_cmd_xkb_numlock }, | 39 | { "xkb_numlock", input_cmd_xkb_numlock }, |
40 | }; | 40 | }; |
41 | 41 | ||
42 | /** | ||
43 | * Re-translate keysyms if a change in the input config could affect them. | ||
44 | */ | ||
45 | static void retranslate_keysyms(struct input_config *input_config) { | ||
46 | bool matched = false; | ||
47 | for (int i = 0; i < config->input_configs->length; ++i) { | ||
48 | struct input_config *ic = config->input_configs->items[i]; | ||
49 | matched |= ic->identifier == input_config->identifier; | ||
50 | |||
51 | // the first configured xkb_layout | ||
52 | if (ic->xkb_layout) { | ||
53 | if (matched) { | ||
54 | translate_keysyms(ic->xkb_layout); | ||
55 | } | ||
56 | |||
57 | // nothing has changed | ||
58 | return; | ||
59 | } | ||
60 | } | ||
61 | |||
62 | // no xkb_layout has been set, restore the default | ||
63 | translate_keysyms(getenv("XKB_DEFAULT_LAYOUT")); | ||
64 | } | ||
65 | |||
42 | struct cmd_results *cmd_input(int argc, char **argv) { | 66 | struct cmd_results *cmd_input(int argc, char **argv) { |
43 | struct cmd_results *error = NULL; | 67 | struct cmd_results *error = NULL; |
44 | if ((error = checkarg(argc, "input", EXPECTED_AT_LEAST, 2))) { | 68 | if ((error = checkarg(argc, "input", EXPECTED_AT_LEAST, 2))) { |
@@ -73,6 +97,7 @@ struct cmd_results *cmd_input(int argc, char **argv) { | |||
73 | store_input_config(config->handler_context.input_config); | 97 | store_input_config(config->handler_context.input_config); |
74 | 98 | ||
75 | input_manager_apply_input_config(ic); | 99 | input_manager_apply_input_config(ic); |
100 | retranslate_keysyms(ic); | ||
76 | } else { | 101 | } else { |
77 | free_input_config(config->handler_context.input_config); | 102 | free_input_config(config->handler_context.input_config); |
78 | } | 103 | } |
diff --git a/sway/config.c b/sway/config.c index e14ea83a..45d16758 100644 --- a/sway/config.c +++ b/sway/config.c | |||
@@ -33,6 +33,31 @@ | |||
33 | 33 | ||
34 | struct sway_config *config = NULL; | 34 | struct sway_config *config = NULL; |
35 | 35 | ||
36 | static struct keysym_translation_data new_keysym_translation_data( | ||
37 | const char *layout) { | ||
38 | struct xkb_rule_names rules = { | ||
39 | .layout = layout, | ||
40 | }; | ||
41 | |||
42 | struct xkb_keymap *xkb_keymap = xkb_keymap_new_from_names( | ||
43 | xkb_context_new(XKB_CONTEXT_NO_FLAGS), | ||
44 | &rules, | ||
45 | XKB_KEYMAP_COMPILE_NO_FLAGS); | ||
46 | |||
47 | struct keysym_translation_data result = { | ||
48 | .xkb_keymap = xkb_keymap, | ||
49 | .xkb_state = xkb_state_new(xkb_keymap), | ||
50 | }; | ||
51 | |||
52 | return result; | ||
53 | } | ||
54 | |||
55 | static void free_keysym_translation_data( | ||
56 | struct keysym_translation_data config) { | ||
57 | xkb_state_unref(config.xkb_state); | ||
58 | xkb_keymap_unref(config.xkb_keymap); | ||
59 | } | ||
60 | |||
36 | static void free_mode(struct sway_mode *mode) { | 61 | static void free_mode(struct sway_mode *mode) { |
37 | if (!mode) { | 62 | if (!mode) { |
38 | return; | 63 | return; |
@@ -146,6 +171,7 @@ void free_config(struct sway_config *config) { | |||
146 | free(config->swaynag_command); | 171 | free(config->swaynag_command); |
147 | free((char *)config->current_config_path); | 172 | free((char *)config->current_config_path); |
148 | free((char *)config->current_config); | 173 | free((char *)config->current_config); |
174 | free_keysym_translation_data(config->keysym_translation); | ||
149 | free(config); | 175 | free(config); |
150 | } | 176 | } |
151 | 177 | ||
@@ -317,6 +343,9 @@ static void config_defaults(struct sway_config *config) { | |||
317 | if (!(config->feature_policies = create_list())) goto cleanup; | 343 | if (!(config->feature_policies = create_list())) goto cleanup; |
318 | if (!(config->ipc_policies = create_list())) goto cleanup; | 344 | if (!(config->ipc_policies = create_list())) goto cleanup; |
319 | 345 | ||
346 | // The keysym to keycode translation | ||
347 | config->keysym_translation = new_keysym_translation_data(getenv("XKB_DEFAULT_LAYOUT")); | ||
348 | |||
320 | return; | 349 | return; |
321 | cleanup: | 350 | cleanup: |
322 | sway_abort("Unable to allocate config structures"); | 351 | sway_abort("Unable to allocate config structures"); |
@@ -937,3 +966,50 @@ void config_update_font_height(bool recalculate) { | |||
937 | arrange_root(); | 966 | arrange_root(); |
938 | } | 967 | } |
939 | } | 968 | } |
969 | |||
970 | static void translate_binding_list(list_t *bindings, list_t *bindsyms, | ||
971 | list_t *bindcodes) { | ||
972 | for (int i = 0; i < bindings->length; ++i) { | ||
973 | struct sway_binding *binding = bindings->items[i]; | ||
974 | translate_binding(binding); | ||
975 | |||
976 | list_t *bindings; | ||
977 | switch (binding->type) { | ||
978 | case BINDING_KEYSYM: | ||
979 | bindings = bindsyms; | ||
980 | break; | ||
981 | case BINDING_KEYCODE: | ||
982 | bindings = bindcodes; | ||
983 | break; | ||
984 | default: | ||
985 | sway_assert(false, "unexpected translated binding type: %d", | ||
986 | binding->type); | ||
987 | break; | ||
988 | } | ||
989 | |||
990 | binding_add_translated(binding, bindings); | ||
991 | } | ||
992 | } | ||
993 | |||
994 | void translate_keysyms(const char *layout) { | ||
995 | free_keysym_translation_data(config->keysym_translation); | ||
996 | config->keysym_translation = new_keysym_translation_data(layout); | ||
997 | |||
998 | for (int i = 0; i < config->modes->length; ++i) { | ||
999 | struct sway_mode *mode = config->modes->items[i]; | ||
1000 | |||
1001 | list_t *bindsyms = create_list(); | ||
1002 | list_t *bindcodes = create_list(); | ||
1003 | |||
1004 | translate_binding_list(mode->keysym_bindings, bindsyms, bindcodes); | ||
1005 | translate_binding_list(mode->keycode_bindings, bindsyms, bindcodes); | ||
1006 | |||
1007 | list_free(mode->keysym_bindings); | ||
1008 | list_free(mode->keycode_bindings); | ||
1009 | |||
1010 | mode->keysym_bindings = bindsyms; | ||
1011 | mode->keycode_bindings = bindcodes; | ||
1012 | } | ||
1013 | |||
1014 | sway_log(SWAY_DEBUG, "Translated keysyms for layout %s", layout); | ||
1015 | } | ||