终端键位映射 —— 箭头键、修饰键组合、kitty CSI u 协议
按下 Up 箭头时 \x1b[A 意味着什么?为什么 Ctrl+Up 发出 \x1b[1;5A?为什么单独的 ESC 键是最难可靠检测的按键?本页面覆盖输入侧 ANSI 词汇 —— 终端为每次按键发回给应用的字节 —— 涵盖 DECCKM 光标键模式、xterm 的 1;mod 修饰键约定、F1–F4 的 SS3 与 F5+ CSI tilde 之分、三种并存的 Home/End 编码、消除所有旧歧义的现代 kitty CSI u 协议,以及每个 TUI 用于区分单独 ESC 与序列开头的 20–50ms 超时模式。
输出与输入 —— 词汇相同、方向相反
ansicode 的逐序列页面记录的是**输出**侧:应用写出去用于渲染颜色、移动光标、设置窗口标题的字节。本页面覆盖**输入**侧:终端就每次按键回发给应用的字节。两个方向共用同一套 CSI / SS3 / OSC 词汇 —— `\x1b[` 是 CSI、`\x1bO` 是 SS3 —— 但解析器与生产者的角色互换。终端拥有键盘转换表(通过 `infocmp $TERM` 的 `kcuu1` 上箭头、`kf1` F1、`khome` Home 等能力配置),应用是接收方。有趣的组合:箭头键依 DECCKM 模式有两种形式,修饰键组合用一种各模拟器不一致的 CSI 参数约定,单独的 ESC 键与任意序列开头字节相同,kitty 键盘协议则是消除全部歧义的现代尝试。
`cat -v` 把 ESC 显示为 `^[`,逐字符显示真实字节。
# Inspect what your terminal actually emits for a key.
# Run this, then press the key once, then press Enter to exit.
cat -v
# Example output for Up arrow under tmux:
# ^[[A
# Example output for Ctrl+Up under xterm:
# ^[[1;5A箭头键 —— CSI \e[A 与 SS3 \eOA 由 DECCKM 切换
光标键根据 DECCKM(DEC Cursor Keys Mode,CSI ? 1 h 启用、CSI ? 1 l 关闭)发出两种字节序列之一。在**普通**模式(默认)下,Up/Down/Right/Left = `\x1b[A` / `\x1b[B` / `\x1b[C` / `\x1b[D`(CSI)。**应用**模式下同一按键 = `\x1bOA` / `\x1bOB` / `\x1bOC` / `\x1bOD`(SS3 —— escape + 大写 O)。vim 与 emacs 通常进入时设置 DECCKM 应用模式、退出时关闭;bash readline 通过 inputrc 中 `\eOA`(或 `\e[A`)绑定同时识别两种形式。**注意**:DECCKM 独立于 DECKPAM/DECKPNM(小键盘应用/数字模式 —— 见 /sequence/deckpam-deckpnm);箭头键由 DECCKM 控制而**非** DECKPAM。混淆二者的应用在小键盘模式翻转时会错解箭头。
在编写 readline 风格输入层时,两种形式都应当绑定。
# Toggle DECCKM and observe Up arrow.
printf '\e[?1h' # application cursor keys → Up = \eOA
printf '\e[?1l' # normal cursor keys → Up = \e[A
# Bash inputrc example — bind both forms:
# "\e[A": previous-history
# "\eOA": previous-history修饰键约定 —— \e[1;<mod><letter>,mod = 1 + 位掩码
按住修饰键(Shift、Alt、Ctrl、Meta)配合光标键或功能键时,xterm 系终端发出相同的终止字节,但在 CSI 中插入一个修饰参数:修饰过的 Up 是 `\x1b[1;<mod>A`,修饰过的 F1 是 `\x1b[1;<mod>P`,依此类推。`mod` 值为 `1 + (Shift?1:0) + (Alt?2:0) + (Ctrl?4:0) + (Meta?8:0)`,所以 2=Shift、3=Alt、4=Shift+Alt、5=Ctrl、6=Shift+Ctrl、7=Alt+Ctrl、8=Shift+Alt+Ctrl。示例:`\x1b[1;2A`(Shift+Up)、`\x1b[1;5A`(Ctrl+Up)、`\x1b[1;6A`(Shift+Ctrl+Up)、`\x1b[15;5~`(Ctrl+F5)。较旧的 Linux console 与部分 VT100 模拟器完全忽略修饰参数。XTMODKEYS / XTQMODKEYS(见 /sequence/xtmodkeys 与 /sequence/xtqmodkeys)允许应用设置或读取 modifyCursorKeys / modifyFunctionKeys 运行时模式,控制是否发出修饰参数。
记住 1-2-4-8 位掩码一次即可;适用于每个修饰过的箭头与 F 键。
# Modifier byte = 1 + Shift + 2·Alt + 4·Ctrl + 8·Meta
# Shift+Up \e[1;2A
# Alt+Up \e[1;3A
# Shift+Alt+Up \e[1;4A
# Ctrl+Up \e[1;5A
# Shift+Ctrl+Up \e[1;6A
# Alt+Ctrl+Up \e[1;7A
# Shift+Alt+Ctrl \e[1;8A
# Same shape for F-keys: Ctrl+F5 = \e[15;5~功能键 —— F1–F4 是 SS3,F5+ 是 CSI tilde
功能键编码是输入侧最碎片化的部分。xterm 系约定:F1 = `\x1bOP`、F2 = `\x1bOQ`、F3 = `\x1bOR`、F4 = `\x1bOS`(全部 SS3 —— escape + 大写 O + 字母,与应用模式箭头同形)。F5 及以上切换到 CSI tilde:F5 = `\x1b[15~`、F6 = `\x1b[17~`、F7 = `\x1b[18~`、F8 = `\x1b[19~`、F9 = `\x1b[20~`、F10 = `\x1b[21~`、F11 = `\x1b[23~`、F12 = `\x1b[24~`。16 与 22 的空缺是历史原因 —— DEC VT220 把这些数字留给了 xterm 未暴露的键(Help、Do)。Linux console 又不同:F1-F5 = `\x1b[[A` 至 `\x1b[[E`(非标准的双方括号形式),F6-F12 才接上 CSI-tilde 序列。务必查 `infocmp` 或采用 kitty 键盘协议,不要硬编码。
F1–F4 不遵循 F 键的总模式;F5 起则遵循修饰键约定。
# Modified F-keys reuse the modifier convention.
# Shift+F5 \e[15;2~
# Ctrl+F5 \e[15;5~
# Alt+F12 \e[24;3~
# Shift+Ctrl+F11 \e[23;6~
# Linux console F1-F5 are the outlier: \e[[A through \e[[E特殊键 —— Home/End 三种编码并存,Backspace = 0x7F 或 0x08
Home/End 是碎片化最严重的特殊键。**xterm** 发 `\x1b[H`(Home)与 `\x1b[F`(End)—— 终止字节与不带参的光标定位相同。**vt220** 发 `\x1b[1~`(Home)与 `\x1b[4~`(End)。**rxvt** 发 `\x1b[7~`(Home)与 `\x1b[8~`(End)。PgUp = `\x1b[5~`、PgDn = `\x1b[6~` 全局一致。Insert = `\x1b[2~`、Delete = `\x1b[3~`。**退格键**更糟 —— xterm 与 Linux console 发 DEL(`0x7F`,terminfo 名为 `kbs`),而 Windows console 与许多 telnet 主机发 BS(`\x08`)。只读 `^?`(0x7F)忽略 0x08 的工具在 Windows 上失效;规范修复是在输入层同时绑定两者。修饰形式与 F 键约定相同:`\x1b[H` Home、`\x1b[1;5H` Ctrl+Home、`\x1b[1;2F` Shift+End。
三种 Home、三种 End、两种退格的绑定让 readline 在各终端中保持可移植。
# Bind every Home/End encoding readline might see.
# In ~/.inputrc:
"\e[H": beginning-of-line
"\e[1~": beginning-of-line
"\e[7~": beginning-of-line
"\e[F": end-of-line
"\e[4~": end-of-line
"\e[8~": end-of-line
# Backspace defensively:
"\C-?": backward-delete-char # DEL (0x7F)
"\C-h": backward-delete-char # BS (0x08)Kitty 键盘协议 —— CSI u,消除每个按键的歧义
kitty 键盘协议(有时按其终止字节称作「CSI u」)以统一形式替代所有旧编码:任意键 = `\x1b[<codepoint>;<mod>u`,其中 `codepoint` 是未修饰键的 Unicode 码点(如 ESC = 27、Enter = 13、Space = 32、'a' = 97),`mod` 是与 xterm 系相同的 1+位掩码。应用以 `\x1b[>{flags}u`(push)启用,以 `\x1b[<u`(pop)恢复。标志位:1 = 消歧(默认 —— 区分 ESC 与序列开头、Ctrl+I 与 Tab、Ctrl+M 与 Enter)、2 = 报告事件类型(按下/重复/释放)、4 = 报告替代键(受布局影响的键的 shift 形式与基本形式)、8 = 所有键以转义码上报(不再有纯 ASCII)、16 = 附带相关文本。采纳情况:kitty(起源)、foot、WezTerm、ghostty、Konsole 24.02+、neovide;尚未支持的有 xterm、gnome-terminal、alacritty、Windows Terminal。通过 XTGETTCAP 查询(terminfo cap `kkbds`)或 DECRQM 探测检测支持,否则退回到旧编码。
务必在 SIGINT/SIGTERM 处理器中成对 push/pop —— 应用中途崩溃会让用户 shell 卡在 CSI-u 模式。
# Opt into kitty keyboard, run app, restore on exit.
printf '\e[>1u' # push: disambiguate flag
your-app
printf '\e[<u' # pop: restore previous flag set
# Sample shapes the app then sees:
# 'a' \e[97u
# Ctrl+a \e[97;5u
# Shift+Enter \e[13;2u
# Lone ESC \e[27u (NOT mistakable for a sequence start)
# F1 \e[57364u (function keys also unified)可靠读键 —— 单独 ESC 与序列开头的歧义、20–50ms 超时
输入侧最难的 bug:单独按 ESC(用户想取消 vim 插入模式)的起始字节与每个 CSI / SS3 序列完全一致。终端**不会**发送分隔符。经典修复是计时器 —— 若 ESC 之后的字节在 20–50ms 内到达,视为序列开头;否则视为单独的 ESC。大多数 TUI 库(ncurses、blessed、crossterm、termion、tcell、ratatui、bubbletea)通过 `ESCDELAY` 环境变量替你处理(ncurses 默认 1000ms —— 对现代手感来说太长;用户常设为 ESCDELAY=25)。各语言的原始模式原语:bash `read -rsn1`(一字节、不回显)配合 `read -t 0.05`;Python `termios.tcsetattr` + `select.select(timeout=0.05)`;Rust `crossterm::event::poll(Duration::from_millis(50))`;Go `tcell.Screen.PollEvent`。kitty 键盘协议的标志位 1(消歧)通过对单独 ESC 发送 `\e[27u` 直接消除该问题 —— 无歧义、无超时 —— 这是每个现代 TUI 都在抢着采纳它的实际原因。
50ms 足以接住任何真实序列的剩余字节,也短到用户感到响应敏捷。
# Bash readline-style escape disambiguation in pure bash.
IFS= read -rsn1 c
if [ "$c" = $'\e' ]; then
# Could be start of a sequence — wait up to 50ms for more bytes.
IFS= read -rsn1 -t 0.05 c2 && rest="$c2" || rest=""
if [ -z "$rest" ]; then
echo "Lone ESC pressed"
else
echo "Sequence start: \\e$rest..."
fi
fi参见
/sequence/deckpam-deckpnm —— 小键盘应用与数字模式(**不**控制箭头)。/sequence/dec-bracketed-paste —— \e[?2004h 在粘贴输入两端加标记。/sequence/xtmodkeys —— 控制是否发出修饰键参数的运行时开关。/terminfo —— 每个键名 kbs / khome / kcuu1 / kf1–kf12 的 terminfo 能力对照。