1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-22 06:50:18 +03:00

firstboot: Populate XKBLAYOUT and friends as well in vconsole.conf (#36275)

Let's derive XKBLAYOUT and friends from the given keymap and populate
these as well in vconsole.conf so that if the user configures a keymap
it's also respected in display managers such as gdm.
This commit is contained in:
Yu Watanabe 2025-02-07 11:20:21 +09:00 committed by GitHub
commit aedef65e2b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 678 additions and 632 deletions

View File

@ -51,6 +51,7 @@
#include "tmpfile-util.h"
#include "umask-util.h"
#include "user-util.h"
#include "vconsole-util.h"
static char *arg_root = NULL;
static char *arg_image = NULL;
@ -461,7 +462,7 @@ static int prompt_keymap(int rfd) {
static int process_keymap(int rfd) {
_cleanup_close_ int pfd = -EBADF;
_cleanup_free_ char *f = NULL;
char **keymap;
_cleanup_strv_free_ char **keymap = NULL;
int r;
assert(rfd >= 0);
@ -502,7 +503,18 @@ static int process_keymap(int rfd) {
if (isempty(arg_keymap))
return 0;
keymap = STRV_MAKE(strjoina("KEYMAP=", arg_keymap));
VCContext vc = {
.keymap = arg_keymap,
};
_cleanup_(x11_context_clear) X11Context xc = {};
r = vconsole_convert_to_x11(&vc, /* verify= */ NULL, &xc);
if (r < 0)
return log_error_errno(r, "Failed to convert keymap data: %m");
r = vconsole_serialize(&vc, &xc, &keymap);
if (r < 0)
return log_error_errno(r, "Failed to serialize keymap data: %m");
r = write_vconsole_conf(pfd, f, keymap);
if (r < 0)

View File

@ -5,19 +5,15 @@
#include <sys/types.h>
#include <unistd.h>
#include "bus-polkit.h"
#include "copy.h"
#include "env-file-label.h"
#include "env-file.h"
#include "env-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "kbd-util.h"
#include "localed-util.h"
#include "macro.h"
#include "mkdir-label.h"
#include "nulstr-util.h"
#include "process-util.h"
#include "stat-util.h"
#include "string-util.h"
@ -25,134 +21,6 @@
#include "tmpfile-util.h"
#include "xkbcommon-util.h"
static bool startswith_comma(const char *s, const char *prefix) {
assert(s);
assert(prefix);
s = startswith(s, prefix);
if (!s)
return false;
return IN_SET(*s, ',', '\0');
}
static const char* systemd_kbd_model_map(void) {
const char* s;
s = getenv("SYSTEMD_KBD_MODEL_MAP");
if (s)
return s;
return SYSTEMD_KBD_MODEL_MAP;
}
static const char* systemd_language_fallback_map(void) {
const char* s;
s = getenv("SYSTEMD_LANGUAGE_FALLBACK_MAP");
if (s)
return s;
return SYSTEMD_LANGUAGE_FALLBACK_MAP;
}
void x11_context_clear(X11Context *xc) {
assert(xc);
xc->layout = mfree(xc->layout);
xc->options = mfree(xc->options);
xc->model = mfree(xc->model);
xc->variant = mfree(xc->variant);
}
void x11_context_replace(X11Context *dest, X11Context *src) {
assert(dest);
assert(src);
x11_context_clear(dest);
*dest = TAKE_STRUCT(*src);
}
bool x11_context_isempty(const X11Context *xc) {
assert(xc);
return
isempty(xc->layout) &&
isempty(xc->model) &&
isempty(xc->variant) &&
isempty(xc->options);
}
void x11_context_empty_to_null(X11Context *xc) {
assert(xc);
/* Do not call x11_context_clear() for the passed object. */
xc->layout = empty_to_null(xc->layout);
xc->model = empty_to_null(xc->model);
xc->variant = empty_to_null(xc->variant);
xc->options = empty_to_null(xc->options);
}
bool x11_context_is_safe(const X11Context *xc) {
assert(xc);
return
(!xc->layout || string_is_safe(xc->layout)) &&
(!xc->model || string_is_safe(xc->model)) &&
(!xc->variant || string_is_safe(xc->variant)) &&
(!xc->options || string_is_safe(xc->options));
}
bool x11_context_equal(const X11Context *a, const X11Context *b) {
assert(a);
assert(b);
return
streq_ptr(a->layout, b->layout) &&
streq_ptr(a->model, b->model) &&
streq_ptr(a->variant, b->variant) &&
streq_ptr(a->options, b->options);
}
int x11_context_copy(X11Context *dest, const X11Context *src) {
bool modified;
int r;
assert(dest);
if (dest == src)
return 0;
if (!src) {
modified = !x11_context_isempty(dest);
x11_context_clear(dest);
return modified;
}
r = free_and_strdup(&dest->layout, src->layout);
if (r < 0)
return r;
modified = r > 0;
r = free_and_strdup(&dest->model, src->model);
if (r < 0)
return r;
modified = modified || r > 0;
r = free_and_strdup(&dest->variant, src->variant);
if (r < 0)
return r;
modified = modified || r > 0;
r = free_and_strdup(&dest->options, src->options);
if (r < 0)
return r;
modified = modified || r > 0;
return modified;
}
int x11_context_verify_and_warn(const X11Context *xc, int log_level, sd_bus_error *error) {
int r;
@ -182,75 +50,6 @@ int x11_context_verify_and_warn(const X11Context *xc, int log_level, sd_bus_erro
return 0;
}
void vc_context_clear(VCContext *vc) {
assert(vc);
vc->keymap = mfree(vc->keymap);
vc->toggle = mfree(vc->toggle);
}
void vc_context_replace(VCContext *dest, VCContext *src) {
assert(dest);
assert(src);
vc_context_clear(dest);
*dest = TAKE_STRUCT(*src);
}
bool vc_context_isempty(const VCContext *vc) {
assert(vc);
return
isempty(vc->keymap) &&
isempty(vc->toggle);
}
void vc_context_empty_to_null(VCContext *vc) {
assert(vc);
/* Do not call vc_context_clear() for the passed object. */
vc->keymap = empty_to_null(vc->keymap);
vc->toggle = empty_to_null(vc->toggle);
}
bool vc_context_equal(const VCContext *a, const VCContext *b) {
assert(a);
assert(b);
return
streq_ptr(a->keymap, b->keymap) &&
streq_ptr(a->toggle, b->toggle);
}
int vc_context_copy(VCContext *dest, const VCContext *src) {
bool modified;
int r;
assert(dest);
if (dest == src)
return 0;
if (!src) {
modified = !vc_context_isempty(dest);
vc_context_clear(dest);
return modified;
}
r = free_and_strdup(&dest->keymap, src->keymap);
if (r < 0)
return r;
modified = r > 0;
r = free_and_strdup(&dest->toggle, src->toggle);
if (r < 0)
return r;
modified = modified || r > 0;
return modified;
}
static int verify_keymap(const char *keymap, int log_level, sd_bus_error *error) {
int r;
@ -499,27 +298,7 @@ int vconsole_write_data(Context *c) {
if (r < 0 && r != -ENOENT)
return r;
r = strv_env_assign(&l, "KEYMAP", empty_to_null(c->vc.keymap));
if (r < 0)
return r;
r = strv_env_assign(&l, "KEYMAP_TOGGLE", empty_to_null(c->vc.toggle));
if (r < 0)
return r;
r = strv_env_assign(&l, "XKBLAYOUT", empty_to_null(xc->layout));
if (r < 0)
return r;
r = strv_env_assign(&l, "XKBMODEL", empty_to_null(xc->model));
if (r < 0)
return r;
r = strv_env_assign(&l, "XKBVARIANT", empty_to_null(xc->variant));
if (r < 0)
return r;
r = strv_env_assign(&l, "XKBOPTIONS", empty_to_null(xc->options));
r = vconsole_serialize(&c->vc, xc, &l);
if (r < 0)
return r;
@ -599,373 +378,6 @@ int x11_write_data(Context *c) {
return 0;
}
static int read_next_mapping(
const char *filename,
unsigned min_fields,
unsigned max_fields,
FILE *f,
unsigned *n,
char ***ret) {
assert(f);
assert(n);
assert(ret);
for (;;) {
_cleanup_strv_free_ char **b = NULL;
_cleanup_free_ char *line = NULL;
size_t length;
int r;
r = read_stripped_line(f, LONG_LINE_MAX, &line);
if (r < 0)
return r;
if (r == 0)
break;
(*n)++;
if (IN_SET(line[0], 0, '#'))
continue;
r = strv_split_full(&b, line, WHITESPACE, EXTRACT_UNQUOTE);
if (r < 0)
return r;
length = strv_length(b);
if (length < min_fields || length > max_fields) {
log_debug("Invalid line %s:%u, ignoring.", strna(filename), *n);
continue;
}
*ret = TAKE_PTR(b);
return 1;
}
*ret = NULL;
return 0;
}
int vconsole_convert_to_x11(const VCContext *vc, X11Context *ret) {
_cleanup_fclose_ FILE *f = NULL;
const char *map;
X11Context xc;
int r;
assert(vc);
assert(ret);
if (isempty(vc->keymap)) {
*ret = (X11Context) {};
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;
r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
if (r < 0)
return r;
if (r == 0)
break;
if (!streq(vc->keymap, a[0]))
continue;
xc = (X11Context) {
.layout = empty_or_dash_to_null(a[1]),
.model = empty_or_dash_to_null(a[2]),
.variant = empty_or_dash_to_null(a[3]),
.options = empty_or_dash_to_null(a[4]),
};
if (x11_context_verify(&xc) < 0)
continue;
return x11_context_copy(ret, &xc);
}
/* No custom mapping has been found, see if the keymap is a converted one. In such case deducing the
* corresponding x11 layout is easy. */
_cleanup_free_ char *xlayout = NULL, *converted = NULL;
char *xvariant;
xlayout = strdup(vc->keymap);
if (!xlayout)
return -ENOMEM;
xvariant = strchr(xlayout, '-');
if (xvariant) {
xvariant[0] = '\0';
xvariant++;
}
/* Note: by default we use keyboard model "microsoftpro" which should be equivalent to "pc105" but
* with the internet/media key mapping added. */
xc = (X11Context) {
.layout = xlayout,
.model = (char*) "microsoftpro",
.variant = xvariant,
.options = (char*) "terminate:ctrl_alt_bksp",
};
/* This sanity check seems redundant with the verification of the X11 layout done on the next
* step. However xkbcommon is an optional dependency hence the verification might be a NOP. */
r = find_converted_keymap(&xc, &converted);
if (r == 0 && xc.variant) {
/* If we still haven't find a match, try with no variant, it's still better than nothing. */
xc.variant = NULL;
r = find_converted_keymap(&xc, &converted);
}
if (r < 0)
return r;
if (r == 0 || x11_context_verify(&xc) < 0) {
*ret = (X11Context) {};
return 0;
}
return x11_context_copy(ret, &xc);
}
int find_converted_keymap(const X11Context *xc, char **ret) {
_cleanup_free_ char *n = NULL, *p = NULL, *pz = NULL;
_cleanup_strv_free_ char **keymap_dirs = NULL;
int r;
assert(xc);
assert(!isempty(xc->layout));
assert(ret);
if (xc->variant)
n = strjoin(xc->layout, "-", xc->variant);
else
n = strdup(xc->layout);
if (!n)
return -ENOMEM;
p = strjoin("xkb/", n, ".map");
pz = strjoin("xkb/", n, ".map.gz");
if (!p || !pz)
return -ENOMEM;
r = keymap_directories(&keymap_dirs);
if (r < 0)
return r;
STRV_FOREACH(dir, keymap_dirs) {
_cleanup_close_ int dir_fd = -EBADF;
bool uncompressed;
dir_fd = open(*dir, O_CLOEXEC | O_DIRECTORY | O_PATH);
if (dir_fd < 0) {
if (errno != ENOENT)
log_debug_errno(errno, "Failed to open %s, ignoring: %m", *dir);
continue;
}
uncompressed = faccessat(dir_fd, p, F_OK, 0) >= 0;
if (uncompressed || faccessat(dir_fd, pz, F_OK, 0) >= 0) {
log_debug("Found converted keymap %s at %s/%s", n, *dir, uncompressed ? p : pz);
*ret = TAKE_PTR(n);
return 1;
}
}
*ret = NULL;
return 0;
}
int find_legacy_keymap(const X11Context *xc, char **ret) {
const char *map;
_cleanup_fclose_ FILE *f = NULL;
_cleanup_free_ char *new_keymap = NULL;
unsigned best_matching = 0;
int r;
assert(xc);
assert(!isempty(xc->layout));
map = systemd_kbd_model_map();
f = fopen(map, "re");
if (!f)
return -errno;
for (unsigned n = 0;;) {
_cleanup_strv_free_ char **a = NULL;
unsigned matching = 0;
r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
if (r < 0)
return r;
if (r == 0)
break;
/* Determine how well matching this entry is */
if (streq(xc->layout, a[1]))
/* If we got an exact match, this is the best */
matching = 10;
else {
/* see if we get an exact match with the order reversed */
_cleanup_strv_free_ char **b = NULL;
_cleanup_free_ char *c = NULL;
r = strv_split_full(&b, a[1], ",", 0);
if (r < 0)
return r;
strv_reverse(b);
c = strv_join(b, ",");
if (!c)
return log_oom();
if (streq(xc->layout, c))
matching = 9;
else {
/* We have multiple X layouts, look for an
* entry that matches our key with everything
* but the first layout stripped off. */
if (startswith_comma(xc->layout, a[1]))
matching = 5;
else {
_cleanup_free_ char *x = NULL;
/* If that didn't work, strip off the
* other layouts from the entry, too */
x = strdupcspn(a[1], ",");
if (!x)
return -ENOMEM;
if (startswith_comma(xc->layout, x))
matching = 1;
}
}
}
if (matching > 0) {
if (isempty(xc->model) || streq_ptr(xc->model, a[2])) {
matching++;
if (streq_ptr(xc->variant, a[3]) || ((isempty(xc->variant) || streq_skip_trailing_chars(xc->variant, "", ",")) && streq(a[3], "-"))) {
matching++;
if (streq_ptr(xc->options, a[4]))
matching++;
}
}
}
/* The best matching entry so far, then let's save that */
if (matching >= MAX(best_matching, 1u)) {
log_debug("Found legacy keymap %s with score %u", a[0], matching);
if (matching > best_matching) {
best_matching = matching;
r = free_and_strdup(&new_keymap, a[0]);
if (r < 0)
return r;
}
}
}
if (best_matching < 9 && !isempty(xc->layout)) {
_cleanup_free_ char *l = NULL, *v = NULL, *converted = NULL;
/* The best match is only the first part of the X11
* keymap. Check if we have a converted map which
* matches just the first layout.
*/
l = strdupcspn(xc->layout, ",");
if (!l)
return -ENOMEM;
if (!isempty(xc->variant)) {
v = strdupcspn(xc->variant, ",");
if (!v)
return -ENOMEM;
}
r = find_converted_keymap(
&(X11Context) {
.layout = l,
.variant = v,
},
&converted);
if (r < 0)
return r;
if (r > 0)
free_and_replace(new_keymap, converted);
}
*ret = TAKE_PTR(new_keymap);
return !!*ret;
}
int x11_convert_to_vconsole(const X11Context *xc, VCContext *ret) {
_cleanup_free_ char *keymap = NULL;
int r;
assert(xc);
assert(ret);
if (isempty(xc->layout)) {
*ret = (VCContext) {};
return 0;
}
r = find_converted_keymap(xc, &keymap);
if (r == 0) {
r = find_legacy_keymap(xc, &keymap);
if (r == 0 && xc->variant)
/* If we still haven't find a match, try with no variant, it's still better than
* nothing. */
r = find_converted_keymap(
&(X11Context) {
.layout = xc->layout,
},
&keymap);
}
if (r < 0)
return r;
*ret = (VCContext) {
.keymap = TAKE_PTR(keymap),
};
return 0;
}
int find_language_fallback(const char *lang, char **ret) {
const char *map;
_cleanup_fclose_ FILE *f = NULL;
unsigned n = 0;
int r;
assert(lang);
assert(ret);
map = systemd_language_fallback_map();
f = fopen(map, "re");
if (!f)
return -errno;
for (;;) {
_cleanup_strv_free_ char **a = NULL;
r = read_next_mapping(map, 2, 2, f, &n, &a);
if (r <= 0)
return r;
if (streq(lang, a[0])) {
assert(strv_length(a) == 2);
*ret = TAKE_PTR(a[1]);
return 1;
}
}
}
bool locale_gen_check_available(void) {
#if HAVE_LOCALEGEN
if (access(LOCALEGEN_PATH, X_OK) < 0) {

View File

@ -7,18 +7,7 @@
#include "hashmap.h"
#include "locale-setup.h"
typedef struct X11Context {
char *layout;
char *model;
char *variant;
char *options;
} X11Context;
typedef struct VCContext {
char *keymap;
char *toggle;
} VCContext;
#include "vconsole-util.h"
typedef struct Context {
sd_bus_message *locale_cache;
@ -36,13 +25,6 @@ typedef struct Context {
Hashmap *polkit_registry;
} Context;
void x11_context_clear(X11Context *xc);
void x11_context_replace(X11Context *dest, X11Context *src);
bool x11_context_isempty(const X11Context *xc);
void x11_context_empty_to_null(X11Context *xc);
bool x11_context_is_safe(const X11Context *xc);
bool x11_context_equal(const X11Context *a, const X11Context *b);
int x11_context_copy(X11Context *dest, const X11Context *src);
int x11_context_verify_and_warn(const X11Context *xc, int log_level, sd_bus_error *error);
static inline int x11_context_verify(const X11Context *xc) {
return x11_context_verify_and_warn(xc, LOG_DEBUG, NULL);
@ -50,29 +32,17 @@ static inline int x11_context_verify(const X11Context *xc) {
X11Context *context_get_x11_context(Context *c);
void vc_context_clear(VCContext *vc);
void vc_context_replace(VCContext *dest, VCContext *src);
bool vc_context_isempty(const VCContext *vc);
void vc_context_empty_to_null(VCContext *vc);
bool vc_context_equal(const VCContext *a, const VCContext *b);
int vc_context_copy(VCContext *dest, const VCContext *src);
int vc_context_verify_and_warn(const VCContext *vc, int log_level, sd_bus_error *error);
static inline int vc_context_verify(const VCContext *vc) {
return vc_context_verify_and_warn(vc, LOG_DEBUG, NULL);
}
int find_converted_keymap(const X11Context *xc, char **ret);
int find_legacy_keymap(const X11Context *xc, char **ret);
int find_language_fallback(const char *lang, char **ret);
int locale_read_data(Context *c, sd_bus_message *m);
int vconsole_read_data(Context *c, sd_bus_message *m);
int x11_read_data(Context *c, sd_bus_message *m);
void context_clear(Context *c);
int vconsole_convert_to_x11(const VCContext *vc, X11Context *ret);
int vconsole_write_data(Context *c);
int x11_convert_to_vconsole(const X11Context *xc, VCContext *ret);
int x11_write_data(Context *c);
bool locale_gen_check_available(void);

View File

@ -362,7 +362,7 @@ static int method_set_vc_keyboard(sd_bus_message *m, void *userdata, sd_bus_erro
}
if (convert) {
r = vconsole_convert_to_x11(&in, &converted);
r = vconsole_convert_to_x11(&in, x11_context_verify, &converted);
if (r < 0) {
log_error_errno(r, "Failed to convert keymap data: %m");
return sd_bus_error_set_errnof(error, r, "Failed to convert keymap data: %m");

View File

@ -77,40 +77,40 @@ TEST(vconsole_convert_to_x11) {
int r;
log_info("/* test empty keymap */");
assert_se(vconsole_convert_to_x11(&vc, &xc) >= 0);
assert_se(vconsole_convert_to_x11(&vc, x11_context_verify, &xc) >= 0);
assert_se(x11_context_isempty(&xc));
log_info("/* test without variant, new mapping (es:) */");
assert_se(free_and_strdup(&vc.keymap, "es") >= 0);
assert_se(vconsole_convert_to_x11(&vc, &xc) >= 0);
assert_se(vconsole_convert_to_x11(&vc, x11_context_verify, &xc) >= 0);
assert_se(streq(xc.layout, "es"));
assert_se(xc.variant == NULL);
x11_context_clear(&xc);
log_info("/* test with known variant, new mapping (es:dvorak) */");
assert_se(free_and_strdup(&vc.keymap, "es-dvorak") >= 0);
assert_se(vconsole_convert_to_x11(&vc, &xc) >= 0);
assert_se(vconsole_convert_to_x11(&vc, x11_context_verify, &xc) >= 0);
assert_se(streq(xc.layout, "es"));
assert_se(streq(xc.variant, "dvorak"));
x11_context_clear(&xc);
log_info("/* test with old mapping (fr:latin9) */");
assert_se(free_and_strdup(&vc.keymap, "fr-latin9") >= 0);
assert_se(vconsole_convert_to_x11(&vc, &xc) >= 0);
assert_se(vconsole_convert_to_x11(&vc, x11_context_verify, &xc) >= 0);
assert_se(streq(xc.layout, "fr"));
assert_se(streq(xc.variant, "latin9"));
x11_context_clear(&xc);
log_info("/* test with a compound mapping (ru,us) */");
assert_se(free_and_strdup(&vc.keymap, "ru") >= 0);
assert_se(vconsole_convert_to_x11(&vc, &xc) >= 0);
assert_se(vconsole_convert_to_x11(&vc, x11_context_verify, &xc) >= 0);
assert_se(streq(xc.layout, "ru,us"));
assert_se(xc.variant == NULL);
x11_context_clear(&xc);
log_info("/* test with a simple mapping (us) */");
assert_se(free_and_strdup(&vc.keymap, "us") >= 0);
assert_se(vconsole_convert_to_x11(&vc, &xc) >= 0);
assert_se(vconsole_convert_to_x11(&vc, x11_context_verify, &xc) >= 0);
assert_se(streq(xc.layout, "us"));
assert_se(xc.variant == NULL);
x11_context_clear(&xc);
@ -118,7 +118,7 @@ TEST(vconsole_convert_to_x11) {
/* "gh" has no mapping in kbd-model-map and kbd provides a converted keymap for this layout. */
log_info("/* test with a converted keymap (gh:) */");
assert_se(free_and_strdup(&vc.keymap, "gh") >= 0);
r = vconsole_convert_to_x11(&vc, &xc);
r = vconsole_convert_to_x11(&vc, x11_context_verify, &xc);
if (r == 0) {
log_info("Skipping rest of %s: keymaps are not installed", __func__);
return;
@ -130,14 +130,14 @@ TEST(vconsole_convert_to_x11) {
log_info("/* test with converted keymap and with a known variant (gh:ewe) */");
assert_se(free_and_strdup(&vc.keymap, "gh-ewe") >= 0);
assert_se(vconsole_convert_to_x11(&vc, &xc) > 0);
assert_se(vconsole_convert_to_x11(&vc, x11_context_verify, &xc) > 0);
assert_se(streq(xc.layout, "gh"));
assert_se(streq(xc.variant, "ewe"));
x11_context_clear(&xc);
log_info("/* test with converted keymap and with an unknown variant (gh:ewe) */");
assert_se(free_and_strdup(&vc.keymap, "gh-foobar") > 0);
assert_se(vconsole_convert_to_x11(&vc, &xc) > 0);
assert_se(vconsole_convert_to_x11(&vc, x11_context_verify, &xc) > 0);
assert_se(streq(xc.layout, "gh"));
assert_se(xc.variant == NULL);
x11_context_clear(&xc);

View File

@ -201,6 +201,7 @@ shared_sources = files(
'varlink-io.systemd.service.c',
'varlink-io.systemd.sysext.c',
'varlink-serialize.c',
'vconsole-util.c',
'verb-log-control.c',
'verbs.c',
'vlan-util.c',

609
src/shared/vconsole-util.c Normal file
View File

@ -0,0 +1,609 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "env-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "kbd-util.h"
#include "string-util.h"
#include "strv.h"
#include "vconsole-util.h"
static bool startswith_comma(const char *s, const char *prefix) {
assert(s);
assert(prefix);
s = startswith(s, prefix);
if (!s)
return false;
return IN_SET(*s, ',', '\0');
}
static const char* systemd_kbd_model_map(void) {
const char* s;
s = getenv("SYSTEMD_KBD_MODEL_MAP");
if (s)
return s;
return SYSTEMD_KBD_MODEL_MAP;
}
static const char* systemd_language_fallback_map(void) {
const char* s;
s = getenv("SYSTEMD_LANGUAGE_FALLBACK_MAP");
if (s)
return s;
return SYSTEMD_LANGUAGE_FALLBACK_MAP;
}
void x11_context_clear(X11Context *xc) {
assert(xc);
xc->layout = mfree(xc->layout);
xc->options = mfree(xc->options);
xc->model = mfree(xc->model);
xc->variant = mfree(xc->variant);
}
void x11_context_replace(X11Context *dest, X11Context *src) {
assert(dest);
assert(src);
x11_context_clear(dest);
*dest = TAKE_STRUCT(*src);
}
bool x11_context_isempty(const X11Context *xc) {
assert(xc);
return
isempty(xc->layout) &&
isempty(xc->model) &&
isempty(xc->variant) &&
isempty(xc->options);
}
void x11_context_empty_to_null(X11Context *xc) {
assert(xc);
/* Do not call x11_context_clear() for the passed object. */
xc->layout = empty_to_null(xc->layout);
xc->model = empty_to_null(xc->model);
xc->variant = empty_to_null(xc->variant);
xc->options = empty_to_null(xc->options);
}
bool x11_context_is_safe(const X11Context *xc) {
assert(xc);
return
(!xc->layout || string_is_safe(xc->layout)) &&
(!xc->model || string_is_safe(xc->model)) &&
(!xc->variant || string_is_safe(xc->variant)) &&
(!xc->options || string_is_safe(xc->options));
}
bool x11_context_equal(const X11Context *a, const X11Context *b) {
assert(a);
assert(b);
return
streq_ptr(a->layout, b->layout) &&
streq_ptr(a->model, b->model) &&
streq_ptr(a->variant, b->variant) &&
streq_ptr(a->options, b->options);
}
int x11_context_copy(X11Context *dest, const X11Context *src) {
bool modified;
int r;
assert(dest);
if (dest == src)
return 0;
if (!src) {
modified = !x11_context_isempty(dest);
x11_context_clear(dest);
return modified;
}
r = free_and_strdup(&dest->layout, src->layout);
if (r < 0)
return r;
modified = r > 0;
r = free_and_strdup(&dest->model, src->model);
if (r < 0)
return r;
modified = modified || r > 0;
r = free_and_strdup(&dest->variant, src->variant);
if (r < 0)
return r;
modified = modified || r > 0;
r = free_and_strdup(&dest->options, src->options);
if (r < 0)
return r;
modified = modified || r > 0;
return modified;
}
void vc_context_clear(VCContext *vc) {
assert(vc);
vc->keymap = mfree(vc->keymap);
vc->toggle = mfree(vc->toggle);
}
void vc_context_replace(VCContext *dest, VCContext *src) {
assert(dest);
assert(src);
vc_context_clear(dest);
*dest = TAKE_STRUCT(*src);
}
bool vc_context_isempty(const VCContext *vc) {
assert(vc);
return
isempty(vc->keymap) &&
isempty(vc->toggle);
}
void vc_context_empty_to_null(VCContext *vc) {
assert(vc);
/* Do not call vc_context_clear() for the passed object. */
vc->keymap = empty_to_null(vc->keymap);
vc->toggle = empty_to_null(vc->toggle);
}
bool vc_context_equal(const VCContext *a, const VCContext *b) {
assert(a);
assert(b);
return
streq_ptr(a->keymap, b->keymap) &&
streq_ptr(a->toggle, b->toggle);
}
int vc_context_copy(VCContext *dest, const VCContext *src) {
bool modified;
int r;
assert(dest);
if (dest == src)
return 0;
if (!src) {
modified = !vc_context_isempty(dest);
vc_context_clear(dest);
return modified;
}
r = free_and_strdup(&dest->keymap, src->keymap);
if (r < 0)
return r;
modified = r > 0;
r = free_and_strdup(&dest->toggle, src->toggle);
if (r < 0)
return r;
modified = modified || r > 0;
return modified;
}
static int read_next_mapping(
const char *filename,
unsigned min_fields,
unsigned max_fields,
FILE *f,
unsigned *n,
char ***ret) {
assert(f);
assert(n);
assert(ret);
for (;;) {
_cleanup_strv_free_ char **b = NULL;
_cleanup_free_ char *line = NULL;
size_t length;
int r;
r = read_stripped_line(f, LONG_LINE_MAX, &line);
if (r < 0)
return r;
if (r == 0)
break;
(*n)++;
if (IN_SET(line[0], 0, '#'))
continue;
r = strv_split_full(&b, line, WHITESPACE, EXTRACT_UNQUOTE);
if (r < 0)
return r;
length = strv_length(b);
if (length < min_fields || length > max_fields) {
log_debug("Invalid line %s:%u, ignoring.", strna(filename), *n);
continue;
}
*ret = TAKE_PTR(b);
return 1;
}
*ret = NULL;
return 0;
}
int vconsole_convert_to_x11(const VCContext *vc, X11VerifyCallback verify, X11Context *ret) {
_cleanup_fclose_ FILE *f = NULL;
const char *map;
X11Context xc;
int r;
assert(vc);
assert(ret);
if (isempty(vc->keymap)) {
*ret = (X11Context) {};
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;
r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
if (r < 0)
return r;
if (r == 0)
break;
if (!streq(vc->keymap, a[0]))
continue;
xc = (X11Context) {
.layout = empty_or_dash_to_null(a[1]),
.model = empty_or_dash_to_null(a[2]),
.variant = empty_or_dash_to_null(a[3]),
.options = empty_or_dash_to_null(a[4]),
};
if (verify && verify(&xc) < 0)
continue;
return x11_context_copy(ret, &xc);
}
/* No custom mapping has been found, see if the keymap is a converted one. In such case deducing the
* corresponding x11 layout is easy. */
_cleanup_free_ char *xlayout = NULL, *converted = NULL;
char *xvariant;
xlayout = strdup(vc->keymap);
if (!xlayout)
return -ENOMEM;
xvariant = strchr(xlayout, '-');
if (xvariant) {
xvariant[0] = '\0';
xvariant++;
}
/* Note: by default we use keyboard model "microsoftpro" which should be equivalent to "pc105" but
* with the internet/media key mapping added. */
xc = (X11Context) {
.layout = xlayout,
.model = (char*) "microsoftpro",
.variant = xvariant,
.options = (char*) "terminate:ctrl_alt_bksp",
};
/* This sanity check seems redundant with the verification of the X11 layout done on the next
* step. However xkbcommon is an optional dependency hence the verification might be a NOP. */
r = find_converted_keymap(&xc, &converted);
if (r == 0 && xc.variant) {
/* If we still haven't find a match, try with no variant, it's still better than nothing. */
xc.variant = NULL;
r = find_converted_keymap(&xc, &converted);
}
if (r < 0)
return r;
if (r == 0 || (verify && verify(&xc) < 0)) {
*ret = (X11Context) {};
return 0;
}
return x11_context_copy(ret, &xc);
}
int find_converted_keymap(const X11Context *xc, char **ret) {
_cleanup_free_ char *n = NULL, *p = NULL, *pz = NULL;
_cleanup_strv_free_ char **keymap_dirs = NULL;
int r;
assert(xc);
assert(!isempty(xc->layout));
assert(ret);
if (xc->variant)
n = strjoin(xc->layout, "-", xc->variant);
else
n = strdup(xc->layout);
if (!n)
return -ENOMEM;
p = strjoin("xkb/", n, ".map");
pz = strjoin("xkb/", n, ".map.gz");
if (!p || !pz)
return -ENOMEM;
r = keymap_directories(&keymap_dirs);
if (r < 0)
return r;
STRV_FOREACH(dir, keymap_dirs) {
_cleanup_close_ int dir_fd = -EBADF;
bool uncompressed;
dir_fd = open(*dir, O_CLOEXEC | O_DIRECTORY | O_PATH);
if (dir_fd < 0) {
if (errno != ENOENT)
log_debug_errno(errno, "Failed to open %s, ignoring: %m", *dir);
continue;
}
uncompressed = faccessat(dir_fd, p, F_OK, 0) >= 0;
if (uncompressed || faccessat(dir_fd, pz, F_OK, 0) >= 0) {
log_debug("Found converted keymap %s at %s/%s", n, *dir, uncompressed ? p : pz);
*ret = TAKE_PTR(n);
return 1;
}
}
*ret = NULL;
return 0;
}
int find_legacy_keymap(const X11Context *xc, char **ret) {
const char *map;
_cleanup_fclose_ FILE *f = NULL;
_cleanup_free_ char *new_keymap = NULL;
unsigned best_matching = 0;
int r;
assert(xc);
assert(!isempty(xc->layout));
map = systemd_kbd_model_map();
f = fopen(map, "re");
if (!f)
return -errno;
for (unsigned n = 0;;) {
_cleanup_strv_free_ char **a = NULL;
unsigned matching = 0;
r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
if (r < 0)
return r;
if (r == 0)
break;
/* Determine how well matching this entry is */
if (streq(xc->layout, a[1]))
/* If we got an exact match, this is the best */
matching = 10;
else {
/* see if we get an exact match with the order reversed */
_cleanup_strv_free_ char **b = NULL;
_cleanup_free_ char *c = NULL;
r = strv_split_full(&b, a[1], ",", 0);
if (r < 0)
return r;
strv_reverse(b);
c = strv_join(b, ",");
if (!c)
return log_oom();
if (streq(xc->layout, c))
matching = 9;
else {
/* We have multiple X layouts, look for an
* entry that matches our key with everything
* but the first layout stripped off. */
if (startswith_comma(xc->layout, a[1]))
matching = 5;
else {
_cleanup_free_ char *x = NULL;
/* If that didn't work, strip off the
* other layouts from the entry, too */
x = strdupcspn(a[1], ",");
if (!x)
return -ENOMEM;
if (startswith_comma(xc->layout, x))
matching = 1;
}
}
}
if (matching > 0) {
if (isempty(xc->model) || streq_ptr(xc->model, a[2])) {
matching++;
if (streq_ptr(xc->variant, a[3]) || ((isempty(xc->variant) || streq_skip_trailing_chars(xc->variant, "", ",")) && streq(a[3], "-"))) {
matching++;
if (streq_ptr(xc->options, a[4]))
matching++;
}
}
}
/* The best matching entry so far, then let's save that */
if (matching >= MAX(best_matching, 1u)) {
log_debug("Found legacy keymap %s with score %u", a[0], matching);
if (matching > best_matching) {
best_matching = matching;
r = free_and_strdup(&new_keymap, a[0]);
if (r < 0)
return r;
}
}
}
if (best_matching < 9 && !isempty(xc->layout)) {
_cleanup_free_ char *l = NULL, *v = NULL, *converted = NULL;
/* The best match is only the first part of the X11
* keymap. Check if we have a converted map which
* matches just the first layout.
*/
l = strdupcspn(xc->layout, ",");
if (!l)
return -ENOMEM;
if (!isempty(xc->variant)) {
v = strdupcspn(xc->variant, ",");
if (!v)
return -ENOMEM;
}
r = find_converted_keymap(
&(X11Context) {
.layout = l,
.variant = v,
},
&converted);
if (r < 0)
return r;
if (r > 0)
free_and_replace(new_keymap, converted);
}
*ret = TAKE_PTR(new_keymap);
return !!*ret;
}
int x11_convert_to_vconsole(const X11Context *xc, VCContext *ret) {
_cleanup_free_ char *keymap = NULL;
int r;
assert(xc);
assert(ret);
if (isempty(xc->layout)) {
*ret = (VCContext) {};
return 0;
}
r = find_converted_keymap(xc, &keymap);
if (r == 0) {
r = find_legacy_keymap(xc, &keymap);
if (r == 0 && xc->variant)
/* If we still haven't find a match, try with no variant, it's still better than
* nothing. */
r = find_converted_keymap(
&(X11Context) {
.layout = xc->layout,
},
&keymap);
}
if (r < 0)
return r;
*ret = (VCContext) {
.keymap = TAKE_PTR(keymap),
};
return 0;
}
int find_language_fallback(const char *lang, char **ret) {
const char *map;
_cleanup_fclose_ FILE *f = NULL;
unsigned n = 0;
int r;
assert(lang);
assert(ret);
map = systemd_language_fallback_map();
f = fopen(map, "re");
if (!f)
return -errno;
for (;;) {
_cleanup_strv_free_ char **a = NULL;
r = read_next_mapping(map, 2, 2, f, &n, &a);
if (r <= 0)
return r;
if (streq(lang, a[0])) {
assert(strv_length(a) == 2);
*ret = TAKE_PTR(a[1]);
return 1;
}
}
}
int vconsole_serialize(const VCContext *vc, const X11Context *xc, char ***env) {
int r;
/* This function modifies the passed strv in place. */
assert(vc);
assert(xc);
assert(env);
r = strv_env_assign(env, "KEYMAP", empty_to_null(vc->keymap));
if (r < 0)
return r;
r = strv_env_assign(env, "KEYMAP_TOGGLE", empty_to_null(vc->toggle));
if (r < 0)
return r;
r = strv_env_assign(env, "XKBLAYOUT", empty_to_null(xc->layout));
if (r < 0)
return r;
r = strv_env_assign(env, "XKBMODEL", empty_to_null(xc->model));
if (r < 0)
return r;
r = strv_env_assign(env, "XKBVARIANT", empty_to_null(xc->variant));
if (r < 0)
return r;
r = strv_env_assign(env, "XKBOPTIONS", empty_to_null(xc->options));
if (r < 0)
return r;
return 0;
}

View File

@ -0,0 +1,42 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <stdbool.h>
typedef struct X11Context {
char *layout;
char *model;
char *variant;
char *options;
} X11Context;
typedef struct VCContext {
char *keymap;
char *toggle;
} VCContext;
void x11_context_clear(X11Context *xc);
void x11_context_replace(X11Context *dest, X11Context *src);
bool x11_context_isempty(const X11Context *xc);
void x11_context_empty_to_null(X11Context *xc);
bool x11_context_is_safe(const X11Context *xc);
bool x11_context_equal(const X11Context *a, const X11Context *b);
int x11_context_copy(X11Context *dest, const X11Context *src);
int find_converted_keymap(const X11Context *xc, char **ret);
int find_legacy_keymap(const X11Context *xc, char **ret);
int find_language_fallback(const char *lang, char **ret);
void vc_context_clear(VCContext *vc);
void vc_context_replace(VCContext *dest, VCContext *src);
bool vc_context_isempty(const VCContext *vc);
void vc_context_empty_to_null(VCContext *vc);
bool vc_context_equal(const VCContext *a, const VCContext *b);
int vc_context_copy(VCContext *dest, const VCContext *src);
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 vconsole_serialize(const VCContext *vc, const X11Context *xc, char ***ret);