Ghostty 源码拆解

平台宿主:macOS、GTK 与 Embedded Runtime

共享 Zig 核心如何适配原生窗口和事件循环

平台宿主:macOS、GTK 与 Embedded Runtime

Ghostty 的跨平台不是把一个窗口 toolkit 包在所有系统上,而是让共享核心稳定,再让每个平台用自己的 UI、字体、GPU 和系统集成。源码中的 apprtmacos/Sources/Ghosttysrc/apprt/gtk 和 embedded C API 是四个观察点。

apprt 是核心与窗口系统的协议

src/apprt.zig 重新导出 runtime 的 AppSurface、消息和尺寸类型。核心通过这些接口请求:创建/关闭 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.swiftghostty_surface_t 包成 Swift Surface,并把 sendTextsendKeyEvent、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 / mailbox

Metal view、CoreText 字体发现、菜单、AppleScript、Shortcuts、窗口和 tab 等属于 macOS 壳;VT parser、terminal grid、selection 语义和大部分 action 仍在 Zig。

GTK:Linux 的原生运行时

src/apprt/gtk/ 包含 GTK AppSurface、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.zigmain_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 细节。

跨平台代码的代价

平台边界带来三类维护成本:

  1. @import("macos")、GTK C API、C ABI 的 build-time 条件分支;
  2. 同一个 action 需要在 core、C header、Swift、GTK 三处保持语义一致;
  3. 线程规则不同:macOS draw 可能必须回主线程,GTK 也有 main context 约束。

因此阅读平台代码时,先定位它实现了哪个 apprt contract,再看平台特有的产品行为,避免把平台 callback 当成终端核心逻辑。

On this page