Ghostty 開発ログ 003
こんにちは! Ghostty 👻 の3回目となる公式開発ログへようこそ!
前回の開発ログを見逃した方や、Ghostty とは何かについてもっと知りたい方は、こちらのウェブサイトの Ghostty ページをご覧ください。
コミュニティアップデート:公式 Discord 開設!
このたび公式 Discord サーバーを開設しましたことをお知らせできて嬉しいです!どなたでも参加を歓迎します。Ghostty に関する質問はもちろん、ターミナルエミュレータやシステムプログラミング、GPU プログラミングなどについて語り合いましょう。Discord はベータプログラムを拡大する場としても活用していますので、ベータへの参加に興味がある方は、ぜひ Discord サーバーに参加して積極的に活動してみてください。
次に、9月5日に Zig Showtime で Ghostty についてお話しする予定です。正確な時間やリンクなどの詳細は、Zig Showtime のウェブサイトや X(旧 Twitter)、Mastodon などでご確認ください。内容はまだ準備中ですが、Ghostty の実装を駆け足で巡りつつ、興味深いポイントをいくつか取り上げる予定です。なにしろ Zig Showtime ですので、Ghostty の中でも特に Zig に関わる部分に焦点を当てます。
キーボード入力の扱いはあなたを嫌っている(#282)1
私は、アメリカ標準のキーボードレイアウトを使う、ごく普通のアメリカ人です。アメリカ標準のキーボードでは、a と打てば「a」が、'(アポストロフィ)と打てば「'」が出ます。shift+a と打てば「A」になります。簡単、簡単。——あなたは嘘の玉座に座っているのです。
最初につまずくのが、Dvorak や Colemak のような、英語圏向けでありながら非標準のキーボードレイアウトです。US 配列の物理キーボードで Dvorak を使う場合、s と打って「o」が出ることを期待し、ctrl+i を押して ctrl+c と同じ動作を期待します。
2つ目のつまずきは Unicode の入力です。Mac の US 配列では、alt+a で å が入力されます。最新のベータグループに参加したノルウェーのユーザーから、US 配列を好んで使っているが、ノルウェー語の単語を打つために alt を使った入力がとても重要だという指摘がありました。
3つ目のつまずきは、非標準のキーボードレイアウトそのものです。デッドキー列をご存じですか?私は知りませんでした(なにしろ私は愚かな US 標準ユーザーですから)。US 標準配列では、すべてのキー入力はステートレスであることに慣れています。大文字にするには、shift+A を同時に押します。デッドキー列では、shift を押して離し、次に a と打つと A が表示される、といった挙動になります(これは実際のデッドキー列ではなく、あくまで例です)。実際のデッドキー列の例として、US インターナショナル配列では、' を押して離し、次に a と打つと á が入力されます。
ユーザーがキーボードのキーを押すと、いくつかのことが起こります。キーボードは物理スキャンコードを送信します。ソフトウェア側のキーボードレイアウト処理(たとえば QWERTY キーボードで Dvorak 配列を使う場合)は、このスキャンコードを別のスキャンコードに変換します(Dvorak では「i」が「c」のように振る舞います)。修飾キー(ctrl、alt など)と他のキー(文字キーなど)の組み合わせは、場合によって1つ以上の文字を生成することもあれば、しないこともあります(たとえば macOS で alt+p は「π」になりますが、US 物理キーボードで Dvorak を使っている場合は alt+r がそれに相当します)。
最後に、すべての修飾キーが消費されるとは限らず、それがキーバインドでは重要になります。たとえば US 配列で ctrl+= をバインドすると、ノルウェー語配列では ctrl+shift+0 に相当します。shift+0 で = が生成されたが ctrl は消費されなかったことを理解しておく必要があり、最終的な入力は ctrl+= として扱われます。しかし、ユーザーがさらに ctrl+shift+0 もバインドしていたら、どちらを適用すべきでしょうか?🤔
これらの説明は、どれもかなり大雑把で詳細を省いています。キーボードレイアウト、入力処理、ターミナルのキーボード「プロトコル」、OS レベルの API といった、得体の知れない恐怖については、いつかブログ記事を丸ごと書くつもりですので、この開発ログでは詳細は割愛します。ご期待ください!
Ghostty は、(結局のところ US 標準以外のキーボードユーザーも存在するのですから)こうしたユースケースに対応しなければならないだけでなく、すでに macOS と Linux で動作し、将来的には Windows にも対応予定という、クロスプラットフォームでの対応が求められます。😰 そして、Ghostty は今やそれを実現しました。
目に見える形で示せるのはデッドキー状態の処理だけで、現在は保留中の最後のデッドキーを描画するようにしています。残りの部分が正しく動作するようになったことは、どうか私を信じてください。
さらに、Ghostty ではオプションで明示的な物理キーマッピングもサポートするようになりました。キーバインドを ctrl+physical:0 のように指定すると(physical: という接頭辞にご注目ください)、物理的な 0 キーだけがそのバインドを発動します。キーボードレイアウトによっては、このキーバインドが押せなくなる場合もあります。デフォルトでは、キーバインドは論理(変換後)のキーに基づいています。
Kitty キーボードプロトコルなど(#292、#295)
キーボード入力の話にうんざりしている方は、このセクションは飛ばしてください。
キーボードのキーを文字入力にマッピングするだけでなく、キーボードイベントはエスケープシーケンスにもマッピングされなければなりません。そうすることで、ターミナル上で動作しているプログラムがどのキーが押されたかを知ることができるのです。
元々のターミナルプロトコルでは、キーボードのキーが生成した文字がそのまま送信されていました。「a」と打てば「a」が、「A」(shift+A)と打てば「A」が送られる、という具合です。これには多くの制限がありました。たとえば、ターミナル上のプログラムは ctrl-i と ctrl-shift-i を区別できませんでした。どちらも同じデータを生成してしまうからです。
こうした欠点を補うために、多くの追加キーボードプロトコルが開発されました。xterm はmodified keys シーケンスを、Paul Evans 氏はfixterms プロトコルを、そして Kitty の Kovid Goyal 氏はKitty キーボードプロトコルを開発しました。これらのプロトコルの要点は、キーが押されたときにターミナルが実行中のプログラムへ何を送るかを定義している、という点にあります。
これらは机上の空論ではありません。これらのプロトコルは tmux や neovim などの主要なプログラムに実装されており、そうでなければ実現できない何百ものキーバインドを使えるようにしてくれます。
Ghostty は、これらすべてを実装しました。
私の知る限り、Ghostty はこれらすべてを実装している数少ないターミナルの一つです。xterm の modified keys と Kitty の両方を実装しているのは、iTerm、Foot、そしておそらく WezTerm だけだと思います。Kitty プロトコル自体を実装しているのは、それらに Kitty を加えたものだけでしょう。ほとんどのターミナル(Terminal.app、Windows Terminal、Alacritty、Warp)は、どのキーボードプロトコルも実装していません。2
以下は、Kitty キーボードプロトコルが動作している様子の動画です:
そしてこちらは、xterm の modified keys プロトコルが動作している様子の動画です:
Linux デスクトップの年(GTK)
前回のベータユーザー募集で、常時 Linux を使っているユーザーが2、3人から5、6人ほどに増え、それに伴い Linux 版の体験も大きく改善されました。
まず、GTK シングルインスタンスモード(#247)が有効になり、サポートされました。GTK 版では、Ghostty は単一のプロセスで動作するようになりました。Ghostty を2回目に起動すると、既存の Ghostty プロセス内に新しいウィンドウが開きます。これは標準的なグラフィカルデスクトップ環境とうまく連携し、初回起動後の Ghostty の起動を非常に高速にします。
次に、Ghostty はプライマリ(選択)クリップボード(#266)に対応しました。Linux デスクトップには複数のクリップボードがあります。「プライマリ」(または「選択」)クリップボードは、テキストを選択するだけで自動的にコピーされ、通常はマウスの中ボタンで貼り付けを行うものです。これは ctrl+shift+c や ctrl+shift+v で使われるクリップボードとは別のクリップボードで、複数の内容を同時にクリップボードに保持できます。この機能については賛否が分かれることもあるのでご安心ください。copy-on-select 設定で無効にできます。下の動画で実際の動作をご覧ください。キーボードショートカットは一切押していません。選択クリップボードだけを使っています。
Linux 版でも自動シェル統合(#245)に対応しました。開発ログ 001 でシェル統合についてお話ししました。以前は Linux ユーザーは Ghostty の share ディレクトリを指す環境変数を設定する必要がありました。現在、Linux 版アプリケーションは ghostty 実行ファイルから上位に向かって、適切なフォルダ構成を持つ share ディレクトリを探します。Ghostty を標準的なFHS ディレクトリレイアウト(Ghostty がパッケージで採用している標準レイアウト)でインストールしていれば、自動的に見つけてシェルを設定してくれます。
そのほかにも、Ghostty のアプリアイコンが正しく表示されるようになったり(#251)、GTK アプリケーションが非 US キーボードレイアウトに対応したり(#271)、GTK アプリケーションが半透明のウィンドウ背景をサポートしたり(#264)など、多くの改善があります。
私は Ghostty を GTK ベースの Linux ターミナルとして一流のものにしたいと考えています。私は普段 GTK を常用していないため、この体験を素晴らしいものにするにはテスターの皆さんからのフィードバックが欠かせません。GTK 版の Ghostty がとても良い感触になってきていることを、とても嬉しく思っています!GTK 版アプリはまだ macOS 版に比べると遅れていますが、今後同等の品質に引き上げていくのが楽しみです!
おわりに
直近のベータテスターの皆さんには本当に助けられました。キーボードレイアウトやマッピングの数々の問題を明らかにし、そのすべてを修正するよう後押ししてくれた @hovsater 氏に感謝します。@cryptocode 氏は、幅広文字や複数コードポイントからなる書記素クラスタ、そしてそれらと選択やソフト ラップとの相互作用に関する、多くのエッジケースのバグを明らかにしてくれました。そして Linux の改善に貢献してくれた、感謝に値する多くの新しいテスターの皆さんにもお礼を申し上げます。ありがとうございます!
最新情報を追いたい方は、フッターのリンクから X(旧 Twitter)や Mastodon で私をフォローしてください。このブログには RSS フィードもあります。
Boo. 👻
脚注
これは優れたブログ記事「Text Rendering Hates You」への言及で、ぜひ一読をおすすめします。↩
私はこれらのターミナルエミュレータの作者全員に深い敬意を抱いており、この記述が攻撃と受け取られないことを願っています。あくまで各機能の対応有無を事実として述べているだけです。もし誤りがあれば、ぜひお知らせください。すぐに修正します。↩
記事をランダムに読む