aboutsummaryrefslogtreecommitdiff
path: root/src/util/genkeymap.py
diff options
context:
space:
mode:
authorMichael Brown <mcb30@ipxe.org>2022-02-14 13:22:48 +0000
committerMichael Brown <mcb30@ipxe.org>2022-02-15 11:58:47 +0000
commit871dd236d4aff66e871c25addcf522fe75a4ccd7 (patch)
tree92972b82b4469c1802a7b335d439e04aa0f8e604 /src/util/genkeymap.py
parent1150321595c44acfac2b2c56590d4d7f1d2ad70c (diff)
downloadipxe-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-xsrc/util/genkeymap.py140
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)