Skip to contentSkip to conversation

Allow to convert xkb keymap to kernel keymap

PATCH New Alexey Gladkov 1 participant 5 messages Jan 4, 2024

lore
Share & tools
lore
#0 lore raw

cover Allow to convert xkb keymap to kernel keymap

This patchset adds the ability to use keymaps from libxkbcommon. It gives the loadkeys utility additional options for passing xkb-specific parameters.

The idea is to make it possible to unify the keymap configuration between linux console and xorg/wayland and unification of the keymaps configuration method.

Unfortunately, we cannot use xkb keymap one to one. The linux kernel has a number of limitations that allow the use of xkb keymaps.

Many thanks to Anton Zinoviev who helped me figure out how console-setup solved the problems of converting xkb to linux keymaps.

Alexey Gladkov (4):
Add keymap generation from the xkb database
xkbsupport: Create a list of used codes
xkbsupport: Extract translation table
xkbsupport: Add xkb compose

 configure.ac        |  10 +
 data/xkbtrans/names | 154 ++++++++++
 src/Makefile.am     |   6 +
 src/loadkeys.c      |  80 ++++-
 src/loadkeys.h      |  13 +
 src/xkbsupport.c    | 693 ++++++++++++++++++++++++++++++++++++++++++++
 src/xkbsupport.h    |  18 ++
 7 files changed, 965 insertions(+), 9 deletions(-)
 create mode 100644 data/xkbtrans/names
 create mode 100644 src/loadkeys.h
 create mode 100644 src/xkbsupport.c
 create mode 100644 src/xkbsupport.h
Signature · 1 line
2.43.0
#1 lore raw

PATCH 1/4 Add keymap generation from the xkb database

Xorg/Wayland uses its own keyboard configuration mechanism. Because of this, the configuration of the text console and Xorg layouts may differ. Not all combinations of Xorg layouts have a keymap in the kbd database. It's inconvenient for users.

To solve this we can try converting the Xorg layout to a text console keymap.

To access the XKB (X Keyboard Extension) database, you can use libxkbcommon, which does not depend on the X11 libraries.

libxkbcommon is a keyboard keymap compiler and support library which processes a reduced subset of keymaps as defined by the XKB specification [1].

libxkbcommon does not distribute a keymap dataset itself, other than for testing purposes. The most common dataset is xkeyboard-config, which is used by all current distributions for their X11 XKB data [2].

There is a project that uses a similar approach to unify configurations. This is console-setup [3].

I talked with Anton Zinoviev and he helped me figure out their idea of switching multiple layouts with a single key combination:

Quoted 2 lines

how you solved the problem of switching more than two layouts with one key combination ?

The keys with ISO_Next_Group, for example the right Alt (keynumber 100), are defined like this:

keycode 100 = ShiftL_lock
ShiftL keycode 100 = ShiftR_lock
ShiftR keycode 100 = ShiftR_lock
ShiftL ShiftR keycode 100 = ShiftL_lock

So this key will work in the following way:

no modifiers are active -> ShiftL is active
ShiftL is active -> ShiftL and ShiftR are active
ShiftL and ShiftR are active -> ShiftR is active
ShiftR is active -> no modifiers are active

The groups (the layouts) are placed in the following way:

active modifiers  1 group    2 groups    3 groups    4 groups
----------------  -------    --------    --------    --------
no modifier       group 1    group 1     group 1     group 1
ShiftL            group 1    group 2     group 2     group 2
ShiftR            group 1    group 2     group 3     group 4
ShiftL+ShiftR     group 1    group 1     group 1     group 3

In this way we have the following cycles:

1 group: gr1->gr1->gr1->...
2 groups: gr1->gr2->gr1->gr2->...
3 groups: gr1->gr2->gr1->gr4->gr1->gr2->gr1->gr4->... 4 groups: gr1->gr2->gr3->gr4->gr1->gr2->gr3->gr4->...

--
Anton Zinoviev

The difference with this implementation is that I am not trying to read and parse the XKB base files. I analyze the resulting xkb keymap.

[1] https://xkbcommon.org/doc/current/index.html
[2] https://www.freedesktop.org/wiki/Software/XKeyboardConfig/
[3] https://salsa.debian.org/installer-team/console-setup.git

---
 configure.ac     |  10 +
 src/Makefile.am  |   6 +
 src/loadkeys.c   |  64 +++++-
 src/loadkeys.h   |  13 ++
 src/xkbsupport.c | 513 +++++++++++++++++++++++++++++++++++++++++++++++
 src/xkbsupport.h |  16 ++
 6 files changed, 613 insertions(+), 9 deletions(-)
 create mode 100644 src/loadkeys.h
 create mode 100644 src/xkbsupport.c
 create mode 100644 src/xkbsupport.h
configure.ac +10 -0
index a4be8383..f57317e5 100644
@@ -179,6 +179,15 @@ AM_CONDITIONAL(BUILD_TESTS, test "$build_tests" != "no")
 AC_CHECK_PROG([HAVE_DOXYGEN], [doxygen], [yes], [no])
 AM_CONDITIONAL(HAVE_DOXYGEN, test "$HAVE_DOXYGEN" = "yes")
 
+AC_ARG_ENABLE([xkb],
+	[AS_HELP_STRING([--enable-xkb],
+			[allow to generate kernel keymaps based on xkb database @<:@default=yes@:>@])],
+	[USE_XKB=$enableval],[USE_XKB=yes])
+AS_IF([test "x$USE_XKB" != xno],
+	[PKG_CHECK_MODULES(XKBCOMMON, xkbcommon, [HAVE_XKBCOMMON=yes], [HAVE_XKBCOMMON=no])],
+	[USE_XKB=no])
+AM_CONDITIONAL(USE_XKB, test "$USE_XKB" = "yes")
+
 AC_CONFIG_FILES([Makefile
 		data/Makefile
 		docs/Makefile
@@ -228,4 +237,5 @@ AC_MSG_RESULT([
 	libkbdfile:             ${BUILD_LIBKBDFILE}
 	libkeymap:              ${BUILD_LIBKEYMAP}
 	libkfont:               ${BUILD_LIBKFONT}
+	xkb support:            ${USE_XKB}
 ])
src/Makefile.am +6 -0
index c495883d..7e4757e5 100644
@@ -78,6 +78,12 @@ LDADD = \
 	@LIBINTL@ \
 	$(CODE_COVERAGE_LIBS)
 
+if USE_XKB
+loadkeys_SOURCES += xkbsupport.c
+loadkeys_CFLAGS = -DUSE_XKB @XKBCOMMON_CFLAGS@
+LDADD += @XKBCOMMON_LIBS@
+endif
+
 install-exec-hook:
 	for i in psfaddtable psfgettable psfstriptable; do \
 		rm -f $(DESTDIR)$(bindir)/$$i; \
src/loadkeys.c +55 -9

103 lines not on this page · raw message

src/loadkeys.h new file +13 -0
new file mode 100644
index 00000000..d6ff6e7c
@@ -0,0 +1,13 @@
+#ifndef __LOADKEYS_H__
+#define __LOADKEYS_H__
+
+enum options {
+	OPT_A = (1 << 1),
+	OPT_B = (1 << 2),
+	OPT_D = (1 << 3),
+	OPT_M = (1 << 4),
+	OPT_U = (1 << 5),
+	OPT_P = (1 << 6)
+};
+
+#endif /* __LOADKEYS_H__ */
src/xkbsupport.c new file +513 -0

513 lines not on this page · raw message

src/xkbsupport.h new file +16 -0
new file mode 100644
index 00000000..e12e39f6
@@ -0,0 +1,16 @@
+#ifndef _XKB_SUPPORT_H_
+#define _XKB_SUPPORT_H_
+
+#include <xkbcommon/xkbcommon.h>
+#include "keymap.h"
+
+struct xkeymap_params {
+	const char *model;
+	const char *layout;
+	const char *variant;
+	const char *options;
+};
+
+int convert_xkb_keymap(struct lk_ctx *ctx, struct xkeymap_params *params, int options);
+
+#endif /* _XKB_SUPPORT_H_ */
Signature · 1 line
2.43.0
#2 lore raw

PATCH 2/4 xkbsupport: Create a list of used codes

In reply to Alexey Gladkov

This is preparation with the following changes. We create a binary search tree with all the unicodes that are used in the generated keymap so that we can later check whether the unicode symbol is used or not.

---
 src/xkbsupport.c | 50 +++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 47 insertions(+), 3 deletions(-)
src/xkbsupport.c +47 -3
index 694d516f..3caf8465 100644
@@ -1,3 +1,6 @@
+#define _GNU_SOURCE
+#include <search.h>
+
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
@@ -37,6 +40,7 @@ struct xkeymap {
 	struct xkb_context *xkb;
 	struct xkb_keymap *keymap;
 	struct lk_ctx *ctx;
+	void *used_codes;
 };
 
 /*
@@ -171,6 +175,15 @@ static const char *map_xkbsym_to_ksym(const char *xkb_sym)
 	return NULL;
 }
 
+static int compare_codes(const void *pa, const void *pb)
+{
+	if (*(int *) pa < *(int *) pb)
+		return -1;
+	if (*(int *) pa > *(int *) pb)
+		return 1;
+	return 0;
+}
+
 static void print_modifiers(struct xkb_keymap *keymap, struct xkb_mask *mask)
 {
 	xkb_mod_index_t num_mods = xkb_keymap_num_mods(keymap);
@@ -351,6 +364,32 @@ static int xkeymap_get_symbol(struct xkb_keymap *keymap,
 	return 1;
 }
 
+static int used_code(struct xkeymap *xkeymap, int code)
+{
+	return tfind(&code, &xkeymap->used_codes, compare_codes) != NULL;
+}
+
+static void remember_code(struct xkeymap *xkeymap, int code)
+{
+	int *ptr, **val;
+
+	if (used_code(xkeymap, code))
+		return;
+
+	ptr = malloc(sizeof(code));
+	if (!ptr)
+		kbd_error(1, errno, "out of memory");
+
+	*ptr = code;
+
+	val = tsearch(ptr, &xkeymap->used_codes, compare_codes);
+	if (!val)
+		kbd_error(1, errno, "out of memory");
+
+	if (*val != ptr)
+		free(ptr);
+}
+
 static void xkeymap_add_value(struct xkeymap *xkeymap, int modifier, int code, int keyvalue[MAX_NR_KEYMAPS])
 {
 	if (!modifier || (modifier & (1 << KG_SHIFT)))
@@ -438,9 +477,6 @@ static int xkeymap_walk(struct xkeymap *xkeymap)
 			}
 		}
 
-		if (getenv("LK_XKB_DEBUG"))
-			continue;
-
 process_keycode:
 		unsigned short layout = 0;
 
@@ -455,6 +491,11 @@ process_keycode:
 					keyvalue[i] = keyvalue[layout_switch[layout]];
 			}
 
+			remember_code(xkeymap, keyvalue[i]);
+
+			if (getenv("LK_XKB_DEBUG"))
+				continue;
+
 			if (lk_add_key(xkeymap->ctx, i, (int) KERN_KEYCODE(keycode), keyvalue[i]) < 0)
 				goto err;
 		}
@@ -506,6 +547,9 @@ int convert_xkb_keymap(struct lk_ctx *ctx, struct xkeymap_params *params, int op
 	}
 
 end:
+	if (xkeymap.used_codes)
+		tdestroy(xkeymap.used_codes, free);
+
 	xkb_keymap_unref(xkeymap.keymap);
 	xkb_context_unref(xkeymap.xkb);
 
Signature · 1 line
2.43.0
#3 lore raw

PATCH 3/4 xkbsupport: Extract translation table

In reply to Alexey Gladkov

This will allow us to expand the xkb to kbd keys translation table.

---
 data/xkbtrans/names | 154 ++++++++++++++++++++++++++++++++++++++
 src/xkbsupport.c    | 176 ++++++++++++++++++++++++--------------------
 2 files changed, 251 insertions(+), 79 deletions(-)
 create mode 100644 data/xkbtrans/names
data/xkbtrans/names new file +154 -0
new file mode 100644
index 00000000..f39e8456
@@ -0,0 +1,154 @@
+0	zero
+1	one
+2	two
+3	three
+4	four
+5	five
+6	six
+7	seven
+8	eight
+9	nine
+Alt_L	Alt
+Alt_R	Alt
+Begin	Begin
+Break	Break
+Control_L	Control
+Control_R	Control
+Down	Down
+End	End
+F1	F1
+F10	F10
+F11	F11
+F12	F12
+F13	F13
+F14	F14
+F15	F15
+F16	F16
+F17	F17
+F18	F18
+F19	F19
+F2	F2
+F20	F20
+F21	F21
+F22	F22
+F23	F23
+F24	F24
+F25	F25
+F26	F26
+F27	F27
+F28	F28
+F29	F29
+F3	F3
+F30	F30
+F31	F31
+F32	F32
+F33	F33
+F34	F34
+F35	F35
+F4	F4
+F5	F5
+F6	F6
+F7	F7
+F8	F8
+F9	F9
+Find	Find
+Help	Help
+Home	Home
+Hyper_L	Alt
+Hyper_R	Alt
+ISO_First_Group	AltGr_Lock
+ISO_First_Group_Lock	AltGr_Lock
+ISO_Group_Latch	AltGr
+ISO_Group_Lock	AltGr_Lock
+ISO_Group_Shift	AltGr
+ISO_Last_Group	AltGr_Lock
+ISO_Last_Group_Lock	AltGr_Lock
+ISO_Left_Tab	Meta_Tab
+ISO_Level2_Latch	Shift
+ISO_Level3_Latch	AltGr
+ISO_Level3_Lock		AltGr_Lock
+ISO_Level3_Shift	AltGr
+ISO_Level5_Latch	AltGr
+ISO_Level5_Lock	AltGr_Lock
+ISO_Level5_Shift	AltGr
+ISO_Lock	Caps_Lock
+ISO_Next_Group	AltGr_Lock
+ISO_Next_Group_Lock	AltGr_Lock
+ISO_Prev_Group	AltGr_Lock
+ISO_Prev_Group_Lock	AltGr_Lock
+Insert	Insert
+KP_Down	KP_2
+KP_End	KP_1
+KP_Home	KP_7
+KP_Insert	KP_0
+KP_Left	KP_4
+KP_Next	KP_3
+KP_Prior	KP_9
+KP_Right	KP_6
+KP_Up	KP_8
+L1	F11
+L10	F20
+L2	F12
+L3	F13
+L4	F14
+L5	F15
+L6	F16
+L7	F17
+L8	F18
+L9	F19
+Left	Left
+Meta_L	Alt
+Meta_R	Alt
+Mode_switch	AltGr
+Multi_key	Compose
+Next	Next
+Num_Lock	Num_Lock
+Pause	Pause
+Print	Control_backslash
+Prior	Prior
+R1	F21
+R10	F30
+R11	F31
+R12	F32
+R13	F33
+R14	F34
+R15	F35
+R2	F22
+R3	F23
+R4	F24
+R5	F25
+R6	F26
+R7	F27
+R8	F28
+R9	F29
+Right	Right
+Scroll_Lock	Scroll_Lock
+Shift_L	Shift
+Shift_R	Shift
+Super_L	Alt
+Super_R	Alt
+Sys_Req	Last_Console
+Up	Up
+XF86Switch_VT_1	Console_1
+XF86Switch_VT_10	Console_10
+XF86Switch_VT_11	Console_11
+XF86Switch_VT_12	Console_12
+XF86Switch_VT_2	Console_2
+XF86Switch_VT_3	Console_3
+XF86Switch_VT_4	Console_4
+XF86Switch_VT_5	Console_5
+XF86Switch_VT_6	Console_6
+XF86Switch_VT_7	Console_7
+XF86Switch_VT_8	Console_8
+XF86Switch_VT_9	Console_9
+dead_V	dead_caron
+dead_acute	dead_acute
+dead_breve	dead_breve
+dead_caron	dead_caron
+dead_cedilla	dead_cedilla
+dead_circumflex	dead_circumflex
+dead_diaeresis	dead_diaeresis
+dead_doubleacute	dead_doubleacute
+dead_grave	dead_grave
+dead_ogonek	dead_ogonek
+dead_tilde	dead_tilde
src/xkbsupport.c +97 -79

256 lines not on this page · raw message

Signature · 1 line
2.43.0
#4 lore raw

PATCH 4/4 xkbsupport: Add xkb compose

In reply to Alexey Gladkov

The approach to using compose differs between libxkbcommon and kernel. Compose in the kernel has stricter restrictions.

1. Length of sequence.

xkb: The length of the compose sequence can be any (or very long).
kbd: The length of the sequence is 2.

2. Length of result.

xkb: Again, the result of the sequence can be anything.
kbd: The result of compose restricted by unsigned int.

3. How to use compose.

xkb: The libxkbcommon library loads the entire table for all locales (more than 5000 combinations) and as characters are typed, it narrows the number of variants until there is only one sequence left. The library is not limited by the amount of memory by the number of stored variants.

kbd: Compose sequences are loaded into the kernel (ioctl KDGKBDIACR) and after that it is applied in the kernel without our participation. A table in the kernel is limited to 256 records.

To reduce the number of combinations:

1. We must ignore all xkb compose sequences longer than 2.
2. We should ignore sequences that use unicodes that are not used in the generated keymap.

Such tricks allow you to reduce 5000+ to ~220 (for en,ru keymap). Unfortunately, there is no guarantee yet that the amount of compose will be within the limit and there is no way to control it yet.

---
 src/loadkeys.c   |  26 ++++++++--
 src/xkbsupport.c | 126 +++++++++++++++++++++++++++++++++++++++++++++--
 src/xkbsupport.h |   2 +
 3 files changed, 145 insertions(+), 9 deletions(-)
src/loadkeys.c +21 -5
index d0178c77..c9a3304d 100644
@@ -101,7 +101,8 @@ int main(int argc, char *argv[])
 		.model = "pc104",
 		.layout = NULL,
 		.variant = NULL,
-		.options = NULL
+		.options = NULL,
+		.locale = NULL,
 	};
 	int use_xkb = 0;
 #endif
@@ -130,15 +131,17 @@ int main(int argc, char *argv[])
 		{ "xkb-layout", required_argument, NULL, 3 },
 		{ "xkb-variant", required_argument, NULL, 4 },
 		{ "xkb-options", required_argument, NULL, 5 },
+		{ "xkb-locale", required_argument, NULL, 6 },
 #endif
 		{ NULL, 0, NULL, 0 }
 	};
 	const struct kbd_help opthelp[] = {
 #ifdef USE_XKB
-		{ "--xkb-model=STR",    _("Specifies model used to choose component names.") },
-		{ "--xkb-layout=STR",   _("Specifies layout used to choose component names.") },
-		{ "--xkb-variant=STR",  _("Specifies layout variant used to choose component names.") },
-		{ "--xkb-options=STR",  _("Adds an option used to choose component names.") },
+		{ "--xkb-model=STR",     _("Specifies model used to choose component names.") },
+		{ "--xkb-layout=STR",    _("Specifies layout used to choose component names.") },
+		{ "--xkb-variant=STR",   _("Specifies layout variant used to choose component names.") },
+		{ "--xkb-options=STR",   _("Adds an option used to choose component names.") },
+		{ "--xkb-locale=LOCALE", _("Use LOCALE to search an appropriate compose file.") },
 #endif
 		{ "-C, --console=DEV",  _("the console device to be used.") },
 		{ "-a, --ascii",        _("force conversion to ASCII.") },
@@ -183,6 +186,10 @@ int main(int argc, char *argv[])
 				xkeymap_params.options = optarg;
 				use_xkb = 1;
 				break;
+			case 6:
+				xkeymap_params.locale = optarg;
+				use_xkb = 1;
+				break;
 #endif
 			case 'a':
 				options |= OPT_A;
@@ -289,6 +296,15 @@ int main(int argc, char *argv[])
 
 #ifdef USE_XKB
 	} else if (use_xkb) {
+		if (!xkeymap_params.locale || !*xkeymap_params.locale)
+			xkeymap_params.locale = getenv("LC_ALL");
+		if (!xkeymap_params.locale || !*xkeymap_params.locale)
+			xkeymap_params.locale = getenv("LC_CTYPE");
+		if (!xkeymap_params.locale || !*xkeymap_params.locale)
+			xkeymap_params.locale = getenv("LANG");
+		if (!xkeymap_params.locale || !*xkeymap_params.locale)
+			xkeymap_params.locale = "C";
+
 		rc = convert_xkb_keymap(ctx, &xkeymap_params, options);
 
 		if (rc == -1)
src/xkbsupport.c +122 -4

180 lines not on this page · raw message

src/xkbsupport.h +2 -0
index e12e39f6..3d424395 100644
@@ -2,6 +2,7 @@
 #define _XKB_SUPPORT_H_
 
 #include <xkbcommon/xkbcommon.h>
+#include <xkbcommon/xkbcommon-compose.h>
 #include "keymap.h"
 
 struct xkeymap_params {
@@ -9,6 +10,7 @@ struct xkeymap_params {
 	const char *layout;
 	const char *variant;
 	const char *options;
+	const char *locale;
 };
 
 int convert_xkb_keymap(struct lk_ctx *ctx, struct xkeymap_params *params, int options);
Signature · 1 line
2.43.0