From 22bb7a1af0780230e446895867d38dcfa978c492 Mon Sep 17 00:00:00 2001 From: Pierre Le Marre Date: Sun, 7 Apr 2024 22:43:50 +0200 Subject: [PATCH 01/10] Refactor scan codes - Moved all the info about scan codes in the YAML file - Added a dedicated class `Key` that gather all the scan codes - Simplify the code --- kalamine/data/scan_codes.yaml | 659 ++++++++++++++++++++++--------- kalamine/generators/ahk.py | 52 +-- kalamine/generators/keylayout.py | 71 ++-- kalamine/generators/klc.py | 35 +- kalamine/generators/web.py | 31 +- kalamine/generators/xkb.py | 40 +- kalamine/help.py | 14 +- kalamine/key.py | 110 ++++++ kalamine/layout.py | 15 +- kalamine/template.py | 5 +- kalamine/utils.py | 62 --- tests/test_serializer_xkb.py | 8 - 12 files changed, 721 insertions(+), 381 deletions(-) create mode 100644 kalamine/key.py diff --git a/kalamine/data/scan_codes.yaml b/kalamine/data/scan_codes.yaml index e3aefd2..b19ab1c 100644 --- a/kalamine/data/scan_codes.yaml +++ b/kalamine/data/scan_codes.yaml @@ -1,188 +1,471 @@ -klc: - spce: '39' - - # digits - ae01: '02' - ae02: '03' - ae03: '04' - ae04: '05' - ae05: '06' - ae06: '07' - ae07: '08' - ae08: '09' - ae09: '0a' - ae10: '0b' - - # letters, first row - ad01: '10' - ad02: '11' - ad03: '12' - ad04: '13' - ad05: '14' - ad06: '15' - ad07: '16' - ad08: '17' - ad09: '18' - ad10: '19' - - # letters, second row - ac01: '1e' - ac02: '1f' - ac03: '20' - ac04: '21' - ac05: '22' - ac06: '23' - ac07: '24' - ac08: '25' - ac09: '26' - ac10: '27' - - # letters, third row - ab01: '2c' - ab02: '2d' - ab03: '2e' - ab04: '2f' - ab05: '30' - ab06: '31' - ab07: '32' - ab08: '33' - ab09: '34' - ab10: '35' - - # pinky keys - tlde: '29' - ae11: '0c' - ae12: '0d' - ae13: '0d' # XXX FIXME - ad11: '1a' - ad12: '1b' - ac11: '28' - ab11: '28' # XXX FIXME - bksl: '2b' - lsgt: '56' - -osx: - spce: 49 - - # digits - ae01: 18 # 1 - ae02: 19 # 2 - ae03: 20 # 3 - ae04: 21 # 4 - ae05: 23 # 5 - ae06: 22 # 6 - ae07: 26 # 7 - ae08: 28 # 8 - ae09: 25 # 9 - ae10: 29 # 0 - - # letters, first row - ad01: 12 # Q - ad02: 13 # W - ad03: 14 # E - ad04: 15 # R - ad05: 17 # T - ad06: 16 # Y - ad07: 32 # U - ad08: 34 # I - ad09: 31 # O - ad10: 35 # P - - # letters, second row - ac01: 0 # A - ac02: 1 # S - ac03: 2 # D - ac04: 3 # F - ac05: 5 # G - ac06: 4 # H - ac07: 38 # J - ac08: 40 # K - ac09: 37 # L - ac10: 41 # ★ - - # letters, third row - ab01: 6 # Z - ab02: 7 # X - ab03: 8 # C - ab04: 9 # V - ab05: 11 # B - ab06: 45 # N - ab07: 46 # M - ab08: 43 # , - ab09: 47 # . - ab10: 44 # / - - # pinky keys - tlde: 50 # ~ - ae11: 27 # - - ae12: 24 # = - ae13: 42 # XXX FIXME - ad11: 33 # [ - ad12: 30 # ] - ac11: 39 # ' - ab11: 39 # XXX FIXME - bksl: 42 # \ - lsgt: 10 # < - -web: - spce: 'Space' - - # digits - ae01: 'Digit1' - ae02: 'Digit2' - ae03: 'Digit3' - ae04: 'Digit4' - ae05: 'Digit5' - ae06: 'Digit6' - ae07: 'Digit7' - ae08: 'Digit8' - ae09: 'Digit9' - ae10: 'Digit0' - - # letters, 1st row - ad01: 'KeyQ' - ad02: 'KeyW' - ad03: 'KeyE' - ad04: 'KeyR' - ad05: 'KeyT' - ad06: 'KeyY' - ad07: 'KeyU' - ad08: 'KeyI' - ad09: 'KeyO' - ad10: 'KeyP' - - # letters, 2nd row - ac01: 'KeyA' - ac02: 'KeyS' - ac03: 'KeyD' - ac04: 'KeyF' - ac05: 'KeyG' - ac06: 'KeyH' - ac07: 'KeyJ' - ac08: 'KeyK' - ac09: 'KeyL' - ac10: 'Semicolon' - - # letters, 3rd row - ab01: 'KeyZ' - ab02: 'KeyX' - ab03: 'KeyC' - ab04: 'KeyV' - ab05: 'KeyB' - ab06: 'KeyN' - ab07: 'KeyM' - ab08: 'Comma' - ab09: 'Period' - ab10: 'Slash' - - # pinky keys - tlde: 'Backquote' - ae11: 'Minus' - ae12: 'Equal' - ae13: 'IntlYen' - ad11: 'BracketLeft' - ad12: 'BracketRight' - bksl: 'Backslash' - ac11: 'Quote' - ab11: 'IntlRo' - lsgt: 'IntlBackslash' +digits: +- xkb: "ae01" + web: "Digit1" + windows: "02" + macos: "18" + hand: null +- xkb: "ae02" + web: "Digit2" + windows: "03" + macos: "19" + hand: null +- xkb: "ae03" + web: "Digit3" + windows: "04" + macos: "20" + hand: null +- xkb: "ae04" + web: "Digit4" + windows: "05" + macos: "21" + hand: null +- xkb: "ae05" + web: "Digit5" + windows: "06" + macos: "23" + hand: null +- xkb: "ae06" + web: "Digit6" + windows: "07" + macos: "22" + hand: null +- xkb: "ae07" + web: "Digit7" + windows: "08" + macos: "26" + hand: null +- xkb: "ae08" + web: "Digit8" + windows: "09" + macos: "28" + hand: null +- xkb: "ae09" + web: "Digit9" + windows: "0a" + macos: "25" + hand: null +- xkb: "ae10" + web: "Digit0" + windows: "0b" + macos: "29" + hand: null +# Letters, first row +Letters1: +- xkb: "ad01" + web: "KeyQ" + windows: "10" + macos: "12" + hand: null +- xkb: "ad02" + web: "KeyW" + windows: "11" + macos: "13" + hand: null +- xkb: "ad03" + web: "KeyE" + windows: "12" + macos: "14" + hand: null +- xkb: "ad04" + web: "KeyR" + windows: "13" + macos: "15" + hand: null +- xkb: "ad05" + web: "KeyT" + windows: "14" + macos: "17" + hand: null +- xkb: "ad06" + web: "KeyY" + windows: "15" + macos: "16" + hand: null +- xkb: "ad07" + web: "KeyU" + windows: "16" + macos: "32" + hand: null +- xkb: "ad08" + web: "KeyI" + windows: "17" + macos: "34" + hand: null +- xkb: "ad09" + web: "KeyO" + windows: "18" + macos: "31" + hand: null +- xkb: "ad10" + web: "KeyP" + windows: "19" + macos: "35" + hand: null +# Letters, second row +Letters2: +- xkb: "ac01" + web: "KeyA" + windows: "1e" + macos: "0" + hand: null +- xkb: "ac02" + web: "KeyS" + windows: "1f" + macos: "1" + hand: null +- xkb: "ac03" + web: "KeyD" + windows: "20" + macos: "2" + hand: null +- xkb: "ac04" + web: "KeyF" + windows: "21" + macos: "3" + hand: null +- xkb: "ac05" + web: "KeyG" + windows: "22" + macos: "5" + hand: null +- xkb: "ac06" + web: "KeyH" + windows: "23" + macos: "4" + hand: null +- xkb: "ac07" + web: "KeyJ" + windows: "24" + macos: "38" + hand: null +- xkb: "ac08" + web: "KeyK" + windows: "25" + macos: "40" + hand: null +- xkb: "ac09" + web: "KeyL" + windows: "26" + macos: "37" + hand: null +- xkb: "ac10" + web: "Semicolon" + windows: "27" + macos: "41" + hand: null +# Letters, third row +Letters3: +- xkb: "ab01" + web: "KeyZ" + windows: "2c" + macos: "6" + hand: null +- xkb: "ab02" + web: "KeyX" + windows: "2d" + macos: "7" + hand: null +- xkb: "ab03" + web: "KeyC" + windows: "2e" + macos: "8" + hand: null +- xkb: "ab04" + web: "KeyV" + windows: "2f" + macos: "9" + hand: null +- xkb: "ab05" + web: "KeyB" + windows: "30" + macos: "11" + hand: null +- xkb: "ab06" + web: "KeyN" + windows: "31" + macos: "45" + hand: null +- xkb: "ab07" + web: "KeyM" + windows: "32" + macos: "46" + hand: null +- xkb: "ab08" + web: "Comma" + windows: "33" + macos: "43" + hand: null +- xkb: "ab09" + web: "Period" + windows: "34" + macos: "47" + hand: null +- xkb: "ab10" + web: "Slash" + windows: "35" + macos: "44" + hand: null +# Pinky keys +PinkyKeys: +- xkb: "ae11" + web: "Minus" + windows: "0c" + macos: "27" + hand: null +- xkb: "ae12" + web: "Equal" + windows: "0d" + macos: "24" + hand: null +- xkb: "ae13" + web: "IntlYen" + windows: "0d" + macos: "42" + hand: null +- xkb: "ad11" + web: "BracketLeft" + windows: "1a" + macos: "33" + hand: null +- xkb: "ad12" + web: "BracketRight" + windows: "1b" + macos: "30" + hand: null +- xkb: "ac11" + web: "Quote" + windows: "28" + macos: "39" + hand: null +- xkb: "ab11" + web: "IntlRo" + windows: "28" + macos: "39" + hand: null +- xkb: "tlde" + web: "Backquote" + windows: "29" + macos: "50" + hand: null +- xkb: "bksl" + web: "Backslash" + windows: "2b" + macos: "42" + hand: null +- xkb: "lsgt" + web: "IntlBackslash" + windows: "56" + macos: "10" + hand: null +# Space bar row +SpaceBar: +- xkb: "spce" + web: "Space" + windows: "39" + macos: "49" + hand: null +System: +- xkb: "tab" + web: null + windows: null + macos: null + hand: Left +- xkb: "rtrn" + web: null + windows: null + macos: null + hand: null +- xkb: "bksp" + web: null + windows: null + macos: null + hand: null +- xkb: "dele" + web: null + windows: null + macos: null + hand: null +- xkb: "esc" + web: null + windows: null + macos: null + hand: null +- xkb: "menu" + web: null + windows: null + macos: null + hand: null +- xkb: "home" + web: null + windows: null + macos: null + hand: null +- xkb: "up" + web: null + windows: null + macos: null + hand: null +- xkb: "end" + web: null + windows: null + macos: null + hand: null +- xkb: "pgup" + web: null + windows: null + macos: null + hand: null +- xkb: "left" + web: null + windows: null + macos: null + hand: null +- xkb: "down" + web: null + windows: null + macos: null + hand: null +- xkb: "rght" + web: null + windows: null + macos: null + hand: null +- xkb: "pgdn" + web: null + windows: null + macos: null + hand: null +- xkb: "fk01" + web: null + windows: null + macos: null + hand: null +- xkb: "fk02" + web: null + windows: null + macos: null + hand: null +- xkb: "fk03" + web: null + windows: null + macos: null + hand: null +- xkb: "fk04" + web: null + windows: null + macos: null + hand: null +- xkb: "fk05" + web: null + windows: null + macos: null + hand: null +- xkb: "dele" + web: null + windows: null + macos: null + hand: null +- xkb: "fk06" + web: null + windows: null + macos: null + hand: null +- xkb: "fk07" + web: null + windows: null + macos: null + hand: null +- xkb: "fk08" + web: null + windows: null + macos: null + hand: null +- xkb: "fk09" + web: null + windows: null + macos: null + hand: null +- xkb: "fk10" + web: null + windows: null + macos: null + hand: null +- xkb: "fk11" + web: null + windows: null + macos: null + hand: null +- xkb: "fk12" + web: null + windows: null + macos: null + hand: null +Modifiers: +- xkb: "lfsh" + web: null + windows: null + macos: null + hand: Left +- xkb: "rtsh" + web: null + windows: null + macos: null + hand: Right +- xkb: "caps" + web: null + windows: null + macos: null + hand: Left +- xkb: "lalt" + web: "AltLeft" + windows: null + macos: null + hand: Left +- xkb: "ralt" + web: "AltRight" + windows: null + macos: null + hand: Right +- xkb: "lctl" + web: null + windows: null + macos: null + hand: Left +- xkb: "rctl" + web: null + windows: null + macos: null + hand: Right +InputMethod: +- xkb: "muhe" + web: "NonConvert" + windows: null + macos: null + hand: Left +- xkb: "henk" + web: "Convert" + windows: null + macos: null + hand: Right +# Miscellaneous +Miscellaneous: +- xkb: "i148" # Calc + web: null + windows: null + macos: null + hand: null +- xkb: "i163" # Mail + web: null + windows: null + macos: null + hand: null +- xkb: "i172" # Play/Pause + web: null + windows: null + macos: null + hand: null +- xkb: "i180" # Home page + web: null + windows: null + macos: null + hand: null diff --git a/kalamine/generators/ahk.py b/kalamine/generators/ahk.py index 47558b5..facc10c 100644 --- a/kalamine/generators/ahk.py +++ b/kalamine/generators/ahk.py @@ -6,13 +6,14 @@ """ import json -from typing import TYPE_CHECKING, Dict, List +from typing import TYPE_CHECKING, Dict, List, Optional if TYPE_CHECKING: from ..layout import KeyboardLayout +from ..key import KEYS, KeyCategory from ..template import load_tpl, substitute_lines -from ..utils import LAYER_KEYS, SCAN_CODES, Layer, load_data +from ..utils import Layer, load_data def ahk_keymap(layout: "KeyboardLayout", altgr: bool = False) -> List[str]: @@ -38,30 +39,34 @@ def ahk_actions(symbol: str) -> Dict[str, str]: return actions output = [] - for key_name in LAYER_KEYS: - if key_name.startswith("-"): - output.append(f"; {key_name[1:]}") - output.append("") + prev_category: Optional[KeyCategory] = None + for key in KEYS.values(): + # TODO: delete test? + # if key.id in ["ae13", "ab11"]: # ABNT / JIS keys + # continue # these two keys are not supported yet + if key.windows is None: continue - if key_name in ["ae13", "ab11"]: # ABNT / JIS keys - continue # these two keys are not supported yet + if key.category is not prev_category: + output.append(f"; {key.category.description}") + output.append("") + prev_category = key.category - sc = f"SC{SCAN_CODES['klc'][key_name]}" + sc = f"SC{key.windows}" for i in ( [Layer.ALTGR, Layer.ALTGR_SHIFT] if altgr else [Layer.BASE, Layer.SHIFT] ): layer = layout.layers[i] - if key_name not in layer: + if key.id not in layer: continue - symbol = layer[key_name] + symbol = layer[key.id] sym = ahk_escape(symbol) if symbol in layout.dead_keys: actions = {sym: layout.dead_keys[symbol][symbol]} - elif key_name == "spce": - actions = ahk_actions(key_name) + elif key.id == "spce": + actions = ahk_actions(key.id) else: actions = ahk_actions(symbol) @@ -83,22 +88,25 @@ def ahk_shortcuts(layout: "KeyboardLayout") -> List[str]: qwerty_vk = load_data("qwerty_vk") output = [] - for key_name in LAYER_KEYS: - if key_name.startswith("-"): - output.append(f"; {key_name[1:]}") - output.append("") + prev_category: Optional[KeyCategory] = None + for key in KEYS.values(): + # if key_name in ["ae13", "ab11"]: # ABNT / JIS keys + # continue # these two keys are not supported yet + if key.windows is None: continue - if key_name in ["ae13", "ab11"]: # ABNT / JIS keys - continue # these two keys are not supported yet + if key.category is not prev_category: + output.append(f"; {key.category.description}") + output.append("") + prev_category = key.category - scan_code = SCAN_CODES["klc"][key_name] + scan_code = key.windows for i in [Layer.BASE, Layer.SHIFT]: layer = layout.layers[i] - if key_name not in layer: + if key.id not in layer: continue - symbol = layer[key_name] + symbol = layer[key.id] if layout.qwerty_shortcuts: symbol = qwerty_vk[scan_code] if symbol in enabled: diff --git a/kalamine/generators/keylayout.py b/kalamine/generators/keylayout.py index f084c65..49bc1bd 100644 --- a/kalamine/generators/keylayout.py +++ b/kalamine/generators/keylayout.py @@ -3,13 +3,14 @@ https://developer.apple.com/library/content/technotes/tn2056/ """ -from typing import TYPE_CHECKING, List, Tuple +from typing import TYPE_CHECKING, List, Optional, Tuple if TYPE_CHECKING: from ..layout import KeyboardLayout +from ..key import KEYS, KeyCategory from ..template import load_tpl, substitute_lines -from ..utils import DK_INDEX, LAYER_KEYS, SCAN_CODES, Layer, hex_ord +from ..utils import DK_INDEX, Layer, hex_ord def _xml_proof(char: str) -> str: @@ -44,35 +45,39 @@ def has_dead_keys(letter: str) -> bool: return False output: List[str] = [] - for key_name in LAYER_KEYS: - if key_name in ["ae13", "ab11"]: # ABNT / JIS keys + prev_category: Optional[KeyCategory] = None + for key in KEYS.values(): + # TODO: remove test and use only next? + if key.id in ["ae13", "ab11"]: # ABNT / JIS keys continue # these two keys are not supported yet + if key.macos is None: + continue - if key_name.startswith("-"): + if key.category is not prev_category: if output: output.append("") - output.append("") - continue + output.append("") + prev_category = key.category symbol = "" final_key = True - if key_name in layer: - key = layer[key_name] - if key in layout.dead_keys: - symbol = f"dead_{DK_INDEX[key].name}" + if key.id in layer: + value = layer[key.id] + if value in layout.dead_keys: + symbol = f"dead_{DK_INDEX[value].name}" final_key = False else: - symbol = _xml_proof(key.upper() if caps else key) - final_key = not has_dead_keys(key.upper()) + symbol = _xml_proof(value.upper() if caps else value) + final_key = not has_dead_keys(value.upper()) - char = f"code=\"{SCAN_CODES['osx'][key_name]}\"".ljust(10) + char = f'code="{key.macos}"'.ljust(10) if final_key: action = f'output="{symbol}"' elif symbol.startswith("dead_"): action = f'action="{_xml_proof_id(symbol)}"' else: - action = f'action="{key_name}_{_xml_proof_id(symbol)}"' + action = f'action="{key.id}_{_xml_proof_id(symbol)}"' output.append(f"") ret_str.append(output) @@ -94,17 +99,17 @@ def when(state: str, action: str) -> str: action_attr = f'output="{_xml_proof(action)}"' return f" " - def append_actions(key: str, symbol: str, actions: List[Tuple[str, str]]) -> None: - ret_actions.append(f'') + def append_actions(id: str, symbol: str, actions: List[Tuple[str, str]]) -> None: + ret_actions.append(f'') ret_actions.append(when("none", symbol)) for state, out in actions: ret_actions.append(when(state, out)) ret_actions.append("") # dead key definitions - for key in layout.dead_keys: - name = DK_INDEX[key].name - term = layout.dead_keys[key][key] + for dk in layout.dead_keys: + name = DK_INDEX[dk].name + term = layout.dead_keys[dk][dk] ret_actions.append(f'') ret_actions.append(f' ') if name == "1dk" and term in layout.dead_keys: @@ -114,29 +119,33 @@ def append_actions(key: str, symbol: str, actions: List[Tuple[str, str]]) -> Non continue # normal key actions - for key_name in LAYER_KEYS: - if key_name.startswith("-"): - ret_actions.append("") - ret_actions.append(f"") + prev_category: Optional[KeyCategory] = None + for key in KEYS.values(): + if key.macos is None: continue + if key.category is not prev_category: + ret_actions.append("") + ret_actions.append(f"") + prev_category = key.category + for i in [Layer.BASE, Layer.SHIFT, Layer.ALTGR, Layer.ALTGR_SHIFT]: - if key_name == "spce" or key_name not in layout.layers[i]: + if key.id == "spce" or key.id not in layout.layers[i]: continue - key = layout.layers[i][key_name] - if i and key == layout.layers[Layer.BASE][key_name]: + value = layout.layers[i][key.id] + if i and value == layout.layers[Layer.BASE][key.id]: continue - if key in layout.dead_keys: + if value in layout.dead_keys: continue actions: List[Tuple[str, str]] = [] for k in DK_INDEX: if k in layout.dead_keys: - if key in layout.dead_keys[k]: - actions.append((DK_INDEX[k].name, layout.dead_keys[k][key])) + if value in layout.dead_keys[k]: + actions.append((DK_INDEX[k].name, layout.dead_keys[k][value])) if actions: - append_actions(key_name, _xml_proof(key), actions) + append_actions(key.id, _xml_proof(value), actions) # spacebar actions actions = [] diff --git a/kalamine/generators/klc.py b/kalamine/generators/klc.py index bbc9375..acda0bb 100644 --- a/kalamine/generators/klc.py +++ b/kalamine/generators/klc.py @@ -16,8 +16,9 @@ if TYPE_CHECKING: from ..layout import KeyboardLayout +from ..key import KEYS from ..template import load_tpl, substitute_lines, substitute_token -from ..utils import DK_INDEX, LAYER_KEYS, SCAN_CODES, Layer, hex_ord, load_data +from ..utils import DK_INDEX, Layer, hex_ord, load_data # return the corresponding char for a symbol @@ -101,12 +102,12 @@ def klc_keymap(layout: "KeyboardLayout") -> List[str]: output = [] qwerty_vk = load_data("qwerty_vk") - for key_name in LAYER_KEYS: - if key_name.startswith("-"): - continue - - if key_name in ["ae13", "ab11"]: # ABNT / JIS keys + for key in KEYS.values(): + if key.id in ["ae13", "ab11"]: # ABNT / JIS keys continue # these two keys are not supported yet + if key.windows is None: + # TODO: warning + continue symbols = [] description = "//" @@ -115,8 +116,8 @@ def klc_keymap(layout: "KeyboardLayout") -> List[str]: for i in [Layer.BASE, Layer.SHIFT, Layer.ALTGR, Layer.ALTGR_SHIFT]: layer = layout.layers[i] - if key_name in layer: - symbol = layer[key_name] + if key.id in layer: + symbol = layer[key.id] desc = symbol if symbol in layout.dead_keys: desc = layout.dead_keys[symbol][" "] @@ -132,7 +133,7 @@ def klc_keymap(layout: "KeyboardLayout") -> List[str]: symbols.append("-1") description += " " + desc - scan_code = SCAN_CODES["klc"][key_name] + scan_code = key.windows virtual_key = qwerty_vk[scan_code] if not layout.qwerty_shortcuts: @@ -229,12 +230,12 @@ def c_keymap(layout: "KeyboardLayout") -> List[str]: global oem_idx oem_idx = 0 # Python trick to do equivalent of C static variable output = [] - for key_name in LAYER_KEYS: - if key_name.startswith("-"): - continue - - if key_name in ["ae13", "ab11"]: # ABNT / JIS keys + for key in KEYS.values(): + if key.id in ["ae13", "ab11"]: # ABNT / JIS keys continue # these two keys are not supported yet + if key.windows is None: + # TODO: warning + continue symbols = [] dead_symbols = [] @@ -244,8 +245,8 @@ def c_keymap(layout: "KeyboardLayout") -> List[str]: for i in [Layer.BASE, Layer.SHIFT, Layer.ALTGR, Layer.ALTGR_SHIFT]: layer = layout.layers[i] - if key_name in layer: - symbol = layer[key_name] + if key.id in layer: + symbol = layer[key.id] desc = symbol dead = "WCH_NONE" if symbol in layout.dead_keys: @@ -265,7 +266,7 @@ def c_keymap(layout: "KeyboardLayout") -> List[str]: symbols.append("WCH_NONE") dead_symbols.append("WCH_NONE") - scan_code = SCAN_CODES["klc"][key_name] + scan_code = key.windows virtual_key = qwerty_vk[scan_code] if not layout.qwerty_shortcuts: diff --git a/kalamine/generators/web.py b/kalamine/generators/web.py index 4c9d023..b62b134 100644 --- a/kalamine/generators/web.py +++ b/kalamine/generators/web.py @@ -12,7 +12,8 @@ if TYPE_CHECKING: from ..layout import KeyboardLayout -from ..utils import LAYER_KEYS, ODK_ID, SCAN_CODES, Layer, upper_key +from ..key import KEYS +from ..utils import ODK_ID, Layer, upper_key def raw_json(layout: "KeyboardLayout") -> Dict: @@ -21,15 +22,16 @@ def raw_json(layout: "KeyboardLayout") -> Dict: # flatten the keymap: each key has an array of 2-4 characters # correcponding to Base, Shift, AltGr, AltGr+Shift keymap: Dict[str, List[str]] = {} - for key_name in LAYER_KEYS: - if key_name.startswith("-"): + for key in KEYS.values(): + if key.web is None: + # TODO: warning continue chars = list("") for i in [Layer.BASE, Layer.SHIFT, Layer.ALTGR, Layer.ALTGR_SHIFT]: - if key_name in layout.layers[i]: - chars.append(layout.layers[i][key_name]) + if key.id in layout.layers[i]: + chars.append(layout.layers[i][key.id]) if chars: - keymap[SCAN_CODES["web"][key_name]] = chars + keymap[key.web] = chars return { # fmt: off @@ -90,8 +92,9 @@ def same_symbol(key_name: str, lower: Layer, upper: Layer): return ET.ElementTree() svg = ET.ElementTree(ET.fromstring(res.decode("utf-8"))) - for key_name in LAYER_KEYS: - if key_name.startswith("-"): + for key in KEYS.values(): + if key.web is None: + # TODO: warning continue level = 0 @@ -104,16 +107,16 @@ def same_symbol(key_name: str, lower: Layer, upper: Layer): Layer.ODK_SHIFT, ]: level += 1 - if key_name not in layout.layers[i]: + if key.id not in layout.layers[i]: continue - if level == 1 and same_symbol(key_name, Layer.BASE, Layer.SHIFT): + if level == 1 and same_symbol(key.id, Layer.BASE, Layer.SHIFT): continue - if level == 4 and same_symbol(key_name, Layer.ALTGR, Layer.ALTGR_SHIFT): + if level == 4 and same_symbol(key.id, Layer.ALTGR, Layer.ALTGR_SHIFT): continue - if level == 6 and same_symbol(key_name, Layer.ODK, Layer.ODK_SHIFT): + if level == 6 and same_symbol(key.id, Layer.ODK, Layer.ODK_SHIFT): continue - key = svg.find(f".//g[@id=\"{SCAN_CODES['web'][key_name]}\"]", ns) - set_key_label(key, level, layout.layers[i][key_name]) + key_elem = svg.find(f'.//g[@id="{key.web}"]', ns) + set_key_label(key_elem, level, layout.layers[i][key.id]) return svg diff --git a/kalamine/generators/xkb.py b/kalamine/generators/xkb.py index 3ef688e..f2d7871 100644 --- a/kalamine/generators/xkb.py +++ b/kalamine/generators/xkb.py @@ -4,13 +4,14 @@ - xkb symbols/patch for XOrg (system-wide) & Wayland (system-wide/user-space) """ -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, List, Optional if TYPE_CHECKING: from ..layout import KeyboardLayout +from ..key import KEYS, KeyCategory from ..template import load_tpl, substitute_lines -from ..utils import DK_INDEX, LAYER_KEYS, ODK_ID, hex_ord, load_data +from ..utils import DK_INDEX, ODK_ID, hex_ord, load_data XKB_KEY_SYM = load_data("key_sym") @@ -27,18 +28,14 @@ def xkb_table(layout: "KeyboardLayout", xkbcomp: bool = False) -> List[str]: max_length = 16 # `ISO_Level3_Latch` should be the longest symbol name output: List[str] = [] - for key_name in LAYER_KEYS: - if key_name.startswith("-"): # separator - if output: - output.append("") - output.append("//" + key_name[1:]) - continue - + prev_category: Optional[KeyCategory] = None + for key in KEYS.values(): descs = [] symbols = [] - for layer in layout.layers.values(): - if key_name in layer: - keysym = layer[key_name] + has_symbols = False + for keys in layout.layers.values(): + if key.id in keys: + keysym = keys[key.id] desc = keysym # dead key? if keysym in DK_INDEX: @@ -50,6 +47,7 @@ def xkb_table(layout: "KeyboardLayout", xkbcomp: bool = False) -> List[str]: symbol = XKB_KEY_SYM[keysym] else: symbol = f"U{hex_ord(keysym).upper()}" + has_symbols = True else: desc = " " symbol = "VoidSymbol" @@ -57,17 +55,26 @@ def xkb_table(layout: "KeyboardLayout", xkbcomp: bool = False) -> List[str]: descs.append(desc) symbols.append(symbol.ljust(max_length)) - key = "{{[ {0}, {1}, {2}, {3}]}}" # 4-level layout by default + if not has_symbols: + continue + + if key.category is not prev_category: + if output: + output.append("") + output.append("// " + key.category.description) + prev_category = key.category + + key_template = "{{[ {0}, {1}, {2}, {3}]}}" # 4-level layout by default description = "{0} {1} {2} {3}" if layout.has_altgr and layout.has_1dk: # 6 layers are needed: they won't fit on the 4-level format. if xkbcomp: # user-space XKB keymap file (standalone) # standalone XKB files work best with a dual-group solution: # one 4-level group for base+1dk, one two-level group for AltGr - key = "{{[ {}, {}, {}, {}],[ {}, {}]}}" + key_template = "{{[ {}, {}, {}, {}],[ {}, {}]}}" description = "{} {} {} {} {} {}" else: # eight_level XKB symbols (Neo-like) - key = "{{[ {0}, {1}, {4}, {5}, {2}, {3}]}}" + key_template = "{{[ {0}, {1}, {4}, {5}, {2}, {3}]}}" description = "{0} {1} {4} {5} {2} {3}" elif layout.has_altgr: del symbols[3] @@ -75,7 +82,8 @@ def xkb_table(layout: "KeyboardLayout", xkbcomp: bool = False) -> List[str]: del descs[3] del descs[2] - line = f"key <{key_name.upper()}> {key.format(*symbols)};" + keycode = f"<{key.xkb.upper()}>" + line = f"key {keycode: <6} {key_template.format(*symbols)};" if show_description: line += (" // " + description.format(*descs)).rstrip() if line.endswith("\\"): diff --git a/kalamine/help.py b/kalamine/help.py index 38c0d39..c9e4430 100644 --- a/kalamine/help.py +++ b/kalamine/help.py @@ -1,8 +1,8 @@ from pathlib import Path from typing import Dict, List +from .key import KEYS from .layout import KeyboardLayout -from .template import SCAN_CODES from .utils import Layer, load_data SEPARATOR = ( @@ -84,8 +84,8 @@ def dummy_layout( descriptor["base"] = descriptor.pop("1dk") # XXX this should be a dataclass - for key, val in meta.items(): - descriptor[key] = val + for d, val in meta.items(): + descriptor[d] = val # make a KeyboardLayout matching the input parameters descriptor["geometry"] = "ANSI" # layout.yaml has an ANSI geometry @@ -93,10 +93,10 @@ def dummy_layout( layout.geometry = geometry # ensure there is no empty keys (XXX maybe this should be in layout.py) - for key in SCAN_CODES["web"].keys(): - if key not in layout.layers[Layer.BASE].keys(): - layout.layers[Layer.BASE][key] = "\\" - layout.layers[Layer.SHIFT][key] = "|" + for key in KEYS.values(): + if key.alphanum and key.id not in layout.layers[Layer.BASE].keys(): + layout.layers[Layer.BASE][key.id] = "\\" + layout.layers[Layer.SHIFT][key.id] = "|" return layout diff --git a/kalamine/key.py b/kalamine/key.py new file mode 100644 index 0000000..cff34a9 --- /dev/null +++ b/kalamine/key.py @@ -0,0 +1,110 @@ +from dataclasses import dataclass +from enum import Enum, Flag, auto, unique +from typing import Any, Dict, Optional + +from kalamine.utils import load_data + + +@unique +class Hand(Enum): + Left = auto() + Right = auto() + + @classmethod + def parse(cls, raw: str) -> "Hand": + for h in cls: + if h.name.casefold() == raw.casefold(): + return h + else: + raise ValueError(f"Cannot parse hand: “{raw}”") + + +@unique +class KeyCategory(Flag): + Digits = auto() + Letters1 = auto() + Letters2 = auto() + Letters3 = auto() + PinkyKeys = auto() + SpaceBar = auto() + System = auto() + Modifiers = auto() + InputMethod = auto() + Miscellaneous = auto() + AlphaNum = Digits | Letters1 | Letters2 | Letters3 | PinkyKeys | SpaceBar + + @classmethod + def parse(cls, raw: str) -> "KeyCategory": + for kc in cls: + if kc.name and kc.name.casefold() == raw.casefold(): + return kc + else: + raise ValueError(f"Cannot parse key category: “{raw}”") + + @property + def description(self) -> str: + descriptions = { + KeyCategory.Digits: "Digits", + KeyCategory.Letters1: "Letters, first row", + KeyCategory.Letters2: "Letters, second row", + KeyCategory.Letters3: "Letters, third row", + KeyCategory.PinkyKeys: "Pinky keys", + KeyCategory.SpaceBar: "Space bar", + KeyCategory.System: "System", + KeyCategory.Modifiers: "Modifiers", + KeyCategory.InputMethod: "Input method", + KeyCategory.Miscellaneous: "Miscellaneous", + } + if d := descriptions.get(self): + return d + else: + raise ValueError(f"No description ofr KeyCategory: {self}") + + +@dataclass +class Key: + xkb: str + web: Optional[str] = None + windows: Optional[str] = None + macos: Optional[str] = None + hand: Optional[Hand] = None + category: KeyCategory = KeyCategory.Miscellaneous + "Usual hand on standard (ISO, etc.) keyboard" + + @classmethod + def load_data(cls, data: Dict[str, Any]) -> Dict[str, "Key"]: + return { + key.xkb: key + for category, keys in data.items() + for key in (cls.parse(category=category, **entry) for entry in keys) + } + + @classmethod + def parse( + cls, + category: str, + xkb: str, + web: Optional[str], + windows: Optional[str], + macos: Optional[str], + hand: Optional[str], + ) -> "Key": + return cls( + category=KeyCategory.parse(category), + xkb=xkb, + web=web, + windows=windows, + macos=macos, + hand=Hand.parse(hand) if hand else None, + ) + + @property + def id(self) -> str: + return self.xkb + + @property + def alphanum(self) -> bool: + return bool(self.category & KeyCategory.AlphaNum) + + +KEYS = Key.load_data(load_data("scan_codes")) diff --git a/kalamine/layout.py b/kalamine/layout.py index bae0295..6bb3095 100644 --- a/kalamine/layout.py +++ b/kalamine/layout.py @@ -8,15 +8,8 @@ import tomli import yaml -from .utils import ( - DEAD_KEYS, - LAYER_KEYS, - ODK_ID, - Layer, - load_data, - text_to_lines, - upper_key, -) +from .key import KEYS +from .utils import DEAD_KEYS, ODK_ID, Layer, load_data, text_to_lines, upper_key ### # Helpers @@ -239,9 +232,7 @@ def layout_has_char(char: str) -> bool: if id == ODK_ID: self.has_1dk = True - for key_name in LAYER_KEYS: - if key_name.startswith("-"): - continue + for key_name in KEYS: for layer in [Layer.ODK_SHIFT, Layer.ODK]: if key_name in self.layers[layer]: deadkey[self.layers[layer.necromance()][key_name]] = ( diff --git a/kalamine/template.py b/kalamine/template.py index bdf786e..b1d4d7a 100644 --- a/kalamine/template.py +++ b/kalamine/template.py @@ -3,15 +3,12 @@ import re from typing import TYPE_CHECKING, List -from .utils import lines_to_text, load_data +from .utils import lines_to_text if TYPE_CHECKING: from .layout import KeyboardLayout -SCAN_CODES = load_data("scan_codes") - - def substitute_lines(text: str, variable: str, lines: List[str]) -> str: prefix = "KALAMINE::" exp = re.compile(".*" + prefix + variable + ".*") diff --git a/kalamine/utils.py b/kalamine/utils.py index d19967e..1379ea5 100644 --- a/kalamine/utils.py +++ b/kalamine/utils.py @@ -105,66 +105,4 @@ class DeadKeyDescr: for dk in DEAD_KEYS: DK_INDEX[dk.char] = dk -SCAN_CODES = load_data("scan_codes") - ODK_ID = "**" # must match the value in dead_keys.yaml - -LAYER_KEYS = [ - "- Digits", - "ae01", - "ae02", - "ae03", - "ae04", - "ae05", - "ae06", - "ae07", - "ae08", - "ae09", - "ae10", - "- Letters, first row", - "ad01", - "ad02", - "ad03", - "ad04", - "ad05", - "ad06", - "ad07", - "ad08", - "ad09", - "ad10", - "- Letters, second row", - "ac01", - "ac02", - "ac03", - "ac04", - "ac05", - "ac06", - "ac07", - "ac08", - "ac09", - "ac10", - "- Letters, third row", - "ab01", - "ab02", - "ab03", - "ab04", - "ab05", - "ab06", - "ab07", - "ab08", - "ab09", - "ab10", - "- Pinky keys", - "ae11", - "ae12", - "ae13", - "ad11", - "ad12", - "ac11", - "ab11", - "tlde", - "bksl", - "lsgt", - "- Space bar", - "spce", -] diff --git a/tests/test_serializer_xkb.py b/tests/test_serializer_xkb.py index 302e4a6..45319db 100644 --- a/tests/test_serializer_xkb.py +++ b/tests/test_serializer_xkb.py @@ -70,14 +70,11 @@ def test_ansi(): // Pinky keys key {[ minus , underscore , VoidSymbol , VoidSymbol ]}; // - _ key {[ equal , plus , VoidSymbol , VoidSymbol ]}; // = + - key {[ VoidSymbol , VoidSymbol , VoidSymbol , VoidSymbol ]}; // key {[ bracketleft , braceleft , VoidSymbol , VoidSymbol ]}; // [ { key {[ bracketright , braceright , VoidSymbol , VoidSymbol ]}; // ] } key {[ apostrophe , quotedbl , VoidSymbol , VoidSymbol ]}; // ' " - key {[ VoidSymbol , VoidSymbol , VoidSymbol , VoidSymbol ]}; // key {[ grave , asciitilde , VoidSymbol , VoidSymbol ]}; // ` ~ key {[ backslash , bar , VoidSymbol , VoidSymbol ]}; // \\ | - key {[ VoidSymbol , VoidSymbol , VoidSymbol , VoidSymbol ]}; // // Space bar key {[ space , space , apostrophe , apostrophe ]}; // ' ' @@ -149,11 +146,9 @@ def test_intl(): // Pinky keys key {[ minus , underscore , VoidSymbol , VoidSymbol ]}; // - _ key {[ equal , plus , VoidSymbol , VoidSymbol ]}; // = + - key {[ VoidSymbol , VoidSymbol , VoidSymbol , VoidSymbol ]}; // key {[ bracketleft , braceleft , VoidSymbol , VoidSymbol ]}; // [ { key {[ bracketright , braceright , VoidSymbol , VoidSymbol ]}; // ] } key {[ ISO_Level3_Latch, dead_diaeresis , apostrophe , VoidSymbol ]}; // ' ¨ ' - key {[ VoidSymbol , VoidSymbol , VoidSymbol , VoidSymbol ]}; // key {[ dead_grave , dead_tilde , VoidSymbol , VoidSymbol ]}; // ` ~ key {[ backslash , bar , VoidSymbol , VoidSymbol ]}; // \\ | key {[ backslash , bar , VoidSymbol , VoidSymbol ]}; // \\ | @@ -228,14 +223,11 @@ def test_prog(): // Pinky keys key {[ minus , underscore , VoidSymbol , VoidSymbol ]}; // - _ key {[ equal , plus , VoidSymbol , VoidSymbol ]}; // = + - key {[ VoidSymbol , VoidSymbol , VoidSymbol , VoidSymbol ]}; // key {[ bracketleft , braceleft , VoidSymbol , VoidSymbol ]}; // [ { key {[ bracketright , braceright , VoidSymbol , VoidSymbol ]}; // ] } key {[ apostrophe , quotedbl , dead_acute , dead_diaeresis ]}; // ' " ´ ¨ - key {[ VoidSymbol , VoidSymbol , VoidSymbol , VoidSymbol ]}; // key {[ grave , asciitilde , dead_grave , dead_tilde ]}; // ` ~ ` ~ key {[ backslash , bar , VoidSymbol , VoidSymbol ]}; // \\ | - key {[ VoidSymbol , VoidSymbol , VoidSymbol , VoidSymbol ]}; // // Space bar key {[ space , space , space , space ]}; // From 3b7a506cdc7bbe2f73013a69d3581aa93322bf4b Mon Sep 17 00:00:00 2001 From: Pierre Le Marre Date: Sun, 7 Apr 2024 22:48:27 +0200 Subject: [PATCH 02/10] feat: Add extra key mapping section Using ASCII art to declare the layout is limited to the keys declared in the geometries. Added an extra section `mapping` to the TOML layout configuration file, that enable mappings key explicitly with a dictionary. --- kalamine/layout.py | 41 ++++++++++++++++++++++++++++++++++++++++- kalamine/utils.py | 18 ++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/kalamine/layout.py b/kalamine/layout.py index 6bb3095..1316261 100644 --- a/kalamine/layout.py +++ b/kalamine/layout.py @@ -2,7 +2,7 @@ import sys from dataclasses import dataclass from pathlib import Path -from typing import Dict, List, Optional, Set, Type, TypeVar +from typing import Dict, List, Optional, Set, Type, TypeVar, Union import click import tomli @@ -199,8 +199,47 @@ def __init__( self.layers[Layer.ALTGR]["spce"] = spc["altgr"] self.layers[Layer.ALTGR_SHIFT]["spce"] = spc["altgr_shift"] + # Extra mapping + if mapping := layout_data.get("mapping"): + self._parse_extra_mapping(mapping) + self._parse_dead_keys(spc) + @staticmethod + def _parse_key_ref(raw: str) -> Optional[str]: + """Parse a key reference (e.g. to clone)""" + if raw.startswith("(") and raw.endswith(")"): + if (clone := raw[1:-1]) and clone in KEYS: + return clone + return None + + def _parse_extra_mapping(self, mapping: Dict[str, Union[str, Dict[str, str]]]): + """Parse a layout dict""" + layer: Optional[Layer] + for raw_key, levels in mapping.items(): + # TODO: parse key in various ways (XKB, Linux keycode) + if raw_key not in KEYS: + raise ValueError(f"Unknown key: “{raw_key}”") + key = raw_key + # Check for key clone + if isinstance(levels, str): + # Check for clone + if clone := self._parse_key_ref(levels): + for layer, keys in self.layers.items(): + if value := keys.get(clone): + self.layers[layer][key] = value + continue + raise ValueError(f"Unsupported key mapping: {raw_key}: {levels}") + for raw_layer, raw_value in levels.items(): + if (layer := Layer.parse(raw_layer)) is None: + raise ValueError(f"Cannot parse layer: “{raw_layer}”") + if clone := self._parse_key_ref(raw_value): + if (value := self.layers[layer].get(clone)) is None: + continue + else: + value = raw_value + self.layers[layer][key] = value + def _parse_dead_keys(self, spc: Dict[str, str]) -> None: """Build a deadkey dict.""" diff --git a/kalamine/utils.py b/kalamine/utils.py index 1379ea5..277f6ae 100644 --- a/kalamine/utils.py +++ b/kalamine/utils.py @@ -47,6 +47,24 @@ class Layer(IntEnum): ALTGR = 4 ALTGR_SHIFT = 5 + @classmethod + def parse(cls, raw: str) -> Optional["Layer"]: + rawʹ = raw.casefold() + if rawʹ == "1dk": + return cls(cls.ODK) + elif rawʹ == "1dk_shift": + return cls(cls.ODK_SHIFT) + else: + for layer in cls: + if rawʹ == layer.name.casefold(): + return layer + try: + if int(raw, base=10) == layer.value: + return layer + except ValueError: + pass + return None + def next(self) -> "Layer": """The next layer in the layer ordering.""" return Layer(int(self) + 1) From cfa19db9d82cfc3f8c0fa20d92ea604449f9732c Mon Sep 17 00:00:00 2001 From: Pierre Le Marre Date: Sun, 7 Apr 2024 22:53:50 +0200 Subject: [PATCH 03/10] Fix Enum eq comparison --- kalamine/generators/klc.py | 4 ++-- kalamine/utils.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/kalamine/generators/klc.py b/kalamine/generators/klc.py index acda0bb..4b78e7b 100644 --- a/kalamine/generators/klc.py +++ b/kalamine/generators/klc.py @@ -123,7 +123,7 @@ def klc_keymap(layout: "KeyboardLayout") -> List[str]: desc = layout.dead_keys[symbol][" "] symbol = hex_ord(desc) + "@" else: - if i == Layer.BASE: + if i is Layer.BASE: is_alpha = symbol.upper() != symbol if symbol not in supported_symbols: symbol = hex_ord(symbol) @@ -255,7 +255,7 @@ def c_keymap(layout: "KeyboardLayout") -> List[str]: dead = hex_ord(desc) has_dead_key = True else: - if i == Layer.BASE: + if i is Layer.BASE: is_alpha = symbol.upper() != symbol if symbol not in supported_symbols: symbol = hex_ord(symbol) diff --git a/kalamine/utils.py b/kalamine/utils.py index 277f6ae..e02c035 100644 --- a/kalamine/utils.py +++ b/kalamine/utils.py @@ -71,9 +71,9 @@ def next(self) -> "Layer": def necromance(self) -> "Layer": """Remove the effect of the dead key if any.""" - if self == Layer.ODK: + if self is Layer.ODK: return Layer.BASE - elif self == Layer.ODK_SHIFT: + elif self is Layer.ODK_SHIFT: return Layer.SHIFT return self From d8be6a9a7cd4a0e49b5aa6ad6b2aafb31083e553 Mon Sep 17 00:00:00 2001 From: Pierre Le Marre Date: Sun, 7 Apr 2024 22:54:07 +0200 Subject: [PATCH 04/10] Minor fix --- kalamine/generators/web.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/kalamine/generators/web.py b/kalamine/generators/web.py index b62b134..68306c2 100644 --- a/kalamine/generators/web.py +++ b/kalamine/generators/web.py @@ -97,16 +97,17 @@ def same_symbol(key_name: str, lower: Layer, upper: Layer): # TODO: warning continue - level = 0 - for i in [ - Layer.BASE, - Layer.SHIFT, - Layer.ALTGR, - Layer.ALTGR_SHIFT, - Layer.ODK, - Layer.ODK_SHIFT, - ]: - level += 1 + for level, i in enumerate( + ( + Layer.BASE, + Layer.SHIFT, + Layer.ALTGR, + Layer.ALTGR_SHIFT, + Layer.ODK, + Layer.ODK_SHIFT, + ), + start=1, + ): if key.id not in layout.layers[i]: continue if level == 1 and same_symbol(key.id, Layer.BASE, Layer.SHIFT): From 91cf7f0e61822c4387bf047591bbec4bc521cd32 Mon Sep 17 00:00:00 2001 From: Pierre Le Marre Date: Mon, 8 Apr 2024 07:17:15 +0200 Subject: [PATCH 05/10] Add Numpad keys & more web key codes --- kalamine/data/scan_codes.yaml | 187 ++++++++++++++++++++++++++-------- kalamine/key.py | 2 + 2 files changed, 146 insertions(+), 43 deletions(-) diff --git a/kalamine/data/scan_codes.yaml b/kalamine/data/scan_codes.yaml index b19ab1c..c80ad14 100644 --- a/kalamine/data/scan_codes.yaml +++ b/kalamine/data/scan_codes.yaml @@ -264,155 +264,246 @@ SpaceBar: windows: "39" macos: "49" hand: null +Numpad: +- xkb: kp0 + web: Numpad0 + windows: null + macos: null + hand: null +- xkb: kp1 + web: Numpad1 + windows: null + macos: null + hand: null +- xkb: kp2 + web: Numpad2 + windows: null + macos: null + hand: null +- xkb: kp3 + web: Numpad3 + windows: null + macos: null + hand: null +- xkb: kp4 + web: Numpad4 + windows: null + macos: null + hand: null +- xkb: kp5 + web: Numpad5 + windows: null + macos: null + hand: null +- xkb: kp6 + web: Numpad6 + windows: null + macos: null + hand: null +- xkb: kp7 + web: Numpad7 + windows: null + macos: null + hand: null +- xkb: kp8 + web: Numpad8 + windows: null + macos: null + hand: null +- xkb: kp9 + web: Numpad9 + windows: null + macos: null + hand: null +- xkb: kpen + web: NumpadEnter + windows: null + macos: null + hand: null +- xkb: kpeq + web: NumpadEqual + windows: null + macos: null + hand: null +- xkb: kpdl + web: NumpadDecimal + windows: null + macos: null + hand: null +- xkb: kppt + web: NumpadComma + windows: null + macos: null + hand: null +- xkb: kpdv + web: NumpadDivide + windows: null + macos: null + hand: null +- xkb: kpmu + web: NumpadMultiply + windows: null + macos: null + hand: null +- xkb: kpsu + web: NumpadSubtract + windows: null + macos: null + hand: null +- xkb: kpad + web: NumpadAdd + windows: null + macos: null + hand: null +- xkb: nmlk + web: NumLock + windows: null + macos: null + hand: null System: - xkb: "tab" - web: null + web: Tab windows: null macos: null hand: Left - xkb: "rtrn" - web: null + web: Enter windows: null macos: null hand: null - xkb: "bksp" - web: null + web: Backspace windows: null macos: null hand: null - xkb: "dele" - web: null + web: Delete windows: null macos: null hand: null - xkb: "esc" - web: null + web: Escape windows: null macos: null hand: null - xkb: "menu" - web: null + web: ContextMenu windows: null macos: null hand: null - xkb: "home" - web: null + web: Home windows: null macos: null hand: null -- xkb: "up" - web: null +- xkb: "end" + web: End windows: null macos: null hand: null -- xkb: "end" - web: null +- xkb: "up" + web: ArrowUp windows: null macos: null hand: null -- xkb: "pgup" - web: null +- xkb: "down" + web: ArrowDown windows: null macos: null hand: null - xkb: "left" - web: null + web: ArrowLeft windows: null macos: null hand: null -- xkb: "down" - web: null +- xkb: "rght" + web: ArrowDown windows: null macos: null hand: null -- xkb: "rght" - web: null +- xkb: "pgup" + web: PageUp windows: null macos: null hand: null - xkb: "pgdn" - web: null + web: PageDown windows: null macos: null hand: null - xkb: "fk01" - web: null + web: F1 windows: null macos: null hand: null - xkb: "fk02" - web: null + web: F2 windows: null macos: null hand: null - xkb: "fk03" - web: null + web: F3 windows: null macos: null hand: null - xkb: "fk04" - web: null + web: F4 windows: null macos: null hand: null - xkb: "fk05" - web: null - windows: null - macos: null - hand: null -- xkb: "dele" - web: null + web: F5 windows: null macos: null hand: null - xkb: "fk06" - web: null + web: F6 windows: null macos: null hand: null - xkb: "fk07" - web: null + web: F7 windows: null macos: null hand: null - xkb: "fk08" - web: null + web: F8 windows: null macos: null hand: null - xkb: "fk09" - web: null + web: F9 windows: null macos: null hand: null - xkb: "fk10" - web: null + web: F10 windows: null macos: null hand: null - xkb: "fk11" - web: null + web: F11 windows: null macos: null hand: null - xkb: "fk12" - web: null + web: F12 windows: null macos: null hand: null Modifiers: - xkb: "lfsh" - web: null + web: ShiftLeft windows: null macos: null hand: Left - xkb: "rtsh" - web: null + web: ShiftRight windows: null macos: null hand: Right - xkb: "caps" - web: null + web: CapsLock windows: null macos: null hand: Left @@ -427,12 +518,22 @@ Modifiers: macos: null hand: Right - xkb: "lctl" - web: null + web: ControlLeft windows: null macos: null hand: Left - xkb: "rctl" - web: null + web: ControlRight + windows: null + macos: null + hand: Right +- xkb: lwin + web: MetaLeft + windows: null + macos: null + hand: Left +- xkb: rwin + web: MetaRight windows: null macos: null hand: Right @@ -460,12 +561,12 @@ Miscellaneous: macos: null hand: null - xkb: "i172" # Play/Pause - web: null + web: MediaPlayPause windows: null macos: null hand: null - xkb: "i180" # Home page - web: null + web: BrowserHome windows: null macos: null hand: null diff --git a/kalamine/key.py b/kalamine/key.py index cff34a9..1d1ab1d 100644 --- a/kalamine/key.py +++ b/kalamine/key.py @@ -27,6 +27,7 @@ class KeyCategory(Flag): Letters3 = auto() PinkyKeys = auto() SpaceBar = auto() + Numpad = auto() System = auto() Modifiers = auto() InputMethod = auto() @@ -50,6 +51,7 @@ def description(self) -> str: KeyCategory.Letters3: "Letters, third row", KeyCategory.PinkyKeys: "Pinky keys", KeyCategory.SpaceBar: "Space bar", + KeyCategory.Numpad: "Numeric pad", KeyCategory.System: "System", KeyCategory.Modifiers: "Modifiers", KeyCategory.InputMethod: "Input method", From 4fa8b2f491323efdf34f8c4a1f5e5c790f2cc2b7 Mon Sep 17 00:00:00 2001 From: Pierre Le Marre Date: Mon, 8 Apr 2024 07:57:39 +0200 Subject: [PATCH 06/10] Add Windows scan codes Use full scan codes, in preparation for all key remapping. --- kalamine/data/scan_codes.yaml | 218 +++++++++++++++++----------------- kalamine/generators/ahk.py | 26 ++-- kalamine/generators/klc.py | 23 +++- 3 files changed, 144 insertions(+), 123 deletions(-) diff --git a/kalamine/data/scan_codes.yaml b/kalamine/data/scan_codes.yaml index c80ad14..98a797e 100644 --- a/kalamine/data/scan_codes.yaml +++ b/kalamine/data/scan_codes.yaml @@ -1,551 +1,551 @@ digits: - xkb: "ae01" web: "Digit1" - windows: "02" + windows: "T02" macos: "18" hand: null - xkb: "ae02" web: "Digit2" - windows: "03" + windows: "T03" macos: "19" hand: null - xkb: "ae03" web: "Digit3" - windows: "04" + windows: "T04" macos: "20" hand: null - xkb: "ae04" web: "Digit4" - windows: "05" + windows: "T05" macos: "21" hand: null - xkb: "ae05" web: "Digit5" - windows: "06" + windows: "T06" macos: "23" hand: null - xkb: "ae06" web: "Digit6" - windows: "07" + windows: "T07" macos: "22" hand: null - xkb: "ae07" web: "Digit7" - windows: "08" + windows: "T08" macos: "26" hand: null - xkb: "ae08" web: "Digit8" - windows: "09" + windows: "T09" macos: "28" hand: null - xkb: "ae09" web: "Digit9" - windows: "0a" + windows: "T0A" macos: "25" hand: null - xkb: "ae10" web: "Digit0" - windows: "0b" + windows: "T0B" macos: "29" hand: null # Letters, first row Letters1: - xkb: "ad01" web: "KeyQ" - windows: "10" + windows: "T10" macos: "12" hand: null - xkb: "ad02" web: "KeyW" - windows: "11" + windows: "T11" macos: "13" hand: null - xkb: "ad03" web: "KeyE" - windows: "12" + windows: "T12" macos: "14" hand: null - xkb: "ad04" web: "KeyR" - windows: "13" + windows: "T13" macos: "15" hand: null - xkb: "ad05" web: "KeyT" - windows: "14" + windows: "T14" macos: "17" hand: null - xkb: "ad06" web: "KeyY" - windows: "15" + windows: "T15" macos: "16" hand: null - xkb: "ad07" web: "KeyU" - windows: "16" + windows: "T16" macos: "32" hand: null - xkb: "ad08" web: "KeyI" - windows: "17" + windows: "T17" macos: "34" hand: null - xkb: "ad09" web: "KeyO" - windows: "18" + windows: "T18" macos: "31" hand: null - xkb: "ad10" web: "KeyP" - windows: "19" + windows: "T19" macos: "35" hand: null # Letters, second row Letters2: - xkb: "ac01" web: "KeyA" - windows: "1e" + windows: "T1E" macos: "0" hand: null - xkb: "ac02" web: "KeyS" - windows: "1f" + windows: "T1F" macos: "1" hand: null - xkb: "ac03" web: "KeyD" - windows: "20" + windows: "T20" macos: "2" hand: null - xkb: "ac04" web: "KeyF" - windows: "21" + windows: "T21" macos: "3" hand: null - xkb: "ac05" web: "KeyG" - windows: "22" + windows: "T22" macos: "5" hand: null - xkb: "ac06" web: "KeyH" - windows: "23" + windows: "T23" macos: "4" hand: null - xkb: "ac07" web: "KeyJ" - windows: "24" + windows: "T24" macos: "38" hand: null - xkb: "ac08" web: "KeyK" - windows: "25" + windows: "T25" macos: "40" hand: null - xkb: "ac09" web: "KeyL" - windows: "26" + windows: "T26" macos: "37" hand: null - xkb: "ac10" web: "Semicolon" - windows: "27" + windows: "T27" macos: "41" hand: null # Letters, third row Letters3: - xkb: "ab01" web: "KeyZ" - windows: "2c" + windows: "T2C" macos: "6" hand: null - xkb: "ab02" web: "KeyX" - windows: "2d" + windows: "T2D" macos: "7" hand: null - xkb: "ab03" web: "KeyC" - windows: "2e" + windows: "T2E" macos: "8" hand: null - xkb: "ab04" web: "KeyV" - windows: "2f" + windows: "T2F" macos: "9" hand: null - xkb: "ab05" web: "KeyB" - windows: "30" + windows: "T30" macos: "11" hand: null - xkb: "ab06" web: "KeyN" - windows: "31" + windows: "T31" macos: "45" hand: null - xkb: "ab07" web: "KeyM" - windows: "32" + windows: "T32" macos: "46" hand: null - xkb: "ab08" web: "Comma" - windows: "33" + windows: "T33" macos: "43" hand: null - xkb: "ab09" web: "Period" - windows: "34" + windows: "T34" macos: "47" hand: null - xkb: "ab10" web: "Slash" - windows: "35" + windows: "T35" macos: "44" hand: null # Pinky keys PinkyKeys: - xkb: "ae11" web: "Minus" - windows: "0c" + windows: "T0C" macos: "27" hand: null - xkb: "ae12" web: "Equal" - windows: "0d" + windows: "T0D" macos: "24" hand: null - xkb: "ae13" web: "IntlYen" - windows: "0d" + windows: "T0D" macos: "42" hand: null - xkb: "ad11" web: "BracketLeft" - windows: "1a" + windows: "T1A" macos: "33" hand: null - xkb: "ad12" web: "BracketRight" - windows: "1b" + windows: "T1B" macos: "30" hand: null - xkb: "ac11" web: "Quote" - windows: "28" + windows: "T28" macos: "39" hand: null - xkb: "ab11" web: "IntlRo" - windows: "28" + windows: "T28" macos: "39" hand: null - xkb: "tlde" web: "Backquote" - windows: "29" + windows: "T29" macos: "50" hand: null - xkb: "bksl" web: "Backslash" - windows: "2b" + windows: "T2B" macos: "42" hand: null - xkb: "lsgt" web: "IntlBackslash" - windows: "56" + windows: "T56" macos: "10" hand: null # Space bar row SpaceBar: - xkb: "spce" web: "Space" - windows: "39" + windows: "T39" macos: "49" hand: null Numpad: - xkb: kp0 web: Numpad0 - windows: null + windows: T52 macos: null hand: null - xkb: kp1 web: Numpad1 - windows: null + windows: T4F macos: null hand: null - xkb: kp2 web: Numpad2 - windows: null + windows: T50 macos: null hand: null - xkb: kp3 web: Numpad3 - windows: null + windows: T51 macos: null hand: null - xkb: kp4 web: Numpad4 - windows: null + windows: T4B macos: null hand: null - xkb: kp5 web: Numpad5 - windows: null + windows: T4C macos: null hand: null - xkb: kp6 web: Numpad6 - windows: null + windows: T4D macos: null hand: null - xkb: kp7 web: Numpad7 - windows: null + windows: T47 macos: null hand: null - xkb: kp8 web: Numpad8 - windows: null + windows: T48 macos: null hand: null - xkb: kp9 web: Numpad9 - windows: null + windows: T49 macos: null hand: null - xkb: kpen web: NumpadEnter - windows: null + windows: X1C macos: null hand: null - xkb: kpeq web: NumpadEqual - windows: null + windows: T59 macos: null hand: null - xkb: kpdl web: NumpadDecimal - windows: null + windows: T53 macos: null hand: null - xkb: kppt web: NumpadComma - windows: null + windows: T7E macos: null hand: null - xkb: kpdv web: NumpadDivide - windows: null + windows: X35 macos: null hand: null - xkb: kpmu web: NumpadMultiply - windows: null + windows: T37 macos: null hand: null - xkb: kpsu web: NumpadSubtract - windows: null + windows: T4A macos: null hand: null - xkb: kpad web: NumpadAdd - windows: null + windows: T4E macos: null hand: null - xkb: nmlk web: NumLock - windows: null + windows: T45 macos: null hand: null System: - xkb: "tab" web: Tab - windows: null + windows: T0F macos: null hand: Left - xkb: "rtrn" web: Enter - windows: null + windows: T1C macos: null hand: null - xkb: "bksp" web: Backspace - windows: null + windows: T0E macos: null hand: null - xkb: "dele" web: Delete - windows: null + windows: X53 macos: null hand: null - xkb: "esc" web: Escape - windows: null + windows: T01 macos: null hand: null - xkb: "menu" web: ContextMenu - windows: null + windows: X5D macos: null hand: null - xkb: "home" web: Home - windows: null + windows: X47 macos: null hand: null - xkb: "end" web: End - windows: null + windows: X4F macos: null hand: null - xkb: "up" web: ArrowUp - windows: null + windows: X48 macos: null hand: null - xkb: "down" web: ArrowDown - windows: null + windows: X50 macos: null hand: null - xkb: "left" web: ArrowLeft - windows: null + windows: X4B macos: null hand: null - xkb: "rght" web: ArrowDown - windows: null + windows: X50 macos: null hand: null - xkb: "pgup" web: PageUp - windows: null + windows: X49 macos: null hand: null - xkb: "pgdn" web: PageDown - windows: null + windows: X51 macos: null hand: null - xkb: "fk01" web: F1 - windows: null + windows: T3B macos: null hand: null - xkb: "fk02" web: F2 - windows: null + windows: T3C macos: null hand: null - xkb: "fk03" web: F3 - windows: null + windows: T3D macos: null hand: null - xkb: "fk04" web: F4 - windows: null + windows: T3E macos: null hand: null - xkb: "fk05" web: F5 - windows: null + windows: T3F macos: null hand: null - xkb: "fk06" web: F6 - windows: null + windows: T40 macos: null hand: null - xkb: "fk07" web: F7 - windows: null + windows: T41 macos: null hand: null - xkb: "fk08" web: F8 - windows: null + windows: T42 macos: null hand: null - xkb: "fk09" web: F9 - windows: null + windows: T43 macos: null hand: null - xkb: "fk10" web: F10 - windows: null + windows: T44 macos: null hand: null - xkb: "fk11" web: F11 - windows: null + windows: T57 macos: null hand: null - xkb: "fk12" web: F12 - windows: null + windows: T58 macos: null hand: null Modifiers: - xkb: "lfsh" web: ShiftLeft - windows: null + windows: T2A macos: null hand: Left - xkb: "rtsh" web: ShiftRight - windows: null + windows: T36 macos: null hand: Right - xkb: "caps" web: CapsLock - windows: null + windows: T3A macos: null hand: Left - xkb: "lalt" web: "AltLeft" - windows: null + windows: T38 macos: null hand: Left - xkb: "ralt" web: "AltRight" - windows: null + windows: X38 macos: null hand: Right - xkb: "lctl" web: ControlLeft - windows: null + windows: T1D macos: null hand: Left - xkb: "rctl" web: ControlRight - windows: null + windows: X1D macos: null hand: Right - xkb: lwin web: MetaLeft - windows: null + windows: X5B macos: null hand: Left - xkb: rwin web: MetaRight - windows: null + windows: X5C macos: null hand: Right InputMethod: - xkb: "muhe" web: "NonConvert" - windows: null + windows: T7B macos: null hand: Left - xkb: "henk" web: "Convert" - windows: null + windows: T79 macos: null hand: Right # Miscellaneous @@ -562,11 +562,11 @@ Miscellaneous: hand: null - xkb: "i172" # Play/Pause web: MediaPlayPause - windows: null + windows: X22 macos: null hand: null - xkb: "i180" # Home page web: BrowserHome - windows: null + windows: X32 macos: null hand: null diff --git a/kalamine/generators/ahk.py b/kalamine/generators/ahk.py index facc10c..1499e99 100644 --- a/kalamine/generators/ahk.py +++ b/kalamine/generators/ahk.py @@ -22,6 +22,7 @@ def ahk_keymap(layout: "KeyboardLayout", altgr: bool = False) -> List[str]: prefixes = [" ", "+", "", "", " <^>!", "<^>!+"] specials = " \u00a0\u202f‘’'\"^`~" esc_all = True # set to False to ease the debug (more readable AHK script) + layers = (Layer.ALTGR, Layer.ALTGR_SHIFT) if altgr else (Layer.BASE, Layer.SHIFT) def ahk_escape(key: str) -> str: if len(key) == 1: @@ -44,7 +45,12 @@ def ahk_actions(symbol: str) -> Dict[str, str]: # TODO: delete test? # if key.id in ["ae13", "ab11"]: # ABNT / JIS keys # continue # these two keys are not supported yet - if key.windows is None: + # TODO: add support for all scan codes + if key.windows is None or not key.windows.startswith("T"): + continue + + # Skip key if not defined and is not alphanumeric + if not any(key.id in layout.layers[i] for i in layers) and not key.alphanum: continue if key.category is not prev_category: @@ -52,10 +58,8 @@ def ahk_actions(symbol: str) -> Dict[str, str]: output.append("") prev_category = key.category - sc = f"SC{key.windows}" - for i in ( - [Layer.ALTGR, Layer.ALTGR_SHIFT] if altgr else [Layer.BASE, Layer.SHIFT] - ): + sc = f"SC{key.windows[1:].lower()}" + for i in layers: layer = layout.layers[i] if key.id not in layer: continue @@ -86,13 +90,19 @@ def ahk_shortcuts(layout: "KeyboardLayout") -> List[str]: prefixes = [" ^", "^+"] enabled = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" qwerty_vk = load_data("qwerty_vk") + layers = (Layer.BASE, Layer.SHIFT) output = [] prev_category: Optional[KeyCategory] = None for key in KEYS.values(): # if key_name in ["ae13", "ab11"]: # ABNT / JIS keys # continue # these two keys are not supported yet - if key.windows is None: + # TODO: add support for all scan codes + if key.windows is None or not key.windows.startswith("T"): + continue + + # Skip key if not defined and is not alphanumeric + if not any(key.id in layout.layers[i] for i in layers) and not key.alphanum: continue if key.category is not prev_category: @@ -100,8 +110,8 @@ def ahk_shortcuts(layout: "KeyboardLayout") -> List[str]: output.append("") prev_category = key.category - scan_code = key.windows - for i in [Layer.BASE, Layer.SHIFT]: + scan_code = key.windows[1:].lower() + for i in layers: layer = layout.layers[i] if key.id not in layer: continue diff --git a/kalamine/generators/klc.py b/kalamine/generators/klc.py index 4b78e7b..b17320e 100644 --- a/kalamine/generators/klc.py +++ b/kalamine/generators/klc.py @@ -101,19 +101,24 @@ def klc_keymap(layout: "KeyboardLayout") -> List[str]: oem_idx = 0 # Python trick to do equivalent of C static variable output = [] qwerty_vk = load_data("qwerty_vk") + layers = (Layer.BASE, Layer.SHIFT, Layer.ALTGR, Layer.ALTGR_SHIFT) for key in KEYS.values(): if key.id in ["ae13", "ab11"]: # ABNT / JIS keys continue # these two keys are not supported yet - if key.windows is None: + if key.windows is None or not key.windows.startswith("T"): # TODO: warning continue + # Skip key if not defined and is not alphanumeric + if not any(key.id in layout.layers[i] for i in layers) and not key.alphanum: + continue + symbols = [] description = "//" is_alpha = False - for i in [Layer.BASE, Layer.SHIFT, Layer.ALTGR, Layer.ALTGR_SHIFT]: + for i in layers: layer = layout.layers[i] if key.id in layer: @@ -133,7 +138,7 @@ def klc_keymap(layout: "KeyboardLayout") -> List[str]: symbols.append("-1") description += " " + desc - scan_code = key.windows + scan_code = key.windows[1:].lower() virtual_key = qwerty_vk[scan_code] if not layout.qwerty_shortcuts: @@ -226,6 +231,7 @@ def c_keymap(layout: "KeyboardLayout") -> List[str]: supported_symbols = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" qwerty_vk = load_data("qwerty_vk") + layers = (Layer.BASE, Layer.SHIFT, Layer.ALTGR, Layer.ALTGR_SHIFT) global oem_idx oem_idx = 0 # Python trick to do equivalent of C static variable @@ -233,16 +239,21 @@ def c_keymap(layout: "KeyboardLayout") -> List[str]: for key in KEYS.values(): if key.id in ["ae13", "ab11"]: # ABNT / JIS keys continue # these two keys are not supported yet - if key.windows is None: + # TODO: add support for all scan codes + if key.windows is None or not key.windows.startswith("T"): # TODO: warning continue + # Skip key if not defined and is not alphanumeric + if not any(key.id in layout.layers[i] for i in layers) and not key.alphanum: + continue + symbols = [] dead_symbols = [] is_alpha = False has_dead_key = False - for i in [Layer.BASE, Layer.SHIFT, Layer.ALTGR, Layer.ALTGR_SHIFT]: + for i in layers: layer = layout.layers[i] if key.id in layer: @@ -266,7 +277,7 @@ def c_keymap(layout: "KeyboardLayout") -> List[str]: symbols.append("WCH_NONE") dead_symbols.append("WCH_NONE") - scan_code = key.windows + scan_code = key.windows[1:].lower() virtual_key = qwerty_vk[scan_code] if not layout.qwerty_shortcuts: From c8661032a6e393c42d90f9ed005db8cd8f642745 Mon Sep 17 00:00:00 2001 From: Pierre Le Marre Date: Mon, 8 Apr 2024 08:53:31 +0200 Subject: [PATCH 07/10] test(XKB): Add extra mapping test --- tests/test_serializer_xkb.py | 54 ++++++++++++++++++++++++++++-------- tests/util.py | 11 ++++++-- 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/tests/test_serializer_xkb.py b/tests/test_serializer_xkb.py index 45319db..febf4f5 100644 --- a/tests/test_serializer_xkb.py +++ b/tests/test_serializer_xkb.py @@ -1,4 +1,5 @@ from textwrap import dedent +from typing import Dict from kalamine import KeyboardLayout from kalamine.generators.xkb import xkb_table @@ -6,8 +7,10 @@ from .util import get_layout_dict -def load_layout(filename: str) -> KeyboardLayout: - return KeyboardLayout(get_layout_dict(filename)) +def load_layout( + filename: str, extraMapping: Dict[str, Dict[str, str]] +) -> KeyboardLayout: + return KeyboardLayout(get_layout_dict(filename, extraMapping)) def split(multiline_str: str): @@ -15,7 +18,7 @@ def split(multiline_str: str): def test_ansi(): - layout = load_layout("ansi") + layout = load_layout("ansi", {}) expected = split( """ @@ -91,8 +94,6 @@ def test_ansi(): def test_intl(): - layout = load_layout("intl") - expected = split( """ // Digits @@ -158,17 +159,46 @@ def test_intl(): """ ) - xkbcomp = xkb_table(layout, xkbcomp=True) - assert len(xkbcomp) == len(expected) - assert xkbcomp == expected + extraMapping = { + # NOTE: redefine level + "ae01": {"shift": "?"}, + # NOTE: test case variants and ODK alias + "menu": {"base": "a", "sHiFt": "A", "1dk": "æ", "ODk_shiFt": "Æ"}, + # NOTE: clone level + "esc": {"base": "(ae11)"}, + # NOTE: clone key + "i172": "(lsgt)", + } + + extraSymbols = [ + "", + "// System", + "key {[ minus , VoidSymbol , VoidSymbol , VoidSymbol ]}; // -", + "key {[ a , A , ae , AE ]}; // a A æ Æ", + "", + "// Miscellaneous", + "key {[ backslash , bar , VoidSymbol , VoidSymbol ]}; // \\ |", + ] + + extraExpected = expected + extraSymbols + extraExpected[1] = ( + "key {[ 1 , question , VoidSymbol , VoidSymbol ]}; // 1 ?" + ) - xkbpatch = xkb_table(layout, xkbcomp=False) - assert len(xkbpatch) == len(expected) - assert xkbpatch == expected + for mapping, expectedʹ in (({}, expected), (extraMapping, extraExpected)): + layout = load_layout("intl", mapping) + + xkbcomp = xkb_table(layout, xkbcomp=True) + assert len(xkbcomp) == len(expectedʹ) + assert xkbcomp == expectedʹ + + xkbpatch = xkb_table(layout, xkbcomp=False) + assert len(xkbpatch) == len(expectedʹ) + assert xkbpatch == expectedʹ def test_prog(): - layout = load_layout("prog") + layout = load_layout("prog", {}) expected = split( """ diff --git a/tests/util.py b/tests/util.py index c4b35f2..45a8d0b 100644 --- a/tests/util.py +++ b/tests/util.py @@ -1,14 +1,19 @@ """Some util functions for tests.""" from pathlib import Path -from typing import Dict +from typing import Dict, Optional import tomli -def get_layout_dict(filename: str) -> Dict: +def get_layout_dict( + filename: str, extraMapping: Optional[Dict[str, Dict[str, str]]] = None +) -> Dict: """Return the layout directory path.""" file_path = Path(__file__).parent.parent / f"layouts/{filename}.toml" with file_path.open(mode="rb") as file: - return tomli.load(file) + layout = tomli.load(file) + if extraMapping: + layout.update({"mapping": extraMapping}) + return layout From 482d30e6c983b2a75f0bb7a5fd049a50bda4fe89 Mon Sep 17 00:00:00 2001 From: Pierre Le Marre Date: Mon, 8 Apr 2024 11:41:11 +0200 Subject: [PATCH 08/10] Improve Windows VK handling --- kalamine/data/qwerty_vk.yaml | 169 ++++++++++++++++++++++++----------- kalamine/generators/ahk.py | 2 +- kalamine/generators/klc.py | 72 +++++++++++---- 3 files changed, 175 insertions(+), 68 deletions(-) diff --git a/kalamine/data/qwerty_vk.yaml b/kalamine/data/qwerty_vk.yaml index 1317f3d..9fd2e2d 100644 --- a/kalamine/data/qwerty_vk.yaml +++ b/kalamine/data/qwerty_vk.yaml @@ -1,61 +1,130 @@ # Scancodes <-> Virtual Keys as in qwerty # this is to keep shortcuts at their qwerty location -'39': 'SPACE' +'T39': 'SPACE' # digits -'02': '1' -'03': '2' -'04': '3' -'05': '4' -'06': '5' -'07': '6' -'08': '7' -'09': '8' -'0a': '9' -'0b': '0' +'T02': '1' +'T03': '2' +'T04': '3' +'T05': '4' +'T06': '5' +'T07': '6' +'T08': '7' +'T09': '8' +'T0A': '9' +'T0B': '0' # letters, first row -'10': 'Q' -'11': 'W' -'12': 'E' -'13': 'R' -'14': 'T' -'15': 'Y' -'16': 'U' -'17': 'I' -'18': 'O' -'19': 'P' - +'T10': 'Q' +'T11': 'W' +'T12': 'E' +'T13': 'R' +'T14': 'T' +'T15': 'Y' +'T16': 'U' +'T17': 'I' +'T18': 'O' +'T19': 'P' + # letters, second row -'1e': 'A' -'1f': 'S' -'20': 'D' -'21': 'F' -'22': 'G' -'23': 'H' -'24': 'J' -'25': 'K' -'26': 'L' -'27': 'OEM_1' +'T1E': 'A' +'T1F': 'S' +'T20': 'D' +'T21': 'F' +'T22': 'G' +'T23': 'H' +'T24': 'J' +'T25': 'K' +'T26': 'L' +'T27': 'OEM_1' # letters, third row -'2c': 'Z' -'2d': 'X' -'2e': 'C' -'2f': 'V' -'30': 'B' -'31': 'N' -'32': 'M' -'33': 'OEM_COMMA' -'34': 'OEM_PERIOD' -'35': 'OEM_2' +'T2C': 'Z' +'T2D': 'X' +'T2E': 'C' +'T2F': 'V' +'T30': 'B' +'T31': 'N' +'T32': 'M' +'T33': 'OEM_COMMA' +'T34': 'OEM_PERIOD' +'T35': 'OEM_2' # pinky keys -'29': 'OEM_3' -'0c': 'OEM_MINUS' -'0d': 'OEM_PLUS' -'1a': 'OEM_4' -'1b': 'OEM_6' -'28': 'OEM_7' -'2b': 'OEM_5' -'56': 'OEM_102' \ No newline at end of file +'T29': 'OEM_3' +'T0C': 'OEM_MINUS' +'T0D': 'OEM_PLUS' +'T1A': 'OEM_4' +'T1B': 'OEM_6' +'T28': 'OEM_7' +'T2B': 'OEM_5' +'T56': 'OEM_102' + +# Numpad +'T52': 'NUMPAD0' +'T4F': 'NUMPAD1' +'T50': 'NUMPAD2' +'T51': 'NUMPAD3' +'T4B': 'NUMPAD4' +'T4C': 'NUMPAD5' +'T4D': 'NUMPAD6' +'T47': 'NUMPAD7' +'T48': 'NUMPAD8' +'T49': 'NUMPAD9' +'T37': 'MULTIPLY' +'T4E': 'ADD' +'T4A': 'SUBTRACT' +'X35': 'DIVIDE' +'T53': 'DECIMAL' +# 'X1C': '' # NumpadEnter (maps to Return) +# 'T59': 'VK_CLEAR' # NumpadEqual (VK not mappable) +'T7E': 'VK_ABNT_C2' # NumadComma +# 'T45': 'NUMLOCK' # (VK not mappable) + +# System +'T0F': 'TAB' +'T1C': 'RETURN' +'T0E': 'BACK' # Backspace +# 'X52': 'INSERT' # (VK not mappable) +# 'X53': 'DELETE' # (VK not mappable) +# 'T3B': 'F1' # (VK not mappable) +# 'T3C': 'F2' # (VK not mappable) +# 'T3D': 'F3' # (VK not mappable) +# 'T3E': 'F4' # (VK not mappable) +# 'T3F': 'F5' # (VK not mappable) +# 'T40': 'F6' # (VK not mappable) +# 'T41': 'F7' # (VK not mappable) +# 'T42': 'F8' # (VK not mappable) +# 'T43': 'F9' # (VK not mappable) +# 'T44': 'F10' # (VK not mappable) +# 'T57': 'F11' # (VK not mappable) +# 'T58': 'F12' # (VK not mappable) +# 'X47': 'HOME' # (VK not mappable) +# 'X4F': 'END' # (VK not mappable) +# 'X49': 'PRIOR' # PageUp (VK not mappable) +# 'X51': 'NEXT' # PageDown (VK not mappable) +# 'T01': 'ESCAPE' # (VK not mappable) +# 'X48': 'UP' # (VK not mappable) +# 'X50': 'DOWN' # (VK not mappable) +# 'X4B': 'LEFT' # (VK not mappable) +# 'X4D': 'RIGHT' # (VK not mappable) +# 'X5D': 'APPS' # ContextMenu (VK not mappable) + +# Modifiers +# 'T2A': 'LSHIFT' # ShiftLeft (VK not mappable) +# 'T36': 'RSHIFT' # ShiftRight (VK not mappable) +# 'T3A': 'CAPITAL' # CapsLock (VK not mappable) +# 'T1D': 'LCONTROL' # ControlLeft (VK not mappable) +# 'X1D': 'RCONTROL' # ControlRight (VK not mappable) +# 'T38': 'LMENU' # AltLeft (VK not mappable) +# 'X38': 'RMENU' # AltRight (VK not mappable) +# 'X5B': 'LWIN' # MetaLeft (VK not mappable) +# 'X5C': 'RWIN' # MetaRight (VK not mappable) + +# Input method +# 'T7B': 'NONCONVERT' # Muhenkan (VK not mappable) +# 'T79': 'CONVERT' # Henkan (VK not mappable) + +# Miscellaneous +# 'X22': 'MEDIA_PLAY_PAUSE' # (VK not mappable) +# 'X32': 'BROWSER_HOME' # (VK not mappable) \ No newline at end of file diff --git a/kalamine/generators/ahk.py b/kalamine/generators/ahk.py index 1499e99..bd93025 100644 --- a/kalamine/generators/ahk.py +++ b/kalamine/generators/ahk.py @@ -118,7 +118,7 @@ def ahk_shortcuts(layout: "KeyboardLayout") -> List[str]: symbol = layer[key.id] if layout.qwerty_shortcuts: - symbol = qwerty_vk[scan_code] + symbol = qwerty_vk[key.windows] if symbol in enabled: output.append(f"{prefixes[i]}SC{scan_code}::Send {prefixes[i]}{symbol}") diff --git a/kalamine/generators/klc.py b/kalamine/generators/klc.py index b17320e..0c12fa1 100644 --- a/kalamine/generators/klc.py +++ b/kalamine/generators/klc.py @@ -11,7 +11,7 @@ """ import re -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, Dict, List if TYPE_CHECKING: from ..layout import KeyboardLayout @@ -47,38 +47,62 @@ def _get_langid(locale: str) -> str: oem_idx = 0 -def klc_virtual_key(layout: "KeyboardLayout", symbols: list, scan_code: str) -> str: - if scan_code == "56": +def check_virtual_key_symbols( + virtual_keys: Dict[str, List[str]], vk: str, symbols: List[str] +): + return (symbolsʹ := virtual_keys.get(vk)) is None or symbolsʹ == symbols + + +def klc_virtual_key( + layout: "KeyboardLayout", + symbols: list, + scan_code: str, + virtual_keys: Dict[str, List[str]], +) -> str: + if scan_code == "T56": # manage the ISO key (between shift and Z on ISO keyboards). # We're assuming that its scancode is always 56 # https://www.win.tue.nl/~aeb/linux/kbd/scancodes.html return "OEM_102" + # Check that the target VK is not already assigned to different symbols + def check(vk: str): + return check_virtual_key_symbols(virtual_keys, vk, symbols) + + # TODO: add support for Numpad keys base = _get_chr(symbols[0]) shifted = _get_chr(symbols[1]) # Can’t use `isdigit()` because `²` is a digit but we don't want that as a VK allowed_digit = "0123456789" # We assume that digit row always have digit as VK - if base in allowed_digit: + if base in allowed_digit and check(base): return base - elif shifted in allowed_digit: + elif shifted in allowed_digit and check(shifted): return shifted if shifted.isascii() and shifted.isalpha(): return shifted # VK_OEM_* case - if base == "," or shifted == ",": + if (base == "," or shifted == ",") and check("OEM_COMMA"): return "OEM_COMMA" - elif base == "." or shifted == ".": + elif (base == "." or shifted == ".") and check("OEM_PERIOD"): return "OEM_PERIOD" - elif base == "+" or shifted == "+": + elif (base == "+" or shifted == "+") and check("OEM_PLUS"): return "OEM_PLUS" - elif base == "-" or shifted == "-": + elif (base == "-" or shifted == "-") and check("OEM_MINUS"): return "OEM_MINUS" - elif base == " ": + elif base == " " and check("SPACE"): return "SPACE" + elif base == "\t" and check("TAB"): + return "TAB" + elif base == "\r" and check("RETURN"): + return "RETURN" + elif base == "\b" and check("BACK"): + return "BACK" + elif base == "\x1b" and check("ESCAPE"): + return "ESCAPE" else: MAX_OEM = 8 # We affect abitrary OEM VK and it will not match the one @@ -102,6 +126,7 @@ def klc_keymap(layout: "KeyboardLayout") -> List[str]: output = [] qwerty_vk = load_data("qwerty_vk") layers = (Layer.BASE, Layer.SHIFT, Layer.ALTGR, Layer.ALTGR_SHIFT) + virtual_keys: Dict[str, List[str]] = {} for key in KEYS.values(): if key.id in ["ae13", "ab11"]: # ABNT / JIS keys @@ -140,9 +165,15 @@ def klc_keymap(layout: "KeyboardLayout") -> List[str]: scan_code = key.windows[1:].lower() - virtual_key = qwerty_vk[scan_code] - if not layout.qwerty_shortcuts: - virtual_key = klc_virtual_key(layout, symbols, scan_code) + if ( + layout.qwerty_shortcuts + and key.windows in qwerty_vk + and check_virtual_key_symbols(virtual_keys, qwerty_vk[key.windows], symbols) + ): + virtual_key = qwerty_vk[key.windows] + else: + virtual_key = klc_virtual_key(layout, symbols, key.windows, virtual_keys) + virtual_keys[virtual_key] = symbols if layout.has_altgr: output.append( @@ -232,6 +263,7 @@ def c_keymap(layout: "KeyboardLayout") -> List[str]: supported_symbols = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" qwerty_vk = load_data("qwerty_vk") layers = (Layer.BASE, Layer.SHIFT, Layer.ALTGR, Layer.ALTGR_SHIFT) + virtual_keys: Dict[str, List[str]] = {} global oem_idx oem_idx = 0 # Python trick to do equivalent of C static variable @@ -277,11 +309,17 @@ def c_keymap(layout: "KeyboardLayout") -> List[str]: symbols.append("WCH_NONE") dead_symbols.append("WCH_NONE") - scan_code = key.windows[1:].lower() + # scan_code = key.windows[1:].lower() - virtual_key = qwerty_vk[scan_code] - if not layout.qwerty_shortcuts: - virtual_key = klc_virtual_key(layout, symbols, scan_code) + if ( + layout.qwerty_shortcuts + and key.windows in qwerty_vk + and check_virtual_key_symbols(virtual_keys, qwerty_vk[key.windows], symbols) + ): + virtual_key = qwerty_vk[key.windows] + else: + virtual_key = klc_virtual_key(layout, symbols, key.windows, virtual_keys) + virtual_keys[virtual_key] = symbols if len(virtual_key) == 1: virtual_key_id = f"'{virtual_key}'" From cc53a663378ef3b1fb666a4fe8aa046e61922fe9 Mon Sep 17 00:00:00 2001 From: Pierre Le Marre Date: Mon, 8 Apr 2024 11:41:25 +0200 Subject: [PATCH 09/10] test(klc): Add extra mapping test --- tests/test_serializer_klc.py | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/tests/test_serializer_klc.py b/tests/test_serializer_klc.py index ce916ae..8153686 100644 --- a/tests/test_serializer_klc.py +++ b/tests/test_serializer_klc.py @@ -80,8 +80,7 @@ def test_ansi_deadkeys(): def test_intl_keymap(): keymap = klc_keymap(LAYOUTS["intl"]) - assert len(keymap) == 49 - assert keymap == split( + keymap_ref = split( """ 02 1 0 1 0021 -1 -1 // 1 ! 03 2 0 2 0040 -1 -1 // 2 @ @@ -134,6 +133,32 @@ def test_intl_keymap(): 39 SPACE 0 0020 0020 -1 -1 // """ ) + assert len(keymap) == len(keymap_ref) + assert keymap == keymap_ref + + extraMapping = { + # NOTE: redefine level + "ae01": {"shift": "?"}, + # TODO + # # NOTE: test case variants and ODK alias + # "kppt": {"base": ",", "sHiFt": ";", "1dk": ".", "ODk_shiFt": ":"}, + # NOTE: clone level + "esc": {"base": "\x1b", "shift": "(ae11)"}, + # NOTE: clone key + "henk": "(lsgt)", + } + + extraSymbols = [ + "01 ESCAPE 0 001b 005f -1 -1 // \x1b _", + "79 OEM_8 0 005c 007c -1 -1 // \\ |", + ] + keymap_extra_ref = keymap_ref + extraSymbols + keymap_extra_ref[0] = "02 1 0 1 003f -1 -1 // 1 ?" + + layout = KeyboardLayout(get_layout_dict("intl", extraMapping)) + keymap = klc_keymap(layout) + assert len(keymap) == len(keymap_extra_ref) + assert keymap == keymap_extra_ref def test_intl_deadkeys(): From 9c76a5e2271e069217daf5ac851de7507889269e Mon Sep 17 00:00:00 2001 From: Pierre Le Marre Date: Wed, 10 Apr 2024 07:54:01 +0200 Subject: [PATCH 10/10] Review fixes --- kalamine/data/qwerty_vk.yaml | 216 +++++------ kalamine/data/scan_codes.yaml | 671 ++++++---------------------------- kalamine/key.py | 20 +- kalamine/utils.py | 4 + tests/test_serializer_klc.py | 10 +- tests/test_serializer_xkb.py | 11 +- 6 files changed, 246 insertions(+), 686 deletions(-) diff --git a/kalamine/data/qwerty_vk.yaml b/kalamine/data/qwerty_vk.yaml index 9fd2e2d..b114d9e 100644 --- a/kalamine/data/qwerty_vk.yaml +++ b/kalamine/data/qwerty_vk.yaml @@ -1,130 +1,130 @@ # Scancodes <-> Virtual Keys as in qwerty # this is to keep shortcuts at their qwerty location -'T39': 'SPACE' +T39: 'SPACE' # digits -'T02': '1' -'T03': '2' -'T04': '3' -'T05': '4' -'T06': '5' -'T07': '6' -'T08': '7' -'T09': '8' -'T0A': '9' -'T0B': '0' +T02: '1' +T03: '2' +T04: '3' +T05: '4' +T06: '5' +T07: '6' +T08: '7' +T09: '8' +T0A: '9' +T0B: '0' # letters, first row -'T10': 'Q' -'T11': 'W' -'T12': 'E' -'T13': 'R' -'T14': 'T' -'T15': 'Y' -'T16': 'U' -'T17': 'I' -'T18': 'O' -'T19': 'P' +T10: 'Q' +T11: 'W' +T12: 'E' +T13: 'R' +T14: 'T' +T15: 'Y' +T16: 'U' +T17: 'I' +T18: 'O' +T19: 'P' # letters, second row -'T1E': 'A' -'T1F': 'S' -'T20': 'D' -'T21': 'F' -'T22': 'G' -'T23': 'H' -'T24': 'J' -'T25': 'K' -'T26': 'L' -'T27': 'OEM_1' +T1E: 'A' +T1F: 'S' +T20: 'D' +T21: 'F' +T22: 'G' +T23: 'H' +T24: 'J' +T25: 'K' +T26: 'L' +T27: 'OEM_1' # letters, third row -'T2C': 'Z' -'T2D': 'X' -'T2E': 'C' -'T2F': 'V' -'T30': 'B' -'T31': 'N' -'T32': 'M' -'T33': 'OEM_COMMA' -'T34': 'OEM_PERIOD' -'T35': 'OEM_2' +T2C: 'Z' +T2D: 'X' +T2E: 'C' +T2F: 'V' +T30: 'B' +T31: 'N' +T32: 'M' +T33: 'OEM_COMMA' +T34: 'OEM_PERIOD' +T35: 'OEM_2' # pinky keys -'T29': 'OEM_3' -'T0C': 'OEM_MINUS' -'T0D': 'OEM_PLUS' -'T1A': 'OEM_4' -'T1B': 'OEM_6' -'T28': 'OEM_7' -'T2B': 'OEM_5' -'T56': 'OEM_102' +T29: 'OEM_3' +T0C: 'OEM_MINUS' +T0D: 'OEM_PLUS' +T1A: 'OEM_4' +T1B: 'OEM_6' +T28: 'OEM_7' +T2B: 'OEM_5' +T56: 'OEM_102' # Numpad -'T52': 'NUMPAD0' -'T4F': 'NUMPAD1' -'T50': 'NUMPAD2' -'T51': 'NUMPAD3' -'T4B': 'NUMPAD4' -'T4C': 'NUMPAD5' -'T4D': 'NUMPAD6' -'T47': 'NUMPAD7' -'T48': 'NUMPAD8' -'T49': 'NUMPAD9' -'T37': 'MULTIPLY' -'T4E': 'ADD' -'T4A': 'SUBTRACT' -'X35': 'DIVIDE' -'T53': 'DECIMAL' -# 'X1C': '' # NumpadEnter (maps to Return) -# 'T59': 'VK_CLEAR' # NumpadEqual (VK not mappable) -'T7E': 'VK_ABNT_C2' # NumadComma -# 'T45': 'NUMLOCK' # (VK not mappable) +T52: 'NUMPAD0' +T4F: 'NUMPAD1' +T50: 'NUMPAD2' +T51: 'NUMPAD3' +T4B: 'NUMPAD4' +T4C: 'NUMPAD5' +T4D: 'NUMPAD6' +T47: 'NUMPAD7' +T48: 'NUMPAD8' +T49: 'NUMPAD9' +T37: 'MULTIPLY' +T4E: 'ADD' +T4A: 'SUBTRACT' +X35: 'DIVIDE' +T53: 'DECIMAL' +# X1C: '' # NumpadEnter (maps to Return) +# T59: 'VK_CLEAR' # NumpadEqual (VK not mappable) +T7E: 'VK_ABNT_C2' # NumadComma +# T45: 'NUMLOCK' # (VK not mappable) # System -'T0F': 'TAB' -'T1C': 'RETURN' -'T0E': 'BACK' # Backspace -# 'X52': 'INSERT' # (VK not mappable) -# 'X53': 'DELETE' # (VK not mappable) -# 'T3B': 'F1' # (VK not mappable) -# 'T3C': 'F2' # (VK not mappable) -# 'T3D': 'F3' # (VK not mappable) -# 'T3E': 'F4' # (VK not mappable) -# 'T3F': 'F5' # (VK not mappable) -# 'T40': 'F6' # (VK not mappable) -# 'T41': 'F7' # (VK not mappable) -# 'T42': 'F8' # (VK not mappable) -# 'T43': 'F9' # (VK not mappable) -# 'T44': 'F10' # (VK not mappable) -# 'T57': 'F11' # (VK not mappable) -# 'T58': 'F12' # (VK not mappable) -# 'X47': 'HOME' # (VK not mappable) -# 'X4F': 'END' # (VK not mappable) -# 'X49': 'PRIOR' # PageUp (VK not mappable) -# 'X51': 'NEXT' # PageDown (VK not mappable) -# 'T01': 'ESCAPE' # (VK not mappable) -# 'X48': 'UP' # (VK not mappable) -# 'X50': 'DOWN' # (VK not mappable) -# 'X4B': 'LEFT' # (VK not mappable) -# 'X4D': 'RIGHT' # (VK not mappable) -# 'X5D': 'APPS' # ContextMenu (VK not mappable) +T0F: 'TAB' +T1C: 'RETURN' +T0E: 'BACK' # Backspace +# X52: 'INSERT' # (VK not mappable) +# X53: 'DELETE' # (VK not mappable) +# T3B: 'F1' # (VK not mappable) +# T3C: 'F2' # (VK not mappable) +# T3D: 'F3' # (VK not mappable) +# T3E: 'F4' # (VK not mappable) +# T3F: 'F5' # (VK not mappable) +# T40: 'F6' # (VK not mappable) +# T41: 'F7' # (VK not mappable) +# T42: 'F8' # (VK not mappable) +# T43: 'F9' # (VK not mappable) +# T44: 'F10' # (VK not mappable) +# T57: 'F11' # (VK not mappable) +# T58: 'F12' # (VK not mappable) +# X47: 'HOME' # (VK not mappable) +# X4F: 'END' # (VK not mappable) +# X49: 'PRIOR' # PageUp (VK not mappable) +# X51: 'NEXT' # PageDown (VK not mappable) +# T01: 'ESCAPE' # (VK not mappable) +# X48: 'UP' # (VK not mappable) +# X50: 'DOWN' # (VK not mappable) +# X4B: 'LEFT' # (VK not mappable) +# X4D: 'RIGHT' # (VK not mappable) +# X5D: 'APPS' # ContextMenu (VK not mappable) # Modifiers -# 'T2A': 'LSHIFT' # ShiftLeft (VK not mappable) -# 'T36': 'RSHIFT' # ShiftRight (VK not mappable) -# 'T3A': 'CAPITAL' # CapsLock (VK not mappable) -# 'T1D': 'LCONTROL' # ControlLeft (VK not mappable) -# 'X1D': 'RCONTROL' # ControlRight (VK not mappable) -# 'T38': 'LMENU' # AltLeft (VK not mappable) -# 'X38': 'RMENU' # AltRight (VK not mappable) -# 'X5B': 'LWIN' # MetaLeft (VK not mappable) -# 'X5C': 'RWIN' # MetaRight (VK not mappable) +# T2A: 'LSHIFT' # ShiftLeft (VK not mappable) +# T36: 'RSHIFT' # ShiftRight (VK not mappable) +# T3A: 'CAPITAL' # CapsLock (VK not mappable) +# T1D: 'LCONTROL' # ControlLeft (VK not mappable) +# X1D: 'RCONTROL' # ControlRight (VK not mappable) +# T38: 'LMENU' # AltLeft (VK not mappable) +# X38: 'RMENU' # AltRight (VK not mappable) +# X5B: 'LWIN' # MetaLeft (VK not mappable) +# X5C: 'RWIN' # MetaRight (VK not mappable) # Input method -# 'T7B': 'NONCONVERT' # Muhenkan (VK not mappable) -# 'T79': 'CONVERT' # Henkan (VK not mappable) +# T7B: 'NONCONVERT' # Muhenkan (VK not mappable) +# T79: 'CONVERT' # Henkan (VK not mappable) # Miscellaneous -# 'X22': 'MEDIA_PLAY_PAUSE' # (VK not mappable) -# 'X32': 'BROWSER_HOME' # (VK not mappable) \ No newline at end of file +# X22: 'MEDIA_PLAY_PAUSE' # (VK not mappable) +# X32: 'BROWSER_HOME' # (VK not mappable) \ No newline at end of file diff --git a/kalamine/data/scan_codes.yaml b/kalamine/data/scan_codes.yaml index 98a797e..a24ded9 100644 --- a/kalamine/data/scan_codes.yaml +++ b/kalamine/data/scan_codes.yaml @@ -1,572 +1,125 @@ -digits: -- xkb: "ae01" - web: "Digit1" - windows: "T02" - macos: "18" - hand: null -- xkb: "ae02" - web: "Digit2" - windows: "T03" - macos: "19" - hand: null -- xkb: "ae03" - web: "Digit3" - windows: "T04" - macos: "20" - hand: null -- xkb: "ae04" - web: "Digit4" - windows: "T05" - macos: "21" - hand: null -- xkb: "ae05" - web: "Digit5" - windows: "T06" - macos: "23" - hand: null -- xkb: "ae06" - web: "Digit6" - windows: "T07" - macos: "22" - hand: null -- xkb: "ae07" - web: "Digit7" - windows: "T08" - macos: "26" - hand: null -- xkb: "ae08" - web: "Digit8" - windows: "T09" - macos: "28" - hand: null -- xkb: "ae09" - web: "Digit9" - windows: "T0A" - macos: "25" - hand: null -- xkb: "ae10" - web: "Digit0" - windows: "T0B" - macos: "29" - hand: null +Digits: +- {web: Digit1, xkb: ae01, windows: T02, macos: 18, hand: null} +- {web: Digit2, xkb: ae02, windows: T03, macos: 19, hand: null} +- {web: Digit3, xkb: ae03, windows: T04, macos: 20, hand: null} +- {web: Digit4, xkb: ae04, windows: T05, macos: 21, hand: null} +- {web: Digit5, xkb: ae05, windows: T06, macos: 23, hand: null} +- {web: Digit6, xkb: ae06, windows: T07, macos: 22, hand: null} +- {web: Digit7, xkb: ae07, windows: T08, macos: 26, hand: null} +- {web: Digit8, xkb: ae08, windows: T09, macos: 28, hand: null} +- {web: Digit9, xkb: ae09, windows: T0A, macos: 25, hand: null} +- {web: Digit0, xkb: ae10, windows: T0B, macos: 29, hand: null} # Letters, first row Letters1: -- xkb: "ad01" - web: "KeyQ" - windows: "T10" - macos: "12" - hand: null -- xkb: "ad02" - web: "KeyW" - windows: "T11" - macos: "13" - hand: null -- xkb: "ad03" - web: "KeyE" - windows: "T12" - macos: "14" - hand: null -- xkb: "ad04" - web: "KeyR" - windows: "T13" - macos: "15" - hand: null -- xkb: "ad05" - web: "KeyT" - windows: "T14" - macos: "17" - hand: null -- xkb: "ad06" - web: "KeyY" - windows: "T15" - macos: "16" - hand: null -- xkb: "ad07" - web: "KeyU" - windows: "T16" - macos: "32" - hand: null -- xkb: "ad08" - web: "KeyI" - windows: "T17" - macos: "34" - hand: null -- xkb: "ad09" - web: "KeyO" - windows: "T18" - macos: "31" - hand: null -- xkb: "ad10" - web: "KeyP" - windows: "T19" - macos: "35" - hand: null +- {web: KeyQ, xkb: ad01, windows: T10, macos: 12, hand: null} +- {web: KeyW, xkb: ad02, windows: T11, macos: 13, hand: null} +- {web: KeyE, xkb: ad03, windows: T12, macos: 14, hand: null} +- {web: KeyR, xkb: ad04, windows: T13, macos: 15, hand: null} +- {web: KeyT, xkb: ad05, windows: T14, macos: 17, hand: null} +- {web: KeyY, xkb: ad06, windows: T15, macos: 16, hand: null} +- {web: KeyU, xkb: ad07, windows: T16, macos: 32, hand: null} +- {web: KeyI, xkb: ad08, windows: T17, macos: 34, hand: null} +- {web: KeyO, xkb: ad09, windows: T18, macos: 31, hand: null} +- {web: KeyP, xkb: ad10, windows: T19, macos: 35, hand: null} # Letters, second row Letters2: -- xkb: "ac01" - web: "KeyA" - windows: "T1E" - macos: "0" - hand: null -- xkb: "ac02" - web: "KeyS" - windows: "T1F" - macos: "1" - hand: null -- xkb: "ac03" - web: "KeyD" - windows: "T20" - macos: "2" - hand: null -- xkb: "ac04" - web: "KeyF" - windows: "T21" - macos: "3" - hand: null -- xkb: "ac05" - web: "KeyG" - windows: "T22" - macos: "5" - hand: null -- xkb: "ac06" - web: "KeyH" - windows: "T23" - macos: "4" - hand: null -- xkb: "ac07" - web: "KeyJ" - windows: "T24" - macos: "38" - hand: null -- xkb: "ac08" - web: "KeyK" - windows: "T25" - macos: "40" - hand: null -- xkb: "ac09" - web: "KeyL" - windows: "T26" - macos: "37" - hand: null -- xkb: "ac10" - web: "Semicolon" - windows: "T27" - macos: "41" - hand: null +- {web: KeyA, xkb: ac01, windows: T1E, macos: 0, hand: null} +- {web: KeyS, xkb: ac02, windows: T1F, macos: 1, hand: null} +- {web: KeyD, xkb: ac03, windows: T20, macos: 2, hand: null} +- {web: KeyF, xkb: ac04, windows: T21, macos: 3, hand: null} +- {web: KeyG, xkb: ac05, windows: T22, macos: 5, hand: null} +- {web: KeyH, xkb: ac06, windows: T23, macos: 4, hand: null} +- {web: KeyJ, xkb: ac07, windows: T24, macos: 38, hand: null} +- {web: KeyK, xkb: ac08, windows: T25, macos: 40, hand: null} +- {web: KeyL, xkb: ac09, windows: T26, macos: 37, hand: null} +- {web: Semicolon, xkb: ac10, windows: T27, macos: 41, hand: null} # Letters, third row Letters3: -- xkb: "ab01" - web: "KeyZ" - windows: "T2C" - macos: "6" - hand: null -- xkb: "ab02" - web: "KeyX" - windows: "T2D" - macos: "7" - hand: null -- xkb: "ab03" - web: "KeyC" - windows: "T2E" - macos: "8" - hand: null -- xkb: "ab04" - web: "KeyV" - windows: "T2F" - macos: "9" - hand: null -- xkb: "ab05" - web: "KeyB" - windows: "T30" - macos: "11" - hand: null -- xkb: "ab06" - web: "KeyN" - windows: "T31" - macos: "45" - hand: null -- xkb: "ab07" - web: "KeyM" - windows: "T32" - macos: "46" - hand: null -- xkb: "ab08" - web: "Comma" - windows: "T33" - macos: "43" - hand: null -- xkb: "ab09" - web: "Period" - windows: "T34" - macos: "47" - hand: null -- xkb: "ab10" - web: "Slash" - windows: "T35" - macos: "44" - hand: null -# Pinky keys +- {web: KeyZ, xkb: ab01, windows: T2C, macos: 6, hand: null} +- {web: KeyX, xkb: ab02, windows: T2D, macos: 7, hand: null} +- {web: KeyC, xkb: ab03, windows: T2E, macos: 8, hand: null} +- {web: KeyV, xkb: ab04, windows: T2F, macos: 9, hand: null} +- {web: KeyB, xkb: ab05, windows: T30, macos: 11, hand: null} +- {web: KeyN, xkb: ab06, windows: T31, macos: 45, hand: null} +- {web: KeyM, xkb: ab07, windows: T32, macos: 46, hand: null} +- {web: Comma, xkb: ab08, windows: T33, macos: 43, hand: null} +- {web: Period, xkb: ab09, windows: T34, macos: 47, hand: null} +- {web: Slash, xkb: ab10, windows: T35, macos: 44, hand: null} PinkyKeys: -- xkb: "ae11" - web: "Minus" - windows: "T0C" - macos: "27" - hand: null -- xkb: "ae12" - web: "Equal" - windows: "T0D" - macos: "24" - hand: null -- xkb: "ae13" - web: "IntlYen" - windows: "T0D" - macos: "42" - hand: null -- xkb: "ad11" - web: "BracketLeft" - windows: "T1A" - macos: "33" - hand: null -- xkb: "ad12" - web: "BracketRight" - windows: "T1B" - macos: "30" - hand: null -- xkb: "ac11" - web: "Quote" - windows: "T28" - macos: "39" - hand: null -- xkb: "ab11" - web: "IntlRo" - windows: "T28" - macos: "39" - hand: null -- xkb: "tlde" - web: "Backquote" - windows: "T29" - macos: "50" - hand: null -- xkb: "bksl" - web: "Backslash" - windows: "T2B" - macos: "42" - hand: null -- xkb: "lsgt" - web: "IntlBackslash" - windows: "T56" - macos: "10" - hand: null -# Space bar row +- {web: Minus, xkb: ae11, windows: T0C, macos: 27, hand: null} +- {web: Equal, xkb: ae12, windows: T0D, macos: 24, hand: null} +- {web: IntlYen, xkb: ae13, windows: T0D, macos: 42, hand: null} +- {web: BracketLeft, xkb: ad11, windows: T1A, macos: 33, hand: null} +- {web: BracketRight, xkb: ad12, windows: T1B, macos: 30, hand: null} +- {web: Quote, xkb: ac11, windows: T28, macos: 39, hand: null} +- {web: IntlRo, xkb: ab11, windows: T28, macos: 39, hand: null} +- {web: Backquote, xkb: tlde, windows: T29, macos: 50, hand: null} +- {web: Backslash, xkb: bksl, windows: T2B, macos: 42, hand: null} +- {web: IntlBackslash, xkb: lsgt, windows: T56, macos: 10, hand: null} SpaceBar: -- xkb: "spce" - web: "Space" - windows: "T39" - macos: "49" - hand: null +- {web: Space, xkb: spce, windows: T39, macos: 49, hand: null} Numpad: -- xkb: kp0 - web: Numpad0 - windows: T52 - macos: null - hand: null -- xkb: kp1 - web: Numpad1 - windows: T4F - macos: null - hand: null -- xkb: kp2 - web: Numpad2 - windows: T50 - macos: null - hand: null -- xkb: kp3 - web: Numpad3 - windows: T51 - macos: null - hand: null -- xkb: kp4 - web: Numpad4 - windows: T4B - macos: null - hand: null -- xkb: kp5 - web: Numpad5 - windows: T4C - macos: null - hand: null -- xkb: kp6 - web: Numpad6 - windows: T4D - macos: null - hand: null -- xkb: kp7 - web: Numpad7 - windows: T47 - macos: null - hand: null -- xkb: kp8 - web: Numpad8 - windows: T48 - macos: null - hand: null -- xkb: kp9 - web: Numpad9 - windows: T49 - macos: null - hand: null -- xkb: kpen - web: NumpadEnter - windows: X1C - macos: null - hand: null -- xkb: kpeq - web: NumpadEqual - windows: T59 - macos: null - hand: null -- xkb: kpdl - web: NumpadDecimal - windows: T53 - macos: null - hand: null -- xkb: kppt - web: NumpadComma - windows: T7E - macos: null - hand: null -- xkb: kpdv - web: NumpadDivide - windows: X35 - macos: null - hand: null -- xkb: kpmu - web: NumpadMultiply - windows: T37 - macos: null - hand: null -- xkb: kpsu - web: NumpadSubtract - windows: T4A - macos: null - hand: null -- xkb: kpad - web: NumpadAdd - windows: T4E - macos: null - hand: null -- xkb: nmlk - web: NumLock - windows: T45 - macos: null - hand: null +- {web: Numpad0, xkb: kp0, windows: T52, macos: null, hand: null} +- {web: Numpad1, xkb: kp1, windows: T4F, macos: null, hand: null} +- {web: Numpad2, xkb: kp2, windows: T50, macos: null, hand: null} +- {web: Numpad3, xkb: kp3, windows: T51, macos: null, hand: null} +- {web: Numpad4, xkb: kp4, windows: T4B, macos: null, hand: null} +- {web: Numpad5, xkb: kp5, windows: T4C, macos: null, hand: null} +- {web: Numpad6, xkb: kp6, windows: T4D, macos: null, hand: null} +- {web: Numpad7, xkb: kp7, windows: T47, macos: null, hand: null} +- {web: Numpad8, xkb: kp8, windows: T48, macos: null, hand: null} +- {web: Numpad9, xkb: kp9, windows: T49, macos: null, hand: null} +- {web: NumpadEnter, xkb: kpen, windows: X1C, macos: null, hand: null} +- {web: NumpadEqual, xkb: kpeq, windows: T59, macos: null, hand: null} +- {web: NumpadDecimal, xkb: kpdl, windows: T53, macos: null, hand: null} +- {web: NumpadComma, xkb: kppt, windows: T7E, macos: null, hand: null} +- {web: NumpadDivide, xkb: kpdv, windows: X35, macos: null, hand: null} +- {web: NumpadMultipl, xkb: kpmu, windows: T37, macos: null, hand: null} +- {web: NumpadSubtrac, xkb: kpsu, windows: T4A, macos: null, hand: null} +- {web: NumpadAdd, xkb: kpad, windows: T4E, macos: null, hand: null} +- {web: NumLock, xkb: nmlk, windows: T45, macos: null, hand: null} System: -- xkb: "tab" - web: Tab - windows: T0F - macos: null - hand: Left -- xkb: "rtrn" - web: Enter - windows: T1C - macos: null - hand: null -- xkb: "bksp" - web: Backspace - windows: T0E - macos: null - hand: null -- xkb: "dele" - web: Delete - windows: X53 - macos: null - hand: null -- xkb: "esc" - web: Escape - windows: T01 - macos: null - hand: null -- xkb: "menu" - web: ContextMenu - windows: X5D - macos: null - hand: null -- xkb: "home" - web: Home - windows: X47 - macos: null - hand: null -- xkb: "end" - web: End - windows: X4F - macos: null - hand: null -- xkb: "up" - web: ArrowUp - windows: X48 - macos: null - hand: null -- xkb: "down" - web: ArrowDown - windows: X50 - macos: null - hand: null -- xkb: "left" - web: ArrowLeft - windows: X4B - macos: null - hand: null -- xkb: "rght" - web: ArrowDown - windows: X50 - macos: null - hand: null -- xkb: "pgup" - web: PageUp - windows: X49 - macos: null - hand: null -- xkb: "pgdn" - web: PageDown - windows: X51 - macos: null - hand: null -- xkb: "fk01" - web: F1 - windows: T3B - macos: null - hand: null -- xkb: "fk02" - web: F2 - windows: T3C - macos: null - hand: null -- xkb: "fk03" - web: F3 - windows: T3D - macos: null - hand: null -- xkb: "fk04" - web: F4 - windows: T3E - macos: null - hand: null -- xkb: "fk05" - web: F5 - windows: T3F - macos: null - hand: null -- xkb: "fk06" - web: F6 - windows: T40 - macos: null - hand: null -- xkb: "fk07" - web: F7 - windows: T41 - macos: null - hand: null -- xkb: "fk08" - web: F8 - windows: T42 - macos: null - hand: null -- xkb: "fk09" - web: F9 - windows: T43 - macos: null - hand: null -- xkb: "fk10" - web: F10 - windows: T44 - macos: null - hand: null -- xkb: "fk11" - web: F11 - windows: T57 - macos: null - hand: null -- xkb: "fk12" - web: F12 - windows: T58 - macos: null - hand: null +- {web: Tab, xkb: tab, windows: T0F, macos: null, hand: Left} +- {web: Enter, xkb: rtrn, windows: T1C, macos: null, hand: null} +- {web: Backspace, xkb: bksp, windows: T0E, macos: null, hand: null} +- {web: Delete, xkb: dele, windows: X53, macos: null, hand: null} +- {web: Escape, xkb: esc, windows: T01, macos: null, hand: null} +- {web: ContextMenu, xkb: menu, windows: X5D, macos: null, hand: null} +- {web: Home, xkb: home, windows: X47, macos: null, hand: null} +- {web: End, xkb: end, windows: X4F, macos: null, hand: null} +- {web: ArrowUp, xkb: up, windows: X48, macos: null, hand: null} +- {web: ArrowDown, xkb: down, windows: X50, macos: null, hand: null} +- {web: ArrowLeft, xkb: left, windows: X4B, macos: null, hand: null} +- {web: ArrowDown, xkb: rght, windows: X50, macos: null, hand: null} +- {web: PageUp, xkb: pgup, windows: X49, macos: null, hand: null} +- {web: PageDown, xkb: pgdn, windows: X51, macos: null, hand: null} +- {web: F1, xkb: fk01, windows: T3B, macos: null, hand: null} +- {web: F2, xkb: fk02, windows: T3C, macos: null, hand: null} +- {web: F3, xkb: fk03, windows: T3D, macos: null, hand: null} +- {web: F4, xkb: fk04, windows: T3E, macos: null, hand: null} +- {web: F5, xkb: fk05, windows: T3F, macos: null, hand: null} +- {web: F6, xkb: fk06, windows: T40, macos: null, hand: null} +- {web: F7, xkb: fk07, windows: T41, macos: null, hand: null} +- {web: F8, xkb: fk08, windows: T42, macos: null, hand: null} +- {web: F9, xkb: fk09, windows: T43, macos: null, hand: null} +- {web: F10, xkb: fk10, windows: T44, macos: null, hand: null} +- {web: F11, xkb: fk11, windows: T57, macos: null, hand: null} +- {web: F12, xkb: fk12, windows: T58, macos: null, hand: null} Modifiers: -- xkb: "lfsh" - web: ShiftLeft - windows: T2A - macos: null - hand: Left -- xkb: "rtsh" - web: ShiftRight - windows: T36 - macos: null - hand: Right -- xkb: "caps" - web: CapsLock - windows: T3A - macos: null - hand: Left -- xkb: "lalt" - web: "AltLeft" - windows: T38 - macos: null - hand: Left -- xkb: "ralt" - web: "AltRight" - windows: X38 - macos: null - hand: Right -- xkb: "lctl" - web: ControlLeft - windows: T1D - macos: null - hand: Left -- xkb: "rctl" - web: ControlRight - windows: X1D - macos: null - hand: Right -- xkb: lwin - web: MetaLeft - windows: X5B - macos: null - hand: Left -- xkb: rwin - web: MetaRight - windows: X5C - macos: null - hand: Right +- {web: ShiftLeft, xkb: lfsh, windows: T2A, macos: null, hand: Left} +- {web: ShiftRight, xkb: rtsh, windows: T36, macos: null, hand: Right} +- {web: CapsLock, xkb: caps, windows: T3A, macos: null, hand: Left} +- {web: AltLeft, xkb: lalt, windows: T38, macos: null, hand: Left} +- {web: AltRight, xkb: ralt, windows: X38, macos: null, hand: Right} +- {web: ControlLeft, xkb: lctl, windows: T1D, macos: null, hand: Left} +- {web: ControlRight, xkb: rctl, windows: X1D, macos: null, hand: Right} +- {web: MetaLeft, xkb: lwin, windows: X5B, macos: null, hand: Left} +- {web: MetaRight, xkb: rwin, windows: X5C, macos: null, hand: Right} InputMethod: -- xkb: "muhe" - web: "NonConvert" - windows: T7B - macos: null - hand: Left -- xkb: "henk" - web: "Convert" - windows: T79 - macos: null - hand: Right -# Miscellaneous +- {web: NonConvert, xkb: muhe, windows: T7B, macos: null, hand: Left} +- {web: Convert, xkb: henk, windows: T79, macos: null, hand: Right} Miscellaneous: -- xkb: "i148" # Calc - web: null - windows: null - macos: null - hand: null -- xkb: "i163" # Mail - web: null - windows: null - macos: null - hand: null -- xkb: "i172" # Play/Pause - web: MediaPlayPause - windows: X22 - macos: null - hand: null -- xkb: "i180" # Home page - web: BrowserHome - windows: X32 - macos: null - hand: null +- {web: LaunchApp2, xkb: i148, windows: null, macos: null, hand: null} # Calculator +- {web: LaunchMail, xkb: i163, windows: null, macos: null, hand: null} +- {web: MediaPlayPause, xkb: i172, windows: X22, macos: null, hand: null} +- {web: BrowserHome, xkb: i180, windows: X32, macos: null, hand: null} diff --git a/kalamine/key.py b/kalamine/key.py index 1d1ab1d..6b1ce7e 100644 --- a/kalamine/key.py +++ b/kalamine/key.py @@ -70,16 +70,8 @@ class Key: windows: Optional[str] = None macos: Optional[str] = None hand: Optional[Hand] = None - category: KeyCategory = KeyCategory.Miscellaneous "Usual hand on standard (ISO, etc.) keyboard" - - @classmethod - def load_data(cls, data: Dict[str, Any]) -> Dict[str, "Key"]: - return { - key.xkb: key - for category, keys in data.items() - for key in (cls.parse(category=category, **entry) for entry in keys) - } + category: KeyCategory = KeyCategory.Miscellaneous @classmethod def parse( @@ -100,6 +92,14 @@ def parse( hand=Hand.parse(hand) if hand else None, ) + @classmethod + def parse_keys(cls, data: Dict[str, Any]) -> Dict[str, "Key"]: + return { + key.xkb: key + for category, keys in data.items() + for key in (cls.parse(category=category, **entry) for entry in keys) + } + @property def id(self) -> str: return self.xkb @@ -109,4 +109,4 @@ def alphanum(self) -> bool: return bool(self.category & KeyCategory.AlphaNum) -KEYS = Key.load_data(load_data("scan_codes")) +KEYS = Key.parse_keys(load_data("scan_codes")) diff --git a/kalamine/utils.py b/kalamine/utils.py index e02c035..ee49dfc 100644 --- a/kalamine/utils.py +++ b/kalamine/utils.py @@ -50,14 +50,18 @@ class Layer(IntEnum): @classmethod def parse(cls, raw: str) -> Optional["Layer"]: rawʹ = raw.casefold() + # Parse alternate names if rawʹ == "1dk": return cls(cls.ODK) elif rawʹ == "1dk_shift": return cls(cls.ODK_SHIFT) + # Parse native values else: for layer in cls: + # Parse native names if rawʹ == layer.name.casefold(): return layer + # Parse numeric values try: if int(raw, base=10) == layer.value: return layer diff --git a/tests/test_serializer_klc.py b/tests/test_serializer_klc.py index 8153686..4691208 100644 --- a/tests/test_serializer_klc.py +++ b/tests/test_serializer_klc.py @@ -136,18 +136,20 @@ def test_intl_keymap(): assert len(keymap) == len(keymap_ref) assert keymap == keymap_ref + # Extra mapping section extraMapping = { - # NOTE: redefine level + # Redefine level of key previously defined in ASCII art "ae01": {"shift": "?"}, # TODO - # # NOTE: test case variants and ODK alias + # Test layer case variants and ODK alias # "kppt": {"base": ",", "sHiFt": ";", "1dk": ".", "ODk_shiFt": ":"}, - # NOTE: clone level + # Clone level of another key previously defined in ASCII art "esc": {"base": "\x1b", "shift": "(ae11)"}, - # NOTE: clone key + # Clone whole key previously defined "henk": "(lsgt)", } + # Resulting klc keymap extraSymbols = [ "01 ESCAPE 0 001b 005f -1 -1 // \x1b _", "79 OEM_8 0 005c 007c -1 -1 // \\ |", diff --git a/tests/test_serializer_xkb.py b/tests/test_serializer_xkb.py index febf4f5..69c2fba 100644 --- a/tests/test_serializer_xkb.py +++ b/tests/test_serializer_xkb.py @@ -159,17 +159,19 @@ def test_intl(): """ ) + # Extra mapping section extraMapping = { - # NOTE: redefine level + # Redefine level of key previously defined in ASCII art "ae01": {"shift": "?"}, - # NOTE: test case variants and ODK alias + # Test layer case variants and ODK alias "menu": {"base": "a", "sHiFt": "A", "1dk": "æ", "ODk_shiFt": "Æ"}, - # NOTE: clone level + # Clone level of another key previously defined in ASCII art "esc": {"base": "(ae11)"}, - # NOTE: clone key + # Clone whole key previously defined "i172": "(lsgt)", } + # Extra mapping keymap extraSymbols = [ "", "// System", @@ -179,7 +181,6 @@ def test_intl(): "// Miscellaneous", "key {[ backslash , bar , VoidSymbol , VoidSymbol ]}; // \\ |", ] - extraExpected = expected + extraSymbols extraExpected[1] = ( "key {[ 1 , question , VoidSymbol , VoidSymbol ]}; // 1 ?"