アーキテクチャ詳解
起動フロー
エントリポイントは src/main.rs。
clap で CLI サブコマンドを解釈し、GUI 起動時は app::run() に流れる。
bootstrap_runtime() が最初の Workspace / Surface / Pane を作り、
その後でレイアウト復元や宣言的レイアウト適用を行うのが現在の流れだ。
コンポーネント境界
サーバー側
yatamux-server::ServerWorkspace/Surface/PaneTreePaneごとの PTY タスク- 名前付きパイプ IPC サーバー
クライアント側
run_window()が持つ Win32 メッセージループPaneStoreに入る UI 状態- GDI 描画・IME・通知 UI
中継レイヤ
現行実装で特徴的なのは src/app/bridge.rs の存在だ。
サーバーから来る ServerMessage をそのまま GUI に食わせるのではなく、
BridgeEvent に正規化してから次を担当する。
TerminalSinkへの PTY 出力適用PaneStoreのレイアウト更新- 通知バックエンドへのルーティング
- フック実行
- レイアウト切り替えの段階的適用
チャネル構成
主要チャネルは次のとおり。
| チャネル | 型 | 向き |
|---|---|---|
merged_tx | mpsc::Sender<ClientMessage> | GUI / IPC / app 内部 → Server |
server_out_tx | mpsc::Sender<ServerMessage> | Server → fan-out |
ipc_out_tx | mpsc::Sender<ServerMessage> | fan-out → IPC クライアント |
bridge_rx | mpsc::Receiver<BridgeEvent> | fan-out → app bridge |
msg_tx | mpsc::Sender<ClientMessage> | Win32 → Server |
split_tx | mpsc::Sender<(PaneId, SplitDirection)> | Win32 → bridge |
float_tx | mpsc::Sender<()> | Win32 → bridge |
layout_tx | mpsc::Sender<String> | Win32 → bridge |
ServerMessage::Output.data が Arc<[u8]> なのは、
IPC と GUI の両方へ同じ PTY 出力を配る fan-out でコピーを避けるためだ。
Server::run() の責務
crates/server/src/session/mod.rs の
Server::run() は 1 本の tokio::select! で次を並行処理する。
ClientMessageの受信- 各 Pane からの PTY 出力
- 各 Pane からの内部イベント
内部イベント PaneEvent は文字列ベースの制御メッセージではなく、
型付きイベントとして pane.rs と session の間だけで流している。
ここから ServerMessage::Notification や ServerMessage::CommandFinished に変換する。
Pane のタスク構成
crates/server/src/pane.rs の
1 ペインは、実質的には次の 3 つの仕事を持つ。
- 子プロセス終了監視
spawn_blockingでchild.wait()を待ち、終了時にProcessExitedを送る - PTY 書き込み / リサイズ
PtyCmd::InputとPtyCmd::Resizeを処理する - PTY 読み取り / VT 適用
Vec<u8>をvte::Parserに流し、Grid・通知・タイトル・完了イベントを更新する
Pane は Drop 時に ChildKiller を使ってプロセスを止める。
これはテストや異常終了後に shell が孤児化するのを避けるためだ。
IPC プロトコル
名前付きパイプは \\.\pipe\yatamux-<session>。
メッセージは JSON Lines で、ClientMessage / ServerMessage を
#[serde(tag = "type", rename_all = "snake_case")] で直列化している。
送受信例:
{"type":"list_panes"}
{"type":"panes_listed","panes":[{"id":1,"surface":1,"title":"pwsh","cols":80,"rows":24}]}
{"type":"create_pane","surface":1,"split_from":1,"direction":"vertical","size":{"cols":80,"rows":24}}
{"type":"pane_created","id":2,"surface":1,"split_from":1,"direction":"vertical"}
{"type":"capture_pane","pane":1,"lines":50,"plain_text":true}
{"type":"pane_content","pane":1,"content":"PS C:\\Users\\raiga>","capture":{"title":"pwsh","cols":80,"rows":24,"lines_requested":50,"scrollback_len":0,"cursor":{"col":19,"row":0,"visible":true},"visible_text":["PS C:\\Users\\raiga>","",""],"scrollback_tail":[]}}
この capture フィールドが現行実装の強化点で、
CLI だけでなく AI エージェント向けの構造化読取にも使える。
設定ファイル
config.toml
%APPDATA%\yatamux\config.toml は現状 2 セクション構成。
[hooks]
on_pane_created = "echo %YATAMUX_PANE_ID%"
on_pane_closed = ""
[appearance]
font_family = "HackGen Console NF"
font_size = 14
background = "#1e1e2e"
foreground = "#cdd6f4"
cursor = "#f5c2e7"
selection_bg = "#585b70"
status_bar_bg = "#181825"
フックは on_pane_created と on_pane_closed のみで、
実行時には YATAMUX_PANE_ID と YATAMUX_SESSION が環境変数として渡される。
テーマファイル
テーマは %APPDATA%\yatamux\themes\<name>.toml に置き、
[appearance] の色項目だけを読み込んでランタイム適用する。
実装上、ランタイム切り替え時は font_family / font_size は無視される。
レイアウトファイル
宣言的レイアウトは %APPDATA%\yatamux\layouts\<name>.toml に保存し、
[[panes]] の列で順次ペインを作る。
[[panes]]
command = "nvim ."
[[panes]]
split = "vertical"
command = "cargo watch -x test"
ratio = 0.6
共有状態の持ち方
共有状態は何でも Arc<Mutex<_>> に入れているわけではない。
役割ごとに分けている。
Arc<tokio::sync::Mutex<Grid>>PTY 読み取りタスクとクライアント描画で共有する重い状態Arc<std::sync::Mutex<Arc<str>>>Pane.titleのような軽量メタデータArc<std::sync::Mutex<TermSize>>list-panesが同期的に読むサイズ情報Arc<Mutex<PaneStore>>client crate 側の UI 状態集約
この分離のおかげで、Grid のような非同期更新対象と、
CLI 応答で素早く読みたいメタデータを同じロック戦略に押し込めずに済んでいる。