资讯动态

Ghostty ghostty-vt C 库实战:注册与使用终端 Effect 回调(write_pty、bell、title_changed、clipboard)

发布时间:2026/9/6 17:25:59 来源:尧图企业网站定制
Ghostty ghostty-vt C 库实战注册与使用终端 Effect 回调write_pty、bell、title_changed、clipboard【免费下载链接】ghostty Ghostty is a fast, feature-rich, and cross-platform terminal emulator that uses platform-native UI and GPU acceleration.项目地址: https://gitcode.com/GitHub_Trending/gh/ghosttyGhostty 除了作为完整的终端模拟器还以ghostty-vtC 库的形式提供可嵌入的 VT虚拟终端解析与状态引擎。本文基于仓库中的官方示例 example/c-vt-effects完整讲解如何通过ghostty_terminal_set()注册终端“效果effects”回调——包括write_pty、bell、title_changed、clipboard_write、clipboard_read和unknown_sequence——并逐行剖析 示例源码、build.zig 构建脚本与 build.zig.zon 依赖声明让你在自己的 C 程序中嵌入一个行为可控、可回写 PTY 的终端引擎。一、示例定位Effect 回调解决什么问题当一个程序把 VT 流喂给终端引擎后引擎内部产生的“副作用”需要被宿主程序感知和处理应用向 PTY 发起的查询如设备状态报告、光标位置需要引擎回写一段应答到 PTY——由write_pty回调承接应用发出 BEL0x07、改标题OSC 0/2、写剪贴板OSC 52等序列时引擎需要通知宿主——分别对应bell、title_changed、clipboard_write等回调引擎遇到不认识的序列标识符时可交给unknown_sequence回调观察。示例 README 特别强调了一个关键设计剪贴板效果收到的是“一次原子写操作 解码后的、二进制安全的 MIME 表示”而不是协议特定的 OSC 52 原始数据。也就是说引擎已经帮你把 OSC 52 / OSC 1337 / OSC 5522 等不同协议的 base64 载荷解码归一回调里拿到的是一组mimedata内容项你只需按 MIME 处理不必再关心协议差异。二、构建方式用 build.zig 链接 ghostty-vt示例说明中提到本项目使用build.zig和 Zig 来构建这个 C 程序以便复用 Ghostty 的构建逻辑、直接依赖源码树但 Ghostty 产出的是标准 C 库也可以配合任意 C 工具链使用。build.zig 的核心逻辑很短const exe_mod b.createModule(.{ .target target, .optimize optimize, }); exe_mod.addCSourceFiles(.{ .root b.path(src), .files .{main.c}, }); if (b.lazyDependency(ghostty, .{ // Setting simd to false will force a pure static build that // doesnt even require libc, but it has a significant performance // penalty. If your embedding app requires libc anyway, you should // always keep simd enabled. // .simd false, })) |dep| { exe_mod.linkLibrary(dep.artifact(ghostty-vt)); }几个值得注意的点addCSourceFiles用 Zig 的 C 编译器直接编译src/main.cC 头文件include/ghostty/vt.h及其子头文件随ghostty-vt库模块一起可见lazyDependency只有真正需要时才下载/构建 Ghostty 依赖避免无谓开销.simd false选项注释中说明强制纯静态构建连 libc 都不需要但有明显性能惩罚。如果你的嵌入应用本身就要用 libc应保持 simd 启用。build.zig.zon 中依赖声明用的是路径依赖注释里解释了原因.ghostty .{ .path ../../ },示例注释说明实际场景中你更可能使用 URL 依赖示例中给出了archive/COMMIT.tar.gz形式并附带hash的写法这里使用路径依赖是为了简洁并保证示例始终与其捆绑的源码一起被测试。运行方式即 README 给出的命令zig build run需先在仓库根目录准备 Zig 构建环境示例要求 Zig 版本不低于0.15.1见 build.zig.zon 中minimum_zig_version字段。三、逐个实现 Effect 回调以下代码全部来自 example/c-vt-effects/src/main.c注释与原文档中的//! [effects-*]代码块标记一致这些标记也被 include/ghostty/vt/terminal.h 的 Doxygen 文档通过snippet引用即官方 API 文档里的示例代码就出自本文件。3.1 write_pty引擎回写 PTY 的唯一出口void on_write_pty(GhosttyTerminal terminal, void* userdata, const uint8_t* data, size_t len) { (void)terminal; (void)userdata; printf( write_pty (%zu bytes): , len); fwrite(data, 1, len, stdout); printf(\n); }对应签名GhosttyTerminalWritePtyFnterminal.h。每当引擎需要向 PTY 写应答——例如应用发送 DECRQM 查询后引擎回送设备模式报告、或把剪贴板操作的回复发给前台程序——都会走这个回调。示例里直接把数据打印出来真实嵌入场景中应把它写到 PTY 的写端让前台应用收到应答。3.2 bell用 userdata 维护状态void on_bell(GhosttyTerminal terminal, void* userdata) { (void)terminal; int* count (int*)userdata; (*count); printf( bell! (count%d)\n, *count); }对应GhosttyTerminalBellFnterminal.h由 BEL 字符0x07触发。注意 terminal.h 的文档约定GHOSTTY_TERMINAL_OPT_USERDATA设置的指针会传给所有回调让你无需全局变量即可路由到应用状态——但不能为不同回调设置不同的 userdata所以示例用bell_count这一个指针同时服务所有回调。3.3 title_changed标题变更通知void on_title_changed(GhosttyTerminal terminal, void* userdata) { (void)userdata; // Query the cursor position to confirm the terminal processed the // title change (the title itself is tracked by the embedder via the // OSC parser or its own state). uint16_t col 0; ghostty_terminal_get(terminal, GHOSTTY_TERMINAL_DATA_CURSOR_X, col); printf( title changed (cursor at col %u)\n, col); }对应GhosttyTerminalTitleChangedFnterminal.h由 OSC 0 / OSC 2 触发。示例回调里通过ghostty_terminal_get()查询光标列号一方面确认引擎已处理该序列另一方面演示了回调中可以安全地调用查询类 API但不能回写 VT见下节语义约束。3.4 clipboard_write解码后的原子剪贴板写void on_clipboard_write( GhosttyTerminal terminal, void* userdata, const GhosttyClipboardWrite* write) { (void)terminal; (void)userdata; // The write is synchronous: a real embedder would ask the user for // permission here (unless write-granted) and the VT stream waits until // this callback returns. The replied result is sent to the program // (OSC 5522) through the write_pty callback. printf( clipboard write (location%d, contents%zu)\n, (int)write-location, write-contents_len); if (write-contents_len 0) { printf( clear\n); } for (size_t i 0; i write-contents_len; i) { const GhosttyClipboardContent* content write-contents[i]; printf( ); if (content-mime.len 0) { fwrite(content-mime.ptr, 1, content-mime.len, stdout); } printf( (%zu bytes): , content-data.len); if (content-data.len 0) { fwrite(content-data.ptr, 1, content-data.len, stdout); } printf(\n); } GhosttyClipboardWriteReply reply { .size sizeof(reply), .result GHOSTTY_CLIPBOARD_WRITE_RESULT_SUCCESS, .remember false, }; write-reply(write, reply); }对应GhosttyTerminalClipboardWriteFnterminal.h由 OSC 52 / OSC 1337 / OSC 5522 触发。源码注释点出三个关键行为同步语义回调是同步的VT 流会阻塞等待它返回真实嵌入者可以在这里向用户征求权限除非write-granted已授权内容已解码write-contents[i]是{mime, data}对data是二进制安全以len界定非 NUL 结尾OSC 52 的 base64 已由引擎解码回复回送给前台程序你填写GhosttyClipboardWriteReply并调用write-reply()引擎会把结果按协议回复给发起方。回复结果枚举定义在 terminal.hSUCCESS / DENIED / UNSUPPORTED / BUSY / INVALID_DATA / IO_ERROR示例统一回SUCCESSremember false表示不记住本次授权决定。3.5 clipboard_read按需返回本地剪贴板内容void on_clipboard_read( GhosttyTerminal terminal, void* userdata, const GhosttyClipboardRead* read) { (void)terminal; (void)userdata; // The read is synchronous: a real embedder would ask the user for // permission here (unless read-granted) and the VT stream waits until // this callback returns. The reply is sent to the program through the // write_pty callback. printf( clipboard read (location%d, mimes%zu)\n, (int)read-location, read-mimes_len); for (size_t i 0; i read-mimes_len; i) { printf( ); fwrite(read-mimes[i].ptr, 1, read-mimes[i].len, stdout); printf(\n); } // Reply with every requested representation we have. This example only // has text. const char* text Hello from the clipboard; GhosttyClipboardContent content { .mime {.ptr (const uint8_t*)text/plain, .len 10}, .data {.ptr (const uint8_t*)text, .len strlen(text)}, }; GhosttyClipboardReadReply reply { .size sizeof(reply), .result GHOSTTY_CLIPBOARD_READ_RESULT_SUCCESS, .contents content, .contents_len 1, .available NULL, .available_len 0, .remember false, }; read-reply(read, reply); }由 OSC 52 的?查询或 OSC 5522 触发。回调收到的是前台程序请求的 MIME 类型列表read-mimes你用本地剪贴板中能提供的表示构造GhosttyClipboardContent数组填入回复。示例只返回text/plain一种表示available字段可用于告知引擎本地实际还持有哪些 MIME 表示示例置空。结果枚举同样有SUCCESS / DENIED / UNSUPPORTED / BUSY / IO_ERRORterminal.h且只有SUCCESS会携带内容回送其余结果等价于拒绝/失败应答。3.6 unknown_sequence观察未支持的序列标识符void on_unknown_sequence( GhosttyTerminal terminal, void* userdata, const GhosttyTerminalUnknownSequence* sequence) { (void)terminal; (void)userdata; switch (sequence-tag) { case GHOSTTY_TERMINAL_UNKNOWN_SEQUENCE_APC: { const GhosttyTerminalUnknownStringSequence* apc sequence-value.apc; printf( unknown APC (truncated%s, content%zu bytes): , apc-truncated ? yes : no, apc-content.len); if (apc-content.len 0) { fwrite(apc-content.ptr, 1, apc-content.len, stdout); } printf(\n); break; } default: break; } }对应GhosttyTerminalUnknownSequenceFnterminal.h。sequence-tag标识未知序列的种类如GHOSTTY_TERMINAL_UNKNOWN_SEQUENCE_APC 0terminal.hvalue是随之的 union。示例只处理 APC 一种并打印了truncated标志——这正是下一节要讲的捕获上限机制产生的。3.7 main创建终端、注册回调并投喂 VT 序列int main() { // Create a terminal GhosttyTerminal terminal NULL; if (ghostty_terminal_new(NULL, terminal, 80, 24) ! GHOSTTY_SUCCESS) { fprintf(stderr, Failed to create terminal\n); return 1; } // Set up userdata — a simple bell counter int bell_count 0; ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_USERDATA, bell_count); // Register effect callbacks ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_WRITE_PTY, (const void *)on_write_pty); ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_BELL, (const void *)on_bell); ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_TITLE_CHANGED, (const void *)on_title_changed); ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_CLIPBOARD_WRITE, (const void *)on_clipboard_write); ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_CLIPBOARD_READ, (const void *)on_clipboard_read); ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_UNKNOWN_SEQUENCE, (const void *)on_unknown_sequence); // Unknown sequence capture is independently bounded and disabled by // default. This limit will apply to every supported unknown sequence type. size_t unknown_max_bytes 256; ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_UNKNOWN_MAX_BYTES, unknown_max_bytes); // ... 随后依次 ghostty_terminal_vt_write 投喂各序列 ghostty_terminal_free(terminal); return 0; }要点归纳对应 main.cghostty_terminal_new(NULL, terminal, 80, 24)以 80×24 创建引擎实例返回GHOSTTY_SUCCESS表示成功所有回调注册都走同一个ghostty_terminal_set() 选项枚举传NULL指针可清除回调、禁用对应效果terminal.h 明确说明GHOSTTY_TERMINAL_OPT_UNKNOWN_MAX_BYTES未知序列捕获是独立限量且默认关闭的此上限对所有受支持的未知序列类型生效超过即截断并置truncated与 3.6 节打印的truncatedyes/no对应。3.8 触发序列一览main() 投喂的 7 段 VT 数据示例用ghostty_terminal_vt_write()依次注入以下序列逐条触发上述回调步骤序列触发效果10x07BELbellcount12\x1B]2;Hello Effects\x1B\\OSC 2title_changed3\x1B[?7$pDECRQM查询 ?7 回绕模式write_pty引擎回送模式报告4\x1B]52;c;SGVsbG8gY2xpcGJvYXJk\x1B\\OSC 52 写clipboard_write5\x1B]52;c;?\x1B\\OSC 52 读clipboard_read6\x1B_private-command;payload\x1B\\未知 APCunknown_sequence70x07BELbellcount2验证计数器自增其中第 4 步的 base64 载荷SGVsbG8gY2xpcGJvYXJk解码即Hello clipboard——你可以在回调输出里看到引擎解码后的text/plain内容而非 base64 原文。四、回调执行语义同步、不可重入、避免阻塞terminal.h 对全部 effect 回调给出了统一的执行契约这是嵌入时最容易踩坑的地方同步调用所有回调都在 VT 写入过程中被同步调用回调返回前流处理会暂停禁止重入回调内不得对同一终端调用ghostty_terminal_vt_write()或ghostty_terminal_vt_write_until_ground()避免长阻塞回调会阻塞后续 IO 处理不应在其中做昂贵操作。向用户征求剪贴板权限这类交互必须尽快返回或自行在异步路径上完成。这也解释了 3.4/3.5 节中“VT stream waits until this callback returns”的注释含义以及为什么回复reply机制被设计成回调内的一次性调用。五、完整的 Effect 清单本文示例之外的其余回调GHOSTTY_TERMINAL_OPT_*选项与回调类型、触发条件的完整对照表来自 terminal.h 的 API 文档本示例覆盖了其中 6 项选项回调类型触发条件GHOSTTY_TERMINAL_OPT_WRITE_PTYGhosttyTerminalWritePtyFnVT 查询与模式报告回写 PTYGHOSTTY_TERMINAL_OPT_BELLGhosttyTerminalBellFnBEL 字符0x07GHOSTTY_TERMINAL_OPT_TITLE_CHANGEDGhosttyTerminalTitleChangedFnOSC 0 / OSC 2 标题变更GHOSTTY_TERMINAL_OPT_PWD_CHANGEDGhosttyTerminalPwdChangedFnOSC 7 / OSC 9 / OSC 1337 工作目录变更GHOSTTY_TERMINAL_OPT_ENQUIRYGhosttyTerminalEnquiryFnENQ 字符0x05GHOSTTY_TERMINAL_OPT_XTVERSIONGhosttyTerminalXtversionFnXTVERSION 查询CSI qGHOSTTY_TERMINAL_OPT_SIZEGhosttyTerminalSizeFnXTWINOPS 查询CSI 14/16/18 t或启用模式 2048GHOSTTY_TERMINAL_OPT_COLOR_SCHEMEGhosttyTerminalColorSchemeFn颜色方案查询CSI ? 996 nGHOSTTY_TERMINAL_OPT_DEVICE_ATTRIBUTESGhosttyTerminalDeviceAttributesFn设备属性查询CSI c / c / cGHOSTTY_TERMINAL_OPT_CLIPBOARD_WRITEGhosttyTerminalClipboardWriteFnOSC 52 / OSC 1337 / OSC 5522 剪贴板写GHOSTTY_TERMINAL_OPT_CLIPBOARD_READGhosttyTerminalClipboardReadFnOSC 52 ? / OSC 5522 剪贴板读GHOSTTY_TERMINAL_OPT_DESKTOP_NOTIFICATIONGhosttyTerminalDesktopNotificationFnOSC 9 / OSC 777 桌面通知GHOSTTY_TERMINAL_OPT_PROGRESS_REPORTGhosttyTerminalProgressReportFnOSC 9;4 进度报告GHOSTTY_TERMINAL_OPT_UNKNOWN_SEQUENCEGhosttyTerminalUnknownSequenceFn未支持的序列标识符从源码结构看本示例刻意挑选了覆盖面最广、最具代表性的几类回写通道write_pty、简单通知bell/title、有交互权限语义的异步资源剪贴板读写、以及兜底观测unknown_sequence。若你的嵌入应用还需要跟踪前台进程的工作目录OSC 7、弹出桌面通知OSC 9/777或响应尺寸查询XTWINOPS按同一ghostty_terminal_set()模式注册上表对应选项即可签名模式与示例一致。六、小结与继续深入全部 effect 回调通过ghostty_terminal_set()注册选项常量、回调签名与执行契约以 include/ghostty/vt/terminal.h 为权威定义剪贴板读写拿到的是解码后的二进制安全 MIME 内容权限征求与授权记忆remember由宿主在同步回调中决定回复经reply机制回送前台程序OSC 52/5522 应答最终经由write_pty回调写出;未知序列捕获默认关闭、独立限量用GHOSTTY_TERMINAL_OPT_UNKNOWN_MAX_BYTES设置上限超限内容带truncated标记构建层面示例用 ZiglazyDependency拉取ghostty-vt产物并链接 C 源码生产环境可改用 URL 依赖锁定 commit或直接用 Ghostty 产出的标准 C 库配合任意 C 工具链。更多可参考的仓库入口示例目录总览 example/README.md、C API 头文件 include/ghostty/vt.h 及其vt/子头文件、以及同系列的 c-vt基础终端读写与 c-vt-stream流式嵌入示例。【免费下载链接】ghostty Ghostty is a fast, feature-rich, and cross-platform terminal emulator that uses platform-native UI and GPU acceleration.项目地址: https://gitcode.com/GitHub_Trending/gh/ghostty创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价