Let's write β

プログラミング中にできたことか、思ったこととか

Plan9PortのMacOSX環境での日本語入力のサポートを試みている話

Plan9PortのAcmeに前回の記事でも触れましたがぞっこんなわけですが、
どうしても日本語入力ができなくて嫌だったので、iTerm2やdevdrawのIME対応を試みていたプロジェクトなどを
参考に日本語入力に暫定的にですが対応したという話です。

GitHub - pocket7878/plan9port at ime-support

そもそものPlan9Portの構造の話

Plan9portはもともと、各プログラムを地道に変更するという方法ではなく、VMのように各ホストOSとの対話の
部分をdevdrawというプログラムによって抽象化することによって動作しています。

devdrawはウィンドウ内の描画の処理や、ホストOSのマウス、キーボードなどの動きを各アプリケーション側に
抽象的なインタフェースで提供し、アプリケーション側はdevdrawから来た司令をそのまま処理して描画しておけば問題ないように作られています。

そのため、OSXのキーボードのイベントやIMEなどに対応させるには、このdevdrawを改造する必要があります。

IME対応するためにやったこと

devdrawとIMEの関係

もともとdevdrawでは、キーボードについてはOSXと最低限のキーボードのイベントの通信だけをしており、
NSResponderのkeyDown:メッセージで受け取った処理はCmdキーの時にキーコードをチェックしたりplan9がつかっているコードに
少し変換などをしてそのままIMEを介さずにコードをアプリケーション側に送っていました。

この状態ではそもそもIMEを経由していませんから、変換されないのは当然です。そこで、まずはIMEを通して入力を
試みることにしました。

中国語のIME対応に試みているコードが見つかった

そのためには、NSTextInputClientというプロトコルを実装しつつ、キーボードからのイベントをIMEに一度送ってやる必要があります。
その辺りのベースのコードは中国語のIMEに対応を試みてくださっていた

acme中文设置

を参考にさせていただきました。
(なにぶんIMEについて全く無知な状態から始めていたので、NSTextInputClientというプロトコルの存在を学ばせていただくところから
始まりました。この参考となるコードがなければ何もできていなかったと思うので非常に感謝しています)

この実装ではIMEにキーボードのイベントを送信するためにNSResponderのinterpretKeyEventというメッセージを利用していました。

iTermへのAquaSKK対応のパッチが非常に参考になった

このソースを読んだ時に、たしかAquaSKKのiTerm対応でそれがあまりbetterではないという事が

mzp.hatenablog.com

で解説されていたということを思い出し、私の実装の方ではNSTextInputContextのhandleEventを利用することにしました。

また、記事で紹介されているiTermへのパッチでのhandleEvent及び、キャッシュしたイベントをdoCommandBySelectorメッセージで
処理する手法は非常に参考になりました。

devdrawとIMEの苦しい関係

なにぶん、devdrawというのは先ほど説明したように抽象的な層なので、通常のアプリケーションのように「自分が処理できるキーコード」
という概念をそもそも持っていません.どんなキーコードを扱えるのかは、実際にdevdrawを使うアプリケーション次第なのです。

なので、IME側では一切特別なキーボード処理をすることをやめ、変換の部分だけを担当してもらうことにしました。
そのためには:

  • insertText:replacementRange
  • doCommandBySelector:
  • setMarkedText:selectedRange:replacementRange:

の3っつのメソッドを適切に扱えば良いことがわかりました。setMarked~はIMEが変換を開始した時に呼び出してくるもので、
変換途中だとわかるように下線がついていたり、背景色が違っていたりするNSAttributedStringを渡してくれます。
つまり、これはIMEが変換を試みているということを意味しています。

insert~はIMEが確定したタイミングで呼ばれてくるものです、そしてdoCommandBySelectorは、それ以外のキーバインドのような
もので、アプリケーション側にmoveForward:とかinsertTabとかをやってみてほしいという依頼を出してくるものです。

つまり、doCommandBySelectorに来た時点で変換ではなく、なにか特殊なキーである可能性が高いと考えられます。
そのため、この場合はiTermへのパッチを参考にさせていただき、キャッシュしておいたもともとのNSEventを
既存のdevdrawのキーボード処理を参考にアプリケーションに送信しました。

謎の16文字制限

ここまででIMEからざっくりと文字入力はできるようになっていました、しかし快適になったと思って使っているとある事実に
気が付きました。それは変換を確定せずに16文字連続で打っていくと途中で変換途中の文字列が適切に更新されなく成るということでした。

この変換途中の文字列の置換には\bというbackspaceをひとつ前の変換文字列の長さだけ送り出し、更新後の文字を挿入するという方法をとっています
(なにぶん、アプリケーションごとに文字をどう扱っているかは違うので範囲置換とかの共通の方法はなさそうなのです)

これが16文字を超えたタイミングで急にうまく行かなくなって途中までしか消えなくなったりするようになっていました。

ここでしばらく苦しんだのですが、結論としてはdevdrawはもともとIMEを想定していなかったために、
devdrawにやってきたキーボードイベントを「リングバッファ」に保管して定期的にアプリケーションに送信していました。
このリングバッファがIMEが入ってきたことによって一気に大量の文字が流れ込んできてデータが取りこぼされるようになっていました。

この仕様自体はIMEを想定しないならば、時間的にも空間的にも効率が良いので良いのですが、私の方法でn文字置換するために大体2*n文文字を送っているのでは取りこぼされてしまいます。

そこで、リングバッファを諦めて、リンクドリストを利用してキーボードイベントをキューするように実装をしなおしました。

こうすることで実質どれだけ長くキーボードイベントが一気にきてもあふれること無く送信されるようになりました。

現在の苦悩

これで、現状IMEから入力をなんとなく行うことはデキるようになりましたが、現状幾つかの欠点があります。

未確定状態で途中でキャンセルの基準がない

繰り返しになりますがdevdrawはアプリケーションそのものではないために、変換途中でアプリケーションから変換を中断したいという
ことの明確な基準がありません。そのため、現状では変換途中にマウスクリックが発生したらそのまま確定させてしまうようにしました。
しかし、これでは問題があり、acmeの様なFocus follow mouseなアプリケーションでは、クリックを必要とせずにフォーカスのあっている
テキスト領域が変わります。しかし、devdrawからはそのことを知ることはできません。
そのため、未確定状態からどこかにカーソルを動かすには、一度確定するか、マウスをクリックした上で再開する必要が生まれています。

Samでは相変わらずバッファ溢れが起きているような感じがする


Acmeはバックエンドというような概念なしのエディタですが、SamはSamTermというフロントエンドとSamのバックエンドが互いに
通信をしあって描画が行われています。そのため、devdrawの溢れと同じ問題が発生している予感があり、Acmeでは正常に
変換が実施されるタイミングでSamではクチャっとなってしまう場面が見受けられます。

追記
入力中に途中で多く戻りすぎたりというふうに見えていたのでバファ溢れかと思っていたのですが、
どうもsamterm上で、無私すべき入力とそうでない入力の区別を雑然とつけていたようで、Kcmdというコマンドキーの
値よりも大きいキーはすべてKcmdとの組み合わせの可能性があるとして無視するようになっており、そのため
変換の頭に発生するnのようなマルチバイト文字が無視されているためIME的には1文字入力しているのに、されておらず
次の に になるタイミングで1文字削除してとやろうとすると多く削除される形になってしまうというのがその原因でした。

変換の部分変換に未対応かも

IMEでは入力の全体ではなく一部にフォーカスを当てて変換を行うことは多々ああります。
(そのためにselectedRangeとmarkedRangeは明確にNSTextInputClientでは区別されています)が、まだそこには明確には対応していません
なので、そのような変換への上手い対処法も考察中です。



ざっと走り書きですがここ数日やっていることの途中報告です。現状ではまだ幾つか不満なところがありますが
日本語入力が全くできない状態からここまでこれただけでもかなり自分的にはテンションが上がっています。