glfwマルチプラットフォームでのIME対応の困りごとまとめ

[前の記事](http://qiita.com/shibukawa/items/add9a4bb1b1f959aa1fb)でメモを書き散らしているんだけど、とりあえず現状のまとめです。参考文献リスト的なのは前の記事をご覧ください。

現状の設計
============

今のコード
-------------

[ここのリポジトリのブランチ](https://github.com/shibukawa/glfw-1/tree/feature/add-ime-support) にコミットしています。

CMakeのお作法にしたがってビルドするとビルドできます。試したい方はどうぞ。

![ime-test3.png](https://qiita-image-store.s3.amazonaws.com/0/6105/11d16ba0-c2d7-e7a2-eb5c-1421386b0ef9.png)

↑のコードにはないけど、手元ではこんな感じでOpenGL上に作られたテキストボックス内でインライン変換(on-the-spot変換)できています。

__ちなみに、この修正は次のバージョンに入りそうな感じのコメントはもらっています__ [→glfwのPRへのリンク](https://github.com/glfw/glfw/pull/658#issuecomment-164266419)

用語的なもの
-----------------

変換中文字列の呼び方がcomposition(Windows), marked text(Mac), preedit text(X11)と各プラットフォームのAPIごとにバラバラだけど、preeditが一番分かりやすいと思ったのでpreeditにしています。

Windows
-----------

Text Services Frameworkが後継とのことだけど、以下の理由でIMM32を使っている。

* 僕の環境(10と7)でうまく動いてくれなかった
* IMM32は簡単で情報も多かった
* Qt/GTK/Chromiumなど多くの実装が未だにIMM32を使っている
* glfwはC言語(C言語でCOMを扱うのは辛い！)

IMM32は古いAPIではあるけど、今では互換レイヤーが提供されていて、裏では実質Text Services Frameworkが動いている。ストアアプリとしてはTSF対応が必須ということもあって、IME側では対応は進んでいて、TSFを使ってIMEを実装したという話はよく見かける。

ただ、アプリケーション視点で見ると、.NetのSystem.Windows.InputMethodや、Direct XのDXUIなどラッパーを使う人が多いせいか、TSFの情報はあまり多くない。かろうじてFirefoxの中の人ががんばっている話がひっかかる。しかも、Windows XPやVista時代のドキュメントがリンク切れだったりアクセスできなかったりするので、現行APIにも関わらず使える情報は減っているという。

今回対応した中ではコードも簡潔で特に問題なく動いていて、一番安定している。

Mac OS X
-------------

CocoaのAppKitのNSTextInputClient、NSTextInputContext、NSNotificationCenter, CarbonのText Input Source Servicesを使っている。

* NSTextInputClient: Mac OS Xからコールバックを受ける系
* NSTextInputContext: Mac OS Xに指示を送る系
* NSNotificationCenter: IME切り替え通知
* Text Input Source Services: インストールされている入力ソース情報関連

NSTextInputContextがなぜか動いておらず、preeditテキストのクリアができない

Unix系OS(X11)
-----------------

ibus, uim, Fcitxなどいろいろある。Fcitxが今のところ人気のようだけど、D-Busなど多くの新しい依存が発生してしまうので、とりあえず現状のコードが利用していたX Input Method(XIM)をそのまま利用している。

問題点としては、Preedit textが更新されるのがワンテンポ遅い、IMEのステータス変更のコールバックが飛んでこないというのがある。

<blockquote class="twitter-tweet" lang="en"><p lang="ja" dir="ltr">さっきのIME絡みの記事読んで、UNIX系の進歩の無さにうんざりしてる。XIMが、XIMがなぁ…</p>&mdash; MURAOKA Taro (@kaoriya) <a href="https://twitter.com/kaoriya/status/676927794161557505">December 16, 2015</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

ですよねぇ。fcitxのライブラリが利用できる場合はそちらを利用するようにするか・・・

IME対応関数のAPI設計と現状
===========================

``glfwSetPreeditCallback()`` 
---------------------------------

IMEが編集している時に呼ばれるコールバック関数を登録

* Windows(7): OK
* Mac(10.9): OK
* Ubuntu(15.10): NG 
    ちょっと動きがワンテンポ遅い(5文字目までタイプしても、ひとつ前の4文字目までのテキストしか来ない)。実アプリだとメッセージが飛んでこなくなる。


``glfwResetPreedit()`` 
---------------------------------

IMEが編集している文字列のクリア。前にも書いたように、Macだけ動かない。

* Windows(7): OK
* Mac(10.9): NG
* Ubuntu(15.10): NG

``glfwSetPreeditCursorPos()`` / ``glfwGetPreeditCursorPos()``
------------------------------------

入力中のカーソルの座標をアプリ側からGLFWに通知する。通知したものを再取得する関数。候補文字列のウインドウがカーソルを隠さないようにするために使う。

* Windows(7): Windows IMEはOKだけど、Google日本語入力がウインドウの左下から移動してくれない
* Mac(10.9): OK
* Ubuntu(15.10): Mozcがウインドウの左下から移動してくれない

``glfwSetIMEStatusCallback()``
---------------------------------

IMEのON/OFFが切り替わったら呼ばれるコールバック。XIMではstatus callbackを設定しているのに何も来ないです。このあたり、使うIMEなどによっても挙動が変わるらしいので、問題の切り分けが難しいです。fcitx経由のXIM経由なのかな？今は。

* Windows(7): OK
* Mac(10.9): OK
* Ubuntu(15.10): NG

``glfwSetIMEStatus()`` / ``glfwGetIMEStatus()``
-------------------------------------------------

IMEのON/OFF情報の取得、ON/OFFの切り替え

* Windows(7): OK
* Mac(10.9): OK
* Ubuntu(15.10): ON/OFF情報取得のやり方が分からず(上記コールバックが来ればなんとか頑張れるが)。