首页
/ Ghostty C API 按键编码实战:用 ghostty-vt Key Encoder 生成 Kitty 键盘协议序列

Ghostty C API 按键编码实战:用 ghostty-vt Key Encoder 生成 Kitty 键盘协议序列

2026-09-05 11:11:26作者:郁楠烈Hubert

本文以 Ghostty 仓库自带的示例 example/c-vt-encode-key/README.md 为主线,讲清楚如何用 ghostty-vt C 库把一次按键事件编码成终端转义序列:如何创建 Key Encoder、如何配置 Kitty Keyboard Protocol(KKP)标志、如何构造 KeyEvent,以及如何将事件编码为形如 \x1b[57442;5:3u 的转义序列。读完你可以独立编写一个按键编码模块,并理解 Ghostty 内部 encode 的缓冲区策略与底层 Kitty 协议映射。

示例定位与运行方式

该示例位于 example/c-vt-encode-key/,是 ghostty-vt C 库系列示例之一。按照 example/README.md 的说明,所有示例(包括 C API 示例)都通过 Zig 构建系统编译运行——C 示例用的是 Zig 的 build 工具,而非 C 编译器本身:

cd example/c-vt-encode-key
zig build run

README 明确了示例覆盖的四个步骤:

  1. 通过 C API 创建按键编码器(key encoder);
  2. 配置 Kitty 键盘协议标志(示例中启用完整 KKP 能力);
  3. 创建并配置按键事件(key event);
  4. 将按键事件编码为终端转义序列。

README 给出的目标结果是:对「按下 Ctrl 修饰键状态下的 Ctrl 键释放事件」编码,产出转义序列 \x1b[57442;5:3u

完整示例源码逐段解析

示例源码 example/c-vt-encode-key/src/main.c 只有 40 行,完整代码如下:

#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <ghostty/vt.h>

//! [key-encode]
int main() {
  // Create encoder
  GhosttyKeyEncoder encoder;
  GhosttyResult result = ghostty_key_encoder_new(NULL, &encoder);
  assert(result == GHOSTTY_SUCCESS);

  // Enable Kitty keyboard protocol with all features
  ghostty_key_encoder_setopt(encoder, GHOSTTY_KEY_ENCODER_OPT_KITTY_FLAGS,
                             &(uint8_t){GHOSTTY_KITTY_KEY_ALL});

  // Create and configure key event for Ctrl+C press
  GhosttyKeyEvent event;
  result = ghostty_key_event_new(NULL, &event);
  assert(result == GHOSTTY_SUCCESS);
  ghostty_key_event_set_action(event, GHOSTTY_KEY_ACTION_PRESS);
  ghostty_key_event_set_key(event, GHOSTTY_KEY_C);
  ghostty_key_event_set_mods(event, GHOSTTY_MODS_CTRL);

  // Encode the key event
  char buf[128];
  size_t written = 0;
  result = ghostty_key_encoder_encode(encoder, event, buf, sizeof(buf), &written);
  assert(result == GHOSTTY_SUCCESS);

  // Use the encoded sequence (e.g., write to terminal)
  fwrite(buf, 1, written, stdout);

  // Cleanup
  ghostty_key_event_free(event);
  ghostty_key_encoder_free(encoder);
  return 0;
}
//! [key-encode]

逐段说明:

  • 创建编码器ghostty_key_encoder_new(NULL, &encoder) 中第一个参数是分配器,传 NULL 表示使用默认分配器。成功返回 GHOSTTY_SUCCESS,句柄类型是不透明指针 GhosttyKeyEncoder(定义为 include/ghostty/vt/key/encoder.h 中的 struct GhosttyKeyEncoderImpl *)。
  • 配置 Kitty 协议标志ghostty_key_encoder_setopt(encoder, GHOSTTY_KEY_ENCODER_OPT_KITTY_FLAGS, ...)GHOSTTY_KITTY_KEY_ALL 一次性打开全部 KKP 能力位。
  • 构造按键事件ghostty_key_event_new 创建事件后,用 set_action / set_key / set_mods 分别设置动作、物理键码和修饰键。
  • 编码ghostty_key_encoder_encode 把事件写入 128 字节栈上缓冲区,written 返回实际写入字节数,随后输出到 stdout。
  • 清理:事件与编码器都要显式 free,两者都不接受悬空句柄。

需要提醒一个细节:README 描述的演示目标是「Ctrl 键释放事件 → \x1b[57442;5:3u」,而仓库中 main.c 当前的注释和代码实际构造的是「Ctrl+C 按下事件」(GHOSTTY_KEY_ACTION_PRESS + GHOSTTY_KEY_C + GHOSTTY_MODS_CTRL),代码与文档注释存在不一致。要得到 README 所述的 \x1b[57442;5:3u,事件应配置为:

ghostty_key_event_set_action(event, GHOSTTY_KEY_ACTION_RELEASE);
ghostty_key_event_set_key(event, GHOSTTY_KEY_CONTROL_LEFT);
ghostty_key_event_set_mods(event, GHOSTTY_MODS_CTRL);

这一组合在 C 层测试 src/terminal/c/key_encode.zig 中被完整验证:设置 release 动作、control_left 键、ctrl 修饰键后,断言编码结果恰为 "\x1b[57442;5:3u"

Encoder API 与全部配置项

编码器句柄和相关类型声明在 include/ghostty/vt/key/encoder.h,Zig 侧实现包装在 src/terminal/c/key_encode.zig。核心 API 共 4 个:

函数 作用
ghostty_key_encoder_new(allocator, &encoder) 以默认选项创建编码器
ghostty_key_encoder_free(encoder) 释放编码器,句柄可传 NULL
ghostty_key_encoder_setopt(encoder, option, &value) 设置编码行为选项;注意传 NULL 值指针是“不做任何事”,不会重置为默认值
ghostty_key_encoder_setopt_from_terminal(encoder, terminal) 从终端实例当前状态批量同步选项(见下文)
ghostty_key_encoder_encode(encoder, event, buf, buf_size, &written) 编码按键事件到缓冲区

GhosttyKeyEncoderOption 定义了 8 个选项(encoder.h),每个选项对应一个终端模式或协议行为:

选项 取值类型 含义
GHOSTTY_KEY_ENCODER_OPT_CURSOR_KEY_APPLICATION (0) bool DEC mode 1:光标键应用模式
GHOSTTY_KEY_ENCODER_OPT_KEYPAD_KEY_APPLICATION (1) bool DEC mode 66:小键盘应用模式
GHOSTTY_KEY_ENCODER_OPT_IGNORE_KEYPAD_WITH_NUMLOCK (2) bool DEC mode 1035:NumLock 下忽略小键盘
GHOSTTY_KEY_ENCODER_OPT_ALT_ESC_PREFIX (3) bool DEC mode 1036:Alt 发送 ESC 前缀
GHOSTTY_KEY_ENCODER_OPT_MODIFY_OTHER_KEYS_STATE_2 (4) bool xterm modifyOtherKeys 模式 2
GHOSTTY_KEY_ENCODER_OPT_KITTY_FLAGS (5) GhosttyKittyKeyFlags 位掩码 Kitty 键盘协议能力位
GHOSTTY_KEY_ENCODER_OPT_MACOS_OPTION_AS_ALT (6) GhosttyOptionAsAlt macOS Option 键是否当 Alt,对应 macos-option-as-alt 配置
GHOSTTY_KEY_ENCODER_OPT_BACKARROW_KEY_MODE (7) bool DECBKM:false(默认)backspace 发 0x7Ftrue0x08

取值类型在 C 层由 src/terminal/c/key_encode.zigOption.InType() 静态确认:布尔选项传 boolkitty_flagsu8 位掩码,macos_option_as_altOptionAsAlt 枚举(取值 0/1/2/3 分别代表 false/true/仅左 Option/仅右 Option)。

Kitty 键盘协议标志位

GhosttyKittyKeyFlagsuint8_t 位标志,可按位或组合:

标志 位值 含义
GHOSTTY_KITTY_KEY_DISABLED 0 关闭 KKP
GHOSTTY_KITTY_KEY_DISAMBIGUATE 1 << 0 消歧义编码
GHOSTTY_KITTY_KEY_REPORT_EVENTS 1 << 1 上报按下与释放事件
GHOSTTY_KITTY_KEY_REPORT_ALTERNATES 1 << 2 上报替代键码
GHOSTTY_KITTY_KEY_REPORT_ALL 1 << 3 上报本应由终端处理的按键
GHOSTTY_KITTY_KEY_REPORT_ASSOCIATED 1 << 4 随事件附带关联文本
GHOSTTY_KITTY_KEY_ALL 以上全部 示例所用的“全开”组合

Zig 包装层在设置该选项时会把 u8 截断为低 5 位再 @bitCast 成内部 KittyFlags 结构(src/terminal/c/key_encode.zig),即只有 5 个有效位。

从终端状态同步选项

如果你在真实终端场景中使用编码器,通常不需要手工逐个 setoptghostty_key_encoder_setopt_from_terminal 会读取终端当前激活的模式与标志,一次性应用光标键应用模式、小键盘模式、Alt ESC 前缀、modifyOtherKeys 状态和 Kitty 协议标志(encoder.h)。实现上它直接调用 src/terminal/c/key_encode.zigopts = .fromTerminal(t)。注意该调用会把 macos_option_as_alt 重置为 GHOSTTY_OPTION_AS_ALT_FALSE(终端状态无法表达此选项),如需保留需事后用 setopt 重新设置。

按键事件:Action、Mods 与物理键码

事件类型定义在 include/ghostty/vt/key/event.h

动作 GhosttyKeyAction

枚举 含义
GHOSTTY_KEY_ACTION_RELEASE (0) 释放
GHOSTTY_KEY_ACTION_PRESS (1) 按下
GHOSTTY_KEY_ACTION_REPEAT (2) 重复(长按)

修饰键位掩码 GhosttyModsuint16_t):GHOSTTY_MODS_SHIFTGHOSTTY_MODS_CTRLGHOSTTY_MODS_ALTGHOSTTY_MODS_SUPER 四个基础位,外加 GHOSTTY_MODS_CAPS_LOCKGHOSTTY_MODS_NUM_LOCK 状态位。头 6 位之外还有 GHOSTTY_MODS_*_SIDE 位区分左右修饰键,但仅当对应修饰键位已置位时才有意义——头文件明确说明并非所有平台都能区分左右修饰键,Ghostty 按“可能拿不到”来设计。

物理键码 GhosttyKey:基于 W3C UI Events KeyboardEvent code 标准(event.h),与键盘布局无关——例如 US 键盘的 “a” 键和俄语键盘同一物理键的 “ф” 都报同一个 GHOSTTY_KEY_A。布局相关的字符由平台单独以 UTF-8 文本提供,通过 ghostty_key_event_set_utf8 传入,且要求传入未做 Ctrl/Meta 变换前的原始字符,不要传 C0 控制字符或平台功能键 PUA 码点,此时应传 NULL 让编码器回退到逻辑键。事件还提供 set_consumed_mods(被平台消费的修饰键)、set_composing(是否处于组合输入序列)、set_unshifted_codepoint 等细粒度字段。

\x1b[57442;5:3u 是怎么算出来的

README 给出的序列可以逐段拆解,且每一段都能在源码中找到依据:

  • 57442:左 Control 键的 Kitty 键码。在 src/input/kitty.zig 的键码表中登记为 .{ .control_left, 57442, 'u', true },同表还有 shift_left=57441control_right=57448alt_left=57443 等,修饰键条目均标记为可独立上报。
  • 5:修饰键字段。Kitty 序列中的修饰值沿用 xterm modifyOtherKeys 的编码方式(基础值 1,ctrl 贡献 4),因此纯 ctrl 修饰键对应 5
  • :3:事件类型字段。:3 表示释放事件;按下为 1、重复为 2
  • 结尾 u:Kitty 协议(CSI u)的终止符。

编码路径为:C API 的 encode → C 包装层 src/terminal/c/key_encode.zigencode → 核心实现 src/input/key_encode.zigkitty() 函数(当 kitty_flags 非零时走 KKP 分支,否则回退 legacy 编码)。核心测试 src/input/key_encode.zig 中的 test "kitty: ctrl release with ctrl mod set" 与 C 层测试互为印证:release + control_left + ctrl 修饰键 + 全量标志 → [57442;5:3u;而仅按下(press)同键同修饰 → [57442;5u(无 :1 后缀),恰好说明 report_events 标志开启后释放事件才会携带事件类型字段。

缓冲区策略:OUT_OF_SPACE 与两阶段查询

ghostty_key_encoder_encode 的缓冲区语义在 encoder.h 中有完整文档,值得在自研封装中照搬:

  • 缓冲区不足时返回 GHOSTTY_OUT_OF_SPACE,且 out_len 会写入所需字节数,可直接用于分配后重试;
  • 传入 NULL 缓冲区加 0 长度时必然返回 GHOSTTY_OUT_OF_SPACE,可专门用于查询所需大小;
  • 并非所有按键都会产出序列(例如未带修饰键的修饰键本身),需以 out_len 是否为 0 判断。
// 查询所需大小(NULL 缓冲区必然 OUT_OF_SPACE)
size_t required = 0;
GhosttyResult result = ghostty_key_encoder_encode(encoder, event, NULL, 0, &required);
assert(result == GHOSTTY_OUT_OF_SPACE);

char *buf = malloc(required);
size_t written = 0;
result = ghostty_key_encoder_encode(encoder, event, buf, required, &written);
assert(result == GHOSTTY_SUCCESS);

示例 main.c 则采用更常见的静态缓冲写法(128 字节栈数组),多数转义序列很短,静态缓冲足够。

C 包装层的实现揭示了“所需大小”如何得到(src/terminal/c/key_encode.zig):先用定长 writer 写入目标缓冲,若触发 error.WriteFailed,则换一个丢弃型 writer(std.Io.Writer.Discarding)重放一次编码,用其计数得到所需空间并回传给 out_written,最后返回 .out_of_space。C 层测试还专门验证了“查询长度”与“实际写入长度”一致。

验证与延伸阅读

小结

ghostty-vt 的按键编码 API 设计为「编码器 + 事件」两件套:编码器承载所有终端模式与协议开关(DEC 模式、modifyOtherKeys、Kitty 标志、平台行为),事件承载物理键、动作、修饰键与布局文本,encode 负责按当前配置输出转义序列,并以 OUT_OF_SPACE 约定解决缓冲区适配问题。示例 example/c-vt-encode-key/ 虽然只有 40 行,但把创建、配置、构造、编码、释放的完整生命周期都演示到了,是接入 Kitty 键盘协议编码能力的最短路径。

登录后查看全文
热门项目推荐
相关项目推荐