boot,vconsole: Propagate UEFI HII keyboard layout to the OS

UEFI firmware can report the currently-active keyboard layout via
EFI_HII_DATABASE_PROTOCOL.GetKeyboardLayout(). The layout descriptor
includes an RFC 4646 / BCP 47 language tag (e.g. "en-US"). Query this
from sd-boot/sd-stub and write it to a new LoaderKeyboardLayout EFI
variable, advertised through a new EFI_LOADER_FEATURE_KEYBOARD_LAYOUT
feature bit.

On the OS side, systemd-vconsole-setup reads the variable as a
lowest-priority fallback for the console keymap. To map the BCP 47
tag to a vconsole keymap we extend /usr/share/systemd/kbd-model-map
with an optional sixth column listing the comma-separated BCP 47 tags
each row covers; a new find_vconsole_keymap_for_bcp47() helper walks
the file, preferring an exact tag match and otherwise falling back to
the row whose tag matches the input's primary subtag. Credentials,
/etc/vconsole.conf, and vconsole.keymap= on the kernel command line
continue to take precedence.

bootctl status surfaces the new variable, printing the language tag
or "n/a (not reported by firmware)" when sd-boot advertises the
feature but the firmware HII database didn't expose a layout (common
on QEMU without a USB keyboard, since EDK2's PS/2 driver does not
register an HII keyboard layout).
This commit is contained in:
Daan De Meyer
2026-05-11 21:10:11 +02:00
committed by Daan De Meyer
parent 6f49c7e598
commit 7b9d76cba7
15 changed files with 494 additions and 111 deletions
+77
View File
@@ -578,6 +578,83 @@ int find_language_fallback(const char *lang, char **ret) {
}
}
int find_vconsole_keymap_for_bcp47(const char *tag, char **ret) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_free_ char *fallback = NULL;
const char *map;
int r;
/* Look up a vconsole keymap by RFC 4646 / BCP 47 language tag (e.g. "de-DE") using the optional
* sixth column of /usr/share/systemd/kbd-model-map. That column lists comma-separated tags the
* row matches. An exact (case-insensitive) tag match returns immediately; if no exact match
* exists, the first row whose tag matches the input's primary subtag wins. Returns 1 on match,
* 0 otherwise. */
assert(tag);
assert(ret);
if (isempty(tag)) {
*ret = NULL;
return 0;
}
size_t primary_len = strcspn(tag, "-");
if (primary_len == 0) {
*ret = NULL;
return 0;
}
map = systemd_kbd_model_map();
f = fopen(map, "re");
if (!f)
return -errno;
for (unsigned n = 0;;) {
_cleanup_strv_free_ char **a = NULL, **tags = NULL;
r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
if (r < 0)
return r;
if (r == 0)
break;
/* The BCP 47 tag list is the optional 6th column. "-" / empty means "no tags". */
if (strv_length(a) < 6 || isempty(a[5]) || streq(a[5], "-"))
continue;
r = strv_split_full(&tags, a[5], ",", /* flags= */ 0);
if (r < 0)
return r;
STRV_FOREACH(t, tags) {
if (strcaseeq(*t, tag)) {
log_debug("Found vconsole keymap '%s' for BCP 47 tag '%s' (exact match).",
a[0], tag);
r = strdup_to(ret, a[0]);
if (r < 0)
return r;
return 1;
}
if (!fallback && strlen(*t) == primary_len && !strchr(*t, '-') && strncaseeq(*t, tag, primary_len)) {
fallback = strdup(a[0]);
if (!fallback)
return -ENOMEM;
}
}
}
if (!fallback) {
*ret = NULL;
return 0;
}
log_debug("Found vconsole keymap '%s' for BCP 47 tag '%s' (primary subtag match).", fallback, tag);
*ret = TAKE_PTR(fallback);
return 1;
}
int vconsole_serialize(const VCContext *vc, const X11Context *xc, char ***env) {
int r;
+2
View File
@@ -39,4 +39,6 @@ typedef int (*X11VerifyCallback)(const X11Context *xc);
int vconsole_convert_to_x11(const VCContext *vc, X11VerifyCallback verify, X11Context *ret);
int x11_convert_to_vconsole(const X11Context *xc, VCContext *ret);
int find_vconsole_keymap_for_bcp47(const char *tag, char **ret);
int vconsole_serialize(const VCContext *vc, const X11Context *xc, char ***env);