diff options
author | Michael Brown <mcb30@ipxe.org> | 2022-02-14 13:22:48 +0000 |
---|---|---|
committer | Michael Brown <mcb30@ipxe.org> | 2022-02-15 11:58:47 +0000 |
commit | 871dd236d4aff66e871c25addcf522fe75a4ccd7 (patch) | |
tree | 92972b82b4469c1802a7b335d439e04aa0f8e604 /src/util/genkeymap.py | |
parent | 1150321595c44acfac2b2c56590d4d7f1d2ad70c (diff) | |
download | ipxe-871dd236d4aff66e871c25addcf522fe75a4ccd7.zip ipxe-871dd236d4aff66e871c25addcf522fe75a4ccd7.tar.gz ipxe-871dd236d4aff66e871c25addcf522fe75a4ccd7.tar.bz2 |
[console] Allow for named keyboard mappings
Separate the concept of a keyboard mapping from a list of remapped
keys, to allow for the possibility of supporting multiple keyboard
mappings at runtime.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Diffstat (limited to 'src/util/genkeymap.py')
-rwxr-xr-x | src/util/genkeymap.py | 140 |
1 files changed, 77 insertions, 63 deletions
diff --git a/src/util/genkeymap.py b/src/util/genkeymap.py index 081e314..d38552e 100755 --- a/src/util/genkeymap.py +++ b/src/util/genkeymap.py @@ -79,7 +79,7 @@ class KeyModifiers(Flag): return 3 + bin(self.value).count('1') -@dataclass +@dataclass(frozen=True) class Key: """A single key definition""" @@ -120,8 +120,8 @@ class Key: return None -class KeyMapping(UserDict[KeyModifiers, Sequence[Key]]): - """A keyboard mapping""" +class KeyLayout(UserDict[KeyModifiers, Sequence[Key]]): + """A keyboard layout""" BKEYMAP_MAGIC: ClassVar[bytes] = b'bkeymap' """Magic signature for output produced by 'loadkeys -b'""" @@ -163,16 +163,16 @@ class KeyMapping(UserDict[KeyModifiers, Sequence[Key]]): @property def unshifted(self): - """Basic unshifted key mapping""" + """Basic unshifted keyboard layout""" return self[KeyModifiers.NONE] @property def shifted(self): - """Basic shifted key mapping""" + """Basic shifted keyboard layout""" return self[KeyModifiers.SHIFT] @classmethod - def load(cls, name: str) -> KeyMapping: + def load(cls, name: str) -> KeyLayout: """Load keymap using 'loadkeys -b'""" bkeymap = subprocess.check_output(["loadkeys", "-u", "-b", name]) if not bkeymap.startswith(cls.BKEYMAP_MAGIC): @@ -181,21 +181,21 @@ class KeyMapping(UserDict[KeyModifiers, Sequence[Key]]): included = bkeymap[:cls.MAX_NR_KEYMAPS] if len(included) != cls.MAX_NR_KEYMAPS: raise ValueError("Invalid bkeymap inclusion list") - keymaps = bkeymap[cls.MAX_NR_KEYMAPS:] + bkeymap = bkeymap[cls.MAX_NR_KEYMAPS:] keys = {} for modifiers in map(KeyModifiers, range(cls.MAX_NR_KEYMAPS)): if included[modifiers.value]: fmt = Struct('<%dH' % cls.NR_KEYS) - keymap = keymaps[:fmt.size] - if len(keymap) != fmt.size: + bkeylist = bkeymap[:fmt.size] + if len(bkeylist) != fmt.size: raise ValueError("Invalid bkeymap map %#x" % modifiers.value) keys[modifiers] = [ Key(modifiers=modifiers, keycode=keycode, keysym=keysym) - for keycode, keysym in enumerate(fmt.unpack(keymap)) + for keycode, keysym in enumerate(fmt.unpack(bkeylist)) ] - keymaps = keymaps[len(keymap):] - if keymaps: + bkeymap = bkeymap[len(bkeylist):] + if bkeymap: raise ValueError("Trailing bkeymap data") for modifiers, fixups in cls.FIXUPS.get(name, {}).items(): for keycode, keysym in fixups: @@ -218,8 +218,8 @@ class KeyMapping(UserDict[KeyModifiers, Sequence[Key]]): } -class BiosKeyMapping(KeyMapping): - """Keyboard mapping as used by the BIOS +class BiosKeyLayout(KeyLayout): + """Keyboard layout as used by the BIOS To allow for remappings of the somewhat interesting key 86, we arrange for our keyboard drivers to generate this key as "\\|" @@ -247,22 +247,56 @@ class BiosKeyMapping(KeyMapping): return inverse +class KeymapKeys(UserDict[str, str]): + """An ASCII character remapping""" + + @classmethod + def ascii_name(cls, char: str) -> str: + """ASCII character name""" + if char == '\\': + name = "'\\\\'" + elif char == '\'': + name = "'\\\''" + elif ord(char) & BiosKeyLayout.KEY_PSEUDO: + name = "Pseudo-%s" % cls.ascii_name( + chr(ord(char) & ~BiosKeyLayout.KEY_PSEUDO) + ) + elif char.isprintable(): + name = "'%s'" % char + elif ord(char) <= 0x1a: + name = "Ctrl-%c" % (ord(char) + 0x40) + else: + name = "0x%02x" % ord(char) + return name + + @property + def code(self): + """Generated source code for C array""" + return '{\n' + ''.join( + '\t{ 0x%02x, 0x%02x },\t/* %s => %s */\n' % ( + ord(source), ord(target), + self.ascii_name(source), self.ascii_name(target) + ) + for source, target in self.items() + ) + '\t{ 0, 0 }\n}' + + @dataclass -class KeyRemapping: - """A keyboard remapping""" +class Keymap: + """An iPXE keyboard mapping""" name: str """Mapping name""" - source: KeyMapping - """Source keyboard mapping""" + source: KeyLayout + """Source keyboard layout""" - target: KeyMapping - """Target keyboard mapping""" + target: KeyLayout + """Target keyboard layout""" @property - def ascii(self) -> MutableMapping[str, str]: - """Remapped ASCII key table""" + def basic(self) -> KeymapKeys: + """Basic remapping table""" # Construct raw mapping from source ASCII to target ASCII raw = {source: self.target[key.modifiers][key.keycode].ascii for source, key in self.source.inverse.items()} @@ -273,7 +307,7 @@ class KeyRemapping: if target and ord(source) != 0x7f and ord(target) != 0x7f - and ord(source) & ~BiosKeyMapping.KEY_PSEUDO != ord(target)} + and ord(source) & ~BiosKeyLayout.KEY_PSEUDO != ord(target)} # Recursively delete any mappings that would produce # unreachable alphanumerics (e.g. the "il" keymap, which maps # away the whole lower-case alphabet) @@ -291,35 +325,17 @@ class KeyRemapping: if digits not in (shifted, unshifted): raise ValueError("Inconsistent numeric remapping %s / %s" % (unshifted, shifted)) - return dict(sorted(table.items())) + return KeymapKeys(dict(sorted(table.items()))) - @property - def cname(self) -> str: + def cname(self, suffix: str) -> str: """C variable name""" - return re.sub(r'\W', '_', self.name) + "_mapping" - - @classmethod - def ascii_name(cls, char: str) -> str: - """ASCII character name""" - if char == '\\': - name = "'\\\\'" - elif char == '\'': - name = "'\\\''" - elif ord(char) & BiosKeyMapping.KEY_PSEUDO: - name = "Pseudo-%s" % cls.ascii_name( - chr(ord(char) & ~BiosKeyMapping.KEY_PSEUDO) - ) - elif char.isprintable(): - name = "'%s'" % char - elif ord(char) <= 0x1a: - name = "Ctrl-%c" % (ord(char) + 0x40) - else: - name = "0x%02x" % ord(char) - return name + return re.sub(r'\W', '_', (self.name + '_' + suffix)) @property def code(self) -> str: """Generated source code""" + keymap_name = self.cname("keymap") + basic_name = self.cname("basic") code = textwrap.dedent(f""" /** @file * @@ -333,17 +349,15 @@ class KeyRemapping: #include <ipxe/keymap.h> - /** "{self.name}" keyboard mapping */ - struct key_mapping {self.cname}[] __keymap = {{ - """).lstrip() + ''.join( - '\t{ 0x%02x, 0x%02x },\t/* %s => %s */\n' % ( - ord(source), ord(target), - self.ascii_name(source), self.ascii_name(target) - ) - for source, target in self.ascii.items() - ) + textwrap.dedent(""" - }; - """).strip() + /** "{self.name}" basic remapping */ + static struct keymap_key {basic_name}[] = %s; + + /** "{self.name}" keyboard map */ + struct keymap {keymap_name} __keymap = {{ + \t.name = "{self.name}", + \t.basic = {basic_name}, + }}; + """).strip() % self.basic.code return code @@ -356,12 +370,12 @@ if __name__ == '__main__': parser.add_argument('layout', help="Target keyboard layout") args = parser.parse_args() - # Load source and target keymaps - source = BiosKeyMapping.load('us') - target = KeyMapping.load(args.layout) + # Load source and target keyboard layouts + source = BiosKeyLayout.load('us') + target = KeyLayout.load(args.layout) - # Construct remapping - remap = KeyRemapping(name=args.layout, source=source, target=target) + # Construct keyboard mapping + keymap = Keymap(name=args.layout, source=source, target=target) # Output generated code - print(remap.code) + print(keymap.code) |