mirror of
https://github.com/ipxe/ipxe
synced 2026-02-09 15:21:29 +03:00
[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>
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user