首页
/ Ghostty C API 实战:通过 ghostty-vt 系统接口集成 Kitty 图形协议并发送 PNG 图像

Ghostty C API 实战:通过 ghostty-vt 系统接口集成 Kitty 图形协议并发送 PNG 图像

2026-09-05 12:23:29作者:段琳惟

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 的执行链路可以概括为五步:

  1. 安装全局解码回调ghostty_sys_set 设置 userdataGHOSTTY_SYS_OPT_DECODE_PNG
  2. 创建终端ghostty_terminal_new 创建 80×24 终端,并用 ghostty_terminal_resize 设置单元格像素尺寸(8×16),让 Kitty 图形能够计算网格尺寸;
  3. 启用图形存储:通过 GHOSTTY_TERMINAL_OPT_KITTY_IMAGE_STORAGE_LIMIT 设置非零存储上限(示例为 64 MiB),并通过 GHOSTTY_TERMINAL_OPT_WRITE_PTY 安装写回调以捕获协议应答;
  4. 发送转义序列:构造 ESC _G a=T,f=100,q=1;<base64 PNG> ESC \ 的 APC 序列并 ghostty_terminal_vt_write
  5. 验证与查询:取回 GhosttyKittyGraphics 句柄,读取存储代际、遍历 placement、查询图像属性与渲染尺寸。

示例目录还包含构建文件 build.zigbuild.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 字节;
  • 出参 outGhosttySysImage 结构: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_getGHOSTTY_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、搜索、选区等一组姊妹示例可作对照参考。
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
504
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384