平台宿主:macOS、GTK 与 Embedded Runtime
共享 Zig 核心如何适配原生窗口和事件循环
平台宿主:macOS、GTK 与 Embedded Runtime
Ghostty 的跨平台不是把一个窗口 toolkit 包在所有系统上,而是让共享核心稳定,再让每个平台用自己的 UI、字体、GPU 和系统集成。源码中的 apprt、macos/Sources/Ghostty、src/apprt/gtk 和 embedded C API 是四个观察点。
apprt 是核心与窗口系统的协议
src/apprt.zig 重新导出 runtime 的 App、Surface、消息和尺寸类型。核心通过这些接口请求:创建/关闭 surface、改变标题、打开 URL、通知、剪贴板、设置 cursor、重绘和 quit timer;runtime 通过回调把 focus、key、mouse、resize、paste 和平台 action 送回核心。
核心不知道 surface 最终是 window、tab、split 还是嵌入第三方应用。这使 Surface.zig 的命名有意避开 Window。
macOS:Swift 宿主 + C ABI
macOS 目录是一个真正的 SwiftUI/AppKit 应用,而不是 GTK 的兼容层。Ghostty.App.swift 保存配置、ghostty_app_t、readiness 和 callback;它通过 ghostty_app_new 创建 Zig app,把 wakeup、action、clipboard、close surface 等函数指针交给 core。
Ghostty.Surface.swift 把 ghostty_surface_t 包成 Swift Surface,并把 sendText、sendKeyEvent、mouse、binding action、foreground PID、TTY name 等操作暴露给原生 UI。它还处理 deinit 必须回到 MainActor 的生命周期约束。
SwiftUI/AppKit event
→ Ghostty.App / Ghostty.Surface
→ GhosttyKit C declarations
→ libghostty exported function
→ Surface core
→ runtime callback / mailboxMetal view、CoreText 字体发现、菜单、AppleScript、Shortcuts、窗口和 tab 等属于 macOS 壳;VT parser、terminal grid、selection 语义和大部分 action 仍在 Zig。
GTK:Linux 的原生运行时
src/apprt/gtk/ 包含 GTK App、Surface、portal、GSettings、media、window protocol、cgroup、Flatpak 和 post-fork/pre-exec 逻辑。Linux runtime 不是把 macOS API 翻译一遍,而是直接结合 GTK4/Adwaita、systemd 和桌面门户。
runtime.default 把 Linux/FreeBSD 指向 GTK;在没有 GUI runtime 的目标上使用 none,以便只编译 library/helper CLI。
Embedded runtime 的边界
src/apprt/embedded.zig 和 main_c.zig 为 libghostty 提供 embedded runtime。它不默认替宿主管理窗口,而是让宿主传入 userdata、wakeup callback、action callback、clipboard callback 和 close callback。核心可以请求平台动作,但不能自己调用宿主的 UIKit/AppKit 对象。
这种 callback 设计的关键是方向性:
- 宿主调用 core 的 C API;
- core 通过 callback 请求宿主;
- 宿主在合适线程执行 UI;
- 需要回 core 的工作再通过 app/surface tick 或 mailbox 汇入。
为什么不能把所有平台代码放进 Zig
因为“原生体验”本身是产品目标:macOS 的窗口、菜单、CoreText、Metal、AppKit 生命周期与 GTK 的 object model、portal、systemd 不仅 API 不同,线程规则和用户期望也不同。共享的是终端语义和数据结构,不是每个 UI 细节。
跨平台代码的代价
平台边界带来三类维护成本:
@import("macos")、GTK C API、C ABI 的 build-time 条件分支;- 同一个 action 需要在 core、C header、Swift、GTK 三处保持语义一致;
- 线程规则不同:macOS draw 可能必须回主线程,GTK 也有 main context 约束。
因此阅读平台代码时,先定位它实现了哪个 apprt contract,再看平台特有的产品行为,避免把平台 callback 当成终端核心逻辑。