* [PATCH v2 1/3] libkeymap: add support for KT_CSI keysym type
2026-06-23 22:48 [PATCH v2 0/3] support for the kernel 7.1 modifier-aware KT_CSI keysym type Nicolas Pitre
@ 2026-06-23 22:48 ` Nicolas Pitre
2026-06-23 22:48 ` [PATCH v2 2/3] Add kbd-terminfo-fixup script and systemd service Nicolas Pitre
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Nicolas Pitre @ 2026-06-23 22:48 UTC (permalink / raw)
To: kbd; +Cc: gladkov.alexey
Add support for the new KT_CSI keysym type which provides modifier-aware
CSI escape sequences for navigation and function keys. This eliminates
the need to exhaust the limited func_table string entries for modifier
combinations.
The new Csi_* keysyms (Csi_Home, Csi_End, Csi_Delete, Csi_Insert,
Csi_PgUp, Csi_PgDn, Csi_F1-F20) generate tilde-format CSI sequences
with automatic modifier encoding when used with a supporting kernel.
The kernel automatically falls back to the plain keymap entry when
modifiers are held, encoding the modifier state into the output
sequence, so only plain map entries need to be defined.
Also bump NR_TYPES in summary.c so that the new KT_CSI type is included
in the action-code range listing of "dumpkeys --long-info", add
documentation for modifier-aware keys to keymaps(5), and update the
libkeymap test fixture for the extended symbol table.
Note: Requires Linux kernel 7.1 or later for KT_CSI support. On older
kernels these keysyms load without effect on consoles in Unicode mode
(the affected keys produce no output), and are rejected with EINVAL on
non-Unicode consoles (loadkeys reports an error for each such entry
but carries on).
Signed-off-by: Nicolas Pitre <nico@fluxnic.net>
---
docs/man/man5/keymaps.5 | 77 ++++++++++++++++++++++++++++++++++
src/libkeymap/ksyms.c | 3 +-
src/libkeymap/summary.c | 2 +-
src/libkeymap/syms.ktyp.h | 39 +++++++++++++++++
tests/data/keymap0-summary.txt | 26 ++++++++++++
5 files changed, 145 insertions(+), 2 deletions(-)
diff --git a/docs/man/man5/keymaps.5 b/docs/man/man5/keymaps.5
index 072afe3..bfa75aa 100644
--- a/docs/man/man5/keymaps.5
+++ b/docs/man/man5/keymaps.5
@@ -353,6 +353,83 @@ and describe how two bytes are combined to form a third one
(when a dead accent or compose key is used).
This is used to get accented letters and the like on a standard
keyboard.
+.SH "MODIFIER-AWARE KEYS"
+Certain key types automatically encode the current modifier state into
+the escape sequence they produce. This eliminates the need to define
+separate keymap entries for each modifier combination.
+.SS "Cursor Keys"
+The cursor keys (Up, Down, Left, Right) automatically include modifier
+information when Shift, Alt, or Control are held. With no modifiers,
+they produce the standard sequences (e.g., ESC [ A for Up). With
+modifiers, they produce extended sequences in the format ESC [ 1 ; \fImod\fP X,
+where \fImod\fP encodes the modifier state and X is the direction letter.
+.LP
+The modifier value is calculated as: 1 + (Shift?1:0) + (Alt?2:0) + (Control?4:0).
+AltGr counts as Alt for this purpose.
+For example, Shift+Up produces ESC [ 1 ; 2 A, and Control+Alt+Up
+produces ESC [ 1 ; 7 A.
+.SS "CSI Keys"
+The Csi_* keysyms provide similar modifier-aware behavior for navigation
+and function keys, generating sequences in the CSI tilde format
+(ESC [ \fIn\fP ~ or ESC [ \fIn\fP ; \fImod\fP ~ with modifiers).
+.LP
+Available CSI keysyms and their unmodified sequences:
+.RS
+.TP 16
+.B Csi_Home
+ESC [ 1 ~
+.TP
+.B Csi_Insert
+ESC [ 2 ~
+.TP
+.B Csi_Delete
+ESC [ 3 ~
+.TP
+.B Csi_End
+ESC [ 4 ~
+.TP
+.B Csi_PgUp
+ESC [ 5 ~
+.TP
+.B Csi_PgDn
+ESC [ 6 ~
+.TP
+.B Csi_F1 \fR...\fB Csi_F12
+ESC [ 11 ~ through ESC [ 24 ~ (skipping 16 and 22)
+.TP
+.B Csi_F13 \fR...\fB Csi_F20
+ESC [ 25 ~ through ESC [ 34 ~ (skipping 27 and 30)
+.RE
+.LP
+Note: The Csi_F* sequences follow the xterm numbering scheme, including
+its gaps in the parameter numbering, which differs from the traditional
+Linux console F1-F5 sequences (ESC [ [ A through ESC [ [ E).
+.SS "Fallback Behavior"
+When a cursor or CSI key is pressed with modifiers, and no explicit binding
+exists for that modifier combination, the kernel automatically falls back
+to the plain (unmodified) keymap entry. The modifier state is still encoded
+in the output sequence.
+.LP
+This means you only need to define the plain keymap entry:
+.LP
+.RS
+.nf
+keycode 102 = Csi_Home
+.fi
+.RE
+.LP
+and all modifier combinations (Shift+Home, Control+Home, etc.) will
+automatically produce the correct modified sequences without additional
+keymap entries.
+.LP
+.B Note:
+Modifier-aware key behavior requires Linux kernel 7.1 or later.
+On older kernels, cursor keys produce their traditional unmodified
+sequences, and Csi_* keysyms have no effect: on a console in Unicode
+mode the corresponding keys produce no output, while on a non-Unicode
+console
+.BR loadkeys (1)
+reports an error for each Csi_* entry.
.SH ABBREVIATIONS
Various abbreviations can be used with kbd-0.96 and later.
.TP
diff --git a/src/libkeymap/ksyms.c b/src/libkeymap/ksyms.c
index 219e5f2..98d1cf3 100644
--- a/src/libkeymap/ksyms.c
+++ b/src/libkeymap/ksyms.c
@@ -52,7 +52,8 @@ static const syms_entry syms[] = {
{ NULL, 0 }, /* KT_LETTER */
E(sticky_syms), /* KT_SLOCK */
{ NULL, 0 }, /* KT_DEAD2 */
- E(brl_syms) /* KT_BRL */
+ E(brl_syms), /* KT_BRL */
+ E(csi_syms) /* KT_CSI */
};
#undef E
diff --git a/src/libkeymap/summary.c b/src/libkeymap/summary.c
index 2003a3f..fafcc06 100644
--- a/src/libkeymap/summary.c
+++ b/src/libkeymap/summary.c
@@ -78,7 +78,7 @@ int lk_get_kmapinfo(struct lk_ctx *ctx, struct kmapinfo *res)
return 0;
}
-#define NR_TYPES 15
+#define NR_TYPES 16
void lk_dump_summary(struct lk_ctx *ctx, FILE *fd, int console)
{
diff --git a/src/libkeymap/syms.ktyp.h b/src/libkeymap/syms.ktyp.h
index 504d67e..aca0fc6 100644
--- a/src/libkeymap/syms.ktyp.h
+++ b/src/libkeymap/syms.ktyp.h
@@ -588,3 +588,42 @@ static const char *const brl_syms[] = {
"Brl_dot9",
"Brl_dot10"
};
+
+/*
+ * Keysyms whose KTYP is KT_CSI.
+ * Index is the CSI parameter number for ESC [ <index> ~ sequences.
+ */
+static const char *const csi_syms[] = {
+ "", /* 0: unused */
+ "Csi_Home", /* 1: ESC [ 1 ~ */
+ "Csi_Insert", /* 2: ESC [ 2 ~ */
+ "Csi_Delete", /* 3: ESC [ 3 ~ */
+ "Csi_End", /* 4: ESC [ 4 ~ */
+ "Csi_PgUp", /* 5: ESC [ 5 ~ */
+ "Csi_PgDn", /* 6: ESC [ 6 ~ */
+ "", "", "", "", /* 7-10: unused */
+ "Csi_F1", /* 11: ESC [ 11 ~ */
+ "Csi_F2", /* 12: ESC [ 12 ~ */
+ "Csi_F3", /* 13: ESC [ 13 ~ */
+ "Csi_F4", /* 14: ESC [ 14 ~ */
+ "Csi_F5", /* 15: ESC [ 15 ~ */
+ "", /* 16: unused */
+ "Csi_F6", /* 17: ESC [ 17 ~ */
+ "Csi_F7", /* 18: ESC [ 18 ~ */
+ "Csi_F8", /* 19: ESC [ 19 ~ */
+ "Csi_F9", /* 20: ESC [ 20 ~ */
+ "Csi_F10", /* 21: ESC [ 21 ~ */
+ "", /* 22: unused */
+ "Csi_F11", /* 23: ESC [ 23 ~ */
+ "Csi_F12", /* 24: ESC [ 24 ~ */
+ "Csi_F13", /* 25: ESC [ 25 ~ */
+ "Csi_F14", /* 26: ESC [ 26 ~ */
+ "", /* 27: unused */
+ "Csi_F15", /* 28: ESC [ 28 ~ */
+ "Csi_F16", /* 29: ESC [ 29 ~ */
+ "", /* 30: unused */
+ "Csi_F17", /* 31: ESC [ 31 ~ */
+ "Csi_F18", /* 32: ESC [ 32 ~ */
+ "Csi_F19", /* 33: ESC [ 33 ~ */
+ "Csi_F20", /* 34: ESC [ 34 ~ */
+};
diff --git a/tests/data/keymap0-summary.txt b/tests/data/keymap0-summary.txt
index 0b66c06..6f757fd 100644
--- a/tests/data/keymap0-summary.txt
+++ b/tests/data/keymap0-summary.txt
@@ -810,6 +810,32 @@ nr of compose definitions in actual use: 0
0x0e08 Brl_dot8
0x0e09 Brl_dot9
0x0e0a Brl_dot10
+0x0f01 Csi_Home
+0x0f02 Csi_Insert
+0x0f03 Csi_Delete
+0x0f04 Csi_End
+0x0f05 Csi_PgUp
+0x0f06 Csi_PgDn
+0x0f0b Csi_F1
+0x0f0c Csi_F2
+0x0f0d Csi_F3
+0x0f0e Csi_F4
+0x0f0f Csi_F5
+0x0f11 Csi_F6
+0x0f12 Csi_F7
+0x0f13 Csi_F8
+0x0f14 Csi_F9
+0x0f15 Csi_F10
+0x0f17 Csi_F11
+0x0f18 Csi_F12
+0x0f19 Csi_F13
+0x0f1a Csi_F14
+0x0f1c Csi_F15
+0x0f1d Csi_F16
+0x0f1f Csi_F17
+0x0f20 Csi_F18
+0x0f21 Csi_F19
+0x0f22 Csi_F20
The following synonyms are recognized:
--
2.54.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH v2 2/3] Add kbd-terminfo-fixup script and systemd service
2026-06-23 22:48 [PATCH v2 0/3] support for the kernel 7.1 modifier-aware KT_CSI keysym type Nicolas Pitre
2026-06-23 22:48 ` [PATCH v2 1/3] libkeymap: add support for " Nicolas Pitre
@ 2026-06-23 22:48 ` Nicolas Pitre
2026-06-23 22:48 ` [PATCH v2 3/3] keymaps: add opt-in CSI overlay and load it when the kernel supports it Nicolas Pitre
2026-06-24 8:51 ` [PATCH v2 0/3] support for the kernel 7.1 modifier-aware KT_CSI keysym type Alexey Gladkov
3 siblings, 0 replies; 5+ messages in thread
From: Nicolas Pitre @ 2026-06-23 22:48 UTC (permalink / raw)
To: kbd; +Cc: gladkov.alexey
This script handles two terminfo mismatches with the linux terminal:
1. The Linux console has a long-standing discrepancy where F1-F5 use
non-standard sequences (\e[[A through \e[[E) while F6 and above use
standard CSI sequences (\e[17~ etc.). The new Csi_F1 through Csi_F5
keysyms (Linux kernel 7.1 and later) fix this by making F1-F5 use
CSI sequences as well (\e[11~ through \e[15~). However, the terminfo
entry for "linux" still expects the old sequences, causing
applications to not recognize F1-F5.
2. The Backtab keysym produces the standard backtab sequence \e[Z,
but the linux terminfo entry expects \e^I (ESC + Tab).
Some applications may recognize these sequences regardless of terminfo,
but not all do. The kbd-terminfo-fixup script ensures consistent
behavior across all terminfo-aware applications by detecting Csi_F*
and Backtab keysyms in the current keymap (via dumpkeys) and installing
an updated "linux" terminfo entry in /etc/terminfo/.
The override is always rebuilt from the distribution's pristine
terminfo entry (looked up in /usr/share/terminfo, /lib/terminfo or
/usr/lib/terminfo) rather than from the active search path, so the
script never feeds on its own output and fixups that are no longer
needed are automatically dropped when the keymap changes. Rewrites
are skipped when the installed override is already up to date.
The generated entry is tagged with a "(kbd-terminfo-fixup)" marker in
its terminfo description field. An /etc/terminfo/l/linux file without
that marker is treated as the administrator's own and is never
overwritten or removed.
A systemd service unit is provided to run this automatically at boot,
after systemd-vconsole-setup.service loads the keymap. The service is
only installed when systemd is detected. The unit file is generated
at make time so that the libexec path is fully expanded.
Configure options:
--with-systemdsystemunitdir=DIR Set systemd unit directory
(auto-detected via pkg-config)
Signed-off-by: Nicolas Pitre <nico@fluxnic.net>
---
.gitignore | 1 +
Makefile.am | 16 ++
configure.ac | 18 +++
contrib/kbd-terminfo-fixup | 222 ++++++++++++++++++++++++++
contrib/kbd-terminfo-fixup.service.in | 13 ++
5 files changed, 270 insertions(+)
create mode 100755 contrib/kbd-terminfo-fixup
create mode 100644 contrib/kbd-terminfo-fixup.service.in
diff --git a/.gitignore b/.gitignore
index 539fe88..4f4913f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -105,3 +105,4 @@ tests/libkbdfile/libkbdfile-test[0-9][0-9]
tests/libkeymap/libkeymap-test[0-9][0-9]
tests/libkfont/libkfont-test[0-9][0-9]
tests/testsuite
+contrib/kbd-terminfo-fixup.service
diff --git a/Makefile.am b/Makefile.am
index f722e97..9eafcae 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -33,6 +33,22 @@ EXTRA_DIST = \
CREDITS \
contrib docs
+pkglibexec_SCRIPTS = contrib/kbd-terminfo-fixup
+
+if HAVE_SYSTEMD
+systemdsystemunit_DATA = contrib/kbd-terminfo-fixup.service
+endif
+
+# Substitute directory variables at make time so they are fully
+# expanded (configure would leave ${exec_prefix} unexpanded).
+contrib/kbd-terminfo-fixup.service: contrib/kbd-terminfo-fixup.service.in Makefile
+ $(AM_V_GEN)$(MKDIR_P) contrib && \
+ $(SED) -e 's|@libexecdir[@]|$(libexecdir)|g' \
+ -e 's|@PACKAGE[@]|$(PACKAGE)|g' \
+ $(srcdir)/contrib/kbd-terminfo-fixup.service.in > $@
+
+CLEANFILES = contrib/kbd-terminfo-fixup.service
+
SUBDIRS = src data po docs
if BUILD_TESTS
SUBDIRS += tests
diff --git a/configure.ac b/configure.ac
index ffd67fe..a3f30ec 100644
--- a/configure.ac
+++ b/configure.ac
@@ -368,6 +368,22 @@ AS_IF([test "x$USE_XKB" != xno],
[USE_XKB=no])
AM_CONDITIONAL(USE_XKB, test "$USE_XKB" = "yes")
+AC_ARG_WITH([systemdsystemunitdir],
+ [AS_HELP_STRING([--with-systemdsystemunitdir=DIR],
+ [directory for systemd service files @<:@default=auto@:>@])],
+ [],
+ [with_systemdsystemunitdir=auto]
+)
+AS_IF([test "$with_systemdsystemunitdir" = "auto"], [
+ PKG_CHECK_VAR([systemdsystemunitdir], [systemd], [systemdsystemunitdir],
+ [with_systemdsystemunitdir="$systemdsystemunitdir"],
+ [with_systemdsystemunitdir=no])
+])
+AS_IF([test "$with_systemdsystemunitdir" != "no"], [
+ AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])
+])
+AM_CONDITIONAL(HAVE_SYSTEMD, test "$with_systemdsystemunitdir" != "no")
+
AC_MSG_NOTICE([generation of Makefiles...])
# Remove -h (dereference) from am__tar
@@ -431,4 +447,6 @@ AC_MSG_RESULT([
standalone libkeymap: ${BUILD_LIBKEYMAP}
standalone libkfont: ${BUILD_LIBKFONT}
xkb support: ${USE_XKB}
+
+ systemd unit dir: ${with_systemdsystemunitdir}
])
diff --git a/contrib/kbd-terminfo-fixup b/contrib/kbd-terminfo-fixup
new file mode 100755
index 0000000..965460d
--- /dev/null
+++ b/contrib/kbd-terminfo-fixup
@@ -0,0 +1,222 @@
+#!/bin/sh
+# kbd-terminfo-fixup - Update terminfo when new kbd keysym bindings are used
+#
+# This script handles two terminfo mismatches:
+#
+# 1. The Linux console has a long-standing discrepancy where F1-F5 use
+# non-standard sequences (\e[[A through \e[[E) while F6 and above use
+# standard CSI sequences. The Csi_F1 through Csi_F5 keysyms fix this.
+#
+# 2. The Backtab keysym produces the standard \e[Z sequence, but the
+# linux terminfo entry expects \e^I (ESC + Tab).
+#
+# This script detects when these keysyms are in use and installs a
+# modified "linux" terminfo entry in /etc/terminfo so applications can
+# correctly recognize them.
+#
+# The override is always rebuilt from the distribution's pristine entry
+# (never from the active search path, which may include a previous
+# override), so fixups that are no longer needed are automatically
+# dropped when the keymap changes. The generated entry is tagged with a
+# "(kbd-terminfo-fixup)" marker in its description; an existing
+# /etc/terminfo/l/linux without that marker is considered the
+# administrator's own and is never modified or removed.
+#
+# Some applications may recognize these sequences regardless of terminfo,
+# but not all do. This ensures consistent behavior across all
+# terminfo-aware applications.
+#
+# Usage: kbd-terminfo-fixup [--update | --remove | --check]
+#
+# Options:
+# --update Create/update terminfo if CSI keysyms detected (default)
+# --remove Remove system terminfo override
+# --check Check if CSI keysyms are in use (exit 0 if yes)
+
+set -e
+
+TERMINFO_DIR="${TERMINFO_DIR:-/etc/terminfo}"
+MARKER="kbd-terminfo-fixup"
+
+# Check which Csi_F1-F5 keysyms are in use
+# (-w prevents e.g. Csi_F11 from matching as Csi_F1)
+detect_csi_keys() {
+ dumpkeys 2>/dev/null | grep -woE 'Csi_F[1-5]' | sort -u
+}
+
+# Check if Backtab keysym is in use
+detect_backtab() {
+ if dumpkeys 2>/dev/null | grep -qw 'Backtab'; then
+ echo "Backtab"
+ fi
+}
+
+# The compiled entry stores the description string verbatim, so the
+# marker added by this script can be detected with a plain grep.
+override_is_ours() {
+ grep -q "(${MARKER})" "${TERMINFO_DIR}/l/linux" 2>/dev/null
+}
+
+# Print the distribution's pristine "linux" entry, one capability per
+# line, bypassing $TERMINFO_DIR so we never read back our own override.
+pristine_entry() {
+ for dir in /usr/share/terminfo /lib/terminfo /usr/lib/terminfo; do
+ if [ -e "$dir/l/linux" ]; then
+ TERMINFO="$dir" infocmp -1 -x linux 2>/dev/null || true
+ return 0
+ fi
+ done
+ # Unusual layout: fall back to the default search path
+ infocmp -1 -x linux 2>/dev/null || true
+}
+
+# Tag the entry description so we can recognize our own override later
+add_marker() {
+ sed "/^linux|/{/(${MARKER})/!s/,\$/ (${MARKER}),/;}"
+}
+
+do_check() {
+ csi_keys=$(detect_csi_keys)
+ backtab=$(detect_backtab)
+ if [ -n "$csi_keys" ] || [ -n "$backtab" ]; then
+ [ -n "$csi_keys" ] && echo "CSI function keys in use: $csi_keys"
+ [ -n "$backtab" ] && echo "Backtab keysym in use"
+ return 0
+ else
+ echo "No CSI function keys (F1-F5) or Backtab detected"
+ return 1
+ fi
+}
+
+do_update() {
+ target="${TERMINFO_DIR}/l/linux"
+
+ # Never touch an override file we did not create
+ if [ -f "$target" ] && ! override_is_ours; then
+ echo "$target was not created by this script; leaving it alone"
+ return 0
+ fi
+
+ csi_keys=$(detect_csi_keys)
+ backtab=$(detect_backtab)
+
+ if [ -z "$csi_keys" ] && [ -z "$backtab" ]; then
+ # No CSI keys or Backtab - remove override if present
+ do_remove quiet
+ return 0
+ fi
+
+ pristine=$(pristine_entry)
+ if [ -z "$pristine" ]; then
+ echo "Cannot read the 'linux' terminfo entry" >&2
+ return 1
+ fi
+
+ # Build the desired entry from the pristine one
+ sed_expr=""
+ for key in $csi_keys; do
+ fnum="${key#Csi_F}"
+ # Csi_Fn produces \E[1n~ for n in 1..5
+ sed_expr="${sed_expr}s/kf${fnum}=[^,]*/kf${fnum}=\\\\E[1${fnum}~/;"
+ done
+ if [ -n "$backtab" ]; then
+ sed_expr="${sed_expr}s/kcbt=[^,]*/kcbt=\\\\E[Z/;"
+ fi
+
+ desired=$(printf '%s\n' "$pristine" | sed "$sed_expr")
+
+ if [ "$desired" = "$pristine" ]; then
+ # The distribution entry already has the right sequences
+ do_remove quiet
+ echo "Terminfo already has correct sequences"
+ return 0
+ fi
+
+ desired=$(printf '%s\n' "$desired" | add_marker)
+
+ # Skip rewriting if our existing override is already up to date
+ if [ -f "$target" ]; then
+ current=$(TERMINFO="$TERMINFO_DIR" infocmp -1 -x linux 2>/dev/null | grep -v '^#' || true)
+ if [ "$(printf '%s\n' "$desired" | grep -v '^#' || true)" = "$current" ]; then
+ echo "Terminfo already up to date"
+ return 0
+ fi
+ fi
+
+ echo "Updating terminfo for:" $csi_keys $backtab
+
+ # Create terminfo directory if needed
+ mkdir -p "${TERMINFO_DIR}/l"
+
+ # Compile the modified terminfo
+ printf '%s\n' "$desired" | tic -x -o "$TERMINFO_DIR" -
+
+ echo "Terminfo updated in $target"
+}
+
+do_remove() {
+ quiet="${1:-}"
+ target="${TERMINFO_DIR}/l/linux"
+ if [ ! -f "$target" ]; then
+ [ "$quiet" = "quiet" ] || echo "No system terminfo override found"
+ return 0
+ fi
+ if ! override_is_ours; then
+ [ "$quiet" = "quiet" ] || echo "$target was not created by this script; leaving it alone"
+ return 0
+ fi
+ rm -f "$target"
+ [ "$quiet" = "quiet" ] || echo "Removed $target"
+ # Clean up the subdirectory if empty, but leave $TERMINFO_DIR
+ # itself alone: it usually belongs to the distribution
+ rmdir "${TERMINFO_DIR}/l" 2>/dev/null || true
+}
+
+show_usage() {
+ cat <<EOF
+Usage: kbd-terminfo-fixup [--update | --remove | --check]
+
+Update system terminfo when new kbd keysym bindings are used.
+
+Options:
+ --update Create/update terminfo if new keysyms detected, or remove
+ the override if not (default)
+ --remove Remove system terminfo override
+ --check Check if new keysyms are in use
+
+This script handles two cases:
+
+1. The Linux console has a long-standing discrepancy where F1-F5 use
+ non-standard sequences (\e[[A through \e[[E) while F6 and above use
+ standard CSI sequences (\e[17~ etc.). The Csi_F1 through Csi_F5 keysyms
+ fix this by making F1-F5 use CSI sequences (\e[11~ through \e[15~).
+
+2. The Backtab keysym produces the standard backtab sequence \e[Z,
+ but the linux terminfo entry expects \e^I (ESC + Tab).
+
+This script detects when these keysyms are in use and installs a
+modified "linux" terminfo entry accordingly.
+
+Some applications may recognize these sequences regardless of terminfo,
+but not all do. This ensures consistent behavior across all
+terminfo-aware applications.
+
+The modified entry is rebuilt from the distribution's pristine terminfo
+entry, installed to /etc/terminfo/ (which takes precedence over the
+default system entry), and tagged with a "($MARKER)" marker
+in its description. An /etc/terminfo/l/linux file without that marker
+is treated as the administrator's own and is never touched.
+EOF
+}
+
+case "${1:---update}" in
+ --update) do_update ;;
+ --remove) do_remove ;;
+ --check) do_check ;;
+ --help|-h) show_usage ;;
+ *)
+ echo "Unknown option: $1" >&2
+ show_usage >&2
+ exit 1
+ ;;
+esac
diff --git a/contrib/kbd-terminfo-fixup.service.in b/contrib/kbd-terminfo-fixup.service.in
new file mode 100644
index 0000000..df9591e
--- /dev/null
+++ b/contrib/kbd-terminfo-fixup.service.in
@@ -0,0 +1,13 @@
+[Unit]
+Description=Update terminfo for CSI function keys
+Documentation=man:keymaps(5)
+After=systemd-vconsole-setup.service
+ConditionPathExists=/dev/tty0
+
+[Service]
+Type=oneshot
+ExecStart=@libexecdir@/@PACKAGE@/kbd-terminfo-fixup --update
+RemainAfterExit=yes
+
+[Install]
+WantedBy=multi-user.target
--
2.54.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH v2 3/3] keymaps: add opt-in CSI overlay and load it when the kernel supports it
2026-06-23 22:48 [PATCH v2 0/3] support for the kernel 7.1 modifier-aware KT_CSI keysym type Nicolas Pitre
2026-06-23 22:48 ` [PATCH v2 1/3] libkeymap: add support for " Nicolas Pitre
2026-06-23 22:48 ` [PATCH v2 2/3] Add kbd-terminfo-fixup script and systemd service Nicolas Pitre
@ 2026-06-23 22:48 ` Nicolas Pitre
2026-06-24 8:51 ` [PATCH v2 0/3] support for the kernel 7.1 modifier-aware KT_CSI keysym type Alexey Gladkov
3 siblings, 0 replies; 5+ messages in thread
From: Nicolas Pitre @ 2026-06-23 22:48 UTC (permalink / raw)
To: kbd; +Cc: gladkov.alexey
The KT_CSI keysyms only work on Linux 7.1 and later. Converting the
shared include files (linux-keys-bare.inc and friends) in place would
silently switch every keymap that includes them to CSI sequences, which
regress the function and navigation keys on older kernels: on a Unicode
console the keys produce nothing, and on a non-Unicode console loadkeys
reports errors. Since kbd is updated independently of the kernel, this
would break existing installs.
Instead, keep the default include files as the traditional (legacy)
bindings and add an opt-in overlay, linux-keys-csi.inc, that rebinds the
function keys (F1-F12) and navigation keys (Home, End, Insert, Delete,
PageUp, PageDown) to the Csi_* keysyms. It carries no "keymaps" line so
loadkeys merges it onto an existing keymap rather than clearing unrelated
tables, and it only sets the specific (modifier, keycode) entries it
needs: the legacy F13-F36 shift/control bindings are overridden, while
Alt/Control+Alt console switching and the Shift+PageUp/PageDown scroll
bindings are left intact.
Two ways to use it:
- Include it after linux-keys-bare in a custom keymap:
include "linux-keys-bare"
include "linux-keys-csi"
- Let kbd-terminfo-fixup apply it at boot. The script now detects
whether the running kernel supports KT_CSI (by version, since a
runtime probe is unreliable on Unicode consoles) and, if so, loads
the overlay before reconciling terminfo. For the same reason it only
updates the terminfo F1-F5 entries when the kernel honors KT_CSI;
Backtab works on any kernel and is always reconciled.
KBD_FORCE_CSI=1/0 overrides the detection for backported kernels. If
the script is not installed, or the kernel is older than 7.1, the
legacy bindings remain in effect.
This makes the migration safe by construction: nothing changes for
systems on pre-7.1 kernels, and the modifier-aware keys are enabled only
where the kernel can honor them.
Signed-off-by: Nicolas Pitre <nico@fluxnic.net>
---
contrib/kbd-terminfo-fixup | 117 ++++++++++++++++---
contrib/kbd-terminfo-fixup.service.in | 2 +-
data/keymaps/i386/include/linux-keys-csi.inc | 83 +++++++++++++
3 files changed, 183 insertions(+), 19 deletions(-)
create mode 100644 data/keymaps/i386/include/linux-keys-csi.inc
diff --git a/contrib/kbd-terminfo-fixup b/contrib/kbd-terminfo-fixup
index 965460d..8cf4188 100755
--- a/contrib/kbd-terminfo-fixup
+++ b/contrib/kbd-terminfo-fixup
@@ -1,21 +1,35 @@
#!/bin/sh
-# kbd-terminfo-fixup - Update terminfo when new kbd keysym bindings are used
+# kbd-terminfo-fixup - Enable modifier-aware console keys and reconcile terminfo
#
-# This script handles two terminfo mismatches:
+# This script does two things, in order:
#
-# 1. The Linux console has a long-standing discrepancy where F1-F5 use
-# non-standard sequences (\e[[A through \e[[E) while F6 and above use
-# standard CSI sequences. The Csi_F1 through Csi_F5 keysyms fix this.
+# A. Keymap upgrade (kernel-aware migration).
+# If the running kernel supports the KT_CSI keysym type (Linux 7.1 and
+# later), it loads the linux-keys-csi.inc overlay, rebinding the
+# function and navigation keys to modifier-aware CSI sequences. The
+# default keymaps ship the traditional (legacy) bindings, so a system
+# running an older kernel - or one where this script is not installed -
+# keeps working unchanged. The CSI variant is enabled at runtime only
+# where the kernel can honor it. Set KBD_FORCE_CSI=1 (or =0) to force
+# the overlay on (or off) regardless of the kernel version.
#
-# 2. The Backtab keysym produces the standard \e[Z sequence, but the
-# linux terminfo entry expects \e^I (ESC + Tab).
+# B. Terminfo reconciliation. Two console sequences do not match the
+# stock "linux" terminfo entry:
#
-# This script detects when these keysyms are in use and installs a
-# modified "linux" terminfo entry in /etc/terminfo so applications can
-# correctly recognize them.
+# 1. The Linux console has a long-standing discrepancy where F1-F5 use
+# non-standard sequences (\e[[A through \e[[E) while F6 and above
+# use standard CSI sequences. The Csi_F1 through Csi_F5 keysyms fix
+# this.
#
-# The override is always rebuilt from the distribution's pristine entry
-# (never from the active search path, which may include a previous
+# 2. The Backtab keysym produces the standard \e[Z sequence, but the
+# linux terminfo entry expects \e^I (ESC + Tab).
+#
+# This script detects when these keysyms are in use (after the overlay
+# above, if any) and installs a modified "linux" terminfo entry in
+# /etc/terminfo so applications can correctly recognize them.
+#
+# The terminfo override is always rebuilt from the distribution's pristine
+# entry (never from the active search path, which may include a previous
# override), so fixups that are no longer needed are automatically
# dropped when the keymap changes. The generated entry is tagged with a
# "(kbd-terminfo-fixup)" marker in its description; an existing
@@ -29,7 +43,8 @@
# Usage: kbd-terminfo-fixup [--update | --remove | --check]
#
# Options:
-# --update Create/update terminfo if CSI keysyms detected (default)
+# --update Apply the CSI overlay if supported, then create/update
+# terminfo if CSI keysyms are detected (default)
# --remove Remove system terminfo override
# --check Check if CSI keysyms are in use (exit 0 if yes)
@@ -38,6 +53,50 @@ set -e
TERMINFO_DIR="${TERMINFO_DIR:-/etc/terminfo}"
MARKER="kbd-terminfo-fixup"
+# Keymap overlay loaded by name from the kbd keymap search path. The
+# ".inc" is given explicitly because loadkeys only auto-appends .map/.kmap.
+OVERLAY_KEYMAP="linux-keys-csi.inc"
+
+# Return success if the running kernel supports the KT_CSI keysym type.
+# KT_CSI and modifier-aware cursor keys were added in Linux 7.1. A runtime
+# probe is unreliable (an old kernel silently accepts unknown keysym types
+# on a Unicode-mode console), so gate on the kernel version, with an
+# override for backports.
+kernel_supports_csi() {
+ case "${KBD_FORCE_CSI:-}" in
+ 1|yes|true) return 0 ;;
+ 0|no|false) return 1 ;;
+ esac
+ kver=$(uname -r)
+ kmaj=${kver%%.*}
+ krest=${kver#*.}
+ kmin=${krest%%.*}
+ # Keep only the leading digits (e.g. "1-rc8" -> "1")
+ kmaj=${kmaj%%[!0-9]*}
+ kmin=${kmin%%[!0-9]*}
+ if [ -z "$kmaj" ]; then return 1; fi
+ if [ -z "$kmin" ]; then kmin=0; fi
+ if [ "$kmaj" -gt 7 ]; then
+ return 0
+ fi
+ if [ "$kmaj" -eq 7 ] && [ "$kmin" -ge 1 ]; then
+ return 0
+ fi
+ return 1
+}
+
+# Apply the modifier-aware CSI keymap overlay if the kernel supports it.
+# Best effort: never fail the boot if the overlay cannot be loaded.
+apply_csi_overlay() {
+ kernel_supports_csi || return 0
+ command -v loadkeys >/dev/null 2>&1 || return 0
+ if loadkeys "$OVERLAY_KEYMAP" >/dev/null 2>&1; then
+ echo "Applied CSI keymap overlay ($OVERLAY_KEYMAP)"
+ else
+ echo "Warning: kernel supports KT_CSI but loading $OVERLAY_KEYMAP failed" >&2
+ fi
+}
+
# Check which Csi_F1-F5 keysyms are in use
# (-w prevents e.g. Csi_F11 from matching as Csi_F1)
detect_csi_keys() {
@@ -91,13 +150,26 @@ do_check() {
do_update() {
target="${TERMINFO_DIR}/l/linux"
+ # Step A: enable the CSI keymap where the kernel supports it. Done
+ # before detection so the terminfo update below sees the freshly
+ # bound Csi_* keysyms. This is independent of terminfo ownership.
+ apply_csi_overlay
+
# Never touch an override file we did not create
if [ -f "$target" ] && ! override_is_ours; then
echo "$target was not created by this script; leaving it alone"
return 0
fi
- csi_keys=$(detect_csi_keys)
+ # The Csi_F* terminfo fix only makes sense when the kernel honors
+ # KT_CSI; otherwise those keys emit nothing and kf1-5 must stay at the
+ # legacy sequences. Backtab is an ordinary string keysym that works on
+ # any kernel, so it is always considered.
+ if kernel_supports_csi; then
+ csi_keys=$(detect_csi_keys)
+ else
+ csi_keys=
+ fi
backtab=$(detect_backtab)
if [ -z "$csi_keys" ] && [ -z "$backtab" ]; then
@@ -176,15 +248,24 @@ show_usage() {
cat <<EOF
Usage: kbd-terminfo-fixup [--update | --remove | --check]
-Update system terminfo when new kbd keysym bindings are used.
+Enable modifier-aware console keys and reconcile the terminfo entry.
Options:
- --update Create/update terminfo if new keysyms detected, or remove
- the override if not (default)
+ --update Apply the CSI keymap overlay if the running kernel supports
+ KT_CSI (Linux 7.1+), then create/update terminfo if new
+ keysyms are detected, or remove the override if not (default)
--remove Remove system terminfo override
--check Check if new keysyms are in use
-This script handles two cases:
+Environment:
+ KBD_FORCE_CSI Set to 1 to force the CSI keymap overlay on, or 0 to
+ force it off, regardless of the detected kernel version.
+
+The CSI keymap overlay (linux-keys-csi.inc) rebinds the function and
+navigation keys to modifier-aware CSI sequences. The default keymaps keep
+the traditional bindings, so older kernels are unaffected.
+
+This script also handles two terminfo cases:
1. The Linux console has a long-standing discrepancy where F1-F5 use
non-standard sequences (\e[[A through \e[[E) while F6 and above use
diff --git a/contrib/kbd-terminfo-fixup.service.in b/contrib/kbd-terminfo-fixup.service.in
index df9591e..6f86e8b 100644
--- a/contrib/kbd-terminfo-fixup.service.in
+++ b/contrib/kbd-terminfo-fixup.service.in
@@ -1,5 +1,5 @@
[Unit]
-Description=Update terminfo for CSI function keys
+Description=Enable modifier-aware console keys and update terminfo
Documentation=man:keymaps(5)
After=systemd-vconsole-setup.service
ConditionPathExists=/dev/tty0
diff --git a/data/keymaps/i386/include/linux-keys-csi.inc b/data/keymaps/i386/include/linux-keys-csi.inc
new file mode 100644
index 0000000..d600fbe
--- /dev/null
+++ b/data/keymaps/i386/include/linux-keys-csi.inc
@@ -0,0 +1,83 @@
+# Modifier-aware CSI variant of the function and navigation keys.
+#
+# This is an opt-in overlay for the keys defined in linux-keys-bare.inc.
+# It rebinds the function keys (F1-F12) and the navigation keys (Home,
+# End, Insert, Delete, PageUp, PageDown) to the Csi_* keysyms, which
+# produce standard CSI sequences and let the kernel encode the modifier
+# state (Shift, Control, Alt) into the emitted sequence.
+#
+# This requires Linux kernel 7.1 or later (KT_CSI support). On older
+# kernels these keysyms have no effect, so this file is NOT included by
+# the default keymaps. There are two ways to use it:
+#
+# - Include it after linux-keys-bare in your own keymap, e.g.
+# include "linux-keys-bare"
+# include "linux-keys-csi"
+#
+# - Let the kbd-terminfo-fixup boot service load it automatically; it
+# does so only when the running kernel supports KT_CSI.
+#
+# Designed to be loaded as an overlay on top of an already loaded keymap:
+# it deliberately has no "keymaps" line so that loadkeys merges it rather
+# than clearing unrelated tables.
+
+#
+# Function keys F1-F12.
+#
+# The plain entry selects the CSI sequence; the kernel adds the modifier
+# parameter at runtime. The shift and control entries are set explicitly
+# to override the legacy F13-F36 bindings from linux-keys-bare.inc (the
+# kernel still encodes the held modifier). Alt and Control+Alt are left
+# untouched so that console switching keeps working.
+#
+plain keycode 59 = Csi_F1
+shift keycode 59 = Csi_F1
+control keycode 59 = Csi_F1
+plain keycode 60 = Csi_F2
+shift keycode 60 = Csi_F2
+control keycode 60 = Csi_F2
+plain keycode 61 = Csi_F3
+shift keycode 61 = Csi_F3
+control keycode 61 = Csi_F3
+plain keycode 62 = Csi_F4
+shift keycode 62 = Csi_F4
+control keycode 62 = Csi_F4
+plain keycode 63 = Csi_F5
+shift keycode 63 = Csi_F5
+control keycode 63 = Csi_F5
+plain keycode 64 = Csi_F6
+shift keycode 64 = Csi_F6
+control keycode 64 = Csi_F6
+plain keycode 65 = Csi_F7
+shift keycode 65 = Csi_F7
+control keycode 65 = Csi_F7
+plain keycode 66 = Csi_F8
+shift keycode 66 = Csi_F8
+control keycode 66 = Csi_F8
+plain keycode 67 = Csi_F9
+shift keycode 67 = Csi_F9
+control keycode 67 = Csi_F9
+plain keycode 68 = Csi_F10
+shift keycode 68 = Csi_F10
+control keycode 68 = Csi_F10
+plain keycode 87 = Csi_F11
+shift keycode 87 = Csi_F11
+control keycode 87 = Csi_F11
+plain keycode 88 = Csi_F12
+shift keycode 88 = Csi_F12
+control keycode 88 = Csi_F12
+
+#
+# Navigation keys.
+#
+# A single plain entry is enough: the kernel falls back to it for any
+# held modifier and encodes the modifier into the sequence. Shift+PageUp
+# and Shift+PageDown are left as the console scroll actions defined in
+# linux-keys-bare.inc.
+#
+plain keycode 110 = Csi_Insert
+plain keycode 102 = Csi_Home
+plain keycode 104 = Csi_PgUp
+plain keycode 111 = Csi_Delete
+plain keycode 107 = Csi_End
+plain keycode 109 = Csi_PgDn
--
2.54.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* Re: [PATCH v2 0/3] support for the kernel 7.1 modifier-aware KT_CSI keysym type
2026-06-23 22:48 [PATCH v2 0/3] support for the kernel 7.1 modifier-aware KT_CSI keysym type Nicolas Pitre
` (2 preceding siblings ...)
2026-06-23 22:48 ` [PATCH v2 3/3] keymaps: add opt-in CSI overlay and load it when the kernel supports it Nicolas Pitre
@ 2026-06-24 8:51 ` Alexey Gladkov
3 siblings, 0 replies; 5+ messages in thread
From: Alexey Gladkov @ 2026-06-24 8:51 UTC (permalink / raw)
To: Nicolas Pitre; +Cc: kbd, gladkov.alexey, Greg Kroah-Hartman
On Tue, Jun 23, 2026 at 06:48:55PM -0400, Nicolas Pitre wrote:
> Linux kernel 7.1 introduces modifier-aware escape sequence generation
> for the console keyboard:
>
> 4af70f151671 ("vt: add modifier support to cursor keys")
> 5cba06c71c71 ("vt: add KT_CSI keysym type for modifier-aware CSI sequences")
> c1d2deb6492f ("vt: add fallback to plain map for modifier-aware key types")
>
> The new KT_CSI keysym type produces xterm-style CSI tilde sequences
> (ESC [ n ~, or ESC [ n ; mod ~ when Shift/Alt/Control are held) with
> the modifier state encoded at runtime. A single plain map entry covers
> all modifier combinations, instead of consuming a func_table string
> slot per combination.
Here's what's bothering me. These patches disrupt the logic behind how
keymaps work.
Previously, userspace determined which modifier key should generate which
sequence. We had `string F1 = "\033[[A"` and use it in any position:
`alt keycode 16 = F1`.
After your patches, modifier handling has been moved to the kernel.
The escape sequence `ESC[11;03~` will be generated only if Csi_F1 is set
to `alt keycode 16 = Csi_F1`. So, for Csi_*, the modifier has taken on
a special meaning. I can't bind the Alt+Csi_F1 sequence to any other
modifier key.
> This series adds the userspace counterpart:
>
> Patch 1 teaches libkeymap the Csi_* keysym names (Csi_Home, Csi_End,
> Csi_Delete, Csi_Insert, Csi_PgUp, Csi_PgDn, Csi_F1..Csi_F20, matching
> the kernel UAPI values) and documents modifier-aware keys in
> keymaps(5).
>
> Patch 2 adds a contrib script (with optional systemd unit) that
> reconciles the "linux" terminfo entry with the loaded keymap: F1-F5
> in CSI form and Backtab as ESC [ Z are not what the stock terminfo
> entry expects.
>
> Patch 3 keeps the default keymaps on their traditional bindings and
> adds an opt-in overlay (linux-keys-csi.inc) that switches the function
> and navigation keys to the Csi_* keysyms. The boot script applies it
> only when the running kernel supports KT_CSI, so nothing changes on
> older kernels.
>
> Changes since v1 (addressing Alexey's review):
>
> - Patch 1: bump NR_TYPES in summary.c so KT_CSI appears in
> "dumpkeys --long-info".
>
> - Patch 3: reworked from an in-place conversion (which broke pre-7.1
> systems) into the opt-in overlay described above.
>
> v1: https://lore.kernel.org/kbd/20260612014857.1668427-1-nico@fluxnic.net
>
> Tested with make check, a staged "make install" (overlay installs and
> resolves by name), and the terminfo script exercised live on Fedora 43.
>
> Nicolas Pitre (3):
> libkeymap: add support for KT_CSI keysym type
> Add kbd-terminfo-fixup script and systemd service
> keymaps: add opt-in CSI overlay and load it when the kernel supports
> it
>
> .gitignore | 1 +
> Makefile.am | 16 +
> configure.ac | 18 ++
> contrib/kbd-terminfo-fixup | 303 +++++++++++++++++++
> contrib/kbd-terminfo-fixup.service.in | 13 +
> data/keymaps/i386/include/linux-keys-csi.inc | 83 +++++
> docs/man/man5/keymaps.5 | 77 +++++
> src/libkeymap/ksyms.c | 3 +-
> src/libkeymap/summary.c | 2 +-
> src/libkeymap/syms.ktyp.h | 39 +++
> tests/data/keymap0-summary.txt | 26 ++
> 11 files changed, 579 insertions(+), 2 deletions(-)
> create mode 100755 contrib/kbd-terminfo-fixup
> create mode 100644 contrib/kbd-terminfo-fixup.service.in
> create mode 100644 data/keymaps/i386/include/linux-keys-csi.inc
>
> --
> 2.54.0
>
--
Rgrds, legion
^ permalink raw reply [flat|nested] 5+ messages in thread