wterm Web终端输入处理详解:鼠标追踪、Kitty键盘协议与中文输入法组合
【免费下载链接】wtermA terminal emulator for the web项目地址: https://gitcode.com/gh_mirrors/wterm1/wterm
wterm 是一个面向 Web 的开源终端模拟器,其核心用 Zig 编写并编译为 WASM,DOM 层则负责渲染与全部输入处理。本文带你快速看懂 wterm 的 Web 终端输入处理三大核心能力:鼠标追踪(Mouse Tracking)、Kitty 键盘协议与中文输入法(IME)组合,帮你理解浏览器环境下的按键、鼠标事件如何被准确转发给终端应用。
为什么 Web 终端的输入处理是难点 🤔
在浏览器里运行终端,输入链路和桌面终端完全不同:
- 键盘事件由
KeyboardEvent描述,但浏览器拿不到物理键位的完整信息,不同布局(如欧洲 AltGr 布局)还会让同一个键产生不同含义; - 鼠标坐标是 CSS 像素,而终端应用期望的是"第几行第几列",需要逐像素换算成网格坐标;
- 中文等输入法有"组合中"和"上屏"两个阶段,未确认的拼音不能直接发给终端。
wterm 的输入逻辑集中在 input.ts 中的InputHandler类,配合 kitty-keys.ts 完成协议编码。
鼠标追踪:5 种编码格式与坐标换算
当 Vim、htop、Tmux 等应用开启鼠标模式(1000 点击 / 1002 拖拽 / 1003 全量运动)后,wterm 会把鼠标事件按应用当前选择的编码格式上报,共支持X10、UTF-8(1005)、SGR(1006)、urxvt(1015)、SGR 像素(1016)五种。
关键实现位于 handleMouse():
- 坐标换算:先取终端视口首行的位置与单元格宽高,再把
clientX/clientY换算为 1 起始的行列号(或 CSS 像素,1016 模式),见 坐标计算片段; - 修饰键位:Shift / Alt / Ctrl 分别对应 4 / 8 / 16,运动事件额外加 32,滚轮事件用 64–67 区分方向;
- X10 二进制通道:X10 编码可能产生非 ASCII 字节,wterm 通过可选的
onBinary回调原样送出,避免被 UTF-8 编码破坏,无法表示的坐标会跳过而不是报错。
一些贴心的细节:
| 场景 | wterm 的行为 |
|---|---|
| 在回滚区(scrollback)上滚动 | 保留给浏览器,继续翻历史直到回到底部 |
| 鼠标追踪开启时翻历史 | 按住 Shift 滚轮即可在实时视口外查看历史 |
| 1003 模式未按键运动 | 每跨一个单元格上报一次(像素模式每跨一个像素一次) |
对应行为可在 input-mouse.test.ts 与 e2e 用例 mouse-motion.spec.ts 中验证。
Kitty 键盘协议:让每个按键都"可分辨"
传统 CSI 序列分不清Ctrl+a(字符)与Ctrl+Backspace(控制键),也区分不了左右 Alt 同时按住时按 a。Kitty 键盘协议通过"协商标志位"解决这一问题,wterm 内置核心与 Ghostty 核心均支持查询、push/pop、set、OR/NOT 等操作。
wterm 在 kitty-keys.ts 定义了四个核心标志:
KITTY_REPORT_EVENTS(1<<1):上报 press / repeat / release 事件类型KITTY_REPORT_ALTERNATES(1<<2):上报 Shift 产生的替代表意字符KITTY_REPORT_ALL(1<<3):全量按键上报,含修饰键自身KITTY_REPORT_ASSOCIATED(1<<4):附带按键关联文本(解决多键盘布局歧义)
编码入口是 encodeKittyKey(),其策略很清晰:
- 无修饰键的普通字符直接走原生文本路径,不占用协议序列;
- 功能键与修饰组合才生成
CSI 1;mod:eventType u形式的序列; - 兼容回退:未开启全量上报时,
Ctrl+Alt组合仍可用旧式 xterm 序列表达,保证老应用行为不变。
在 handleKeyDown() 中可以看到两条路径的分叉:kittyKeyboardFlags()非零时走 Kitty 编码(L378-L394),否则回退到 xterm 风格的keyToSequence。另一个亮点是AltGr 智能直通:当浏览器能识别 AltGr 产生的可打印字符时,wterm 放行原生输入,让欧洲布局用户即使在 Kitty 模式下也能正常输入特殊字符(见 isAltGraphTextInput())。
中文输入法组合:候选字确认后才会发送
对中文用户来说,最关键的体验是:打拼音时的中间态不会发给终端,只有确认上屏的文字才会送达。
wterm 的实现基于浏览器原生的compositionstart/compositionend事件:
- 组合开始(handleCompositionStart()):把透明的输入框
textarea定位到终端光标处,此时输入框可见,拼音暂存在浏览器输入框中,候选窗也自然贴近光标; - 按键屏蔽:组合期间的 keydown 事件全部跳过(L321-L325),防止拼音按键被误编码成控制序列;
- 组合结束(handleCompositionEnd()):通过
e.data拿到确认文本后发送,并记录提交时间与内容; - 去重保护:部分浏览器会在
compositionend后又触发一次携带相同文本的input事件,wterm 用 COMPOSITION_INPUT_DEDUP_MS = 250 毫秒窗口 + 文本比对(L579-L586)过滤重复,避免中文被发送两遍; - 自动回到底部:如果在阅读历史输出时开始输入,wterm 会把视口拉回实时区域,让输入文本与候选窗保持在光标附近。
端到端的中文输入行为由 ime.spec.ts 持续回归,协议层单测在 kitty-keys.test.ts。
输入处理模块速查表 📍
| 能力 | 核心文件 |
|---|---|
| 键盘 / 鼠标 / IME / 触摸总入口 | packages/@wterm/dom/src/input.ts |
| Kitty 协议编码 | packages/@wterm/dom/src/kitty-keys.ts |
| 粘贴与括号粘贴(防 ESC 注入) | sendPaste() |
| 输入 API 与行为说明 | packages/@wterm/dom/README.md |
| 鼠标 / 触摸 / IME 单测 | packages/@wterm/dom/src/__tests__/ |
| e2e 输入回归(鼠标、IME、触摸) | e2e/harness/tests/ |
小结
wterm 把浏览器输入与终端协议之间的"翻译"工作做得到位:鼠标事件按 5 种编码精准换算为行列坐标,Kitty 键盘协议在兼容旧应用的前提下消除了按键歧义,中文输入法则严格遵循"组合中不发送、上屏才送达"的原则并加了去重保护。如果你想深入阅读,建议从 input.ts 的handleKeyDown与handleMouse两个方法入手,再对照 README.md 中鼠标与键盘章节的逐条说明,就能完整掌握这套 Web 终端输入处理的精髓。
【免费下载链接】wtermA terminal emulator for the web项目地址: https://gitcode.com/gh_mirrors/wterm1/wterm
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考