]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[console] Update genkeymap to work with current databases
authorMichael Brown <mcb30@ipxe.org>
Sun, 6 Feb 2022 19:33:20 +0000 (19:33 +0000)
committerMichael Brown <mcb30@ipxe.org>
Thu, 10 Feb 2022 13:59:32 +0000 (13:59 +0000)
Rewrite genkeymap.pl in Python with added sanity checks, and update
the list of keyboard mappings to remove those no longer supported by
the underlying "loadkeys" tool.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
25 files changed:
src/Makefile
src/hci/keymap/keymap_al.c
src/hci/keymap/keymap_az.c
src/hci/keymap/keymap_cz.c
src/hci/keymap/keymap_de.c
src/hci/keymap/keymap_dk.c
src/hci/keymap/keymap_es.c
src/hci/keymap/keymap_et.c
src/hci/keymap/keymap_fi.c
src/hci/keymap/keymap_fr.c
src/hci/keymap/keymap_hu.c
src/hci/keymap/keymap_il.c
src/hci/keymap/keymap_it.c
src/hci/keymap/keymap_mt.c
src/hci/keymap/keymap_nl.c
src/hci/keymap/keymap_no-latin1.c
src/hci/keymap/keymap_no.c
src/hci/keymap/keymap_pt.c
src/hci/keymap/keymap_ru.c
src/hci/keymap/keymap_sr-latin.c [moved from src/hci/keymap/keymap_bg.c with 54% similarity]
src/hci/keymap/keymap_sr.c [deleted file]
src/hci/keymap/keymap_th.c [deleted file]
src/hci/keymap/keymap_wo.c [deleted file]
src/util/genkeymap.pl [deleted file]
src/util/genkeymap.py [new file with mode: 0755]

index 4c4abf1aa1170b0a383461620646dfb3c7a79d93..83642e7894aa1a1472bbcc591414eec25822a834 100644 (file)
@@ -50,7 +50,7 @@ ELF2EFI64     := ./util/elf2efi64
 EFIROM         := ./util/efirom
 EFIFATBIN      := ./util/efifatbin
 EINFO          := ./util/einfo
-GENKEYMAP      := ./util/genkeymap.pl
+GENKEYMAP      := ./util/genkeymap.py
 DOXYGEN                := doxygen
 LCAB           := lcab
 QEMUIMG                := qemu-img
index caf295e8eb6e214438e90fc8f37a9cbc94c76e1a..e4418361b03219946afdd4188808d1946ea17fad 100644 (file)
@@ -14,6 +14,7 @@ FILE_LICENCE ( PUBLIC_DOMAIN );
 struct key_mapping al_mapping[] __keymap = {
        { 0x19, 0x1a }, /* Ctrl-Y => Ctrl-Z */
        { 0x1a, 0x19 }, /* Ctrl-Z => Ctrl-Y */
+       { 0x1c, 0x1d }, /* 0x1c => 0x1d */
        { 0x22, 0x7b }, /* '"' => '{' */
        { 0x27, 0x5b }, /* '\'' => '[' */
        { 0x3c, 0x3b }, /* '<' => ';' */
index 27ce91e7d60e7372cb5191a10f048d8b2d434a44..525ab233614e11e8ad09ed173ba33a7073f8c618 100644 (file)
@@ -12,7 +12,7 @@ FILE_LICENCE ( PUBLIC_DOMAIN );
 
 /** "az" keyboard mapping */
 struct key_mapping az_mapping[] __keymap = {
-       { 0x23, 0x27 }, /* '#' => '\'' */
+       { 0x1e, 0x36 }, /* 0x1e => '6' */
        { 0x24, 0x3b }, /* '$' => ';' */
        { 0x26, 0x3f }, /* '&' => '?' */
        { 0x2f, 0x2e }, /* '/' => '.' */
index 9280f84fd85587b3c9426b7cf128d608e789f302..2b4a21592e5022774ae915506a90f206ed46856a 100644 (file)
@@ -12,16 +12,35 @@ FILE_LICENCE ( PUBLIC_DOMAIN );
 
 /** "cz" keyboard mapping */
 struct key_mapping cz_mapping[] __keymap = {
-       { 0x21, 0x2b }, /* '!' => '+' */
+       { 0x19, 0x1a }, /* Ctrl-Y => Ctrl-Z */
+       { 0x1a, 0x19 }, /* Ctrl-Z => Ctrl-Y */
+       { 0x1f, 0x1c }, /* 0x1f => 0x1c */
+       { 0x21, 0x31 }, /* '!' => '1' */
+       { 0x22, 0x21 }, /* '"' => '!' */
+       { 0x23, 0x33 }, /* '#' => '3' */
+       { 0x24, 0x34 }, /* '$' => '4' */
+       { 0x25, 0x35 }, /* '%' => '5' */
+       { 0x26, 0x37 }, /* '&' => '7' */
+       { 0x28, 0x39 }, /* '(' => '9' */
+       { 0x29, 0x30 }, /* ')' => '0' */
+       { 0x2a, 0x38 }, /* '*' => '8' */
        { 0x2d, 0x3d }, /* '-' => '=' */
        { 0x2f, 0x2d }, /* '/' => '-' */
        { 0x31, 0x2b }, /* '1' => '+' */
-       { 0x3c, 0x2c }, /* '<' => ',' */
-       { 0x3e, 0x2e }, /* '>' => '.' */
-       { 0x3f, 0x2d }, /* '?' => '-' */
+       { 0x3a, 0x22 }, /* ':' => '"' */
+       { 0x3c, 0x3f }, /* '<' => '?' */
+       { 0x3e, 0x3a }, /* '>' => ':' */
+       { 0x3f, 0x5f }, /* '?' => '_' */
+       { 0x40, 0x32 }, /* '@' => '2' */
+       { 0x59, 0x5a }, /* 'Y' => 'Z' */
+       { 0x5a, 0x59 }, /* 'Z' => 'Y' */
        { 0x5d, 0x29 }, /* ']' => ')' */
-       { 0x5f, 0x3d }, /* '_' => '=' */
+       { 0x5e, 0x36 }, /* '^' => '6' */
+       { 0x5f, 0x25 }, /* '_' => '%' */
        { 0x60, 0x3b }, /* '`' => ';' */
-       { 0x7d, 0x29 }, /* '}' => ')' */
-       { 0x7e, 0x3b }, /* '~' => ';' */
+       { 0x79, 0x7a }, /* 'y' => 'z' */
+       { 0x7a, 0x79 }, /* 'z' => 'y' */
+       { 0x7b, 0x2f }, /* '{' => '/' */
+       { 0x7c, 0x27 }, /* '|' => '\'' */
+       { 0x7d, 0x28 }, /* '}' => '(' */
 };
index ffcf912f11a953044010430f537c4e2ead1ec485..2559e1538127bc57de3ecdb2c9b3994f6bff9dde 100644 (file)
@@ -14,33 +14,26 @@ FILE_LICENCE ( PUBLIC_DOMAIN );
 struct key_mapping de_mapping[] __keymap = {
        { 0x19, 0x1a }, /* Ctrl-Y => Ctrl-Z */
        { 0x1a, 0x19 }, /* Ctrl-Z => Ctrl-Y */
-       { 0x22, 0x7d }, /* '"' => '}' */
+       { 0x1c, 0x23 }, /* 0x1c => '#' */
+       { 0x1d, 0x1e }, /* 0x1d => 0x1e */
+       { 0x1e, 0x36 }, /* 0x1e => '6' */
        { 0x26, 0x2f }, /* '&' => '/' */
-       { 0x27, 0x5d }, /* '\'' => ']' */
        { 0x28, 0x29 }, /* '(' => ')' */
        { 0x29, 0x3d }, /* ')' => '=' */
        { 0x2a, 0x28 }, /* '*' => '(' */
-       { 0x2b, 0x60 }, /* '+' => '`' */
-       { 0x2d, 0x5c }, /* '-' => '\\' */
        { 0x2f, 0x2d }, /* '/' => '-' */
-       { 0x3a, 0x7b }, /* ':' => '{' */
-       { 0x3b, 0x5b }, /* ';' => '[' */
        { 0x3c, 0x3b }, /* '<' => ';' */
-       { 0x3d, 0x27 }, /* '=' => '\'' */
        { 0x3e, 0x3a }, /* '>' => ':' */
        { 0x3f, 0x5f }, /* '?' => '_' */
        { 0x40, 0x22 }, /* '@' => '"' */
        { 0x59, 0x5a }, /* 'Y' => 'Z' */
        { 0x5a, 0x59 }, /* 'Z' => 'Y' */
-       { 0x5b, 0x40 }, /* '[' => '@' */
        { 0x5c, 0x23 }, /* '\\' => '#' */
        { 0x5d, 0x2b }, /* ']' => '+' */
        { 0x5e, 0x26 }, /* '^' => '&' */
        { 0x5f, 0x3f }, /* '_' => '?' */
-       { 0x60, 0x5e }, /* '`' => '^' */
        { 0x79, 0x7a }, /* 'y' => 'z' */
        { 0x7a, 0x79 }, /* 'z' => 'y' */
-       { 0x7b, 0x5c }, /* '{' => '\\' */
        { 0x7c, 0x27 }, /* '|' => '\'' */
        { 0x7d, 0x2a }, /* '}' => '*' */
 };
index e409018c81b82742be4ae5ea8955ab3de7bac169..05110dc898b5b4faded237ebce40e56c0cad547b 100644 (file)
@@ -12,11 +12,12 @@ FILE_LICENCE ( PUBLIC_DOMAIN );
 
 /** "dk" keyboard mapping */
 struct key_mapping dk_mapping[] __keymap = {
+       { 0x1c, 0x27 }, /* 0x1c => '\'' */
+       { 0x1e, 0x36 }, /* 0x1e => '6' */
        { 0x26, 0x2f }, /* '&' => '/' */
        { 0x28, 0x29 }, /* '(' => ')' */
        { 0x29, 0x3d }, /* ')' => '=' */
        { 0x2a, 0x28 }, /* '*' => '(' */
-       { 0x2b, 0x60 }, /* '+' => '`' */
        { 0x2d, 0x2b }, /* '-' => '+' */
        { 0x2f, 0x2d }, /* '/' => '-' */
        { 0x3c, 0x3b }, /* '<' => ';' */
@@ -27,5 +28,4 @@ struct key_mapping dk_mapping[] __keymap = {
        { 0x5e, 0x26 }, /* '^' => '&' */
        { 0x5f, 0x3f }, /* '_' => '?' */
        { 0x7c, 0x2a }, /* '|' => '*' */
-       { 0x7d, 0x5e }, /* '}' => '^' */
 };
index c1fe013a98489bb3eb86dd1bd31df9745e1b653a..51dedfff78459aba1e54eccb0d1315b8d5b50a54 100644 (file)
@@ -12,6 +12,8 @@ FILE_LICENCE ( PUBLIC_DOMAIN );
 
 /** "es" keyboard mapping */
 struct key_mapping es_mapping[] __keymap = {
+       { 0x1c, 0x1d }, /* 0x1c => 0x1d */
+       { 0x1e, 0x36 }, /* 0x1e => '6' */
        { 0x26, 0x2f }, /* '&' => '/' */
        { 0x28, 0x29 }, /* '(' => ')' */
        { 0x29, 0x3d }, /* ')' => '=' */
index ad88cecc18fcdbf27b3845c15ff2acc83dedb719..dd0f879b1f64c493275828d44663755ea649cf5d 100644 (file)
@@ -26,5 +26,4 @@ struct key_mapping et_mapping[] __keymap = {
        { 0x5e, 0x26 }, /* '^' => '&' */
        { 0x5f, 0x3f }, /* '_' => '?' */
        { 0x7c, 0x2a }, /* '|' => '*' */
-       { 0x7f, 0x1b }, /* 0x7f => 0x1b */
 };
index c8f6c3a06edee9fda4634a477c40d761fea8490e..c489bf0e59ed7895dacffef1d195509f312e6690 100644 (file)
@@ -12,27 +12,18 @@ FILE_LICENCE ( PUBLIC_DOMAIN );
 
 /** "fi" keyboard mapping */
 struct key_mapping fi_mapping[] __keymap = {
-       { 0x22, 0x5b }, /* '"' => '[' */
        { 0x26, 0x2f }, /* '&' => '/' */
-       { 0x27, 0x7b }, /* '\'' => '{' */
        { 0x28, 0x29 }, /* '(' => ')' */
        { 0x29, 0x3d }, /* ')' => '=' */
        { 0x2a, 0x28 }, /* '*' => '(' */
-       { 0x2b, 0x60 }, /* '+' => '`' */
        { 0x2d, 0x2b }, /* '-' => '+' */
        { 0x2f, 0x2d }, /* '/' => '-' */
-       { 0x3a, 0x5c }, /* ':' => '\\' */
-       { 0x3b, 0x7c }, /* ';' => '|' */
        { 0x3c, 0x3b }, /* '<' => ';' */
-       { 0x3d, 0x27 }, /* '=' => '\'' */
        { 0x3e, 0x3a }, /* '>' => ':' */
        { 0x3f, 0x5f }, /* '?' => '_' */
        { 0x40, 0x22 }, /* '@' => '"' */
-       { 0x5b, 0x7d }, /* '[' => '}' */
        { 0x5c, 0x27 }, /* '\\' => '\'' */
        { 0x5e, 0x26 }, /* '^' => '&' */
        { 0x5f, 0x3f }, /* '_' => '?' */
-       { 0x7b, 0x5d }, /* '{' => ']' */
        { 0x7c, 0x2a }, /* '|' => '*' */
-       { 0x7d, 0x5e }, /* '}' => '^' */
 };
index fd615a4562a1edfe548d06398a78eec33474960c..8f3b4999d49b5c96d5b68d63a56cc86c02e31384 100644 (file)
@@ -16,13 +16,16 @@ struct key_mapping fr_mapping[] __keymap = {
        { 0x11, 0x01 }, /* Ctrl-Q => Ctrl-A */
        { 0x17, 0x1a }, /* Ctrl-W => Ctrl-Z */
        { 0x1a, 0x17 }, /* Ctrl-Z => Ctrl-W */
+       { 0x1c, 0x2a }, /* 0x1c => '*' */
+       { 0x1d, 0x24 }, /* 0x1d => '$' */
+       { 0x1e, 0x1c }, /* 0x1e => 0x1c */
+       { 0x1f, 0x1d }, /* 0x1f => 0x1d */
        { 0x21, 0x31 }, /* '!' => '1' */
        { 0x22, 0x25 }, /* '"' => '%' */
        { 0x23, 0x33 }, /* '#' => '3' */
        { 0x24, 0x34 }, /* '$' => '4' */
        { 0x25, 0x35 }, /* '%' => '5' */
        { 0x26, 0x37 }, /* '&' => '7' */
-       { 0x27, 0x7c }, /* '\'' => '|' */
        { 0x28, 0x39 }, /* '(' => '9' */
        { 0x29, 0x30 }, /* ')' => '0' */
        { 0x2a, 0x38 }, /* '*' => '8' */
@@ -30,39 +33,28 @@ struct key_mapping fr_mapping[] __keymap = {
        { 0x2d, 0x29 }, /* '-' => ')' */
        { 0x2e, 0x3a }, /* '.' => ':' */
        { 0x2f, 0x21 }, /* '/' => '!' */
-       { 0x30, 0x40 }, /* '0' => '@' */
        { 0x31, 0x26 }, /* '1' => '&' */
-       { 0x32, 0x7b }, /* '2' => '{' */
        { 0x33, 0x22 }, /* '3' => '"' */
        { 0x34, 0x27 }, /* '4' => '\'' */
        { 0x35, 0x28 }, /* '5' => '(' */
        { 0x36, 0x2d }, /* '6' => '-' */
-       { 0x37, 0x7d }, /* '7' => '}' */
        { 0x38, 0x5f }, /* '8' => '_' */
-       { 0x39, 0x2f }, /* '9' => '/' */
        { 0x3a, 0x4d }, /* ':' => 'M' */
        { 0x3b, 0x6d }, /* ';' => 'm' */
        { 0x3c, 0x2e }, /* '<' => '.' */
        { 0x3e, 0x2f }, /* '>' => '/' */
-       { 0x3f, 0x5c }, /* '?' => '\\' */
        { 0x40, 0x32 }, /* '@' => '2' */
        { 0x41, 0x51 }, /* 'A' => 'Q' */
        { 0x4d, 0x3f }, /* 'M' => '?' */
        { 0x51, 0x41 }, /* 'Q' => 'A' */
        { 0x57, 0x5a }, /* 'W' => 'Z' */
        { 0x5a, 0x57 }, /* 'Z' => 'W' */
-       { 0x5b, 0x5e }, /* '[' => '^' */
        { 0x5c, 0x2a }, /* '\\' => '*' */
        { 0x5d, 0x24 }, /* ']' => '$' */
        { 0x5e, 0x36 }, /* '^' => '6' */
-       { 0x5f, 0x5d }, /* '_' => ']' */
-       { 0x60, 0x2a }, /* '`' => '*' */
        { 0x61, 0x71 }, /* 'a' => 'q' */
        { 0x6d, 0x2c }, /* 'm' => ',' */
        { 0x71, 0x61 }, /* 'q' => 'a' */
        { 0x77, 0x7a }, /* 'w' => 'z' */
        { 0x7a, 0x77 }, /* 'z' => 'w' */
-       { 0x7b, 0x3c }, /* '{' => '<' */
-       { 0x7c, 0x23 }, /* '|' => '#' */
-       { 0x7d, 0x3e }, /* '}' => '>' */
 };
index 68eff2f4c7c8cf5e04c939935862542a3b48490c..a2eadbc624ce4bbefaafd9597172ea6cd20ab982 100644 (file)
@@ -14,6 +14,7 @@ FILE_LICENCE ( PUBLIC_DOMAIN );
 struct key_mapping hu_mapping[] __keymap = {
        { 0x19, 0x1a }, /* Ctrl-Y => Ctrl-Z */
        { 0x1a, 0x19 }, /* Ctrl-Z => Ctrl-Y */
+       { 0x1e, 0x36 }, /* 0x1e => '6' */
        { 0x21, 0x27 }, /* '!' => '\'' */
        { 0x23, 0x2b }, /* '#' => '+' */
        { 0x24, 0x21 }, /* '$' => '!' */
index 478330c0c389dbd86f942bdbcc2eaa3a99c924b1..f631f7ac96ef279fb7fa3ebad1dbbca8a39360b1 100644 (file)
@@ -12,4 +12,16 @@ FILE_LICENCE ( PUBLIC_DOMAIN );
 
 /** "il" keyboard mapping */
 struct key_mapping il_mapping[] __keymap = {
+       { 0x1d, 0x1b }, /* 0x1d => 0x1b */
+       { 0x27, 0x2c }, /* '\'' => ',' */
+       { 0x28, 0x29 }, /* '(' => ')' */
+       { 0x29, 0x28 }, /* ')' => '(' */
+       { 0x2f, 0x2e }, /* '/' => '.' */
+       { 0x3c, 0x3e }, /* '<' => '>' */
+       { 0x3e, 0x3c }, /* '>' => '<' */
+       { 0x5b, 0x5d }, /* '[' => ']' */
+       { 0x5d, 0x5b }, /* ']' => '[' */
+       { 0x60, 0x3b }, /* '`' => ';' */
+       { 0x7b, 0x7d }, /* '{' => '}' */
+       { 0x7d, 0x7b }, /* '}' => '{' */
 };
index 5bb05471c49eb8e8206f0c971de77ef975a2f88f..d96102c9e0eb0dfb464ae3f2d387e6b971aea20b 100644 (file)
@@ -12,6 +12,7 @@ FILE_LICENCE ( PUBLIC_DOMAIN );
 
 /** "it" keyboard mapping */
 struct key_mapping it_mapping[] __keymap = {
+       { 0x1e, 0x36 }, /* 0x1e => '6' */
        { 0x26, 0x2f }, /* '&' => '/' */
        { 0x28, 0x29 }, /* '(' => ')' */
        { 0x29, 0x3d }, /* ')' => '=' */
index 094a6fc60a364c8a60c9067b71a994fd49029316..dfca2ff66131e293bd5506f2d8362fa12b9ec7ed 100644 (file)
@@ -12,8 +12,8 @@ FILE_LICENCE ( PUBLIC_DOMAIN );
 
 /** "mt" keyboard mapping */
 struct key_mapping mt_mapping[] __keymap = {
+       { 0x1c, 0x1e }, /* 0x1c => 0x1e */
        { 0x22, 0x40 }, /* '"' => '@' */
-       { 0x23, 0x04 }, /* '#' => Ctrl-D */
        { 0x40, 0x22 }, /* '@' => '"' */
        { 0x5c, 0x23 }, /* '\\' => '#' */
        { 0x7c, 0x7e }, /* '|' => '~' */
index ba051705e651f17e87646c7a7a32eaaf86b1875d..2a0fbbcbd78567fca4cac8192d1e762099ec02fb 100644 (file)
@@ -12,11 +12,13 @@ FILE_LICENCE ( PUBLIC_DOMAIN );
 
 /** "nl" keyboard mapping */
 struct key_mapping nl_mapping[] __keymap = {
+       { 0x1c, 0x3c }, /* 0x1c => '<' */
+       { 0x1d, 0x1c }, /* 0x1d => 0x1c */
+       { 0x1e, 0x36 }, /* 0x1e => '6' */
        { 0x26, 0x5f }, /* '&' => '_' */
        { 0x28, 0x29 }, /* '(' => ')' */
        { 0x29, 0x27 }, /* ')' => '\'' */
        { 0x2a, 0x28 }, /* '*' => '(' */
-       { 0x2b, 0x7e }, /* '+' => '~' */
        { 0x2d, 0x2f }, /* '-' => '/' */
        { 0x2f, 0x2d }, /* '/' => '-' */
        { 0x3b, 0x2b }, /* ';' => '+' */
index 8c3e81b31de84820cad12b603b6d280f1e77554e..655e4cef7cf63267381e8724c56622bed78183cc 100644 (file)
@@ -12,6 +12,7 @@ FILE_LICENCE ( PUBLIC_DOMAIN );
 
 /** "no-latin1" keyboard mapping */
 struct key_mapping no_latin1_mapping[] __keymap = {
+       { 0x1d, 0x1e }, /* 0x1d => 0x1e */
        { 0x26, 0x2f }, /* '&' => '/' */
        { 0x28, 0x29 }, /* '(' => ')' */
        { 0x29, 0x3d }, /* ')' => '=' */
index 45cf9e84774c6f528add2ce4172c13480872e870..7a2df7c5af7e0357fb1d64584c590aac22d2aad9 100644 (file)
@@ -12,94 +12,22 @@ FILE_LICENCE ( PUBLIC_DOMAIN );
 
 /** "no" keyboard mapping */
 struct key_mapping no_mapping[] __keymap = {
-       { 0x02, 0x18 }, /* Ctrl-B => Ctrl-X */
-       { 0x03, 0x0a }, /* Ctrl-C => Ctrl-J */
-       { 0x04, 0x05 }, /* Ctrl-D => Ctrl-E */
-       { 0x06, 0x15 }, /* Ctrl-F => Ctrl-U */
-       { 0x07, 0x09 }, /* Ctrl-G => Ctrl-I */
-       { 0x08, 0x04 }, /* Ctrl-H => Ctrl-D */
-       { 0x0a, 0x08 }, /* Ctrl-J => Ctrl-H */
-       { 0x0b, 0x14 }, /* Ctrl-K => Ctrl-T */
-       { 0x0c, 0x0e }, /* Ctrl-L => Ctrl-N */
-       { 0x0e, 0x02 }, /* Ctrl-N => Ctrl-B */
-       { 0x0f, 0x12 }, /* Ctrl-O => Ctrl-R */
-       { 0x10, 0x0c }, /* Ctrl-P => Ctrl-L */
-       { 0x12, 0x10 }, /* Ctrl-R => Ctrl-P */
-       { 0x13, 0x0f }, /* Ctrl-S => Ctrl-O */
-       { 0x14, 0x19 }, /* Ctrl-T => Ctrl-Y */
-       { 0x15, 0x07 }, /* Ctrl-U => Ctrl-G */
-       { 0x16, 0x0b }, /* Ctrl-V => Ctrl-K */
-       { 0x18, 0x11 }, /* Ctrl-X => Ctrl-Q */
-       { 0x19, 0x06 }, /* Ctrl-Y => Ctrl-F */
-       { 0x22, 0x5f }, /* '"' => '_' */
+       { 0x1c, 0x27 }, /* 0x1c => '\'' */
+       { 0x1e, 0x36 }, /* 0x1e => '6' */
        { 0x26, 0x2f }, /* '&' => '/' */
-       { 0x27, 0x2d }, /* '\'' => '-' */
        { 0x28, 0x29 }, /* '(' => ')' */
        { 0x29, 0x3d }, /* ')' => '=' */
        { 0x2a, 0x28 }, /* '*' => '(' */
-       { 0x2b, 0x60 }, /* '+' => '`' */
-       { 0x2c, 0x77 }, /* ',' => 'w' */
        { 0x2d, 0x2b }, /* '-' => '+' */
-       { 0x2e, 0x76 }, /* '.' => 'v' */
-       { 0x2f, 0x7a }, /* '/' => 'z' */
-       { 0x3a, 0x53 }, /* ':' => 'S' */
-       { 0x3b, 0x73 }, /* ';' => 's' */
-       { 0x3c, 0x57 }, /* '<' => 'W' */
+       { 0x2f, 0x2d }, /* '/' => '-' */
+       { 0x3c, 0x3b }, /* '<' => ';' */
        { 0x3d, 0x5c }, /* '=' => '\\' */
-       { 0x3e, 0x56 }, /* '>' => 'V' */
-       { 0x3f, 0x5a }, /* '?' => 'Z' */
+       { 0x3e, 0x3a }, /* '>' => ':' */
+       { 0x3f, 0x5f }, /* '?' => '_' */
        { 0x40, 0x22 }, /* '@' => '"' */
-       { 0x42, 0x58 }, /* 'B' => 'X' */
-       { 0x43, 0x4a }, /* 'C' => 'J' */
-       { 0x44, 0x45 }, /* 'D' => 'E' */
-       { 0x45, 0x3a }, /* 'E' => ':' */
-       { 0x46, 0x55 }, /* 'F' => 'U' */
-       { 0x47, 0x49 }, /* 'G' => 'I' */
-       { 0x48, 0x44 }, /* 'H' => 'D' */
-       { 0x49, 0x43 }, /* 'I' => 'C' */
-       { 0x4a, 0x48 }, /* 'J' => 'H' */
-       { 0x4b, 0x54 }, /* 'K' => 'T' */
-       { 0x4c, 0x4e }, /* 'L' => 'N' */
-       { 0x4e, 0x42 }, /* 'N' => 'B' */
-       { 0x4f, 0x52 }, /* 'O' => 'R' */
-       { 0x50, 0x4c }, /* 'P' => 'L' */
-       { 0x52, 0x50 }, /* 'R' => 'P' */
-       { 0x53, 0x4f }, /* 'S' => 'O' */
-       { 0x54, 0x59 }, /* 'T' => 'Y' */
-       { 0x55, 0x47 }, /* 'U' => 'G' */
-       { 0x56, 0x4b }, /* 'V' => 'K' */
-       { 0x57, 0x3b }, /* 'W' => ';' */
-       { 0x58, 0x51 }, /* 'X' => 'Q' */
-       { 0x59, 0x46 }, /* 'Y' => 'F' */
-       { 0x5b, 0x27 }, /* '[' => '\'' */
-       { 0x5c, 0x3c }, /* '\\' => '<' */
-       { 0x5d, 0x7e }, /* ']' => '~' */
+       { 0x5c, 0x27 }, /* '\\' => '\'' */
        { 0x5e, 0x26 }, /* '^' => '&' */
        { 0x5f, 0x3f }, /* '_' => '?' */
        { 0x60, 0x7c }, /* '`' => '|' */
-       { 0x62, 0x78 }, /* 'b' => 'x' */
-       { 0x63, 0x6a }, /* 'c' => 'j' */
-       { 0x64, 0x65 }, /* 'd' => 'e' */
-       { 0x65, 0x2e }, /* 'e' => '.' */
-       { 0x66, 0x75 }, /* 'f' => 'u' */
-       { 0x67, 0x69 }, /* 'g' => 'i' */
-       { 0x68, 0x64 }, /* 'h' => 'd' */
-       { 0x69, 0x63 }, /* 'i' => 'c' */
-       { 0x6a, 0x68 }, /* 'j' => 'h' */
-       { 0x6b, 0x74 }, /* 'k' => 't' */
-       { 0x6c, 0x6e }, /* 'l' => 'n' */
-       { 0x6e, 0x62 }, /* 'n' => 'b' */
-       { 0x6f, 0x72 }, /* 'o' => 'r' */
-       { 0x70, 0x6c }, /* 'p' => 'l' */
-       { 0x72, 0x70 }, /* 'r' => 'p' */
-       { 0x73, 0x6f }, /* 's' => 'o' */
-       { 0x74, 0x79 }, /* 't' => 'y' */
-       { 0x75, 0x67 }, /* 'u' => 'g' */
-       { 0x76, 0x6b }, /* 'v' => 'k' */
-       { 0x77, 0x2c }, /* 'w' => ',' */
-       { 0x78, 0x71 }, /* 'x' => 'q' */
-       { 0x79, 0x66 }, /* 'y' => 'f' */
-       { 0x7b, 0x2a }, /* '{' => '*' */
-       { 0x7c, 0x3e }, /* '|' => '>' */
-       { 0x7d, 0x5e }, /* '}' => '^' */
+       { 0x7c, 0x2a }, /* '|' => '*' */
 };
index a8e44b6a399bec83463f822d12820d12850cc323..b993902af1dc201d16f0ecbd370911086643bb76 100644 (file)
@@ -12,18 +12,21 @@ FILE_LICENCE ( PUBLIC_DOMAIN );
 
 /** "pt" keyboard mapping */
 struct key_mapping pt_mapping[] __keymap = {
-       { 0x1c, 0x1d }, /* 0x1c => 0x1d */
-       { 0x1d, 0x1b }, /* 0x1d => 0x1b */
-       { 0x22, 0x5e }, /* '"' => '^' */
-       { 0x27, 0x7e }, /* '\'' => '~' */
-       { 0x2f, 0x3b }, /* '/' => ';' */
-       { 0x3f, 0x3a }, /* '?' => ':' */
-       { 0x5b, 0x27 }, /* '[' => '\'' */
-       { 0x5c, 0x5d }, /* '\\' => ']' */
-       { 0x5d, 0x5b }, /* ']' => '[' */
-       { 0x60, 0x27 }, /* '`' => '\'' */
-       { 0x7b, 0x60 }, /* '{' => '`' */
-       { 0x7c, 0x7d }, /* '|' => '}' */
-       { 0x7d, 0x7b }, /* '}' => '{' */
-       { 0x7e, 0x22 }, /* '~' => '"' */
+       { 0x1e, 0x36 }, /* 0x1e => '6' */
+       { 0x26, 0x2f }, /* '&' => '/' */
+       { 0x28, 0x29 }, /* '(' => ')' */
+       { 0x29, 0x3d }, /* ')' => '=' */
+       { 0x2a, 0x28 }, /* '*' => '(' */
+       { 0x2d, 0x27 }, /* '-' => '\'' */
+       { 0x2f, 0x2d }, /* '/' => '-' */
+       { 0x3c, 0x3b }, /* '<' => ';' */
+       { 0x3e, 0x3a }, /* '>' => ':' */
+       { 0x3f, 0x5f }, /* '?' => '_' */
+       { 0x40, 0x22 }, /* '@' => '"' */
+       { 0x5b, 0x2b }, /* '[' => '+' */
+       { 0x5e, 0x26 }, /* '^' => '&' */
+       { 0x5f, 0x3f }, /* '_' => '?' */
+       { 0x60, 0x5c }, /* '`' => '\\' */
+       { 0x7b, 0x2a }, /* '{' => '*' */
+       { 0x7e, 0x7c }, /* '~' => '|' */
 };
index 422b6c69f2e0ed1cb6187a3ba37007d77713f69e..c120ffd820178989a1315a3d9afd9742c506c612 100644 (file)
@@ -12,4 +12,5 @@ FILE_LICENCE ( PUBLIC_DOMAIN );
 
 /** "ru" keyboard mapping */
 struct key_mapping ru_mapping[] __keymap = {
+       { 0x0d, 0x0a }, /* Ctrl-M => Ctrl-J */
 };
similarity index 54%
rename from src/hci/keymap/keymap_bg.c
rename to src/hci/keymap/keymap_sr-latin.c
index 62b6baeacbc92eaac968d1cb712dac36752edcb1..9d76e8a6c6bc8600e31a26a707a49ff34ab6e75b 100644 (file)
@@ -1,6 +1,6 @@
 /** @file
  *
- * "bg" keyboard mapping
+ * "sr-latin" keyboard mapping
  *
  * This file is automatically generated; do not edit
  *
@@ -10,6 +10,6 @@ FILE_LICENCE ( PUBLIC_DOMAIN );
 
 #include <ipxe/keymap.h>
 
-/** "bg" keyboard mapping */
-struct key_mapping bg_mapping[] __keymap = {
+/** "sr-latin" keyboard mapping */
+struct key_mapping sr_latin_mapping[] __keymap = {
 };
diff --git a/src/hci/keymap/keymap_sr.c b/src/hci/keymap/keymap_sr.c
deleted file mode 100644 (file)
index 0552f4d..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-/** @file
- *
- * "sr" keyboard mapping
- *
- * This file is automatically generated; do not edit
- *
- */
-
-FILE_LICENCE ( PUBLIC_DOMAIN );
-
-#include <ipxe/keymap.h>
-
-/** "sr" keyboard mapping */
-struct key_mapping sr_mapping[] __keymap = {
-       { 0x19, 0x1a }, /* Ctrl-Y => Ctrl-Z */
-       { 0x1a, 0x19 }, /* Ctrl-Z => Ctrl-Y */
-       { 0x26, 0x2f }, /* '&' => '/' */
-       { 0x28, 0x29 }, /* '(' => ')' */
-       { 0x29, 0x3d }, /* ')' => '=' */
-       { 0x2a, 0x28 }, /* '*' => '(' */
-       { 0x2b, 0x2a }, /* '+' => '*' */
-       { 0x2d, 0x27 }, /* '-' => '\'' */
-       { 0x2f, 0x2d }, /* '/' => '-' */
-       { 0x3c, 0x3b }, /* '<' => ';' */
-       { 0x3d, 0x2b }, /* '=' => '+' */
-       { 0x3e, 0x3a }, /* '>' => ':' */
-       { 0x3f, 0x5f }, /* '?' => '_' */
-       { 0x40, 0x22 }, /* '@' => '"' */
-       { 0x59, 0x5a }, /* 'Y' => 'Z' */
-       { 0x5a, 0x59 }, /* 'Z' => 'Y' */
-       { 0x5e, 0x26 }, /* '^' => '&' */
-       { 0x5f, 0x3f }, /* '_' => '?' */
-       { 0x79, 0x7a }, /* 'y' => 'z' */
-       { 0x7a, 0x79 }, /* 'z' => 'y' */
-};
diff --git a/src/hci/keymap/keymap_th.c b/src/hci/keymap/keymap_th.c
deleted file mode 100644 (file)
index e8b44d1..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-/** @file
- *
- * "th" keyboard mapping
- *
- * This file is automatically generated; do not edit
- *
- */
-
-FILE_LICENCE ( PUBLIC_DOMAIN );
-
-#include <ipxe/keymap.h>
-
-/** "th" keyboard mapping */
-struct key_mapping th_mapping[] __keymap = {
-};
diff --git a/src/hci/keymap/keymap_wo.c b/src/hci/keymap/keymap_wo.c
deleted file mode 100644 (file)
index b453576..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-/** @file
- *
- * "wo" keyboard mapping
- *
- * This file is automatically generated; do not edit
- *
- */
-
-FILE_LICENCE ( PUBLIC_DOMAIN );
-
-#include <ipxe/keymap.h>
-
-/** "wo" keyboard mapping */
-struct key_mapping wo_mapping[] __keymap = {
-       { 0x01, 0x11 }, /* Ctrl-A => Ctrl-Q */
-       { 0x11, 0x01 }, /* Ctrl-Q => Ctrl-A */
-       { 0x17, 0x1a }, /* Ctrl-W => Ctrl-Z */
-       { 0x1a, 0x17 }, /* Ctrl-Z => Ctrl-W */
-       { 0x21, 0x31 }, /* '!' => '1' */
-       { 0x23, 0x33 }, /* '#' => '3' */
-       { 0x24, 0x34 }, /* '$' => '4' */
-       { 0x25, 0x35 }, /* '%' => '5' */
-       { 0x26, 0x37 }, /* '&' => '7' */
-       { 0x28, 0x39 }, /* '(' => '9' */
-       { 0x29, 0x30 }, /* ')' => '0' */
-       { 0x2a, 0x38 }, /* '*' => '8' */
-       { 0x2c, 0x3b }, /* ',' => ';' */
-       { 0x2d, 0x29 }, /* '-' => ')' */
-       { 0x2e, 0x3a }, /* '.' => ':' */
-       { 0x2f, 0x21 }, /* '/' => '!' */
-       { 0x31, 0x26 }, /* '1' => '&' */
-       { 0x33, 0x22 }, /* '3' => '"' */
-       { 0x34, 0x27 }, /* '4' => '\'' */
-       { 0x35, 0x28 }, /* '5' => '(' */
-       { 0x36, 0x2d }, /* '6' => '-' */
-       { 0x38, 0x5f }, /* '8' => '_' */
-       { 0x3a, 0x4d }, /* ':' => 'M' */
-       { 0x3b, 0x6d }, /* ';' => 'm' */
-       { 0x3c, 0x2e }, /* '<' => '.' */
-       { 0x3e, 0x2f }, /* '>' => '/' */
-       { 0x40, 0x32 }, /* '@' => '2' */
-       { 0x41, 0x51 }, /* 'A' => 'Q' */
-       { 0x4d, 0x3f }, /* 'M' => '?' */
-       { 0x51, 0x41 }, /* 'Q' => 'A' */
-       { 0x57, 0x5a }, /* 'W' => 'Z' */
-       { 0x5a, 0x57 }, /* 'Z' => 'W' */
-       { 0x5d, 0x24 }, /* ']' => '$' */
-       { 0x5e, 0x36 }, /* '^' => '6' */
-       { 0x61, 0x71 }, /* 'a' => 'q' */
-       { 0x6d, 0x2c }, /* 'm' => ',' */
-       { 0x71, 0x61 }, /* 'q' => 'a' */
-       { 0x77, 0x7a }, /* 'w' => 'z' */
-       { 0x7a, 0x77 }, /* 'z' => 'w' */
-       { 0x7e, 0x25 }, /* '~' => '%' */
-};
diff --git a/src/util/genkeymap.pl b/src/util/genkeymap.pl
deleted file mode 100755 (executable)
index 7a5024b..0000000
+++ /dev/null
@@ -1,238 +0,0 @@
-#!/usr/bin/perl -w
-#
-# Copyright (C) 2011 Michael Brown <mbrown@fensystems.co.uk>.
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation; either version 2 of the
-# License, or any later version.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
-
-=head1 NAME
-
-genkeymap.pl
-
-=head1 SYNOPSIS
-
-genkeymap.pl [options] <keymap name>
-
-Options:
-
-    -f,--from=<name>   Set BIOS keymap name (default "us")
-    -h,--help          Display brief help message
-    -v,--verbose       Increase verbosity
-    -q,--quiet         Decrease verbosity
-
-=cut
-
-# With reference to:
-#
-# http://gunnarwrobel.de/wiki/Linux-and-the-keyboard.html
-
-use Getopt::Long;
-use Pod::Usage;
-use strict;
-use warnings;
-
-use constant BIOS_KEYMAP => "us";
-use constant BKEYMAP_MAGIC => "bkeymap";
-use constant MAX_NR_KEYMAPS => 256;
-use constant NR_KEYS => 128;
-use constant KG_SHIFT => 0;
-use constant KG_ALTGR => 1;
-use constant KG_CTRL => 2;
-use constant KG_ALT => 3;
-use constant KG_SHIFTL => 4;
-use constant KG_KANASHIFT => 4;
-use constant KG_SHIFTR => 5;
-use constant KG_CTRLL => 6;
-use constant KG_CTRLR => 7;
-use constant KG_CAPSSHIFT => 8;
-use constant KT_LATIN => 0;
-use constant KT_FN => 1;
-use constant KT_SPEC => 2;
-use constant KT_PAD => 3;
-use constant KT_DEAD => 4;
-use constant KT_CONS => 5;
-use constant KT_CUR => 6;
-use constant KT_SHIFT => 7;
-use constant KT_META => 8;
-use constant KT_ASCII => 9;
-use constant KT_LOCK => 10;
-use constant KT_LETTER => 11;
-use constant KT_SLOCK => 12;
-use constant KT_SPKUP => 14;
-
-my $verbosity = 1;
-my $from_name = BIOS_KEYMAP;
-
-# Read named keymaps using "loadkeys -b"
-#
-sub read_keymaps {
-  my $name = shift;
-  my $keymaps = [];
-
-  # Generate binary keymap
-  open my $pipe, "-|", "loadkeys", "-b", $name
-      or die "Could not load keymap \"".$name."\": $!\n";
-
-  # Check magic
-  read $pipe, my $magic, length BKEYMAP_MAGIC
-      or die "Could not read from \"".$name."\": $!\n";
-  die "Bad magic value from \"".$name."\"\n"
-      unless $magic eq BKEYMAP_MAGIC;
-
-  # Read list of included keymaps
-  read $pipe, my $included, MAX_NR_KEYMAPS
-      or die "Could not read from \"".$name."\": $!\n";
-  my @included = unpack ( "C*", $included );
-  die "Missing or truncated keymap list from \"".$name."\"\n"
-      unless @included == MAX_NR_KEYMAPS;
-
-  # Read each keymap in turn
-  for ( my $keymap = 0 ; $keymap < MAX_NR_KEYMAPS ; $keymap++ ) {
-    if ( $included[$keymap] ) {
-      read $pipe, my $keysyms, ( NR_KEYS * 2 )
-         or die "Could not read from \"".$name."\": $!\n";
-      my @keysyms = unpack ( "S*", $keysyms );
-      die "Missing or truncated keymap ".$keymap." from \"".$name."\"\n"
-         unless @keysyms == NR_KEYS;
-      push @$keymaps, \@keysyms;
-    } else {
-      push @$keymaps, undef;
-    }
-  }
-
-  close $pipe;
-  return $keymaps;
-}
-
-# Translate keysym value to ASCII
-#
-sub keysym_to_ascii {
-  my $keysym = shift;
-
-  # Non-existent keysyms have no ASCII equivalent
-  return unless $keysym;
-
-  # Sanity check
-  if ( $keysym & 0xf000 ) {
-    warn "Unexpected keysym ".sprintf ( "0x%04x", $keysym )."\n";
-    return;
-  }
-
-  # Extract type and value
-  my $type = ( $keysym >> 8 );
-  my $value = ( $keysym & 0xff );
-
-  # Non-simple types have no ASCII equivalent
-  return unless ( ( $type == KT_LATIN ) || ( $type == KT_ASCII ) ||
-                 ( $type == KT_LETTER ) );
-
-  # High-bit-set characters cannot be generated on a US keyboard
-  return if $value & 0x80;
-
-  return $value;
-}
-
-# Translate ASCII to descriptive name
-#
-sub ascii_to_name {
-  my $ascii = shift;
-
-  if ( $ascii == 0x5c ) {
-    return "'\\\\'";
-  } elsif ( $ascii == 0x27 ) {
-    return "'\\\''";
-  } elsif ( ( $ascii >= 0x20 ) && ( $ascii <= 0x7e ) ) {
-    return sprintf ( "'%c'", $ascii );
-  } elsif ( $ascii <= 0x1a ) {
-    return sprintf ( "Ctrl-%c", ( 0x40 + $ascii ) );
-  } else {
-    return sprintf ( "0x%02x", $ascii );
-  }
-}
-
-# Produce translation table between two keymaps
-#
-sub translate_keymaps {
-  my $from = shift;
-  my $to = shift;
-  my $map = {};
-
-  foreach my $keymap ( 0, 1 << KG_SHIFT, 1 << KG_CTRL ) {
-    for ( my $keycode = 0 ; $keycode < NR_KEYS ; $keycode++ ) {
-      my $from_ascii = keysym_to_ascii ( $from->[$keymap]->[$keycode] )
-         or next;
-      my $to_ascii = keysym_to_ascii ( $to->[$keymap]->[$keycode] )
-         or next;
-      my $new_map = ( ! exists $map->{$from_ascii} );
-      my $update_map =
-         ( $new_map || ( $keycode < $map->{$from_ascii}->{keycode} ) );
-      if ( ( $verbosity > 1 ) &&
-          ( ( $from_ascii != $to_ascii ) ||
-            ( $update_map && ! $new_map ) ) ) {
-       printf STDERR "In keymap %d: %s => %s%s\n", $keymap,
-              ascii_to_name ( $from_ascii ), ascii_to_name ( $to_ascii ),
-              ( $update_map ? ( $new_map ? "" : " (override)" )
-                            : " (ignored)" );
-      }
-      if ( $update_map ) {
-       $map->{$from_ascii} = {
-         to_ascii => $to_ascii,
-         keycode => $keycode,
-       };
-      }
-    }
-  }
-  return { map { $_ => $map->{$_}->{to_ascii} } keys %$map };
-}
-
-# Parse command-line options
-Getopt::Long::Configure ( 'bundling', 'auto_abbrev' );
-GetOptions (
-  'verbose|v+' => sub { $verbosity++; },
-  'quiet|q+' => sub { $verbosity--; },
-  'from|f=s' => sub { shift; $from_name = shift; },
-  'help|h' => sub { pod2usage ( 1 ); },
-) or die "Could not parse command-line options\n";
-pod2usage ( 1 ) unless @ARGV == 1;
-my $to_name = shift;
-
-# Read and translate keymaps
-my $from = read_keymaps ( $from_name );
-my $to = read_keymaps ( $to_name );
-my $map = translate_keymaps ( $from, $to );
-
-# Generate output
-( my $to_name_c = $to_name ) =~ s/\W/_/g;
-printf "/** \@file\n";
-printf " *\n";
-printf " * \"".$to_name."\" keyboard mapping\n";
-printf " *\n";
-printf " * This file is automatically generated; do not edit\n";
-printf " *\n";
-printf " */\n";
-printf "\n";
-printf "FILE_LICENCE ( PUBLIC_DOMAIN );\n";
-printf "\n";
-printf "#include <ipxe/keymap.h>\n";
-printf "\n";
-printf "/** \"".$to_name."\" keyboard mapping */\n";
-printf "struct key_mapping ".$to_name_c."_mapping[] __keymap = {\n";
-foreach my $from_sym ( sort { $a <=> $b } keys %$map ) {
-  my $to_sym = $map->{$from_sym};
-  next if $from_sym == $to_sym;
-  printf "\t{ 0x%02x, 0x%02x },\t/* %s => %s */\n", $from_sym, $to_sym,
-        ascii_to_name ( $from_sym ), ascii_to_name ( $to_sym );
-}
-printf "};\n";
diff --git a/src/util/genkeymap.py b/src/util/genkeymap.py
new file mode 100755 (executable)
index 0000000..1bb494f
--- /dev/null
@@ -0,0 +1,346 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 Michael Brown <mbrown@fensystems.co.uk>.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+"""Generate iPXE keymaps"""
+
+from __future__ import annotations
+
+import argparse
+from collections import UserDict
+from collections.abc import Sequence, Mapping, MutableMapping
+from dataclasses import dataclass
+from enum import Flag, IntEnum
+import re
+import subprocess
+from struct import Struct
+import textwrap
+from typing import ClassVar, Optional
+
+
+class KeyType(IntEnum):
+    """Key types"""
+
+    LATIN = 0
+    FN = 1
+    SPEC = 2
+    PAD = 3
+    DEAD = 4
+    CONS = 5
+    CUR = 6
+    SHIFT = 7
+    META = 8
+    ASCII = 9
+    LOCK = 10
+    LETTER = 11
+    SLOCK = 12
+    DEAD2 = 13
+    BRL = 14
+    UNKNOWN = 0xf0
+
+
+class KeyModifiers(Flag):
+    """Key modifiers"""
+
+    NONE = 0
+    SHIFT = 1
+    ALTGR = 2
+    CTRL = 4
+    ALT = 8
+    SHIFTL = 16
+    SHIFTR = 32
+    CTRLL = 64
+    CTRLR = 128
+
+    @property
+    def complexity(self) -> int:
+        """Get complexity value of applied modifiers"""
+        if self == self.NONE:
+            return 0
+        if self == self.SHIFT:
+            return 1
+        if self == self.CTRL:
+            return 2
+        return 3 + bin(self.value).count('1')
+
+
+@dataclass
+class Key:
+    """A single key definition"""
+
+    keycode: int
+    """Opaque keycode"""
+
+    keysym: int
+    """Key symbol"""
+
+    modifiers: KeyModifiers
+    """Applied modifiers"""
+
+    ASCII_TYPES: ClassVar[set[KeyType]] = {KeyType.LATIN, KeyType.ASCII,
+                                           KeyType.LETTER}
+    """Key types with direct ASCII values"""
+
+    @property
+    def keytype(self) -> Optional[KeyType]:
+        """Key type"""
+        try:
+            return KeyType(self.keysym >> 8)
+        except ValueError:
+            return None
+
+    @property
+    def value(self) -> int:
+        """Key value"""
+        return self.keysym & 0xff
+
+    @property
+    def ascii(self) -> Optional[str]:
+        """ASCII character"""
+        if self.keytype in self.ASCII_TYPES:
+            value = self.value
+            char = chr(value)
+            if value and char.isascii():
+                return char
+        return None
+
+
+class KeyMapping(UserDict[KeyModifiers, Sequence[Key]]):
+    """A keyboard mapping"""
+
+    BKEYMAP_MAGIC: ClassVar[bytes] = b'bkeymap'
+    """Magic signature for output produced by 'loadkeys -b'"""
+
+    MAX_NR_KEYMAPS: ClassVar[int] = 256
+    """Maximum number of keymaps produced by 'loadkeys -b'"""
+
+    NR_KEYS: ClassVar[int] = 128
+    """Number of keys in each keymap produced by 'loadkeys -b'"""
+
+    KEY_BACKSPACE: ClassVar[int] = 14
+    """Key code for backspace
+
+    Keyboard maps seem to somewhat arbitrarily pick an interpretation
+    for the backspace key and its various modifiers, according to the
+    personal preference of the keyboard map transcriber.
+    """
+
+    KEY_NON_US: ClassVar[int] = 86
+    """Key code 86
+
+    Key code 86 is somewhat bizarre.  It doesn't physically exist on
+    most US keyboards.  The database used by "loadkeys" defines it as
+    "<>", while most other databases either define it as a duplicate
+    "\\|" or omit it entirely.
+    """
+
+    FIXUPS: ClassVar[Mapping[str, Mapping[KeyModifiers,
+                                          Sequence[tuple[int, int]]]]] = {
+        'us': {
+            # Redefine erroneous key 86 as generating "\\|"
+            KeyModifiers.NONE: [(KEY_NON_US, ord('\\'))],
+            KeyModifiers.SHIFT: [(KEY_NON_US, ord('|'))],
+            # Treat Ctrl-Backspace as producing Backspace rather than Ctrl-H
+            KeyModifiers.CTRL: [(KEY_BACKSPACE, 0x7f)],
+        },
+    }
+    """Fixups for erroneous keymappings produced by 'loadkeys -b'"""
+
+    @property
+    def unshifted(self):
+        """Basic unshifted key mapping"""
+        return self[KeyModifiers.NONE]
+
+    @property
+    def shifted(self):
+        """Basic shifted key mapping"""
+        return self[KeyModifiers.SHIFT]
+
+    @classmethod
+    def load(cls, name: str) -> KeyMapping:
+        """Load keymap using 'loadkeys -b'"""
+        bkeymap = subprocess.check_output(["loadkeys", "-u", "-b", name])
+        if not bkeymap.startswith(cls.BKEYMAP_MAGIC):
+            raise ValueError("Invalid bkeymap magic signature")
+        bkeymap = bkeymap[len(cls.BKEYMAP_MAGIC):]
+        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:]
+        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:
+                    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))
+                ]
+                keymaps = keymaps[len(keymap):]
+        if keymaps:
+            raise ValueError("Trailing bkeymap data")
+        for modifiers, fixups in cls.FIXUPS.get(name, {}).items():
+            for keycode, keysym in fixups:
+                keys[modifiers][keycode] = Key(modifiers=modifiers,
+                                               keycode=keycode, keysym=keysym)
+        return cls(keys)
+
+    @property
+    def inverse(self) -> MutableMapping[str, Key]:
+        """Construct inverse mapping from ASCII value to key"""
+        return {
+            key.ascii: key
+            # Give priority to simplest modifier for a given ASCII code
+            for modifiers in sorted(self.keys(), reverse=True,
+                                    key=lambda x: (x.complexity, x.value))
+            # Give priority to lowest keycode for a given ASCII code
+            for key in reversed(self[modifiers])
+            # Ignore keys with no ASCII value
+            if key.ascii
+        }
+
+
+class BiosKeyMapping(KeyMapping):
+    """Keyboard mapping as used by the BIOS"""
+
+    @property
+    def inverse(self) -> MutableMapping[str, Key]:
+        inverse = super().inverse
+        assert len(inverse) == 0x7f
+        assert all(x.modifiers in {KeyModifiers.NONE, KeyModifiers.SHIFT,
+                                   KeyModifiers.CTRL}
+                   for x in inverse.values())
+        return inverse
+
+
+@dataclass
+class KeyRemapping:
+    """A keyboard remapping"""
+
+    name: str
+    """Mapping name"""
+
+    source: KeyMapping
+    """Source keyboard mapping"""
+
+    target: KeyMapping
+    """Target keyboard mapping"""
+
+    @property
+    def ascii(self) -> MutableMapping[str, str]:
+        """Remapped ASCII key 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()}
+        # Eliminate any null mappings, mappings that attempt to remap
+        # the backspace key, or identity mappings
+        table = {source: target for source, target in raw.items()
+                 if target
+                 and ord(source) != 0x7f
+                 and ord(target) != 0x7f
+                 and ord(source) != ord(target)}
+        # Recursively delete any mappings that would produce
+        # unreachable alphanumerics (e.g. the "il" keymap, which maps
+        # away the whole lower-case alphabet)
+        while True:
+            unreachable = set(table.keys()) - set(table.values())
+            delete = {x for x in unreachable if x.isascii() and x.isalnum()}
+            if not delete:
+                break
+            table = {k: v for k, v in table.items() if k not in delete}
+        # Sanity check: ensure that all numerics are reachable using
+        # the same shift state
+        digits = '1234567890'
+        unshifted = ''.join(table.get(x, x) for x in '1234567890')
+        shifted = ''.join(table.get(x, x) for x in '!@#$%^&*()')
+        if digits not in (shifted, unshifted):
+            raise ValueError("Inconsistent numeric remapping %s / %s" %
+                             (unshifted, shifted))
+        return dict(sorted(table.items()))
+
+    @property
+    def cname(self) -> str:
+        """C variable name"""
+        return re.sub(r'\W', '_', self.name) + "_mapping"
+
+    @staticmethod
+    def ascii_name(char: str) -> str:
+        """ASCII character name"""
+        if char == '\\':
+            name = "'\\\\'"
+        elif char == '\'':
+            name = "'\\\''"
+        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) -> str:
+        """Generated source code"""
+        code = textwrap.dedent(f"""
+        /** @file
+         *
+         * "{self.name}" keyboard mapping
+         *
+         * This file is automatically generated; do not edit
+         *
+         */
+
+        FILE_LICENCE ( PUBLIC_DOMAIN );
+
+        #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()
+        return code
+
+
+if __name__ == '__main__':
+
+    # Parse command-line arguments
+    parser = argparse.ArgumentParser(description="Generate iPXE keymaps")
+    parser.add_argument('--verbose', '-v', action='count', default=0,
+                        help="Increase verbosity")
+    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)
+
+    # Construct remapping
+    remap = KeyRemapping(name=args.layout, source=source, target=target)
+
+    # Output generated code
+    print(remap.code)