2012-07-08

ChromeOSで動作するSKKを作った

ChromeOSには標準でいろんな言語がサポートされています。言語サポートとひとくちにいってもいろいろあるわけですが、入力方法もそうしたサポートのひとつでしょう。日本語にはMozcが使われており、中国語や韓国語も各種のOSSライブラリが使われています。CJK以外のアジア諸語ではlibm17nが使われています。ラテン文字を使った言語についても、様々なキーボードレイアウトをサポートすることで入力に対処しています。
とはいっても、そういうのは全然完全じゃないわけです。Mozcだけでは日本語はサポートしきれません。中国語でも、主として本土の人向けのピンイン入力や、台湾でよく使われるzhuyin入力や、Canjie(部首変換)はサポートされていますが、boshiamyのようなプロプラエタリなインプットメソッドは導入できません。
そういったわけで、ChromeOS用にインプットメソッド拡張機能APIというものが提案されており、これが現在、バージョン21(devチャンネル)でのみ利用できる状態になっています。このAPIを使えば、たんにJavaScriptで拡張機能を書くだけでインプットメソッドを導入することができます。
というわけで、昔とった杵柄、という塩梅でSKKを書いてみた、というのがこちらになります。ソースコードはgithubに公開しています。そういえばライセンスとかreadmeとか、ちゃんと書かないとな……。
折角なので、苦労話とか実装の話を適当に何回かにわけて書こうかと思います。ところでSKKといえば、openlab.jpがもうかれこれ2週間以上、ストップしているように思われ、何があったのか不安な面が強いのですが、辞書については諸般の事情から、てきとうなappengineインスタンスを作ってそちらでサーブしていますので、この拡張機能の辞書のダウンロードについては心配ありません。

さて、手始めにIME拡張機能とはいったいなんなのか、どう動くのか、という話を書いてみたいと思います。
はじめに、おそらく誤解をしている人も多いと思うので書いておきましょう。IME拡張機能は、よくあるAJAX-IMEのブックマークレットなどのようなものとは根本的に異なります。どちらが良い悪いというのではないです。ブックマークレットだけでブラウザ内で動くというのはとても優れたハックだし、すごい話だなと思うのです。ですがその場合、インプットメソッドはウェブのコンテントエリア内でしか動きません。
ChromeOSの場合はnothing but webですから、ウェブ以外に何があるのだろうと疑問に思うかもしれませんが、実際にはたくさんの入力エリアがあります。たとえばomniboxや検索ボックス。ネットワーク設定用のダイアログ。アプリケーションリストのポップアップ。などなど、実は意外とあるのですね。また、候補ウィンドウの描画などを、ホストするウェブページと独立にうまく描画するのは、そう単純な話でもないのです。
ChromeOSでは、内部的には現在、ibusというIMEフレームワークが使われています。ibusはマルチプロセスなIPCベースのフレームワークで、各IMEの入力エンジンはibus-daemonと別なプロセスで動作します。Chromeでキーイベントが発生すると、まずはibus-daemonを介して入力エンジンへキーイベントが届けられ、処理結果がまたIPCメッセージとしてChromeに戻ってくるという構造になっています。ちなみにChromeOSでは候補ウィンドウはibusではなくChrome自身が描画するという実装になっています。
図にするとこんな感じかな。
表現としてはわかりづらいですが、composition(未確定文字列)とかcandidates(変換候補)はブラウザプロセス内で表示/描画されます。
一方、AJAX-IMEブックマークレットなどの構成では、ウェブページ内にIMEが寄生します。てことで、ざっくり書くとこんな感じ?
表記上、わけられていますが、レンダラプロセス内にあるJSエンジン(v8)のなかにIMEの処理がロードされ、ブラウザプロセスから届けられたキーイベントを横取りして処理するというイメージ。この場合、すべての処理はレンダラプロセス内にあり、compositionやcandidatesの描画もレンダラを使って行ないます。htmlを使って自由に描画できるという面ではいいのかもしれないけど、ホストとなるウェブページの描画事情に応じて様々な厄介な問題が引き起こされがちです。あともちろん、omniboxなどウェブページの外側にあるモノにはアクセスできません。
IME-APIでは拡張機能を使うので、やはりIMEの処理がレンダラプロセスの中にロードされます。と書くと後者と似ているように聞こえるかもしれませんが、やはり異なります。なぜかというと、IMEの処理がロードされるレンダラプロセスは、ユーザがいま見て入力しようとしているウェブページのものとは異なるものだからです。
Chromeの拡張機能には「バックグラウンドページ」というものがいます。これは、その拡張機能が有効になっているあいだは、ずっと起動されているレンダラプロセスで、様々な処理を受け持つことができます(ちなみについ最近、こないだのGoogle I/Oにて、バックグラウンドページも必要なあいだだけ起動してあとは止めておく、という機能が紹介されています)。IME-APIを使った拡張機能でも、このバックグラウンドページにIMEの処理を持たせることが想定されています。この場合、バックグラウンドページのコンテンツというのは存在せず、ユーザが実際に目にすることはありません。どちらかといえば、さまざまな拡張機能APIにアクセスするJSコードのデーモン的な位置付けになっていると言えると思います。
ここに来ると、IME-APIというのは既存のibusに相当するレイヤとみなすことができます。ibusはd-busをベースにしたIPCフレームワークを使っていますが、IME-APIではChromeがブラウザプロセスとレンダラプロセスのあいだで行われるIPCレイヤによってibusと同じようなことを代替するという試みとなります。
このため、compositionやcandidatesの描画は、ibusと同等にブラウザプロセス内で動作します。ブラウザから見た場合、ある変換エンジンが拡張機能を使ってJavaScriptで書かれているのか、それともibusクライアントになっているのか、というのは大きな差がないようになっているわけです。

実装上はどうなっているかというと、バックグラウンドページは、基本的にはいくつかの初期化処理をしたら後はなんにもしない状態にしておきます。初期化のなかには、chrome.input.ime.onKeyEventというイベントハンドラにaddListenerによってハンドラ関数を登録します。そうするとユーザがキーを打鍵すると、そのたびに登録したハンドラ関数が呼ばれます。関数のなかでは、setCompositionやcommitText、candidatesの設定などの関数を呼ぶことができ、IMEの状態を任意に変えることができます。起動時のonActivateや、フォーカスの入出にかかわるonFocus/onBlurも使えば便利であろうと思われます。
そんな感じで、あとはキーイベントハンドラを上手く処理するJSコードを書きさえすれば完成という次第です。
次では、開発プロセスに関係することを書こうかと思っています。