Ghostty C API 实战:用 ghostty-vt 的 Focus 编码接口把焦点事件编码为终端转义序列
Ghostty 除了作为完整终端仿真器之外,还对外发布了名为 ghostty-vt 的标准 C 库(libghostty),其中包含一组轻量的“编码”辅助接口。example/c-vt-encode-focus 示例演示了其中最简单的一类用法:调用 ghostty_focus_encode() 把“窗口获得焦点 / 失去焦点”事件编码为终端转义序列(CSI I / CSI O)。读完本文,你将掌握如何编写并构建一个链接 ghostty-vt 的 C 程序,理解该接口的函数签名、返回约定与缓冲区语义,并能从源码层面确认它实际输出的是哪几个字节。
示例的定位与运行方式
该示例位于仓库的 example/c-vt-encode-focus 目录,其 README 说明:这是一个展示如何使用 ghostty-vt focus 编码 API 把 focus gained/lost 事件编码为转义序列的简单示例。示例本身是 C 程序,但通过 build.zig 和 Zig 构建系统来编译——这样做可以直接复用 Ghostty 仓库的构建逻辑并依赖源码树本身,而 Ghostty 实际发布的是标准 C 库,任何 C 工具链都可以链接使用。
按照 example/README.md 的统一约定,所有示例(包括以 c- 开头的 C API 示例)都可以进入目录后执行以下命令构建并运行:
cd example/c-vt-encode-focus
zig build run
其中 zig build run 是 build.zig 中注册的 run 步骤,它依赖 install 步骤先编译产物再执行。
完整的 C 示例代码
示例的全部 C 源码只有 src/main.c 一个文件:
#include <stdio.h>
#include <ghostty/vt.h>
//! [focus-encode]
int main() {
char buf[8];
size_t written = 0;
GhosttyResult result = ghostty_focus_encode(
GHOSTTY_FOCUS_GAINED, buf, sizeof(buf), &written);
if (result == GHOSTTY_SUCCESS) {
printf("Encoded %zu bytes: ", written);
fwrite(buf, 1, written, stdout);
printf("\n");
}
return 0;
}
//! [focus-encode]
代码要点:
- 调用
ghostty_focus_encode()时传入事件GHOSTTY_FOCUS_GAINED(获得焦点),一个 8 字节的输出缓冲区buf,缓冲区长度sizeof(buf),以及输出参数&written用于接收实际写入的字节数; - 只有当返回值等于
GHOSTTY_SUCCESS时,才把written字节写入 stdout; - 源码中的
//! [focus-encode]标记不是注释的普通内容,而是 Doxygen 的 snippet 边界标记(见下文“与文档系统联动”一节)。
示例选用 8 字节缓冲区并非偶然设计,而是出于健壮性考虑:底层实现保证一次编码最多只写 3 个字节(见下节),8 字节足以容纳,同时演示了“调用方提供缓冲区”这一 API 约定。
接口定义:include/ghostty/vt/focus.h
该接口的权威定义在头文件 include/ghostty/vt/focus.h 中,头部注释说明这是 “focus encoding” 模块——把 focus in/out 事件编码为终端转义序列(CSI I / CSI O),服务于焦点报告模式(focus reporting mode,即 mode 1004)。
焦点事件由一个枚举表示:
/**
* Focus event types for focus reporting mode (mode 1004).
*/
typedef enum GHOSTTY_ENUM_TYPED {
/** Terminal window gained focus */
GHOSTTY_FOCUS_GAINED = 0,
/** Terminal window lost focus */
GHOSTTY_FOCUS_LOST = 1,
GHOSTTY_FOCUS_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
} GhosttyFocusEvent;
编码函数原型为:
GHOSTTY_API GhosttyResult ghostty_focus_encode(
GhosttyFocusEvent event,
char* buf,
size_t buf_len,
size_t* out_written);
参数语义(来自头文件注释):
| 参数 | 说明 |
|---|---|
event |
要编码的焦点事件(GHOSTTY_FOCUS_GAINED 或 GHOSTTY_FOCUS_LOST) |
buf |
输出缓冲区,写入编码后的转义序列;可以为 NULL |
buf_len |
输出缓冲区的字节容量 |
out_written |
成功时写入实际写入的字节数;缓冲区不足时写入所需缓冲区大小 |
返回值约定是该 API 值得注意的设计:成功返回 GHOSTTY_SUCCESS;若缓冲区太小则返回 GHOSTTY_OUT_OF_SPACE,并把所需大小写回 out_written,调用方据此用足够大的缓冲区重试。也就是说,buf 允许为 NULL 时可以用一次调用探测所需长度,这是嵌入式场景下典型的“先量后写”契约。
底层实现:实际输出的是哪几个字节
ghostty_focus_encode 是 C 导出符号,真正的实现在 Zig 侧。src/lib_vt.zig 中有显式导出:
@export(&c.focus_encode, .{ .name = "ghostty_focus_encode" });
该符号指向 src/terminal/focus.zig 中的 encode 函数:
/// Maximum number of bytes that `encode` will write. Any users of this
/// should be resilient to this changing, so this is always a specific
/// value (e.g. we don't add unnecessary padding).
pub const max_encode_size = 3;
/// Encode a focus in/out report (CSI I / CSI O).
pub fn encode(
writer: *std.Io.Writer,
event: Event,
) std.Io.Writer.Error!void {
try writer.writeAll(switch (event) {
.gained => "\x1B[I",
.lost => "\x1B[O",
});
}
由此可以确认几个实现事实:
- 获得焦点输出 3 个字节
ESC [ I(即\x1B[I,CSI I),失去焦点输出ESC [ O(即\x1B[O,CSI O); - 编码结果的上限是常量
max_encode_size = 3,因此示例中 8 字节的栈缓冲区必然足够,GHOSTTY_OUT_OF_SPACE分支在该场景下不会触发; - 同一文件内还附带了两个单元测试(
test "encode focus gained"/test "encode focus lost"),用固定缓冲区 writer 断言两种事件分别编码为\x1B[I和\x1B[O],是验证该接口行为的最直接依据。
从源码结构看,focus.zig 是终端内部实现,而 C 库通过 src/terminal/c/focus.zig 中的 encode 包装(经由 src/terminal/c/main.zig 的 pub const focus_encode = focus.encode;)对外暴露,形成“C 头文件声明 → 导出符号 → Zig 编码函数”的调用链。
构建系统:build.zig 与 build.zig.zon
这个示例也完整展示了第三方项目如何依赖 ghostty-vt。build.zig 的核心逻辑:
const exe_mod = b.createModule(.{ .target = target, .optimize = optimize });
exe_mod.addCSourceFiles(.{ .root = b.path("src"), .files = &.{"main.c"} });
// 使用 lazy dependency:只有真正需要时才解析 ghostty 依赖
if (b.lazyDependency("ghostty", .{ /* .simd = false 可做纯静态构建(无 libc),但有明显性能损耗 */ })) |dep| {
exe_mod.linkLibrary(dep.artifact("ghostty-vt"));
}
const exe = b.addExecutable(.{ .name = "c_vt_encode_focus", .root_module = exe_mod });
b.installArtifact(exe);
关键约定:
- 可执行目标名使用下划线:
c_vt_encode_focus(对应目录名中的连字符),这是 example 目录的统一规范; - 通过
lazyDependency("ghostty", ...)链接ghostty-vtartifact,注释同时说明:设置.simd = false会强制得到不依赖 libc 的纯静态构建,但性能代价显著——如果宿主应用本就依赖 libc,应保持 simd 启用; - 依赖声明在 build.zig.zon 中。仓库内的示例使用路径依赖
.ghostty = .{ .path = "../../" },以便始终对照随示例捆绑的源码树进行测试;zon 文件里保留了注释掉的 URL 依赖写法示例,说明真实外部项目通常用带 hash 的 URL 归档依赖指向某个固定提交;minimum_zig_version为0.15.1。
与文档系统联动:snippet 标记的由来
回到 src/main.c 里那两行 //! [focus-encode]。example/AGENTS.md 解释了这一约定:示例源码使用 Doxygen snippet 标记,让 include/ghostty/vt/focus.h 等头文件通过 @snippet c-vt-encode-focus/src/main.c focus-encode 引用同一份代码,而不是在头文件里重复内联代码块。这正是头文件中“Basic Usage / Example”一节直接指向本示例的原因——修改示例代码时需要保持 snippet 标记与头文件引用同步。
另外,example 目录约定所有新示例会被 CI 通过 example/*/build.zig.zon 通配自动发现,因此该示例同时充当了仓库自身的构建与文档集成样例。
小结
c-vt-encode-focus 用不到 20 行 C 代码展示了嵌入 ghostty-vt 的最小路径:包含 ghostty/vt.h → 调用 ghostty_focus_encode(GHOSTTY_FOCUS_GAINED, buf, len, &written) → 检查 GHOSTTY_SUCCESS 并消费 out_written。底层实现确认其输出固定为 3 字节的 CSI I(src/terminal/focus.zig 中的 max_encode_size),且配套单元测试覆盖了 gained/lost 两种事件。若你的终端嵌入场景需要向应用转发窗口焦点变化(配合 mode 1004 焦点报告),可以直接以 example/c-vt-encode-focus 为模板,替换依赖声明为指向发布归档的 URL 依赖,即可脱离 Ghostty 源码树独立构建。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00