欲しかったターミナル(端末エミュレーター)
昔から頭の痛い問題だった事を一つ片付けた(かもしれない)話です。
いきさつ
私は、多数のPCで並行作業をやることが多く、それぞれの環境で完全に開発環境を一致させるのも 保守的にダルいと言うこともあって、環境のデフォルト構成を重視する傾向があります。
例えば、現在は主にUbuntuを使っているのですが、そこに用意するアプリケーション、 例えばvscodeやらjetbrainsなどのIDE類、小さなところで言えばbashの構成など、 できるだけデフォルトのままで使うようにして、 別の環境でもこれらのデフォルトが同じかあるいは近しいものを選択して、できるだけ自分で構成を弄らない、 みたいなことに拘ってしまいます。
これの背景を語りだすと、つまらない話がとめどなく流れてしまうのでざっくりと説明すると、 昔 NFS+NIS が全盛だった頃に、アカウント情報とホームディレクトリの完全一致の守護者、 みたいなことに心血を注いでいた黒歴史があり(これは後にADでもやって結構な地獄を見た)、 その反省から来ています。
なので、異なる環境でも同じように使用できて、この環境を実現するのに苦労しない、 みたいなことがあると大変うれしいのです(スイッチングコストが辛いというのも大きい)。
そして、それは「端末エミュレーター」のジャンルにも言えます。

これはUbuntu標準のgnome-terminalなんですが、使っていると不満が出てくるんですよ:
- フォントサイズの変更がダルすぎる。そしてキーバインドでvscodeなどと同じように Ctrl+Plus や Minus で変えられるようにしたい(これは今のバージョンでは出来るようになっています)。
- 多数のターミナルを開くと、どれがどれかわからなくなる。
- リモート接続をsshでやるのがダルいので Remmina を使うけど、そうすると gnome-terminal じゃないのが不満。
- 同様に、IoT開発でシリアルデバイスやるのに minicom とか思い出すのが大変。
- 更に、vscode上でターミナルを開いていると、更に「ムキーッ!」って叫びたくなる。同じように、同じようにやりたい... 微妙に操作やキーバインドが違うのは辛い...
- ウインドウサイズ変更したい時にマウスでボーダーを掴みにくい(これはウインドウマネージャやコンポジタの問題ではあるけど)
- 上記に絡み、そもそもボーダーが視認しにくい(特にDark theme)。
他にも色々あった気がするけど忘れました。 それで、これだけモヤモヤしていると、ふと気がつくわけです、普通なら考えないことを。
「そこまでフラストレーションが溜まるぐらいなら、天秤にかけて、端末エミュレーター作ったほうが良くないか?」
幸いなことに (?) 最近であればCodexが手を貸してくれそうです。
端末エミュレータの核
端末エミュレーターは、一昔前なら不満があるからと言って自分で実装しようなどとは思わないジャンルです:
- 受信したデータ列をストリーミングパースして、エスケープシーケンスや特殊コードに対応した処理を実現させる
- ASCII範囲ならいざ知らず、UTF-8の解釈、多国語領域のコードのレンダリング(CJKやその他)、合字、RTL、絵文字などなど
- キー入力、IME制御とインライン編集バッファ処理
- 最近だと、Kitty protocolなどのリンクサポートやよりリッチなインタラクションのサポート
いや、ウインドウ表示してテキスト表示して、できたできたーってやつなら簡単なんですが、目的が実用になった途端に難易度がプラチナ級です。
で、現代では端末エミューレーターを構成するのに補助ライブラリもいくつかあって、例えば libvterm や libtsm、JavaScriptで使える xterm.js などがあります。
これらを使用すると、上記の実装部分のコアになる部分は手が抜けますが、UI周りの実装はまだまだ大変です。
で、いくつか見た感じで良さそうに見えたのが、 libvte です。
これは、GTK上に構築された端末エミュレーターのウィジェットライブラリで、要するにこれの外側に肉付けしてやれば、端末エミューレーターアプリが作れてしまうというスグレモノだと言うことが分かりました。
実際、gnome-terminalはこのlibvteを土台としています。
特に、今回は端末エミュレーター自体を深く追いかけたいわけではなく、できるだけ可及的速やかに満足出来る端末エミュレーターを作って実用する、ことが目的なので、落とし所としては十分でしょうきっと。
出来上がった
それでは見て下さい:
名前がアレだって? ようつべで散々TES4/5のBGM流しながら作ったので、少し侵食されたことは認めますが、名前のとおりです。 「あの頃」が現代の技術で再現されている、と聞いて興味を持ったなら、あなたにもフィットするかも知れません。
誤解を恐れずに言うなら、大御所のTeraTermさんと更にその昔の数多のシリアルターミナルアプリケーションが魔改造され、モダン化でドーピングした、そういうものです。
仕様だけです。中身はフルスクラッチです、念の為。

これがターミナルウインドウです:
えっ、なんかフツーのターミナルで何が良いのか良くわからないって? そうでしょうそうでしょう、まず「奇抜性」は狙ってません。 普段使いするのです。gnome-terminalの代わりに使うのだから、背景が透過するだとか、デコやアニメーションが凝ってるだとかは不要なのです。 でも、それだとgnome-terminalを使うのと変わらないのでは、という感じもしますが、もちろん見えないところで色々違います。
フィーチャー
- Linux GTK/
libvteを使用して、飾らない、必要十分なターミナルを実現します。 - シリアルやTELNET接続だけではなく、ローカルターミナル、SSH、SFTPもサポートし、現代で同じ生活を取り戻せます。
- ランチャーから複数の接続先を管理したり、そこからターミナルの起動を行えます。
- 接続先の設定は、全てINI形式のファイルに保存されます。 INI形式なので、自由にエディタで再編集出来ます。
- BS/DEL、カーソルキー変換、Enter/Returnコード、文字コード変換を指定できます。
- ターミナルの行数と桁数を、接続毎の設定として記憶出来ます。
- TELNET/SSHで、ターミナル種別 (
xtermやxterm-256colorなど) を明示的に指定できます。 - X/Y/ZMODEMファイル送受信をサポートしています(ローカルターミナルを除く)。 ZMODEMなら、自動送受信を有効化出来ます。
- SFTPで、SSH接続を行うホストとファイルの送受信を実行出来ます。
- テキストのペーストやテキストファイルの送信を実行出来ます。 その際に、送信速度と改行コードの扱いを指定して、ホスト側のバッファオーバーフローや改行コードの不一致を回避出来ます。
- インジケータがターミナルの下部に表示されます。アナログモデムを思い出して、余韻に浸れます。
- BELに、組み込みbeepまたは任意のWAV/Ogg Vorbisサウンドを指定出来ます。
- フォントサイズをショートカットキーで変更したり、マウスのホイールで変更したり出来ます。
- 接続毎にウインドウのエクステリアカラーやターミナルの背景色をカスタマイズ出来ます。
- ホットキー一発で、指定された接続を起動出来ます(Waylandでは、XDG Global Shortcutsポータルが必要)。 まずは、ローカルターミナルをホットキーで起動できるようにすることから始めて下さい。 全てがelder-termsに置き換わるのも、時間の問題かもしれません。
- ランチャーを自動起動させたり、システムトレイに常駐させることが出来ます。
- 受信文字列を正規表現で監視して、自動的に文字列を送信したり、指定されたコマンドを実行できるルールを指定出来ます。
- OSC 8ハイパーリンクを
Ctrlキーを押しながら左クリックして、正規表現から抽出したパスや行番号を任意のコマンドへ渡せます。 - ログをファイルに記録できます。接続先や日時によるディレクトリの分離によって、ログの整理が捗ります。
- 多国語表示に対応しています (英語、アラビア語、スペイン語、フランス語、ヒンディー語、日本語、韓国語、ポルトガル語、ロシア語、簡体字中国語)
いくつかスクリーンショットを貼っておきます:
ランチャー

ターミナル設定

複雑な表示もOK

色指定

シリアル接続

SFTP

内部の話
フィーチャー一覧で気になった人は、日本に2〜3人ぐらいは居るかも知れません。elder-termsはX/Y/ZMODEM転送をサポートしています。
X/Y/ZMODEMのプロトコルスタックとして、著名な lrzsz を使おうとしたのですが、いくつか問題がありました:
- アプリケーション組み込みには構造上向かない。lrzszはCLIプログラムとして作られていて、ライブラリとして使用するには微妙だった
- ライセンスが不明瞭。elder-termsはMITで公開しようとしていたため、そのまま持ってくるわけには行かなかった
Codexを使うのだから、X/Y/ZMODEMの仕様書があれば一から書かせることも不可能ではなかったハズなのですが、 形式的な仕様書がなく、昔書かれた網羅的ではないドキュメントやソースコード、ネットニューズ(NNTP)の言い伝え(?)みたいなやつしか参照できません。
そこからブレークダウンして仕様を確定、あるいはテストベンチの検討といった泥臭い作業は自動化出来ないため、壁打ちを繰り返して確定させていきました。
一応、すごく昔にZMODEMをTurbo Pascalに移植したことがあるので、 全く心得がなかったわけではありません。 そのコードも一応某所に未だに公開されているので、 今回自分の書いたコードをダウンロードして、 本当に久しぶりに眺めて、そして黒歴史大ダメージを浴びてしまう実績を解除しました。
特に面白かったのが、Codexが散々X/Y/ZMODEMの基本的な環境前提を誤解し続けたことです。
X/Y/ZMODEMは、現在接続中の通信経路を「横取り」して、そこにそのままバイナリプロトコルを乗せます。 どうもこれが理解できないようで、プロトコルの開始や終了でptyとの接続切り替えを行っていなかったり、libvteとX/Y/ZMODEMのパーサーの両方にデータを同時に流したり、手順終了時にパーサー切断と同時にptyも切断してしまったりと。
「いや、小難しいことを考えずにもっとリラックスして単純に考えれば良いんだよ」と言いたくなるようなミスを連発していました。 まあ、こういうプロトコルの例は現代にはあまりないのと、そもそも資料の学習が希薄であろうことは想像できるので、ここもやはり学習の物量がものを言う世界なんだなと、改めて感じたことでした。
そういうわけで、X/Y/ZMODEMのプロトコル処理だけを純粋に取り出した libxyzm を作りました。もちろんMITです。 このライブラリは、同期バージョンと非同期バージョンがあり、同期バージョンは完全なるC APIなので、組み込み向けにも使用できるはずです(そのうち別のなにかに使おうとは思っている)。
また、非同期バージョンでは C++20 の co_await を使用して、同期バージョンと同じような書き心地でコードが書けるようにしてあります。
しれっと爆弾発言したなと思った方が更に少人数いたかも知れませんが、C++20の co_await を使用可能にしているのは cardio で、libxyzmはこれを使用しています。
cardioの話をし始めるとまた長くなってしまうので省きますが、elder-termsの全ての処理はcardioを使用していて、当然libxyzmも非同期バージョンを使用して、全面的に非同期処理で実現していて、ワーカースレッドを立てないようにしています。この辺りのこだわりは .NET から来ています。
elder-termsの開発は、だいたい2ヶ月ぐらい掛かっているのですが、libxyzmとcardioを開発・整備する時間も含まれているので、これらのお膳立てがあれば半月ぐらいで完成したんじゃないかと思います。 なお、elder-termsは完成間近の段階で一回完全に捨てて作り直しています:
- ほとんどのフィーチャーは実装済みですぐ使える状態だったけど、保守に不安を感じる品質
- 捨てる決断は結構辛かった。折れかかった...
- この過程で、非同期処理の包括的な土台の必要性を痛感した結果、cardioが生まれた
使用感
今回(いつもだけど)、実用できる事を目標にしていたので、自分で作っておきながらその使用感にわくわくしていました。結果として:
- ホットキーで接続エントリに対応するターミナルを即開ける。
- 接続エントリ毎に異なる外郭色を指定できるので、ひと目で何のターミナルかわかる。
- ボーダーがつかみやすい、視認しやすい!
- 実用レベルで普通に軽い(GTK使っているので極端にリソースが足りない環境では苦しいと思います。その点は未評価)
- gnome-terminalをUbuntuのランチャーから外すことが出来た。
- debでインストールするだけなので、ギリ管理可能。
- TELNETしか使えないシチュエーションでも同じように使える。シリアルも同様。
- インジケーターがとても良い。可視化重要。
- 代替フォント指定が正しく機能する。
- オートパイロットが可能なマクロと全ログ記録。
一方、まだ課題があると感じた部分:
- libvteにちょくちょく問題がある:
- GTK4が未だにIME絡みの問題を抱えていて、GTK4に移行できない。
- フォント周り(特にサイズ変更)に非常に泥臭いハックコードを書かざるを得ず、回避できない。
- 接続先エントリ毎の外郭色だけではちょっと分離として不足感がある:
- 例えば作業中ディレクトリ毎に異なるターミナルを何とか視覚的に一瞬で区別出来るようにしたい。
- これはLLM開発系でそのような要望に答えるターミナルがいくつか開発されているらしいので、参考になるかも知れない。
- SFTPはもちろん良いけど、FTPも必要だった。なんで考えなかったのか。
ウインドウアレンジをウインドウマネージャやコンポジタに任せる、という判断を早い時期にしたので、全体的な設計としてウインドウ配置(タブ化も含む)の制御を放棄していて、そこにあまり不満は感じてはいません。 しかし、Ubuntuのその辺りがあまり自分とマッチしていない、というのは漫然と感じています。
特にタブ化はやらないリストに加えたぐらいで、それ自体は問題ないのですが、 ディスプレイという限られたスペースに、もう少し論理的な枠組みでターミナル群を配置できるような、良いアイデアが無いかなぁ、と感じています。
ええ、私は Windows 1.0 を割と良い妥協点だと思ってますよ。 その当時は全く魅力的に見えませんでしたが...
それではまた。

