public inbox for kbd@lists.linux.dev
 help / color / mirror / Atom feed
* [PATCH 0/4] Allow to convert xkb keymap to kernel keymap
@ 2024-01-04 18:15 Alexey Gladkov
  2024-01-04 18:15 ` [PATCH 1/4] Add keymap generation from the xkb database Alexey Gladkov
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Alexey Gladkov @ 2024-01-04 18:15 UTC (permalink / raw)
  To: kbd

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

-- 
2.43.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 1/4] Add keymap generation from the xkb database
  2024-01-04 18:15 [PATCH 0/4] Allow to convert xkb keymap to kernel keymap Alexey Gladkov
@ 2024-01-04 18:15 ` Alexey Gladkov
  2024-01-04 18:15 ` [PATCH 2/4] xkbsupport: Create a list of used codes Alexey Gladkov
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Alexey Gladkov @ 2024-01-04 18:15 UTC (permalink / raw)
  To: kbd

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:

> 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

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 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

diff --git a/configure.ac b/configure.ac
index a4be8383..f57317e5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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}
 ])
diff --git a/src/Makefile.am b/src/Makefile.am
index c495883d..7e4757e5 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -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; \
diff --git a/src/loadkeys.c b/src/loadkeys.c
index 0176c518..d0178c77 100644
--- a/src/loadkeys.c
+++ b/src/loadkeys.c
@@ -25,6 +25,12 @@
 #include "paths.h"
 #include "keymap.h"
 
+#include "loadkeys.h"
+
+#ifdef USE_XKB
+#include "xkbsupport.h"
+#endif
+
 static const char *const dirpath1[] = {
 	DATADIR "/" KEYMAPDIR "/**",
 	KERNDIR "/",
@@ -90,6 +96,16 @@ int main(int argc, char *argv[])
 	struct kbdfile_ctx *fctx;
 	struct kbdfile *fp = NULL;
 
+#ifdef USE_XKB
+	struct xkeymap_params xkeymap_params = {
+		.model = "pc104",
+		.layout = NULL,
+		.variant = NULL,
+		.options = NULL
+	};
+	int use_xkb = 0;
+#endif
+
 	set_progname(argv[0]);
 	setuplocale();
 
@@ -108,9 +124,22 @@ int main(int argc, char *argv[])
 		{ "quiet", no_argument, NULL, 'q' },
 		{ "verbose", no_argument, NULL, 'v' },
 		{ "version", no_argument, NULL, 'V' },
+#ifdef USE_XKB
+		//{ "xkb-rules", required_argument, NULL, 1 },
+		{ "xkb-model", required_argument, NULL, 2 },
+		{ "xkb-layout", required_argument, NULL, 3 },
+		{ "xkb-variant", required_argument, NULL, 4 },
+		{ "xkb-options", required_argument, NULL, 5 },
+#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.") },
+#endif
 		{ "-C, --console=DEV",  _("the console device to be used.") },
 		{ "-a, --ascii",        _("force conversion to ASCII.") },
 		{ "-b, --bkeymap",      _("output a binary keymap to stdout.") },
@@ -127,15 +156,6 @@ int main(int argc, char *argv[])
 		{ NULL, NULL }
 	};
 
-	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)
-	};
-
 	ctx = lk_init();
 	if (!ctx) {
 		exit(EXIT_FAILURE);
@@ -146,6 +166,24 @@ int main(int argc, char *argv[])
 
 	while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
 		switch (c) {
+#ifdef USE_XKB
+			case 2:
+				xkeymap_params.model = optarg;
+				use_xkb = 1;
+				break;
+			case 3:
+				xkeymap_params.layout = optarg;
+				use_xkb = 1;
+				break;
+			case 4:
+				xkeymap_params.variant = optarg;
+				use_xkb = 1;
+				break;
+			case 5:
+				xkeymap_params.options = optarg;
+				use_xkb = 1;
+				break;
+#endif
 			case 'a':
 				options |= OPT_A;
 				break;
@@ -249,6 +287,14 @@ int main(int argc, char *argv[])
 		if (rc == -1)
 			goto fail;
 
+#ifdef USE_XKB
+	} else if (use_xkb) {
+		rc = convert_xkb_keymap(ctx, &xkeymap_params, options);
+
+		if (rc == -1)
+			goto fail;
+
+#endif
 	} else if (optind == argc) {
 		if (!(fp = kbdfile_new(fctx)))
 			kbd_error(EXIT_FAILURE, 0, _("Unable to create kbdfile instance: %m"));
diff --git a/src/loadkeys.h b/src/loadkeys.h
new file mode 100644
index 00000000..d6ff6e7c
--- /dev/null
+++ b/src/loadkeys.h
@@ -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__ */
diff --git a/src/xkbsupport.c b/src/xkbsupport.c
new file mode 100644
index 00000000..694d516f
--- /dev/null
+++ b/src/xkbsupport.c
@@ -0,0 +1,513 @@
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "libcommon.h"
+#include "keymap.h"
+#include "loadkeys.h"
+#include "xkbsupport.h"
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+
+static const int layout_switch[] = {
+	0,
+	(1 << KG_SHIFTL),
+	(1 << KG_SHIFTR),
+	(1 << KG_SHIFTL) | (1 << KG_SHIFTR)
+};
+
+/*
+ * number    |
+ * of groups | group
+ * --------------------------
+ *         0 | { 0, 0, 0, 0 }
+ *         1 | { 0, 1, 1, 0 }
+ *         2 | { 0, 1, 2, 0 }
+ *         3 | { 0, 1, 3, 2 }
+ */
+static const unsigned int layouts[4][4] = {
+	{ 0, 0, 0, 0 },
+	{ 0, 1, 1, 0 },
+	{ 0, 1, 2, 0 },
+	{ 0, 1, 3, 2 },
+};
+
+struct xkeymap {
+	struct xkb_context *xkb;
+	struct xkb_keymap *keymap;
+	struct lk_ctx *ctx;
+};
+
+/*
+ * Offset between evdev keycodes (where KEY_ESCAPE is 1), and the evdev XKB
+ * keycode set (where ESC is 9).
+ */
+#define EVDEV_OFFSET 8
+
+/*
+ * Convert xkb keycode to kernel keycode.
+ */
+#define KERN_KEYCODE(keycode) (keycode - EVDEV_OFFSET)
+
+/*
+ * Copied from libxkbcommon.
+ * Don't allow more modifiers than we can hold in xkb_mod_mask_t.
+ */
+#define XKB_MAX_MODS ((xkb_mod_index_t)(sizeof(xkb_mod_mask_t) * 8))
+
+struct xkb_mask {
+	xkb_mod_mask_t mask[XKB_MAX_MODS];
+	size_t num;
+};
+
+struct modifier_mapping {
+	const char *xkb_mod;
+	const char *krn_mod;
+	const int bit;
+};
+
+static struct modifier_mapping modifier_mapping[] = {
+	{ "Shift",    "shift",   (1u << KG_SHIFT) },
+	{ "Control",  "control", (1u << KG_CTRL)  },
+	{ "Lock",     "???",     0                },
+	{ "Mod1",     "alt",     (1u << KG_ALT)   },
+	{ "Mod2",     "???",     0                },
+	{ "Mod3",     "???",     0                },
+	{ "Mod4",     "???",     0                },
+	{ "Mod5",     "altgr",   (1u << KG_ALT)   },
+	{ NULL, NULL, 0 },
+};
+
+static struct modifier_mapping *convert_modifier(const char *xkb_name)
+{
+	struct modifier_mapping *map = modifier_mapping;
+
+	while (map->xkb_mod) {
+		if (!strcasecmp(map->xkb_mod, xkb_name))
+			return map;
+		map++;
+	}
+
+	return NULL;
+}
+
+struct symbols_mapping {
+	const char *xkb_sym;
+	const char *krn_sym;
+};
+
+static struct symbols_mapping symbols_mapping[] = {
+	{ "0", "zero" },
+	{ "1", "one" },
+	{ "2", "two" },
+	{ "3", "three" },
+	{ "4", "four" },
+	{ "5", "five" },
+	{ "6", "six" },
+	{ "7", "seven" },
+	{ "8", "eight" },
+	{ "9", "nine" },
+	{ "KP_Insert", "KP_0" },
+	{ "KP_End", "KP_1" },
+	{ "KP_Down", "KP_2" },
+	{ "KP_Next", "KP_3" },
+	{ "KP_Left", "KP_4" },
+	{ "KP_Right", "KP_6" },
+	{ "KP_Home", "KP_7" },
+	{ "KP_Up", "KP_8" },
+	{ "KP_Prior", "KP_9" },
+	{ "KP_Begin", "VoidSymbol" },
+	{ "KP_Delete", "VoidSymbol" },
+	{ "Alt_R", "Alt" },
+	{ "Alt_L", "Alt" },
+	{ "Control_R", "Control" },
+	{ "Control_L", "Control" },
+	{ "Super_R", "Alt" },
+	{ "Super_L", "Alt" },
+	{ "Hyper_R", "Alt" },
+	{ "Hyper_L", "Alt" },
+	{ "Mode_switch", "AltGr" },
+	{ "ISO_Group_Shift", "AltGr" },
+	{ "ISO_Group_Latch", "AltGr" },
+	{ "ISO_Group_Lock", "AltGr_Lock" },
+	{ "ISO_Next_Group", "AltGr_Lock" },
+	{ "ISO_Next_Group_Lock", "AltGr_Lock" },
+	{ "ISO_Prev_Group", "AltGr_Lock" },
+	{ "ISO_Prev_Group_Lock", "AltGr_Lock" },
+	{ "ISO_First_Group", "AltGr_Lock" },
+	{ "ISO_First_Group_Lock", "AltGr_Lock" },
+	{ "ISO_Last_Group", "AltGr_Lock" },
+	{ "ISO_Last_Group_Lock", "AltGr_Lock" },
+	{ "ISO_Level3_Shift", "AltGr" },
+	{ "ISO_Left_Tab", "Meta_Tab" },
+	{ "XF86Switch_VT_1", "Console_1" },
+	{ "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" },
+	{ "XF86Switch_VT_10", "Console_10" },
+	{ "XF86Switch_VT_11", "Console_11" },
+	{ "XF86Switch_VT_12", "Console_12" },
+	{ "Sys_Req", "Last_Console" },
+	{ "Print", "Control_backslash" },
+	{ NULL, NULL },
+};
+
+static const char *map_xkbsym_to_ksym(const char *xkb_sym)
+{
+	struct symbols_mapping *map = symbols_mapping;
+
+	while (map->xkb_sym) {
+		if (!strcmp(map->xkb_sym, xkb_sym))
+			return map->krn_sym;
+		map++;
+	}
+
+	return NULL;
+}
+
+static void print_modifiers(struct xkb_keymap *keymap, struct xkb_mask *mask)
+{
+	xkb_mod_index_t num_mods = xkb_keymap_num_mods(keymap);
+
+	for (size_t m = 0; m < mask->num; m++) {
+		for (xkb_mod_index_t mod = 0; mod < num_mods; mod++) {
+			if ((mask->mask[m] & (1u << mod)) == 0)
+				continue;
+
+			const char *modname = xkb_keymap_mod_get_name(keymap, mod);
+
+			printf("%ld:%s(%d) ", m, modname, mod);
+		}
+	}
+}
+
+static void xkeymap_keycode_mask(struct xkb_keymap *keymap,
+                                 xkb_layout_index_t layout, xkb_level_index_t level, xkb_keycode_t keycode,
+                                 struct xkb_mask *out)
+{
+	memset(out->mask, 0, sizeof(out->mask));
+	out->num = xkb_keymap_key_get_mods_for_level(keymap, keycode, layout, level, out->mask, ARRAY_SIZE(out->mask));
+}
+
+static void xkeymap_walk_printer(struct xkeymap *xkeymap,
+                                 xkb_layout_index_t layout, xkb_level_index_t level,
+				 xkb_keycode_t keycode, xkb_keysym_t sym)
+{
+	switch (sym) {
+		case XKB_KEY_ISO_Next_Group:
+			printf("* ");
+			break;
+		default:
+			printf("  ");
+			break;
+	}
+
+	printf("{%-12s} %d ", xkb_keymap_layout_get_name(xkeymap->keymap, layout), layout);
+	printf("%d ", level);
+
+	char s[255];
+	const char *symname = NULL;
+	int ret, kunicode;
+	uint32_t unicode = xkb_keysym_to_utf32(sym);
+
+	ret = xkb_keysym_get_name(sym, s, sizeof(s));
+
+	if (ret < 0 || (size_t)ret >= sizeof(s)) {
+		kbd_warning(0, "failed to get name of keysym");
+		return;
+	}
+
+	if (!(symname = map_xkbsym_to_ksym(s)))
+		symname = s;
+
+	if (unicode == 0) {
+		if ((kunicode = lk_ksym_to_unicode(xkeymap->ctx, symname)) < 0) {
+			printf("keycode %3d = %s", KERN_KEYCODE(keycode), symname);
+		} else {
+			unicode = (uint32_t) kunicode;
+		}
+	}
+
+	if (unicode > 0)
+		printf("keycode %3d = U+%04x %-32s", KERN_KEYCODE(keycode), unicode, symname);
+
+	printf(" ( ");
+
+	struct xkb_mask keycode_mask;
+	xkeymap_keycode_mask(xkeymap->keymap, layout, level, keycode, &keycode_mask);
+
+	if (keycode_mask.num > 0) {
+		xkb_mod_index_t num_mods = xkb_keymap_num_mods(xkeymap->keymap);
+
+		for (xkb_mod_index_t mod = 0; mod < num_mods; mod++) {
+			if ((keycode_mask.mask[0] & (1u << mod)) == 0)
+				continue;
+
+			const struct modifier_mapping *map = convert_modifier(xkb_keymap_mod_get_name(xkeymap->keymap, mod));
+
+			printf("%s ", map->krn_mod);
+		}
+	}
+
+	printf(") ( ");
+	print_modifiers(xkeymap->keymap, &keycode_mask);
+	printf(")\n");
+}
+
+static int parse_hexcode(const char *symname)
+{
+	int unicode;
+	size_t ret = strlen(symname);
+
+	if (ret >= 4 && symname[0] == '0' && symname[1] == 'x') {
+		errno = 0;
+		unicode = (int) strtol(symname + 2, NULL, 16);
+		if (errno == ERANGE) {
+			kbd_warning(0, "unable to convert unnamed non-Unicode xkb symbol `%s'", symname);
+			return -1;
+		}
+		return unicode;
+	}
+
+	if (ret >= 5 && symname[0] == 'U' &&
+	   isxdigit(symname[1]) && isxdigit(symname[2]) &&
+	   isxdigit(symname[3]) && isxdigit(symname[4])) {
+		errno = 0;
+		unicode = (int) strtol(symname + 1, NULL, 16);
+		if (errno == ERANGE) {
+			kbd_warning(0, "unable to convert unnamed unicode xkb symbol `%s'", symname);
+			return -1;
+		}
+		return unicode;
+	}
+
+	return 0;
+}
+
+static int xkeymap_get_code(struct lk_ctx *ctx, xkb_keysym_t symbol)
+{
+	uint32_t xkb_unicode;
+	int ret, unicode;
+	const char *symname;
+	char symbuf[BUFSIZ];
+
+	symbuf[0] = 0;
+
+	ret = xkb_keysym_get_name(symbol, symbuf, sizeof(symbuf));
+
+	if (ret < 0 || (size_t)ret >= sizeof(symbuf)) {
+		kbd_warning(0, "failed to get name of keysym");
+		return K_HOLE;
+	}
+
+	if ((unicode = parse_hexcode(symbuf)) != 0) {
+		;
+	} else if ((symname = map_xkbsym_to_ksym(symbuf)) != NULL) {
+		unicode = parse_hexcode(symname);
+		if (!unicode)
+			unicode = lk_ksym_to_unicode(ctx, symname);
+
+	} else if ((xkb_unicode = xkb_keysym_to_utf32(symbol)) > 0) {
+		unicode = (int) xkb_unicode;
+	} else {
+		unicode = K_HOLE;
+	}
+
+	ret = CODE_FOR_UNKNOWN_KSYM;
+
+	if (unicode > 0)
+		ret = lk_convert_code(ctx, unicode, TO_AUTO);
+
+	if (ret == CODE_FOR_UNKNOWN_KSYM) {
+		kbd_warning(0, "unable to convert code <%s> and replace it with VoidSymbol", symbuf);
+		ret = K_HOLE;
+	}
+
+	return ret;
+}
+
+static int xkeymap_get_symbol(struct xkb_keymap *keymap,
+		xkb_keycode_t keycode, xkb_layout_index_t layout, xkb_level_index_t level,
+		xkb_keysym_t *symbol)
+{
+	int num_syms;
+	const xkb_keysym_t *syms = NULL;
+
+	if (!(num_syms = xkb_keymap_key_get_syms_by_level(keymap, keycode, layout, level, &syms)))
+		return 0;
+
+	if (num_syms > 1) {
+		kbd_warning(0, "fixme: more %d symbol on level", num_syms);
+		return -1;
+	}
+
+	*symbol = syms[0];
+	return 1;
+}
+
+static void xkeymap_add_value(struct xkeymap *xkeymap, int modifier, int code, int keyvalue[MAX_NR_KEYMAPS])
+{
+	if (!modifier || (modifier & (1 << KG_SHIFT)))
+		code = lk_add_capslock(xkeymap->ctx, code);
+
+	keyvalue[modifier] = code;
+}
+
+static int xkeymap_walk(struct xkeymap *xkeymap)
+{
+	struct xkb_mask keycode_mask;
+	xkb_mod_index_t num_mods = xkb_keymap_num_mods(xkeymap->keymap);
+	xkb_keycode_t min_keycode = xkb_keymap_min_keycode(xkeymap->keymap);
+	xkb_keycode_t max_keycode = xkb_keymap_max_keycode(xkeymap->keymap);
+
+	if (KERN_KEYCODE(min_keycode) >= NR_KEYS) {
+		kbd_warning(0, "keymap defines more keycodes than the kernel can handle.");
+		min_keycode = (NR_KEYS - 1) + EVDEV_OFFSET;
+	}
+
+	if (KERN_KEYCODE(max_keycode) >= NR_KEYS) {
+		kbd_warning(0, "keymap defines more keycodes than the kernel can handle.");
+		max_keycode = (NR_KEYS - 1) + EVDEV_OFFSET;
+	}
+
+	xkb_layout_index_t num_layouts = xkb_keymap_num_layouts(xkeymap->keymap);
+
+	for (xkb_keycode_t keycode = min_keycode; keycode <= max_keycode; keycode++) {
+		int keyvalue[MAX_NR_KEYMAPS];
+
+		memset(keyvalue, 0, sizeof(keyvalue));
+
+		for (xkb_layout_index_t layout = 0; layout < num_layouts; layout++) {
+			xkb_level_index_t num_levels = xkb_keymap_num_levels_for_key(xkeymap->keymap, keycode, layout);
+
+			for (xkb_level_index_t level = 0; level < num_levels; level++) {
+				xkb_keysym_t sym;
+				int ret, value;
+
+				if (!(ret = xkeymap_get_symbol(xkeymap->keymap, keycode, layout, level, &sym)))
+					continue;
+				else if (ret < 0)
+					goto err;
+
+				if (getenv("LK_XKB_DEBUG")) {
+					xkeymap_walk_printer(xkeymap, layout, level, keycode, sym);
+					continue;
+				}
+
+				if (sym == XKB_KEY_ISO_Next_Group) {
+					int shiftl_lock = lk_ksym_to_unicode(xkeymap->ctx, "ShiftL_Lock");
+					int shiftr_lock = lk_ksym_to_unicode(xkeymap->ctx, "ShiftR_Lock");
+
+					xkeymap_add_value(xkeymap, layout_switch[0], shiftl_lock, keyvalue);
+					xkeymap_add_value(xkeymap, layout_switch[1], shiftr_lock, keyvalue);
+					xkeymap_add_value(xkeymap, layout_switch[2], shiftr_lock, keyvalue);
+					xkeymap_add_value(xkeymap, layout_switch[3], shiftl_lock, keyvalue);
+
+					goto process_keycode;
+				}
+
+				if ((value = xkeymap_get_code(xkeymap->ctx, sym)) < 0)
+					continue;
+
+				xkeymap_keycode_mask(xkeymap->keymap, layout, level, keycode, &keycode_mask);
+
+				for (unsigned short i = 0; i < ARRAY_SIZE(layout_switch); i++) {
+					const struct modifier_mapping *map;
+					int modifier = layout_switch[i];
+
+					if (layout != layouts[num_layouts - 1][i])
+						continue;
+
+					for (xkb_mod_index_t mod = 0; mod < num_mods; mod++) {
+						if (!(keycode_mask.mask[0] & (1u << mod)))
+							continue;
+
+						map = convert_modifier(xkb_keymap_mod_get_name(xkeymap->keymap, mod));
+						if (map)
+							modifier |= map->bit;
+					}
+
+					xkeymap_add_value(xkeymap, modifier, value, keyvalue);
+				}
+			}
+		}
+
+		if (getenv("LK_XKB_DEBUG"))
+			continue;
+
+process_keycode:
+		unsigned short layout = 0;
+
+		for (unsigned short i = 0; i < ARRAY_SIZE(keyvalue); i++) {
+			if (layout < ARRAY_SIZE(layout_switch) && i == layout_switch[layout + 1])
+				layout++;
+
+			if (!keyvalue[i]) {
+				if ((i & (1 << KG_SHIFT)))
+					keyvalue[i] = keyvalue[layout_switch[layout] | (1 << KG_SHIFT)];
+				if (!keyvalue[i])
+					keyvalue[i] = keyvalue[layout_switch[layout]];
+			}
+
+			if (lk_add_key(xkeymap->ctx, i, (int) KERN_KEYCODE(keycode), keyvalue[i]) < 0)
+				goto err;
+		}
+	}
+
+	return 0;
+err:
+	return -1;
+}
+
+
+int convert_xkb_keymap(struct lk_ctx *ctx, struct xkeymap_params *params, int options)
+{
+	struct xkeymap xkeymap;
+	int ret = -1;
+
+	struct xkb_rule_names names = {
+		.rules = "evdev",
+		.model = params->model,
+		.layout = params->layout,
+		.variant = params->variant,
+		.options = params->options,
+	};
+
+	xkeymap.ctx = ctx;
+
+	lk_set_keywords(ctx, LK_KEYWORD_ALTISMETA | LK_KEYWORD_STRASUSUAL);
+
+	xkeymap.xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+	if (!xkeymap.xkb) {
+		kbd_warning(0, "xkb_context_new failed");
+		goto end;
+	}
+
+	xkeymap.keymap = xkb_keymap_new_from_names(xkeymap.xkb, &names, XKB_KEYMAP_COMPILE_NO_FLAGS);
+	if (!xkeymap.keymap) {
+		kbd_warning(0, "xkb_keymap_new_from_names failed");
+		goto end;
+	}
+
+	if (xkb_keymap_num_layouts(xkeymap.keymap) > ARRAY_SIZE(layout_switch)) {
+		kbd_warning(0, "too many layouts specified. At the moment, you can use no more than %ld", ARRAY_SIZE(layout_switch));
+		goto end;
+	}
+
+	if (!(ret = xkeymap_walk(&xkeymap))) {
+		if (options & OPT_P)
+			lk_dump_keymap(ctx, stdout, LK_SHAPE_SEPARATE_LINES, 0);
+	}
+
+end:
+	xkb_keymap_unref(xkeymap.keymap);
+	xkb_context_unref(xkeymap.xkb);
+
+	return ret;
+}
diff --git a/src/xkbsupport.h b/src/xkbsupport.h
new file mode 100644
index 00000000..e12e39f6
--- /dev/null
+++ b/src/xkbsupport.h
@@ -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_ */
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 2/4] xkbsupport: Create a list of used codes
  2024-01-04 18:15 [PATCH 0/4] Allow to convert xkb keymap to kernel keymap Alexey Gladkov
  2024-01-04 18:15 ` [PATCH 1/4] Add keymap generation from the xkb database Alexey Gladkov
@ 2024-01-04 18:15 ` Alexey Gladkov
  2024-01-04 18:15 ` [PATCH 3/4] xkbsupport: Extract translation table Alexey Gladkov
  2024-01-04 18:15 ` [PATCH 4/4] xkbsupport: Add xkb compose Alexey Gladkov
  3 siblings, 0 replies; 5+ messages in thread
From: Alexey Gladkov @ 2024-01-04 18:15 UTC (permalink / raw)
  To: kbd

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.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 src/xkbsupport.c | 50 +++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 47 insertions(+), 3 deletions(-)

diff --git a/src/xkbsupport.c b/src/xkbsupport.c
index 694d516f..3caf8465 100644
--- a/src/xkbsupport.c
+++ b/src/xkbsupport.c
@@ -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);
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 3/4] xkbsupport: Extract translation table
  2024-01-04 18:15 [PATCH 0/4] Allow to convert xkb keymap to kernel keymap Alexey Gladkov
  2024-01-04 18:15 ` [PATCH 1/4] Add keymap generation from the xkb database Alexey Gladkov
  2024-01-04 18:15 ` [PATCH 2/4] xkbsupport: Create a list of used codes Alexey Gladkov
@ 2024-01-04 18:15 ` Alexey Gladkov
  2024-01-04 18:15 ` [PATCH 4/4] xkbsupport: Add xkb compose Alexey Gladkov
  3 siblings, 0 replies; 5+ messages in thread
From: Alexey Gladkov @ 2024-01-04 18:15 UTC (permalink / raw)
  To: kbd

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

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 data/xkbtrans/names | 154 ++++++++++++++++++++++++++++++++++++++
 src/xkbsupport.c    | 176 ++++++++++++++++++++++++--------------------
 2 files changed, 251 insertions(+), 79 deletions(-)
 create mode 100644 data/xkbtrans/names

diff --git a/data/xkbtrans/names b/data/xkbtrans/names
new file mode 100644
index 00000000..f39e8456
--- /dev/null
+++ b/data/xkbtrans/names
@@ -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
diff --git a/src/xkbsupport.c b/src/xkbsupport.c
index 3caf8465..77abada0 100644
--- a/src/xkbsupport.c
+++ b/src/xkbsupport.c
@@ -36,11 +36,17 @@ static const unsigned int layouts[4][4] = {
 	{ 0, 1, 3, 2 },
 };
 
+struct sym_pair {
+	char *xkb_sym;
+	char *krn_sym;
+};
+
 struct xkeymap {
 	struct xkb_context *xkb;
 	struct xkb_keymap *keymap;
 	struct lk_ctx *ctx;
 	void *used_codes;
+	void *syms_map;
 };
 
 /*
@@ -83,6 +89,7 @@ static struct modifier_mapping modifier_mapping[] = {
 	{ NULL, NULL, 0 },
 };
 
+
 static struct modifier_mapping *convert_modifier(const char *xkb_name)
 {
 	struct modifier_mapping *map = modifier_mapping;
@@ -96,82 +103,32 @@ static struct modifier_mapping *convert_modifier(const char *xkb_name)
 	return NULL;
 }
 
-struct symbols_mapping {
-	const char *xkb_sym;
-	const char *krn_sym;
-};
-
-static struct symbols_mapping symbols_mapping[] = {
-	{ "0", "zero" },
-	{ "1", "one" },
-	{ "2", "two" },
-	{ "3", "three" },
-	{ "4", "four" },
-	{ "5", "five" },
-	{ "6", "six" },
-	{ "7", "seven" },
-	{ "8", "eight" },
-	{ "9", "nine" },
-	{ "KP_Insert", "KP_0" },
-	{ "KP_End", "KP_1" },
-	{ "KP_Down", "KP_2" },
-	{ "KP_Next", "KP_3" },
-	{ "KP_Left", "KP_4" },
-	{ "KP_Right", "KP_6" },
-	{ "KP_Home", "KP_7" },
-	{ "KP_Up", "KP_8" },
-	{ "KP_Prior", "KP_9" },
-	{ "KP_Begin", "VoidSymbol" },
-	{ "KP_Delete", "VoidSymbol" },
-	{ "Alt_R", "Alt" },
-	{ "Alt_L", "Alt" },
-	{ "Control_R", "Control" },
-	{ "Control_L", "Control" },
-	{ "Super_R", "Alt" },
-	{ "Super_L", "Alt" },
-	{ "Hyper_R", "Alt" },
-	{ "Hyper_L", "Alt" },
-	{ "Mode_switch", "AltGr" },
-	{ "ISO_Group_Shift", "AltGr" },
-	{ "ISO_Group_Latch", "AltGr" },
-	{ "ISO_Group_Lock", "AltGr_Lock" },
-	{ "ISO_Next_Group", "AltGr_Lock" },
-	{ "ISO_Next_Group_Lock", "AltGr_Lock" },
-	{ "ISO_Prev_Group", "AltGr_Lock" },
-	{ "ISO_Prev_Group_Lock", "AltGr_Lock" },
-	{ "ISO_First_Group", "AltGr_Lock" },
-	{ "ISO_First_Group_Lock", "AltGr_Lock" },
-	{ "ISO_Last_Group", "AltGr_Lock" },
-	{ "ISO_Last_Group_Lock", "AltGr_Lock" },
-	{ "ISO_Level3_Shift", "AltGr" },
-	{ "ISO_Left_Tab", "Meta_Tab" },
-	{ "XF86Switch_VT_1", "Console_1" },
-	{ "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" },
-	{ "XF86Switch_VT_10", "Console_10" },
-	{ "XF86Switch_VT_11", "Console_11" },
-	{ "XF86Switch_VT_12", "Console_12" },
-	{ "Sys_Req", "Last_Console" },
-	{ "Print", "Control_backslash" },
-	{ NULL, NULL },
-};
+static void xkeymap_pair_free(void *pa)
+{
+	struct sym_pair *pair = pa;
+	if (!pair)
+		return;
+	free(pair->xkb_sym);
+	free(pair->krn_sym);
+	free(pair);
+}
 
-static const char *map_xkbsym_to_ksym(const char *xkb_sym)
+static int compare_by_xkbsym(const void *pa, const void *pb)
 {
-	struct symbols_mapping *map = symbols_mapping;
+	return strcmp(((struct sym_pair *)pa)->xkb_sym,
+		      ((struct sym_pair *)pb)->xkb_sym);
+}
 
-	while (map->xkb_sym) {
-		if (!strcmp(map->xkb_sym, xkb_sym))
-			return map->krn_sym;
-		map++;
-	}
+static const char *map_xkbsym_to_ksym(struct xkeymap *xkeymap, char *xkb_sym)
+{
+	struct sym_pair **val;
+	struct sym_pair pair = {
+		.xkb_sym = xkb_sym,
+	};
 
+	val = tfind(&pair, &xkeymap->syms_map, compare_by_xkbsym);
+	if (val)
+		return ((struct sym_pair *) *val)->krn_sym;
 	return NULL;
 }
 
@@ -236,7 +193,7 @@ static void xkeymap_walk_printer(struct xkeymap *xkeymap,
 		return;
 	}
 
-	if (!(symname = map_xkbsym_to_ksym(s)))
+	if (!(symname = map_xkbsym_to_ksym(xkeymap, s)))
 		symname = s;
 
 	if (unicode == 0) {
@@ -303,7 +260,7 @@ static int parse_hexcode(const char *symname)
 	return 0;
 }
 
-static int xkeymap_get_code(struct lk_ctx *ctx, xkb_keysym_t symbol)
+static int xkeymap_get_code(struct xkeymap *xkeymap, xkb_keysym_t symbol)
 {
 	uint32_t xkb_unicode;
 	int ret, unicode;
@@ -321,10 +278,10 @@ static int xkeymap_get_code(struct lk_ctx *ctx, xkb_keysym_t symbol)
 
 	if ((unicode = parse_hexcode(symbuf)) != 0) {
 		;
-	} else if ((symname = map_xkbsym_to_ksym(symbuf)) != NULL) {
+	} else if ((symname = map_xkbsym_to_ksym(xkeymap, symbuf)) != NULL) {
 		unicode = parse_hexcode(symname);
 		if (!unicode)
-			unicode = lk_ksym_to_unicode(ctx, symname);
+			unicode = lk_ksym_to_unicode(xkeymap->ctx, symname);
 
 	} else if ((xkb_unicode = xkb_keysym_to_utf32(symbol)) > 0) {
 		unicode = (int) xkb_unicode;
@@ -335,7 +292,7 @@ static int xkeymap_get_code(struct lk_ctx *ctx, xkb_keysym_t symbol)
 	ret = CODE_FOR_UNKNOWN_KSYM;
 
 	if (unicode > 0)
-		ret = lk_convert_code(ctx, unicode, TO_AUTO);
+		ret = lk_convert_code(xkeymap->ctx, unicode, TO_AUTO);
 
 	if (ret == CODE_FOR_UNKNOWN_KSYM) {
 		kbd_warning(0, "unable to convert code <%s> and replace it with VoidSymbol", symbuf);
@@ -451,7 +408,7 @@ static int xkeymap_walk(struct xkeymap *xkeymap)
 					goto process_keycode;
 				}
 
-				if ((value = xkeymap_get_code(xkeymap->ctx, sym)) < 0)
+				if ((value = xkeymap_get_code(xkeymap, sym)) < 0)
 					continue;
 
 				xkeymap_keycode_mask(xkeymap->keymap, layout, level, keycode, &keycode_mask);
@@ -506,10 +463,60 @@ err:
 	return -1;
 }
 
+static int parsemap(struct xkeymap *xkeymap, FILE *fp)
+{
+	char buffer[BUFSIZ];
+	char *p, *q, *xkb_sym = NULL, *krn_sym = NULL;
+	struct sym_pair **val, *pair = NULL;
+
+	while (fgets(buffer, sizeof(buffer) - 1, fp)) {
+		for (p = buffer; isspace(*p); p++);
+
+		if (*p == '#' || *p == '\0')
+			continue;
+
+		for (q = p; *q != '\0' && !isspace(*q); q++);
+		*q = '\0';
+
+		xkb_sym = p;
+
+		if (p != q)
+			for(p = ++q; isspace(*p); p++);
+
+		for (q = p; *q != '\0' && !isspace(*q); q++);
+		*q = '\0';
+
+		krn_sym = p;
+
+		pair = calloc(1, sizeof(*pair));
+		if (!pair)
+			goto err;
+
+		pair->xkb_sym = strdup(xkb_sym);
+		pair->krn_sym = strdup(krn_sym);
+
+		if (!pair->xkb_sym || !pair->krn_sym)
+			goto err;
+
+		val = tsearch(pair, &xkeymap->syms_map, compare_by_xkbsym);
+		if (!val)
+			goto err;
+
+		if (*val != pair)
+			xkeymap_pair_free(pair);
+
+		pair = NULL;
+	}
+	return 0;
+err:
+	xkeymap_pair_free(pair);
+	return -1;
+}
 
 int convert_xkb_keymap(struct lk_ctx *ctx, struct xkeymap_params *params, int options)
 {
-	struct xkeymap xkeymap;
+	FILE *fp;
+	struct xkeymap xkeymap = { 0 };
 	int ret = -1;
 
 	struct xkb_rule_names names = {
@@ -541,6 +548,14 @@ int convert_xkb_keymap(struct lk_ctx *ctx, struct xkeymap_params *params, int op
 		goto end;
 	}
 
+	if ((fp = fopen(DATADIR "/xkbtrans/names", "r")) != NULL) {
+		if (parsemap(&xkeymap, fp) < 0) {
+			kbd_warning(0, "unable to parse xkb translation names");
+			goto end;
+		}
+		fclose(fp);
+	}
+
 	if (!(ret = xkeymap_walk(&xkeymap))) {
 		if (options & OPT_P)
 			lk_dump_keymap(ctx, stdout, LK_SHAPE_SEPARATE_LINES, 0);
@@ -550,6 +565,9 @@ end:
 	if (xkeymap.used_codes)
 		tdestroy(xkeymap.used_codes, free);
 
+	if (xkeymap.syms_map)
+		tdestroy(xkeymap.syms_map, xkeymap_pair_free);
+
 	xkb_keymap_unref(xkeymap.keymap);
 	xkb_context_unref(xkeymap.xkb);
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 4/4] xkbsupport: Add xkb compose
  2024-01-04 18:15 [PATCH 0/4] Allow to convert xkb keymap to kernel keymap Alexey Gladkov
                   ` (2 preceding siblings ...)
  2024-01-04 18:15 ` [PATCH 3/4] xkbsupport: Extract translation table Alexey Gladkov
@ 2024-01-04 18:15 ` Alexey Gladkov
  3 siblings, 0 replies; 5+ messages in thread
From: Alexey Gladkov @ 2024-01-04 18:15 UTC (permalink / raw)
  To: kbd

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.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 src/loadkeys.c   |  26 ++++++++--
 src/xkbsupport.c | 126 +++++++++++++++++++++++++++++++++++++++++++++--
 src/xkbsupport.h |   2 +
 3 files changed, 145 insertions(+), 9 deletions(-)

diff --git a/src/loadkeys.c b/src/loadkeys.c
index d0178c77..c9a3304d 100644
--- a/src/loadkeys.c
+++ b/src/loadkeys.c
@@ -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)
diff --git a/src/xkbsupport.c b/src/xkbsupport.c
index 77abada0..9fb44879 100644
--- a/src/xkbsupport.c
+++ b/src/xkbsupport.c
@@ -42,8 +42,10 @@ struct sym_pair {
 };
 
 struct xkeymap {
+	const char *debug;
 	struct xkb_context *xkb;
 	struct xkb_keymap *keymap;
+	struct xkb_compose_table *compose;
 	struct lk_ctx *ctx;
 	void *used_codes;
 	void *syms_map;
@@ -89,6 +91,12 @@ static struct modifier_mapping modifier_mapping[] = {
 	{ NULL, NULL, 0 },
 };
 
+static int is_debug(struct xkeymap *xkeymap, const char *val)
+{
+	if (!xkeymap->debug || (val && strcmp(xkeymap->debug, val)))
+		return 0;
+	return 1;
+}
 
 static struct modifier_mapping *convert_modifier(const char *xkb_name)
 {
@@ -391,10 +399,8 @@ static int xkeymap_walk(struct xkeymap *xkeymap)
 				else if (ret < 0)
 					goto err;
 
-				if (getenv("LK_XKB_DEBUG")) {
+				if (is_debug(xkeymap, NULL))
 					xkeymap_walk_printer(xkeymap, layout, level, keycode, sym);
-					continue;
-				}
 
 				if (sym == XKB_KEY_ISO_Next_Group) {
 					int shiftl_lock = lk_ksym_to_unicode(xkeymap->ctx, "ShiftL_Lock");
@@ -513,6 +519,98 @@ err:
 	return -1;
 }
 
+static void xkeymap_compose_printer(struct xkb_compose_table_entry *entry)
+{
+	char buf[128];
+	int offset = 20;
+	size_t seqlen = 0;
+	const xkb_keysym_t *syms = xkb_compose_table_entry_sequence(entry, &seqlen);
+	const char *chr = xkb_compose_table_entry_utf8(entry);
+	xkb_keysym_t keysym = xkb_compose_table_entry_keysym(entry);
+
+	printf("Compose: \"%s\" (chars=%ld) ", chr, strlen(chr));
+
+	if (xkb_keysym_get_name(keysym, buf, sizeof(buf)) > 0)
+		offset -= printf("<%s>", buf);
+	else
+		offset -= printf("<?>");
+
+	for (; offset > 0; offset--)
+		printf(" ");
+
+	printf(" -> sequence[%ld] = { ", seqlen);
+	for (size_t i = 0; i < seqlen; i++) {
+		if (xkb_keysym_get_name(syms[i], buf, sizeof(buf)) > 0)
+			printf("<%s> ", buf);
+		else
+			printf("<?> ");
+	}
+	printf("}\n");
+}
+
+static int xkeymap_compose(struct xkeymap *xkeymap)
+{
+	int ret = 0;
+	struct xkb_compose_table_iterator *iter = xkb_compose_table_iterator_new(xkeymap->compose);
+	struct xkb_compose_table_entry *entry;
+
+	if (!iter) {
+		kbd_warning(0, "xkb_compose_table_iterator_new failed");
+		return -1;
+	}
+
+	while ((entry = xkb_compose_table_iterator_next(iter))) {
+		size_t seqlen = 0;
+		const xkb_keysym_t *syms = xkb_compose_table_entry_sequence(entry, &seqlen);
+		xkb_keysym_t keysym = xkb_compose_table_entry_keysym(entry);
+
+		if (is_debug(xkeymap, "1")) {
+			xkeymap_compose_printer(entry);
+			continue;
+		}
+
+		if (keysym == XKB_KEY_NoSymbol)
+			continue;
+
+		if (seqlen == 2) {
+			struct lk_kbdiacr ptr;
+			int code;
+
+			if ((code = xkeymap_get_code(xkeymap, syms[0])) < 0 ||
+			    !used_code(xkeymap, code))
+				continue;
+
+			ptr.diacr = (unsigned int) code;
+
+			if ((code = xkeymap_get_code(xkeymap, syms[1])) < 0 ||
+			    !used_code(xkeymap, code))
+				continue;
+
+			ptr.base = (unsigned int) code;
+
+			if ((code = xkeymap_get_code(xkeymap, keysym)) < 0 ||
+			    used_code(xkeymap, code))
+				continue;
+
+			ptr.result = (unsigned int) code;
+
+			if (is_debug(xkeymap, "2")) {
+				xkeymap_compose_printer(entry);
+				continue;
+			}
+
+			ret = lk_append_compose(xkeymap->ctx, &ptr);
+
+			if (ret == -1)
+				break;
+		}
+	}
+
+	xkb_compose_table_iterator_free(iter);
+
+	return ret;
+}
+
 int convert_xkb_keymap(struct lk_ctx *ctx, struct xkeymap_params *params, int options)
 {
 	FILE *fp;
@@ -528,6 +626,7 @@ int convert_xkb_keymap(struct lk_ctx *ctx, struct xkeymap_params *params, int op
 	};
 
 	xkeymap.ctx = ctx;
+	xkeymap.debug = getenv("LK_XKB_DEBUG");
 
 	lk_set_keywords(ctx, LK_KEYWORD_ALTISMETA | LK_KEYWORD_STRASUSUAL);
 
@@ -548,6 +647,14 @@ int convert_xkb_keymap(struct lk_ctx *ctx, struct xkeymap_params *params, int op
 		goto end;
 	}
 
+	if (params->locale) {
+		xkeymap.compose = xkb_compose_table_new_from_locale(xkeymap.xkb, params->locale, XKB_COMPOSE_COMPILE_NO_FLAGS);
+		if (!xkeymap.compose) {
+			kbd_warning(0, "xkb_compose_table_new_from_locale failed");
+			goto end;
+		}
+	}
+
 	if ((fp = fopen(DATADIR "/xkbtrans/names", "r")) != NULL) {
 		if (parsemap(&xkeymap, fp) < 0) {
 			kbd_warning(0, "unable to parse xkb translation names");
@@ -557,10 +664,18 @@ int convert_xkb_keymap(struct lk_ctx *ctx, struct xkeymap_params *params, int op
 	}
 
 	if (!(ret = xkeymap_walk(&xkeymap))) {
-		if (options & OPT_P)
+		if (!is_debug(&xkeymap, NULL) && (options & OPT_P))
 			lk_dump_keymap(ctx, stdout, LK_SHAPE_SEPARATE_LINES, 0);
 	}
 
+	if (xkeymap.compose) {
+		if (xkeymap_compose(&xkeymap) < 0) {
+			ret = -1;
+			goto end;
+		}
+		if (!is_debug(&xkeymap, NULL) && (options & OPT_P))
+			lk_dump_diacs(ctx, stdout);
+	}
 end:
 	if (xkeymap.used_codes)
 		tdestroy(xkeymap.used_codes, free);
@@ -568,6 +683,9 @@ end:
 	if (xkeymap.syms_map)
 		tdestroy(xkeymap.syms_map, xkeymap_pair_free);
 
+	if (xkeymap.compose)
+		xkb_compose_table_unref(xkeymap.compose);
+
 	xkb_keymap_unref(xkeymap.keymap);
 	xkb_context_unref(xkeymap.xkb);
 
diff --git a/src/xkbsupport.h b/src/xkbsupport.h
index e12e39f6..3d424395 100644
--- a/src/xkbsupport.h
+++ b/src/xkbsupport.h
@@ -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);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2024-01-04 18:17 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-01-04 18:15 [PATCH 0/4] Allow to convert xkb keymap to kernel keymap Alexey Gladkov
2024-01-04 18:15 ` [PATCH 1/4] Add keymap generation from the xkb database Alexey Gladkov
2024-01-04 18:15 ` [PATCH 2/4] xkbsupport: Create a list of used codes Alexey Gladkov
2024-01-04 18:15 ` [PATCH 3/4] xkbsupport: Extract translation table Alexey Gladkov
2024-01-04 18:15 ` [PATCH 4/4] xkbsupport: Add xkb compose Alexey Gladkov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox