[PATCH v2 3/3] keymaps: add opt-in CSI overlay and load it when the kernel supports it
Nicolas Pitre <[email protected]> Tue, 23 Jun 2026 18:48:58 -0400
| Newsgroups | dev.linux.lists.kbd |
|---|---|
| Message-ID | <[email protected]> |
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 <[email protected]>
---
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