Ghostty C API 实战:通过 ghostty-vt 系统接口集成 Kitty 图形协议并发送 PNG 图像
Ghostty 不仅是一个终端模拟器,还以 ghostty-vt 的形式对外提供一套标准的 C 语言终端库。本篇以仓库中的示例 example/c-vt-kitty-graphics/README.md 及其实现 example/c-vt-kitty-graphics/src/main.c 为主体,完整演示如何嵌入 ghostty-vt:先通过系统接口(ghostty_sys_set)安装 PNG 解码回调,再通过 ghostty_terminal_vt_write 发送一条 Kitty 图形协议(Kitty Graphics Protocol)命令写入一张 PNG 图像,最后用 Kitty 图形查询 API 验证图像被正确存储、并读取其渲染几何信息。读完本文,你可以掌握在任意宿主应用中集成 Ghostty VT 库、解析并渲染终端图像协议的完整链路。
示例的目标与整体流程
示例 README 明确指出其意图:这是一个"如何使用系统接口安装 PNG 解码回调,然后通过 ghostty_terminal_vt_write 发送 Kitty 图形协议图像"的简单示例。整个 main.c 的执行链路可以概括为五步:
- 安装全局解码回调:
ghostty_sys_set设置userdata与GHOSTTY_SYS_OPT_DECODE_PNG; - 创建终端:
ghostty_terminal_new创建 80×24 终端,并用ghostty_terminal_resize设置单元格像素尺寸(8×16),让 Kitty 图形能够计算网格尺寸; - 启用图形存储:通过
GHOSTTY_TERMINAL_OPT_KITTY_IMAGE_STORAGE_LIMIT设置非零存储上限(示例为 64 MiB),并通过GHOSTTY_TERMINAL_OPT_WRITE_PTY安装写回调以捕获协议应答; - 发送转义序列:构造
ESC _G a=T,f=100,q=1;<base64 PNG> ESC \的 APC 序列并ghostty_terminal_vt_write; - 验证与查询:取回
GhosttyKittyGraphics句柄,读取存储代际、遍历 placement、查询图像属性与渲染尺寸。
示例目录还包含构建文件 build.zig 与 build.zig.zon,用于复用 Ghostty 的构建逻辑并直接依赖源码树——但如 README 所说,Ghostty 产出的是标准 C 库,任何 C 工具链(Make、CMake、Meson 等)都可以直接链接使用,Zig 只是本示例的打包方式。
第一步:通过系统接口安装 PNG 解码回调
ghostty-vt 将"依赖外部实现"的功能(例如图像解码)抽象为运行时可替换的系统接口,其头文件 include/ghostty/vt/sys.h 的注释说明:这些是进程级全局配置,必须在启动时、在任何依赖该功能的终端操作发生之前完成设置。设置 PNG 解码器即启用了 Kitty 图形协议的 PNG 图像支持;反之,若未安装解码器,PNG 图像数据会被拒绝。
回调签名与输出结构
解码回调的类型为 GhosttySysDecodePngFn,签名与语义(见 sys.h):
- 入参
userdata是宿主通过GHOSTTY_SYS_OPT_USERDATA注入的上下文指针; - 入参
allocator是库提供的分配器——输出像素缓冲必须通过该分配器分配,库随后取得所有权并用同一分配器释放; - 入参
data/data_len为原始 PNG 字节; - 出参
out是GhosttySysImage结构:width(像素宽)、height(像素高)、data(解码后的 RGBA 像素指针)、data_len(像素数据字节数); - 返回
true表示解码成功。
示例中的 decode_png 回调(main.c)为了演示最小可运行代码,硬编码了一枚 1×1 红色像素(R=255, G=0, B=0, A=255),源码注释明确警告:它并不真正解码传入的 PNG,真实实现应使用 libpng、stb_image 等 PNG 库完成解码。回调内通过 userdata 拿到一个计数器并打印调用次数,方便在运行输出中确认"解码器确实被终端调用过":
bool decode_png(void* userdata,
const GhosttyAllocator* allocator,
const uint8_t* data,
size_t data_len,
GhosttySysImage* out) {
int* count = (int*)userdata;
(*count)++;
printf(" decode_png called (size=%zu, call #%d)\n", data_len, *count);
/* 通过库提供的分配器分配 RGBA 像素数据。 */
const size_t pixel_len = 4; /* 1x1 RGBA */
uint8_t* pixels = ghostty_alloc(allocator, pixel_len);
if (!pixels) return false;
/* 填充红色 (R=255, G=0, B=0, A=255)。 */
pixels[0] = 255;
pixels[1] = 0;
pixels[2] = 0;
pixels[3] = 255;
out->width = 1;
out->height = 1;
out->data = pixels;
out->data_len = pixel_len;
return true;
}
安装与清理
安装回调只需两次 ghostty_sys_set 调用;GHOSTTY_SYS_OPT_USERDATA 用于注入回调的上下文指针,GHOSTTY_SYS_OPT_DECODE_PNG 传入函数指针。注意 ghostty_sys_set 的第二个参数是 const void*,传函数指针需要显式转换;传 NULL 则会清除对应实现并禁用相应功能——示例在退出前就利用这一点做了清理:
/* 通过 sys 接口安装 PNG 解码器。 */
int decode_count = 0;
ghostty_sys_set(GHOSTTY_SYS_OPT_USERDATA, &decode_count);
ghostty_sys_set(GHOSTTY_SYS_OPT_DECODE_PNG, (const void*)decode_png);
/* ... 程序结束前清理 ... */
ghostty_terminal_free(terminal);
ghostty_sys_set(GHOSTTY_SYS_OPT_DECODE_PNG, NULL);
ghostty_sys_set(GHOSTTY_SYS_OPT_USERDATA, NULL);
顺带一提,GhosttySysOption 枚举还提供了 GHOSTTY_SYS_OPT_LOG(内部日志回调,可直接使用内置的 ghostty_sys_log_stderr)与 GHOSTTY_SYS_OPT_RANDOM_SECURE(安全随机源覆盖),但示例仅用到前两个。
第二步:创建终端并启用 Kitty 图形存储
回调安装完毕后,示例依次完成终端创建与图形功能启用,这几步的顺序与参数都有讲究:
/* 创建终端(80 列 × 24 行)。 */
GhosttyTerminal terminal = NULL;
if (ghostty_terminal_new(NULL, &terminal, 80, 24) != GHOSTTY_SUCCESS) {
fprintf(stderr, "Failed to create terminal\n");
return 1;
}
/* 设置单元格像素尺寸,Kitty 图形才能计算网格尺寸。 */
ghostty_terminal_resize(terminal, 80, 24, 8, 16);
/* 设置非零存储上限以启用 Kitty 图形(64 MiB)。 */
uint64_t storage_limit = 64 * 1024 * 1024; /* 64 MiB */
ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_KITTY_IMAGE_STORAGE_LIMIT,
&storage_limit);
/* 安装 write_pty 回调以观察协议应答。 */
ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_WRITE_PTY,
(const void*)on_write_pty);
三个关键点:
- 单元格像素尺寸:
ghostty_terminal_resize后两个参数(8×16)告诉库每个字符格子的物理像素尺寸。Kitty 图形的 placement 需要以"列 × 行"占位,库必须把像素尺寸换算成网格尺寸,因此示例特意先调用 resize 并加注释说明原因; - 存储上限是启用开关:kitty_graphics.h 的文档明确写道"在图像可以存储之前,必须通过
GHOSTTY_TERMINAL_OPT_KITTY_IMAGE_STORAGE_LIMIT设置非零存储上限来启用 Kitty 图形,并且必须通过ghostty_sys_set安装 PNG 解码回调"——存储上限与解码回调二者缺一不可; - write_pty 回调:Kitty 图形协议在图像加载后会向 pty 回发一条 APC 应答(除非请求方以
q=2抑制)。示例安装的on_write_pty只是把回包打印出来,用于在运行输出中验证协议应答确实发生。
第三步:发送 Kitty 图形转义序列
启用完成后,示例构造并写入一条 Kitty 图形 APC(Application Program Command)序列:
/*
* 发送内联 1x1 PNG 图像的 Kitty 图形命令。
* 转义序列形如:
* ESC _G a=T,f=100,q=1; <base64 PNG 数据> ESC \
* 其中:
* a=T —— 传输并显示
* f=100 —— PNG 格式
* q=1 —— 请求应答(q=0 则抑制应答)
*/
const char* kitty_cmd =
"\x1b_Ga=T,f=100,q=1;"
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAA"
"DUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg=="
"\x1b\\";
ghostty_terminal_vt_write(terminal, (const uint8_t*)kitty_cmd,
strlen(kitty_cmd));
字段含义与行为对照:
| 字段 | 取值 | 含义 |
|---|---|---|
a=T |
Transmit | 传输(存入存储)并显示(创建 placement) |
f=100 |
PNG | 数据格式为 base64 编码的 PNG;库会调用你在第一步安装的解码回调 |
q=1 |
请求应答 | 图像加载后向 pty 回发确认 APC,因此 on_write_pty 会打印 response (...) |
base64 片段对应的正是回调所"假设"的那张 1×1 红色 PNG。写入后终端内部完成:base64 解码 → 调用 decode_png 回调 → 得到 RGBA 像素 → 分配图像 ID 存入图像存储 → 按当前光标位置创建 placement。运行输出中可以看到 PNG decode calls: 1,证实解码回调恰好被调用一次。
第四步:查询图像存储、placement 与渲染几何
验证部分完整展示了 kitty_graphics.h 文档描述的"placement 遍历"工作流。
1) 获取句柄与存储代际。GhosttyKittyGraphics 句柄通过 ghostty_terminal_get 以 GHOSTTY_TERMINAL_DATA_KITTY_GRAPHICS 取得;它是从终端借用的,在下一次改变终端状态的调用(如 vt_write 或 reset)后失效:
GhosttyKittyGraphics graphics = NULL;
if (ghostty_terminal_get(terminal, GHOSTTY_TERMINAL_DATA_KITTY_GRAPHICS,
&graphics) != GHOSTTY_SUCCESS || !graphics) {
fprintf(stderr, "Failed to get kitty graphics storage\n");
return 1;
}
/* 存储级 generation:任何图像/放置变更都会使其递增。
* 渲染器可与上一帧的值比较,若未变则可跳过
* placement 遍历与图像过期检查。 */
uint64_t generation = 0;
ghostty_kitty_graphics_get(graphics, GHOSTTY_KITTY_GRAPHICS_DATA_GENERATION,
&generation);
printf("Storage generation: %llu\n", (unsigned long long)generation);
2) 创建并填充 placement 迭代器(文档中的标准六步流程:new → get 填充 → 可选 z 层过滤 → next 推进 → 读数据 → free):
GhosttyKittyGraphicsPlacementIterator iter = NULL;
if (ghostty_kitty_graphics_placement_iterator_new(NULL, &iter) != GHOSTTY_SUCCESS) { /* 错误处理 */ }
if (ghostty_kitty_graphics_get(graphics,
GHOSTTY_KITTY_GRAPHICS_DATA_PLACEMENT_ITERATOR, &iter) != GHOSTTY_SUCCESS) { /* 错误处理 */ }
int placement_count = 0;
while (ghostty_kitty_graphics_placement_next(iter)) {
placement_count++;
uint32_t image_id = 0, placement_id = 0;
bool is_virtual = false;
int32_t z = 0;
/* 批量读取四个字段,减少 FFI 调用次数。 */
ghostty_kitty_graphics_placement_get_multi(iter, 4,
(GhosttyKittyGraphicsPlacementData[]){
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_IMAGE_ID,
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_PLACEMENT_ID,
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_IS_VIRTUAL,
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_Z,
},
(void*[]){ &image_id, &placement_id, &is_virtual, &z },
NULL);
/* ... */
}
ghostty_kitty_graphics_placement_iterator_free(iter);
其中 IS_VIRTUAL 表示该 placement 是否为 Unicode 占位符(即"虚拟" placement),Z 是 z 索引——kitty 协议约定 z < INT32_MIN/2 绘制在单元格背景之下,INT32_MIN/2 ≤ z < 0 在背景之上、文本之下,z ≥ 0 在文本之上;迭代器还可用 ghostty_kitty_graphics_placement_iterator_set 按层过滤。
3) 按 image ID 查询图像属性。对每个 placement,用 ghostty_kitty_graphics_image(graphics, image_id) 取图像句柄,再用批量接口一次取回 6 个字段:
GhosttyKittyGraphicsImage image =
ghostty_kitty_graphics_image(graphics, image_id);
uint32_t width = 0, height = 0, number = 0;
GhosttyKittyImageFormat format = 0;
size_t data_len = 0;
uint64_t image_generation = 0;
ghostty_kitty_graphics_image_get_multi(image, 6,
(GhosttyKittyGraphicsImageData[]){
GHOSTTY_KITTY_IMAGE_DATA_NUMBER,
GHOSTTY_KITTY_IMAGE_DATA_WIDTH,
GHOSTTY_KITTY_IMAGE_DATA_HEIGHT,
GHOSTTY_KITTY_IMAGE_DATA_FORMAT,
GHOSTTY_KITTY_IMAGE_DATA_DATA_LEN,
GHOSTTY_KITTY_IMAGE_DATA_GENERATION,
},
(void*[]){ &number, &width, &height, &format, &data_len,
&image_generation },
NULL);
这里有两个值得注意的存储语义(kitty_graphics.h 的文档说明):
- 图像总是以完全解码的形式存储:PNG 负载在入存储前已被解码为 RGBA,因此
FORMAT查询永远返回 RGBA 而非 PNG;zlib 压缩负载也总是先解压,COMPRESSION恒为 NONE。消费方可以直接把像素数据上传 GPU,无需再做任何解码; - 双代际机制:
GHOSTTY_KITTY_GRAPHICS_DATA_GENERATION是存储级时间戳(任何传输/放置/删除都会变更),GHOSTTY_KITTY_IMAGE_DATA_GENERATION是每图像时间戳(该 ID 每次新增/替换都变更)。代际值在全进程范围内唯一且单调递增,因此可以安全地作为缓存键;尺寸/长度启发式无法检测"同尺寸重传",纹理缓存必须基于代际值判断过期。
4) 计算渲染几何。示例最后调用了两个渲染辅助函数,展示如何把"一张 1×1 像素的图"映射到终端网格:
uint32_t px_w = 0, px_h = 0, cols = 0, rows = 0;
if (ghostty_kitty_graphics_placement_pixel_size(iter, image, terminal,
&px_w, &px_h) == GHOSTTY_SUCCESS) {
printf(" rendered pixel size: %ux%u\n", px_w, px_h);
}
if (ghostty_kitty_graphics_placement_grid_size(iter, image, terminal,
&cols, &rows) == GHOSTTY_SUCCESS) {
printf(" grid size: %u cols x %u rows\n", cols, rows);
}
placement_pixel_size 会综合源矩形、显式指定的列/行数与宽高比算出渲染像素尺寸;placement_grid_size 则返回该 placement 在网格上占据的列数与行数(未显式指定时由像素尺寸除以 8×16 的单元格尺寸推得)。头文件还提供了 placement_viewport_pos(视口相对坐标,滚动后可为负)、placement_source_rect(已按 kitty 语义解析并钳制到图像边界的源矩形,宽/高为 0 表示取整图)、placement_rect,以及一次调用返回全部几何的 placement_render_info(对 FFI/Cgo 等高调用开销环境尤其有用)。
构建与运行
在示例目录内执行 README 给出的命令即可构建并运行:
zig build run
构建逻辑见 build.zig:通过 b.lazyDependency("ghostty", ...) 以惰性依赖方式引入 Ghostty(只有真正构建时才下载/构建),把 C 源文件 src/main.c 加入模块,并链接 ghostty-vt 构件,产物名为 c_vt_kitty_graphics。文件中的注释还提示:将 simd 设为 false 可得到纯静态、不依赖 libc 的构建,但会付出显著性能代价——如果宿主应用本身依赖 libc,应保持 SIMD 开启。
build.zig.zon 中 Ghostty 依赖使用 .path = "../../" 的相对路径方式,注释解释了原因:"为了简单,并保证示例始终针对随附的源码树测试";同时给出了实际项目中更常见的 URL 依赖写法(以某个 commit 的 tarball 加内容哈希作为依赖)作为参考模板,最低 Zig 版本要求为 0.15.1。
要点回顾与集成注意事项
- 启用 Kitty 图形协议需要三件准备:非零的
GHOSTTY_TERMINAL_OPT_KITTY_IMAGE_STORAGE_LIMIT、经ghostty_sys_set安装的GHOSTTY_SYS_OPT_DECODE_PNG回调,以及(为了网格换算有意义)设置过单元格像素尺寸的终端; - 解码回调的内存契约:输出像素必须经库提供的
GhosttyAllocator分配,库持有所有权;回调要求可从任意线程安全调用; - 生命周期纪律:
GhosttyKittyGraphics与图像句柄都是自终端借用的,任何改变终端状态的调用都会使其失效;placement 迭代器则由调用方独立拥有并释放; - 缓存策略:存储级 generation 不变时可整体跳过 placement 遍历与图像过期检查,但 placement 几何 会随滚动/缩放独立变化,脏帧仍需重算渲染几何;
- 完整 API 语义可继续查阅 include/ghostty/vt/sys.h(系统接口)、include/ghostty/vt/kitty_graphics.h(图形查询)与 include/ghostty/vt/terminal.h(终端选项),同目录 example/ 下还有 C 版 stream、搜索、选区等一组姊妹示例可作对照参考。
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