IMEと入力モードの変更

sennという名前のWindowsで動くIMEを自作しています。 sennの言語バーには入力モードを表示・切り替えるボタンがあり、このボタンをクリックするとひらがな入力と直接入力とを切り替えることができます。 この振る舞いは単純なのですが、内部実装はちょっと複雑になっているためここにメモしておきます。

sennはネットワークでつながったフロントエンドとバックエンドで構成されています。 sennの状態はすべてバックエンドで保持されており、入力モードもバックエンド側で管理してます。 フロントエンドは画面の表示のみを担当しています。

入力モードを表示・切り替えるボタンにはITfLangBarItemButtonを利用しています。 ユーザーがこのボタンをクリックすると、以下のように処理が進みます。

  • Windowsが(?)フロントエンドにあるボタンのITfLangBarItemButton::OnClickメソッドを実行する
  • フロントエンドがバックエンドに対して入力モードを切り替えるメソッド (ToggleInputMode) を実行する
  • フロントエンドがWindowsに (?) 対してアイコンアップデートメソッド (ITfLangBarItemSink::OnUpdate(TF_LBI_ICON))を実行する
  • Windowsが(?)フロントエンドにあるボタンのGetIconメソッドを実行する
  • ボタン (のコールバック) がバックエンドに対して現在の入力モードを問い合わせる (GetInputMode)
  • フロントエンドが入力モードに応じてアイコンを返却する

シーケンス図は次の通りです。

f:id:mhkoji:20211104203759p:plain
シーケンス図

ITfLangBarItemButtonのアイコンを更新するにはボタン自身にGetIconメソッドでアイコンを返却させるという仕組みになっているようで、 外側からsetterでボタンのアイコンを指定するようなことはできなさそうでした。 この事情により、バックエンドはToggleInputModeメソッドとGetInputModeメソッドを提供しており、 ToggleInputModeでは更新後の入力モードを返却せず、 GetIconメソッド用のGetInputModeメソッドが別途入力モードを返却するようにしています。

おわりに

ネットワーク越しに状態を更新し、参照するのは、大げさな感がありますが、面白いです。 なお、バックエンドのToggleInputModeGetIconの処理を担当しているのはstateful-imeです。

IMEでの状態の扱い

はじめに

sennという名前のWindowsで動くIMEを個人で開発しています。 IMEの状態の扱いがなかなか複雑なので、ここでメモしてみます。

stateful-ime

sennにはstateful-imeというオブジェクトがあります。キー入力の処理等、ユーザーと対話するのはこのstateful-ime (の派生) です。 stateful-imeは、stateとstatelessな機能の組み合わせです。 stateは画面の状態等、いろいろな状態を保持しています。

stateful-imeはstateの保持、更新を担当するのみで、具体的な機能はstatelessのほうが担当します。 stateful-imeのメソッドが呼び出されると、引数と保持している状態とを合わせてstatelessな機能に処理を委譲します。 stateful-imeはstatelessから返り値と新しい状態を受け取り、受け取った状態で現在の状態を更新したのちに呼び出し側に返り値を渡します。

たとえば、下はprocess-inputの定義です。このメソッドはユーザーのキー入力を処理します。

(defun process-input (stateful-ime key)
  (with-accessors ((input-state state-input-state)
                   (input-mode state-input-mode))
      (stateful-ime-state stateful-ime)
    (let ((result (senn.win.im.process-input:execute
                   stateful-ime input-state input-mode key)))
      (destructuring-bind (can-process view &key state)
          result
        ;; update application state
        (when state
          (setf input-state state))
        (format nil "~A ~A~%"
                (if (and can-process view) 1 0)
                (or view ""))))))

stateful-imestateful-ime-stateで自身の状態を取り出し、statelessなsenn.win.im.process-input:executeに渡します。 senn.win.im.process-input:executeが新しいinput-stateを返却すると、それでstateful-imeの状態を更新します。 (format nil ...の部分は返り値で、この情報はC++実装のフロントエンドが画面を更新するために利用します。

テストを書くときは、このstateful-imeprocess-inputに対してのみテストを書けばよく、senn.win.im.process-input:executeのテストは書かなくていいかなと思っています。 stateful-imeは、ユーザーと対話しているため、ユーザーの行動をテストで模擬し、ちゃんと対話できているのかを確認するのは意味があると思っています。 一方、senn.win.im.process-input:executeは内部処理であったり、stateの定義変更により影響を受けやすいため、stateful-imeほどテストする意味はないと思っています。 stateful-imeは状態をカプセル化しているため、stateの定義が変わっていてもテストケースは変化しないはずです。

おわりに

以上、IMEの状態の扱い方の方針でした。