环境:macOS 13.7.8(Intel)+ Ghostty 1.4.0 + 豆包拼音输入法
现象:输入法激活时Ctrl+C无法中断进程,Ctrl+A/E/B/F/P/N等编辑键同样失效;同机 Kitty 正常
结论:Ghostty 的 macOS 输入快速路径受输入法影响,用物理键名绑定可完整绕过
覆盖:22 个 Ctrl 组合 + 4 个 Alt 组合,已在 4 台 Mac 上验证
TL;DR #
# ~/.config/ghostty/config
# A–Z 中除 H(Backspace)/I(Tab)/J(LF)/M(CR) 外的全部字母
keybind = ctrl+key_a=text:\x01
keybind = ctrl+key_b=text:\x02
keybind = ctrl+key_c=text:\x03
keybind = ctrl+key_d=text:\x04
keybind = ctrl+key_e=text:\x05
keybind = ctrl+key_f=text:\x06
keybind = ctrl+key_g=text:\x07
keybind = ctrl+key_k=text:\x0b
keybind = ctrl+key_l=text:\x0c
keybind = ctrl+key_n=text:\x0e
keybind = ctrl+key_o=text:\x0f
keybind = ctrl+key_p=text:\x10
keybind = ctrl+key_q=text:\x11
keybind = ctrl+key_r=text:\x12
keybind = ctrl+key_s=text:\x13
keybind = ctrl+key_t=text:\x14
keybind = ctrl+key_u=text:\x15
keybind = ctrl+key_v=text:\x16
keybind = ctrl+key_w=text:\x17
keybind = ctrl+key_x=text:\x18
keybind = ctrl+key_y=text:\x19
keybind = ctrl+key_z=text:\x1a
# Alt 组合:终端里 Alt+B 等是 ESC 前缀序列
keybind = alt+key_b=text:\x1bb
keybind = alt+key_f=text:\x1bf
keybind = alt+key_d=text:\x1bd
keybind = alt+backspace=text:\x1b\x7f
保存后按 ⌘ + ⇧ + , 重载配置。
为什么是 Emacs 风格 #
这批按键不是 Ghostty 定义的,而是 GNU Readline 的默认编辑键。bash/zsh 的 emacs 模式、fish 的多数默认绑定都沿用同一套语义。
终端本身不解释这些按键,它只负责把控制字符写进 pty;具体行为由 shell 或运行中的程序决定。因此修复的本质是:
保证控制字符能穿过输入法,原样到达 shell。
现象 #
在一台 Intel Mac 上,Ghostty 里运行 sleep 100,按 Ctrl+C 完全没有反应;换成 Kitty 打开同一个 shell,立刻正常。
继续测试发现:所有 Ctrl + 字母 组合都失效,而 Ctrl + 非字母 正常。
值得注意的是,这台机器的键盘布局是 ABC(US),只有当前输入源是中文输入法。问题不在布局,而在输入法是否激活。
排查过程 #
1. 排除配置冲突 #
ghostty +list-keybinds | grep -i 'ctrl+c'
ghostty +show-config | grep -i keybind
结果:配置里没有任何自定义绑定,默认键位中复制是 super+c,没有东西拦截 ctrl+c。
2. 确认输入源 #
defaults read ~/Library/Preferences/com.apple.HIToolbox.plist AppleSelectedInputSources
输出中出现 com.bytedance.inputmethod.doubaoime。布局仍是 US,但输入法处于激活状态。
3. 对照已知问题 #
Ghostty 在 macOS 上处理 Ctrl + 字母 时走快速路径,直接使用 AppKit 的 charactersIgnoringModifiers。输入法激活时,该接口返回的是输入法处理后的字符,而不是按键本身,于是 Ctrl+C 不再产生 0x03,进程收不到 SIGINT。
为什么 Kitty 正常 #
Kitty 使用自己的按键处理流程,不依赖 charactersIgnoringModifiers 判断 Ctrl + 字母,所以在输入法激活时仍能正确发送控制字符。
同一台机器、同一个 shell、同一个输入法,只换终端就正常——变量被收敛到终端层。
修复:绑定物理键 #
Ghostty 的 keybind 支持物理键名(key_c 表示物理位置上的 C 键)。物理键绑定不经过布局与输入法的字符翻译,直接把控制序列写进 pty。
为什么 Alt 是 \x1bb #
终端里并不存在独立的 Alt+B 键码。传统做法是发送 ESC(0x1b)后跟字母 b,读行程序据此识别为 Alt 组合。
macos-option-as-alt = true 原本会自动做这个转换,但输入法介入时同样会被打断,所以这里显式绑定。
完整映射表 #
Ctrl 组合(22 个) #
| 键 | 字符 | Emacs/readline 语义 |
|---|---|---|
Ctrl+A |
0x01 |
跳到行首 |
Ctrl+B |
0x02 |
后退一个字符 |
Ctrl+C |
0x03 |
中断(SIGINT) |
Ctrl+D |
0x04 |
删除字符;空行时为 EOF |
Ctrl+E |
0x05 |
跳到行尾 |
Ctrl+F |
0x06 |
前进一个字符 |
Ctrl+G |
0x07 |
取消当前命令 |
Ctrl+K |
0x0b |
删除到行尾 |
Ctrl+L |
0x0c |
清屏 |
Ctrl+N |
0x0e |
下一行 / 下一条历史 |
Ctrl+O |
0x0f |
接受并跳到下一条历史 |
Ctrl+P |
0x10 |
上一行 / 上一条历史 |
Ctrl+Q |
0x11 |
XON(恢复输出) |
Ctrl+R |
0x12 |
反向搜索历史 |
Ctrl+S |
0x13 |
XOFF(暂停输出) |
Ctrl+T |
0x14 |
交换两个字符 |
Ctrl+U |
0x15 |
删除到行首(fish 中为清空整行) |
Ctrl+V |
0x16 |
按字面插入下一个字符 |
Ctrl+W |
0x17 |
删除前一个词 |
Ctrl+X |
0x18 |
readline 前缀键 |
Ctrl+Y |
0x19 |
粘贴(yank) |
Ctrl+Z |
0x1a |
挂起(SIGTSTP) |
Alt 组合(4 个) #
| 键 | 序列 | 语义 |
|---|---|---|
Alt+B |
ESC b |
按词后退 |
Alt+F |
ESC f |
按词前进 |
Alt+D |
ESC d |
删除后一个词 |
Alt+Backspace |
ESC DEL |
删除前一个词 |
刻意排除的四个键 #
| 键 | 字符 | 对应功能 | 排除原因 |
|---|---|---|---|
Ctrl+H |
0x08 |
Backspace | 绑定会破坏退格 |
Ctrl+I |
0x09 |
Tab | 绑定会破坏补全 |
Ctrl+J |
0x0a |
换行(LF) | 绑定会破坏回车 |
Ctrl+M |
0x0d |
回车(CR) | 绑定会破坏回车 |
这四个是结构键,终端已经在正确发送,不要覆盖。
验证 #
ghostty +list-keybinds | grep -E 'ctrl\+key_|alt\+key_|alt\+backspace'
ghostty +show-config 2>&1 | grep -icE 'error|invalid' # 期望 0
重载配置后逐项测试:
Ctrl+C中断sleep 100Ctrl+A/Ctrl+E跳到行首 / 行尾Ctrl+B/Ctrl+F前后移动Ctrl+P/Ctrl+N历史上下Alt+B/Alt+F按词移动Alt+Backspace删除前一个词
注意事项 #
- 与布局无关:物理键绑定按位置生效,Dvorak、Colemak、中文输入法激活时都不受影响
- 语义由 shell 决定:
Ctrl+U在 bash 是删到行首、在 fish 是清空整行;Ctrl+W在部分程序中会被覆盖 Ctrl+S会暂停输出:这是标准 XOFF 行为,按Ctrl+Q恢复- 升级不一定能解决:该问题在 1.3.1 上仍可复现,建议保留配置
- 临时替代:把输入源切回 ABC 也能恢复,但每次手动切换很麻烦