演講:介紹 Ghostty 與幾個實用的 Zig 模式
這是我在 Zig Showtime 發表的演講文字稿。如果你比較想看影片,可以在 YouTube 上找到:Zig Showtime: Ghostty。影片結尾還包含一場問答環節,本文並未收錄。

哈囉!我很興奮今天要來談談 Ghostty。Ghostty 是一款全新、從零開始以 Zig 撰寫的終端機模擬器。
註:撰寫本文時,Ghostty 仍未公開發布。我在演講中會多次提到這一點,但想先在此說明,以免大家一開始就產生誤解。計畫上,Ghostty 將會是免費且開放原始碼,並預計於 2024 年的某個時間點發布。目前我們正在進行封閉測試計畫(提供原始碼存取權限)。我在演講的不同段落也會談到這件事。

我叫 Mitchell(米契爾)!我是那種超級、超級、超級熱愛寫程式的人。
過去我曾發起過一些頗受歡迎的軟體專案,或許你聽過,也或許沒有:Vagrant、Terraform、Vault 等。不過,我已經很多年沒有參與那些專案了。
我也是那種一直都有個人專案在進行的人。一直都是如此。大約十年前,我寫過一套 Hearthstone 對戰紀錄分析與重播軟體,並賣給了一支職業電競隊伍。幾年前,我也從零開始寫了自己的郵件伺服器,現在用它來處理我部分網域的郵件。而現在呢,現在我有了一個終端機模擬器。
這張投影片是我在晚餐時間做的,當時我真的很餓,所以就決定放上一堆我吃東西的照片。我並不是特別熱衷於美食之類的。我只是剛好……在此時此刻非常餓。而且我喜歡有個能把整個場面串起來的主题元素。

不過這場演講要談的是 Ghostty!這款終端機模擬器!
所以在進一步說明之前,先來看看 Ghostty 的樣子!從這張截圖就能看出,Ghostty 的功能相當完整!
Ghostty 在 macOS 和 Linux 上都是原生應用程式。它支援分割視窗、支援真實色彩、能順暢執行 vim、擁有多種字型樣式(粗體、斜體等)、支援 Kitty graphics protocol,且還有更多功能。而這些僅僅是在這張截圖中就能看到的!

如果你不知道什麼是終端機模擬器,你很可能已經用過了,這裡列出幾個。實際上還有數十、數百種之多。

既然已經這麼多了,為什麼還要再做一個?

從根本來看,這就是我所觀察到當今終端機模擬器的現況。你有快速的終端機、功能豐富的終端機,以及原生的終端機。你最多只能同時擁有其中兩種特性。

Ghostty 的目標是——而且在我看來已經達成——同時具備所有特性。

快速、功能豐富、原生體驗並非互斥。
我認為 Ghostty 現在的表現就已經足以獲得綠色勾勾,而且我們還有很多可以做得更好的地方。
另外,先說明一下警示標誌,我對於原生平台體驗的標準是:應用程式的感覺與行為就像是為該平台量身打造的原生應用程式。我認為這個標準並不過分。例如,我認為說 Alacritty 有點不原生並不過分,因為開新視窗會建立新的處理程序。或者說 Kitty 有點不原生,因為分頁使用的是非原生的元件。諸如此類(每個都還有更多例子)。

所以在「基礎」層面上,我想做到的是:擁有 Alacritty 的速度、Kitty 的功能豐富度、以及像 iTerm(macOS 版)那樣的原生整合程度等等。

但我想更進一步。我希望終端機能成為文字應用程式開發的現代化平台,就像瀏覽器之於 GUI 應用程式開發的現代化平台一樣(無論好壞)。
瀏覽器每年都會推出數十項(甚至上百項?)新功能。它們快速創新,並讓應用程式開發者感到興奮。這或許有點多,但我認為終端機的創新速度可以比現在更快。
這裡列出一些例子:
- 進度條!為什麼我們每次都要(而且大多時候畫得很糟)重新繪製進度條?
- 拖放功能,讓編輯器和其他應用程式能表現得更原生。
- 分頁/分割視窗控制,讓多工器能善用現代終端機模擬器的效能與功能。
- 滑鼠手勢,讓 TUI 能對輕掃、多點觸控、慣性捲動等做出反應。
- 安全性功能:終端機跳脫序列在今天是個令人不安的領域。之後再詳談。我們可以做得更好。
- 還有更多!

Ghostty 快嗎?我們怎麼知道?衡量終端機模擬器的方法有很多,我不想在這場演講中花太多時間談這個。我想表達的重點是,Ghostty 是「快」的。我不會宣稱它是最「快」的。
在這裡,我展示 Ghostty 執行並記錄 FPS 的 DOOM 火焰動畫。這是一個很好的壓力測試,因為它會修改大量的儲存格,同時也在執行捲動回溯。
可以看到 Ghostty 維持在約 480 到 500 FPS,動畫非常、非常流暢。相比之下,macOS 官方的 Terminal.app(這樣我就不會針對任何優秀的社群專案)則非常卡頓,不到 10 FPS。如果你使用像 Alacritty 或 Kitty 這樣的「快速」終端機模擬器,你會看到與 Ghostty 相近的數據。
重點是:我並不是要說 Ghostty 是最快的,但它屬於「快速」這一類,所以我認為表格中的綠色勾勾是當之無愧的。
你也可以用自己的終端機試試看,結果如何:https://github.com/const-void/DOOM-fire-zig/
文字稿註記:投影片圖片為靜態圖片,因此不會顯示影片。Ghostty 執行連結中的程式時,大約可達 480 到 500 FPS。

Ghostty 功能豐富嗎?我不知道該如何量化這一點,但這裡有一份我們支援的功能質化清單,其中許多是相對少見的。

在 macOS 上,主要的 GUI 體驗是以 Swift 撰寫,使用 AppKit 與 SwiftUI。分頁是原生分頁,分割視窗是原生 UI 元件,多視窗的運作也如你所預期等等。在 Linux 上,GUI 體驗則是使用 GTK,採用真正的 GTK 視窗與其他元件。
像是錯誤訊息等功能,並非以特製的終端機視圖實作,我們實際上使用的是真正的原生 UI 元件。重點在於,雖然終端機畫面與核心邏輯是跨平台的,使用者互動卻是針對每個作業系統量身打造,以實現真正的原生體驗。

接下來快速概覽一下技術堆疊與專案本身。
首先是專案。專案目前處於封閉測試階段,並提供原始碼存取權限。之所以採取封閉測試,是因為這是一個個人專案,我不想讓自己負擔過重。封閉測試讓我可以慢慢邀請使用者、修復他們回報的問題,然後重複這個循環。
當專案最終發布時,它將會是免費且開放原始碼,授權很可能採用 GPL。我說「很可能」是因為歡迎測試者提出意見。目前一個熱烈討論的議題是,或許會選擇更寬鬆的授權,例如 MIT。
現在就有一個公開的 Discord 可以加入。我從該 Discord 中招募測試者。而且我會不定期在個人部落格上撰寫開發日誌。
在技術方面,這場演講稍後會詳細介紹其中大部分,所以這裡就不多花時間。只要知道它是以 Zig 撰寫,原生的部分是真正原生的(例如 macOS 上的 AppKit),而且我們自行撰寫了許多相依套件,例如事件迴圈。

好了,那麼就從最明顯的問題開始:為什麼選擇 Zig?

簡單來說:我喜歡這個社群、語言與建置系統。我認為這個語言與建置系統的特性非常適合這個終端機專案,而這場演講就是要突顯這一點。

想怎麼猜就怎麼猜,我不在乎這個問題,也不想回答它。我選擇了 Zig,我喜歡 Zig,我們繼續吧。

這是依子系統劃分的程式碼架構概覽。這些是 Ghostty 中的主要子系統及其說明。我會在下一張投影片中展示視覺化圖表……

這是整合後的執行階段架構。這張圖中的所有東西都是 100% 以 Zig 撰寫,只有 macOS 上的「apprt」部分是以 Swift 撰寫。
基本概念如下:
- 應用程式啟動,進入點位於「apprt」中。
- apprt 負責建立一個或多個「surface」。surface 是代表單一互動式終端機的任何東西。apprt 可以選擇將其放入視窗、分頁、分割窗格或任何地方。對核心來說都沒有差別!
- 每個 surface 都會啟動一個 IO 執行緒與一個渲染執行緒。
- IO 執行緒會建立 pty 檔案描述子並執行設定好的指令(通常是 shell)。IO 執行緒負責讀寫 pty、處理終端機事件例如跳脫序列等。
- 渲染執行緒會轉換終端機狀態,並以固定的影格率繪製像素。渲染器也負責字型塑形與算繪。

雖然我對終端機模擬器感到非常興奮,但這裡是 Zig Showtime,所以這場演講的重點將轉移到我在 Ghostty 中使用的 Zig 模式。
這些沒有特定的先後順序。
給線上讀者的註記:這一節的大部分內容,我都在實際的 Ghostty 原始碼中(當然是在 Ghostty 終端機內)切換並展示這些模式。如果你將影片快轉到這些投影片,就可以觀看。

首先是 comptime interfaces(編譯期介面)!

comptime interface 是一種實作會依某些編譯期已知的資訊而改變的值。
這裡的原始碼是直接從 Ghostty 擷取的真實範例。此處展示的範例是依某個建置選項而定的「font face」定義。你可以看到,對於某些建置設定我們使用 CoreText,其他則使用 FreeType,我們甚至還支援以網頁為基礎的 Canvas 實作!
下方窗格顯示了介面的使用方式。使用這些介面的程式碼不在乎如何運作,只需有定義好的介面,然後呼叫或使用它即可。

comptime interfaces 是 Ghostty 實作平台特定功能的主要方式,用於字型、渲染器、應用程式執行階段等。
主要好處在於,對於永遠不會在執行期改變的實作,你可以零執行期開銷地替換實作。所有欄位存取與函式分派都在編譯期就決定好了。

Zig 編譯器的一項特性是,它只會分析實際被參照的程式碼。這是一項特性,而非錯誤,因為它讓情境式程式碼得以存在,而無需使用惱人的 #ifdef 樣式防衛來將其隱藏。
缺點是對於 comptime interfaces,你必須確保測試所有建置選項,否則很容易引入建置失敗。
Ghostty 會在 CI 中跑過所有建置選項。

這是稍早展示過的同一張投影片。我想再次展示它,以突顯所有這些功能與模式所帶來的可能性。

這裡列出同樣的子系統、現有的實作,以及未來可能出現的實作。幾乎所有這些替代實作都是 comptime interfaces。

接下來,還是談 comptime,但這次是 data tables(資料表)。

這是一個資料表的範例。在這個例子中,我們看到的是 Kitty Keyboard Protocol 的一些輸入編碼資訊。
RawEntry 的 tuple 形式在實際使用上不太友善,但對於輕鬆建構表格卻非常方便。請注意,這兩者都不是 pub——它們並不打算在這個檔案之外使用。
有了這些非 pub 的項目後,我們接著對它們進行處理……

透過 comptime,我們可以處理這些原始項目。在這個例子中,我們將原始項目的 tuple 轉換為更適合在執行期使用的 struct。
轉換過程完全在編譯期發生,因此沒有執行期成本。此外,由於原始項目從未在執行期程式碼中使用,它們不會在最終的建置成果中佔用任何二進位空間。
在這個例子中,轉換大多是直觀的資料轉換,但我們還能做更酷的事……

這是來自平台特定按鍵碼表格的範例。我們的原始資料包含了每個平台的按鍵碼(Mac、Windows、透過 xkb 的 Linux,甚至 USB 碼)。但在執行期,我們會將其轉為僅包含我們正在建置之平台的 entries。
如此一來,我們的 struct 更緊湊,最終的二進位檔更小,而且我們不需要在執行期進行這種條件式查詢。

我們還能更進一步。這是來自資料表的範例,該表定義了終端機在特定模式下,針對特定按鍵輸入應向執行中的程式發送何種跳脫序列。
其中許多都遵循一種明確、重複的模式。與其手動逐一輸入,我們可以撰寫一個在 comptime 執行的函式,以程式化方式建構資料。
許多專案常常會用 shell、Python 或其他腳本來產生資料。而使用 Zig,你可以在 comptime 中完成所有這些事。
註:在 pcStyle 中包住函式主體的 comptime {} 包裝是一個技巧,用來確保這個函式永遠不會在執行期被呼叫。

接下來,我們將在 comptime data tables 的基礎上,談談 comptime type generation(編譯期型別產生)。

讓我們先用 30 秒快速教學一下 @Type 內建功能。很多人不知道它的存在。@Type 內建功能讓你能在 comptime 建立型別。它強大到不可思議,但也可怕到不可思議。

要小心!這是你會想限制在合理情境下才使用的東西。你要小心不要走向「完全後設程式設計」,因為那會損害可讀性並拖慢編譯速度。

這個範例展示了 Ghostty 如何定義它支援的各種「mode」。mode 是一種終端機模式,執行中的程式可以查詢與設定它,以改變終端機模擬器的行為。總共有數百種 mode。
在這個範例中,我們有標準模式:一個未匯出的 entries 資料表。我們處理這個表格的方式之一,是使用 @Type 內建功能將所有鍵轉為一個窮舉式的 enum。
我之所以不在一開始就定義 enum,是因為每個項目還有相關的額外資料,我想將它們緊密地放在一起,以便於編輯與參照。在這個例子中,每個 mode 都有一個值。

這展示了 FontIndex 結構的建立方式。這個結構在 Ghostty 中用於參照特定的字型。我們使用高位元來表示樣式(一般、粗體、斜體等),並使用低位元來表示陣列的索引。
我們可以透過判斷樣式所需的位元數,來決定索引所需的精確位元數。
如果我們新增更多需要更多位元的樣式,就會限制我們能用索引表示的字型數量。
註:這會搭配測試,以確保我們保有特定的大小可用,這樣如果我們真的要增加 Style 的位元大小,就會是非常審慎的決定。

接下來談談 Ghostty 如何與 Swift 整合。實際上,這是一個更通用的解決方案,適用於任何能呼叫 C API 的語言,而不僅是 Swift。

我們面臨的問題是,現代的 macOS 開發實際上需要 Swift。是的,你可以用 Objective-C 打造不錯的應用程式,但 Apple 正在推出的所有新穎功能與框架都是 Swift 專屬,ObjC 的式微已是昭然若揭。

在 Linux 上,Zig 直接使用 GTK C API。Zig 呼叫 C API 的能力已廣為人知。問題在於 Swift(用於 Mac 應用程式)無法匯出 C API,而 macOS 應用程式預期要擁有 main。
但是……Swift 可以呼叫 C。而 Zig 可以匯出 C API。

所以我最終也將 Ghostty 打包成嵌入式的 C API。我將其編譯成一個靜態函式庫,讓以 Swift 為基礎的 macOS 應用程式相依於它,然後由 Swift 來呼叫這個函式庫。

例如,這裡就是 Swift 初始化 Ghostty 設定的程式碼。

透過這種方式,我們可以在不犧牲與 Linux 或其他未來平台的可攜性的情況下,擁有極為原生的 macOS 體驗。
但更酷的是,Zig(特別是 Zig 建置系統)讓撰寫一個既能編譯為 exe 又能編譯為 lib 的程式變得如此容易。

未來,我計畫將 C lib 成果物作為官方成果物來支援,讓任何人都能在自己的應用程式中嵌入一個功能完整、功能豐富、快速且現代化的終端機。
但就目前而言,這尚未發布,僅用於 macOS 應用程式。

以上就是我們在 Ghostty 中使用的主要模式巡禮。還有更多內容,但我想對於一場演講來說這樣就夠了。其中一些模式大概本身就可以各成一場演講……
讓我們再多談一點 Ghostty 來為這場演講做個收尾!

那麼,這個專案的下一步是什麼?
終端機模擬功能已相當完整。極少會遇到無法在 Ghostty 上運作、算繪錯誤等的程式。當發現這類問題時,它們會被列為最優先的錯誤。有數十位測試者整天使用 Ghostty 進行專業工作,它是可靠的。
現在的重點大多放在原生平台體驗上,讓應用程式感覺越來越像是為該平台量身打造。例如,在 macOS 上,我們正在開發 GUI 設定視窗,而不只是基於檔案的設定。我們也在做像是透過 iCloud 在 Apple 裝置之間同步設定(資料由你自行掌控)這類功能。
還有更多……

Ghostty 已經很快了,但仍有大量的改進空間。我們已知還有許多唾手可得的效能優化,這令人振奮,因為 Ghostty 已經相當快速。
此外,我們也想對 VT 串流解析器與處理器進行模糊測試。我們已經做了一些模糊測試並發現了幾個錯誤,所以我確信還有更多潛藏的問題。
還有各種我們從未量測過的效能面向,例如啟動時間、輸入延遲等。我們已盡量避免讓這些程式碼路徑變慢,但透過量測,我相信我們會找到一些大幅改進的機會。
Ghostty 目前的記憶體使用量不算出色。它不算糟糕,但也不算好。我花了很多時間與精力確保 CPU 與渲染器效能優異,但在記憶體使用上就有些鬆懈。這裡有一些容易的改進,例如我們目前會預先配置整個捲動回溯緩衝區,大小約數 MB。將其改為動態配置應該會相當容易。

我們正在進行活躍的測試計畫。要參與此計畫,你必須加入 Discord。
我們的流程以穩定性為核心:我們會邀請一定數量的測試者,直到所有未解決的問題都獲得解決後,才會邀請下一批。當我說「問題」時,我指的是錯誤或重大缺失的功能。我們不會實現每一個功能請求。

Ghostty 將在明年(2024 年)的某個時間點公開發布。在那之前,我打算持續擴大測試計畫,可能擴展到數百人。
首個發布版本將會是 1.0 版,最差也會是 1.0 的候選發布版。這個密集測試計畫的目標是讓我們一推出就能擁有一個穩定且功能豐富的專案。
我稍早提過,Ghostty 將會是 FOSS,授權很可能為 GPL。我說「很可能」是因為測試者仍可提供意見,但目前的計畫是 GPL。許多其他終端機也是 GPL。特別是關於 libghostty 與 GPL 的部分有一些疑慮,因此這仍是熱烈討論的議題。
除了希望擁有穩定的軟體之外,延遲的部分原因在於我再過幾週就要有個寶寶 我已經有了一個新生兒,而且我知道我會非常非常忙碌,所以我不想透過同時培養並啟動一個完整的 OSS 社群來給自己施加不必要的壓力。
有些父母問我,在家有新生兒的情況下,怎麼可能還有時間寫新的演講、發表演講並撰寫這篇文章。我是在寶寶出生前就寫好了演講稿與這篇文章(除了像這則這樣的一些註記)。發表演講本身在餵奶的空檔中安排並不算太難。而且,是的,我很累。😊

❤️
隨機一篇部落格