diff --git a/doc/faq.md b/doc/faq.md index 66fc6d98c..6635495a1 100644 --- a/doc/faq.md +++ b/doc/faq.md @@ -93,6 +93,36 @@ Use our [debugging tools]. 🚧 TODO +### How do I break a latch before triggering another latch or lock? + +Consider the following use cases: +1. If `Caps_Lock` is on the second level of some key, and `Shift` is + latched, pressing the key locks `Caps` while also breaking the `Shift` + latch, ensuring that the next character is properly uppercase. +2. On the German E1 layout, `ISO_Level5_Latch` is on the third level + of ``. So if a level 3 latch (typically on ``) is used + to access it, the level 5 must break the previous level 3 latch, + else both latches would be active: the effective level would be 7 + instead of the intended 5. + +Both uses cases can be implemented using the following features: +- explicit action; +- multiple actions per level; +- `VoidAction()`: to break latches. + +Patch that fixes the first use case: + +```diff +--- old ++++ new + key { + [ISO_Level2_Latch, Caps_Lock], ++ [LatchMods(modifiers=Shift,latchToLock,clearLocks), ++ {VoidAction(), LockMods(modifiers=Lock)}], + type=\"ALPHABETIC\" + }; +``` + ## Legacy X tools replacement ### xmodmap diff --git a/test/keyseq.c b/test/keyseq.c index a5bb17680..aa3893b8a 100644 --- a/test/keyseq.c +++ b/test/keyseq.c @@ -5,6 +5,7 @@ #include "config.h" +#include "xkbcommon/xkbcommon-keysyms.h" #include "xkbcommon/xkbcommon.h" #include "evdev-scancodes.h" @@ -1152,6 +1153,102 @@ test_mod_latch(struct xkb_context *context) KEY_A , BOTH, XKB_KEY_a, FINISH)); xkb_keymap_unref(keymap); + + /* + * If `Caps_Lock` is on the second level of some key, and `Shift` is + * latched, pressing the key locks `Caps` while also breaking the `Shift` + * latch, ensuring that the next character is properly uppercase. + * + * Implemented using: multiple actions per level + VoidAction() + */ + const char lock_breaks_latch[] = + "xkb_keymap {\n" + " xkb_keycodes { = 50; = 38; };\n" + " xkb_types { include \"basic\" };\n" + " xkb_compat {\n" + " interpret ISO_Level2_Latch {\n" + " action = LatchMods(modifiers=Shift,latchToLock,clearLocks);\n" + " };\n" + /* Activating CapsLock will break all latches */ + " interpret Caps_Lock {\n" + " action = {LockMods(modifiers=Lock), VoidAction()};\n" + " };\n" + " };\n" + " xkb_symbols {\n" + " key { [ISO_Level2_Latch, Caps_Lock], type=\"ALPHABETIC\" };\n" + " key { [a, A] };\n" + " };\n" + "};"; + keymap = test_compile_buffer(context, XKB_KEYMAP_FORMAT_TEXT_V2, + lock_breaks_latch, sizeof(lock_breaks_latch)); + assert(keymap); + assert(test_key_seq(keymap, + KEY_A , BOTH, XKB_KEY_a, NEXT, + /* Regular latch */ + KEY_LEFTSHIFT, BOTH, XKB_KEY_ISO_Level2_Latch, NEXT, + KEY_A , BOTH, XKB_KEY_A, NEXT, + KEY_A , BOTH, XKB_KEY_a, NEXT, + /* Trigger CapsLock */ + KEY_LEFTSHIFT, BOTH, XKB_KEY_ISO_Level2_Latch, NEXT, + KEY_LEFTSHIFT, BOTH, XKB_KEY_Caps_Lock, NEXT, + /* CapsLock active, latch broken */ + KEY_A , BOTH, XKB_KEY_A, NEXT, + KEY_A , BOTH, XKB_KEY_A, NEXT, + /* Unlock Caps */ + KEY_LEFTSHIFT, BOTH, XKB_KEY_Caps_Lock, NEXT, + KEY_A , BOTH, XKB_KEY_a, NEXT, + KEY_A , BOTH, XKB_KEY_a, FINISH)); + xkb_keymap_unref(keymap); + + /* + * Make a latch break a previous latch on the German E1 layout. + * + * Implemented using: multiple actions per level + VoidAction() + */ + const char lv5_latch_breaks_lv3_latch[] = + "xkb_keymap {\n" + " xkb_keycodes { = 50; = 108; = 26; = 41; };\n" + " xkb_types { include \"complete\" };\n" + " xkb_compat { include \"complete\" };\n" + " xkb_symbols {\n" + " virtual_modifiers LevelFive;\n" + " key { [ISO_Level2_Latch], [LatchMods(modifiers=Shift)]};\n" + " key { [ISO_Level3_Latch] };\n" + /* Excerpt from the German E1 `de(e1)` layout */ + " key.type = \"EIGHT_LEVEL_SEMIALPHABETIC\";\n" + " key { [e, E, EuroSign, any, schwa, SCHWA] };\n" + " key { [f, F, ISO_Level5_Latch, any, any, any ],\n" + /* Use VoidAction() to break previous latches */ + " [NoAction(), NoAction(), {VoidAction(), LatchMods(modifiers=LevelFive)}] };\n" + " };\n" + "};"; + keymap = test_compile_buffer(context, XKB_KEYMAP_FORMAT_TEXT_V2, + lv5_latch_breaks_lv3_latch, + sizeof(lv5_latch_breaks_lv3_latch)); + assert(keymap); + assert(test_key_seq(keymap, + KEY_E , BOTH, XKB_KEY_e, NEXT, + /* Level 3 latch */ + KEY_RIGHTALT, BOTH, XKB_KEY_ISO_Level3_Latch, NEXT, + KEY_E , BOTH, XKB_KEY_EuroSign, NEXT, + KEY_E , BOTH, XKB_KEY_e, NEXT, + /* Level 3 latch */ + KEY_RIGHTALT, BOTH, XKB_KEY_ISO_Level3_Latch, NEXT, + /* Level 5 latch */ + KEY_F, BOTH, XKB_KEY_ISO_Level5_Latch, NEXT, + /* Level 3 latch broken, level 5 latch active */ + KEY_E , BOTH, XKB_KEY_schwa, NEXT, + KEY_E , BOTH, XKB_KEY_e, NEXT, + /* Level 3 latch */ + KEY_RIGHTALT, BOTH, XKB_KEY_ISO_Level3_Latch, NEXT, + /* Level 5 latch */ + KEY_F, BOTH, XKB_KEY_ISO_Level5_Latch, NEXT, + /* Level 3 latch broken, level 5 latch active */ + KEY_LEFTSHIFT, BOTH, XKB_KEY_ISO_Level2_Latch, NEXT, + /* Shift + level 5 latches */ + KEY_E , BOTH, XKB_KEY_SCHWA, NEXT, + KEY_E , BOTH, XKB_KEY_e, FINISH)); + xkb_keymap_unref(keymap); } struct key_properties {