ハクソク

世界を動かす技術を、日本語で。

ゴーストリング

概要

Ghostlingは、libghostty C APIを用いた最小限の機能を持つターミナルのデモプロジェクト。
Raylibによるウィンドウ管理と描画を採用し、シングルスレッドで動作。
libghosttyの柔軟性を示すため、2Dレンダラーを利用。
本プロジェクトは日常利用を想定せず、最小動作例に特化。
高機能なターミナルエミュレーションコアであるlibghosttyの特徴や制限も解説。

Ghostlingとは

  • libghostty C APIを使った、最小限のターミナルデモ実装
  • Raylibによるウィンドウ管理・レンダリング
  • シングルスレッド動作(libghostty-vt自体はマルチスレッド対応)
  • 2Dグラフィックスレンダラー利用(Ghostty GUIはGPU直接描画)
  • libghosttyの多様な利用シーンへの適用例
  • デモ用につき、日常利用や完全な正確性は保証しない
  • C言語での実装例、セキュリティやバグチェックは限定的

libghostty とは

  • Ghosttyのコアから抽出された埋め込み用ライブラリ
  • C/Zig APIを提供、各種アプリケーションで高速・正確なターミナルエミュレーション実現可能
  • libghostty-vtはゼロ依存(libc不要)、VTシーケンス解析やターミナル状態管理を担当
  • レンダリングやウィンドウ処理は含まず、利用側で実装
  • Ghosttyの実績あるコアロジックを継承
    • 高精度・完全なエミュレーション
    • SIMD最適化されたパーサ
    • 最先端のUnicode対応
    • メモリ効率の最適化
    • 何百万ものデイリーアクティブユーザーによる実績

Ghostlingで利用可能な機能

  • テキストリフロー対応のリサイズ
  • 24bitカラー・256色パレット
  • 太字・イタリック・反転スタイル
  • Unicode・複数コードポイントのグラフェム処理(字形合成やレイアウトは非対応)
  • Shift/Ctrl/Alt/Super対応キーボード入力
  • Kittyキーボードプロトコル対応
  • マウストラッキング(X10, normal, button, any-event)
  • マウスレポート形式(SGR, URxvt, UTF8, X10)
  • スクロールホイール(ビュー/アプリケーション転送)
  • マウスドラッグ対応のスクロールバー
  • フォーカスレポート(CSI I / CSI O)
  • Ghosttyが持つほぼ全てのエミュレーション機能

今後追加予定の機能

  • Kitty Graphics Protocol
  • OSCクリップボードサポート
  • OSCタイトル設定

現状未対応または未実装の事項

  • Windowsサポート(libghostty-vt自体は対応)
  • その他未検証・未実装機能(随時追加予定)

libghosttyで今後も提供しない機能

  • タブ
  • 複数ウィンドウ
  • 分割表示
  • セッション管理
  • 設定ファイルやGUI
  • 検索UI(libghostty-vtは内部検索機能のみ提供)
  • これらはlibghostty利用者が独自実装する想定
  • Ghostlingは最小実装例のため非対応

ビルド方法

  • 必要要件:CMake 3.19+, Cコンパイラ, Zig 0.15.x(PATHに追加)
  • RaylibはCMakeのFetchContentで自動取得
  • ビルド手順(Ninja利用例):
    • cmake -B build -G Ninja
    • cmake --build build
    • ./build/ghostling
  • デバッグビルドは非常に遅い(安全性・正確性チェック多数のため)
  • ベンチマーク時はリリースビルド利用推奨
    • cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
    • cmake --build build
    • 2回目以降は cmake --build build のみでOK

FAQ

  • なぜCではなくZigを使わないのか?

    • libghostty-vtはZig APIも提供、Ghostty GUIがその代表例
    • 本デモはCの方が幅広い開発者・エコシステムに馴染みやすいためC APIを採用
  • Rustや他の言語で使えるか?

    • libghostty-vtはC APIを持ち、ゼロ依存のため、ほぼ全ての言語で薄いバインディング経由で利用可能
    • 公式バインディングはC/Zigのみだが、コミュニティによる他言語対応を期待
  • libghosttyはRaylib必須か?

    • いいえ。レンダラーやGUIフレームワークには依存しない
    • WASM対応も含め、どんな環境でも利用可能
    • レンダラーステートAPIのみ提供し、描画部分は利用者が自由に実装可能
    • Ghostty GUIのMetal/OpenGL、Raylib 2D APIなど多様な実装例
  • CMakeやRaylibを選んだ理由は?

    • 単なる選択例。どんなビルドシステム・ライブラリでもOK
    • CMakeは広く使われており、Raylibはシンプルでセットアップ容易な2D描画ライブラリ
    • ビルドやライブラリ選定にこだわらず、libghostty自体の柔軟性に注目

Hackerたちの意見

Cファイルは読みやすいサイズだね(数分で読める)。5行目くらいまで読んで、こんな自動生成されたヘッダーでフォントを埋め込む技術は初めて見たなって気づいた。Windowsのリソースに慣れてるから、これはCMakeコードでバイト配列を生成するみたいだね。ちょっと驚きと感心が入り混じってる。ついにクロスプラットフォームのバイナリリソース埋め込みソリューションを発見した気がする。
Windowsプログラマーとして、DrawTextExっていうメソッドの使い方には驚いたよ :) すごくいいサンプルだね。ghostttyライブラリの力をよく示してる。著者は他のライブラリも上手く選んでるし、コードが実際に何をしようとしてるのかを邪魔にならずにデモしてる感じ。自分もターミナルアプリを作りたくなったよ。
面白い事実:XPMビットマップはそのまま#includedできるように設計されていて、ファイルにはCのボイラープレートが含まれてるよ: https://en.wikipedia.org/wiki/X_PixMap
これがビルドスクリプトだよ: https://github.com/ghostty-org/ghostling/blob/main/bin2heade... 1x1ピクセルのGIFに対して実行したら、こうなった: cmake -DINPUT=pixel.gif -DOUTPUT=pixel.h -DARRAY_NAME=pixel_gif -P bin2header.cmake 生成されたのはこれ: // /private/tmp/exp/pixel.gifから自動生成 — 編集しないでね。 static const unsigned char pixel_gif[] = { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x01, 0x00, 0x01, 0x00, 0x80, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x44, 0x01, 0x00, 0x3b };
クロスコンパイルの手間を考えると、サイズの爆発を気にしないなら理にかなってるね。
`xxd`を使って、vimパッケージからこれを生成できます。すぐにわかると思いますが、これは小さいリソースにしか適していません。gccやclangは、大きなリテラル配列では時間もメモリも膨大に消費します。数メガバイト以上を扱う必要があるなら、別の手法を探した方がいいです。私もこの手法をしばらく使っていましたが、私の使い方には問題が多すぎました。今は、https://github.com/lief-project/LIEF を使っています。このプロジェクトは、Windows PE、macOS Mach-O、Linux ELFのバイナリにリソースを追加することができ、その後それを読み戻すためのAPIも提供しています。フォーマットごとに少し違いますが、すべての形式に対応していて、リソースのサイズを気にせずにクロスプラットフォームのリソースバンドルシステムを構築できました。
> なんか、驚きと感心の間にいる感じ。やっとクロスプラットフォームのバイナリリソース埋め込みソリューションを発見した気がする。でも、「やっと発見した」ってコメントはちょっと誤解してるかも。ソースファイルにバイナリリソースを埋め込むのは、全然新しいことじゃないと思う。初期のJavaScriptの頃から、いろんな理由でやられてたし。初期の基本リストにはDATAテキスト行がたくさんあったし、既にそれをやってたんじゃないかな。ポータブルさはともかく、70年代や80年代の話だし、ソースコードにバイナリデータがあったのは確か。例えば、Atari STやAmigaのゲームは、少なくとも一部のソースコードを共有できたし、ソースの中にバイナリをエンコードするのは珍しくなかった。フォントも含めてね!当時は、ゲーム間でフォントを再利用することはなかったし。実際、私はJavaでやったことがある(Javaはクロスプラットフォームだし):Java GUIアプリのためのクイックビジュアルデバッグツールを作って、HUDに似たものを表示するデバッグモードを切り替えるの。ピクセルパーフェクトなフォントを.javaソースファイルにエンコードしてた。これが新しいことだとは全然思わない。追記:Linuxカーネルのフォントにも同じようなことがされてるはず。
もともとはC23の#embedディレクティブを使ってたんだけど(https://en.cppreference.com/w/c/preprocessor/embed)、NixpkgsのGCCはC23をサポートしてないみたいで(私が間違って使ってるのかも)、結局これに戻った。長期的には#embedの方がいい解決策だね。
これ、結構前からあったと思うよ:$ echo 'test' | xxd -i -n foo unsigned char foo[] = { 0x74, 0x65, 0x73, 0x74, 0x0a }; unsigned int foo_len = 5; (編集:30年前)
完全性のために、バイナリから直接オブジェクトファイルを作成する一般的な方法もあるよ。objcopyを使うやつね。オブジェクトファイルには、入力ファイルに基づいた名前の3つのシンボルが含まれることになる。例えば、バイナリファイルlevel0.mapからは、_binary_level0_map_start、_binary_level0_map_end、_binary_level0_map_sizeが得られる。これらは外部宣言を通じてアプリケーションで使えるし、他のオブジェクトファイルと同じようにリンクステップでオブジェクトファイルを含めることができるよ。
僕の好きなテクニック(#embedが使えない時)はobjcopyを使うことだね。 $ echo 'Hello, world!' > hello-world.txt $ objcopy --input-target=binary --output-target=elf64-x86-64 \ hello-world.txt hello-world.o $ cat embed.c #include #include #define EMBED(NAME) \ extern const uint8_t _binary_##NAME##_start[]; \ extern const uint8_t _binary_##NAME##_size[]; #define DATA(NAME) _binary_##NAME##_start #define SIZE(NAME) (size_t)_binary_##NAME##_size int main() { EMBED(hello_world_txt); printf("%.*s", (int)SIZE(hello_world_txt), DATA(hello_world_txt)); } EOF $ gcc hello-world.o embed.c -o hello-world $ ./hello-world Hello, world!
Trolley[0]にはlibghosttyを使ってるんだけど、これはTUIsをデスクトップアプリにパッケージするもので、Electronがウェブアプリにするのと似てる。ほんとに素晴らしいソフトウェアだよ。便利なGUIとバンドル/パッケージCLIでラップしたら、そのまま動いた。Windowsでもちゃんと動くし。Ghosttyの開発者たちに感謝! [0] https://github.com/weedonandscott/trolley
あなたのGitHubのREADMEには、体験をすぐに理解するための画像やスクリーンショットが本当に足りないと思う。つまり、アプリが主にTUIの周りにクローム(UIの周囲のピクセル)を追加することに関するものであれば、そのクロームがどんな感じかを見せるのがいいと思う。
これはかなり面白いアイデアだね。TUIしかないなら、ちょっとした配布ハックって感じ。成功事例とか知ってる?
これを使ってWordgrinderをMacアプリにしたいな、笑。
おお、これはいいね!Android/iOSのサポートが可能だって言ってるけど、プロジェクトが成熟するにつれて、ちゃんとサポートを追加してくれるといいな。これを聞いてるのは、どのデバイス(Mac/Windows/Linuxなど)からでも、例えばGolangのコードベースを指し示せるCLIツールが欲しいからなんだ。Golangのクロスコンパイルの簡単さのおかげで、Android(まあLinux ARMだけど)用にコンパイルして、全部を一つのAndroid APKにまとめられたら最高だよね。それが実現したら、CLIをいい感じのGUIの後ろに隠して、一般的に使いやすくするためにAndroid用のzenityみたいなものが欲しいな。これはほぼ百万ドルの問題だと思う。良いCLIツールがたくさんあるし、そのCLIの上にスクリプトを使ってGUIを作るのはすごく簡単で多用途だけど、Android/iOSはその柔軟性がないことが多いから。
これは面白そうだね。ターミナルエミュレーターにタブやウィンドウ、セッション管理をサポートしてもらわなくてもいいし。自分のWMがタブとウィンドウを管理してるし、tmuxを使ってセッションを管理してるから、スクロールバックバッファや選択、クリップボード、検索なんかもできる。これのおかげで、urxvtやst、今はfootみたいなシンプルなターミナルエミュレーターを問題なく使える。Ghosttyにはあまり魅力を感じなかったけど、これには挑戦してみるかも。OSCサポートが計画されてるのはいいね。stのようなプラグイン風のシステムがあれば、もっと使いやすくなると思う。
タブがアプリケーションの機能じゃなくてウィンドウマネージャーの機能だって人を納得させるのに、どれだけ時間を使ったか笑。Alacrittyの件で話してた人たち、めっちゃ怒ってたよ!
タイリングWMでは、rxvt-unicodeを使って、ウィンドウの装飾なし、隙間なし、1pxのボーダー、スクロールバーなしにしてる。それからtmuxが残りをやってくれる、つまりタブや分割をね。自動セッション保存は、何度も助けられたよ。
tmuxの話が出てきて面白いね。tmux自体がターミナルエミュレーターに似てるから。親エミュレーターがどんな表示ができるかを制御する独自のターミナル機能マトリックスがあるんだ。どうやらtmuxでタブやスプリットを使ってないみたいだけど、実際にはそれも含まれてるよ。セッションの持続性(アタッチ/デタッチ)だけを扱うツール、例えばhttps://zmx.shを使うのもアリかもね。libghosttyも使ってるけど、再アタッチ時の状態復元のためだけだし。
数ヶ月前にGhosttyに切り替えたんだけど、もう閉じられないアプリの一つになったよ。レンダリング速度がiTerm2よりも明らかに速いし、大きなログ出力の時なんか特にね。libghosttyがこういうプロジェクトを可能にしてくれるのが楽しみだな。TUIをネイティブデスクトップアプリとしてパッケージ化するアイデアは、インディー開発者にとって本当に魅力的だよね。
パネルを最大化できるターミナルエミュレーターのアイデアがあるんだけど、ネストした構造を使うやつ。誰か知ってる人いる? tmuxやiTerm2の標準的な「ズーム」機能は、アクティブなパネルだけをウィンドウ全体に最大化するから、他のものが隠れちゃうんだ。例えば、こんなレイアウトがあって: _____________________ | | B | | A |---------| | | C | |_________|_________| Bを拡大したら、Aが隠れて、BとCが一緒に見えるようにしたいんだ。そしたら、その中に新しいネストされたワークスペースを作って、終わったらズームアウトできる。これ、任意の深さでできるかも?