みずぴー日記

月に行こうという目標があったから、アポロは月に行けた。飛行機を改良した結果、月に行けたわけではない。

🌓入力モード

一部の入力メソッドは複数のモードを持つ。例えば、macOS標準の日本語入力がひらがな入力モードとカタカナ入力モードを持つ。

EmojiIMを拡張し、絵文字入力モードとアルファベット入力モードの2つのモードを実装した。

f:id:mzp:20171026084007p:plain

📚関連資料

入力モードに関する資料は複数の箇所に分散している。 一箇所にまとめておいてほしい。

📝ソースコード

https://github.com/mzp/EmojiIM/pull/14

✨ 入力モードの定義

入力モードを定義するために、Info.plistに ComponentInputModeDict キーを追加する。 ComponentInputModeDict の値は辞書型であり、以下の2つのキーを持つ。

  • tsVisibleInputModeOrderedArrayKey: 入力モードを定義する
  • kTSVisibleInputModeOrderedArrayKey: 表示順を定義する。

tsVisibleInputModeOrderedArrayKey の辞書型であり、入力モードを識別するIDをキーとして持つため、おおまかに以下のような構造をとる。

<key>ComponentInputModeDict</key>
<dict>
  <key>tsInputModeListKey</key>
  <dict>
    <Key>入力モードID 1</key>
    <Dict><!-- 入力モードの定義 --></dict>                  
    <Key>入力モードID 2</key>
    <dict><!-- 入力モードの定義 --></dict>
  </dict>
  <key>tsVisibleInputModeOrderedArrayKey</key>
  <array>
    <String>入力モードID 1</string>
    <String>入力モードID 2</string>
  </array>
</dict>

入力モードのIDは既存の入力モードと同じ機能を提供する場合は同じキーを使い、独自の機能を提供する場合は独自のIDを使う。 既存の入力モードのIDはTextServices.hで定義されており、com.apple.inputmethod.Romancom.apple.inputmethod.Japanese.Hiragana などがある。

入力モードの定義も辞書型になっており、以下のキーを持つ。

キー名 必須・任意 意味
TISInputSourceID 任意 入力メソッドのbundle identifierからはじまる識別子。 入力モードのIDと同一である必要はない。|省略した場合は、bundle idと入力モードのIDから自動で決められる。
TISIntendedLanguage 必須 どの言語の入力モードか。
tsInputModePrimaryInScriptKey 必須 |使用する文字体系を指定する (例: 日本語ならsmJapanese)。どこで使われるかは不明。
tsInputModePrimaryInScriptKey 必須 この入力モードが、主となる文字体系か。どこで使われるかは不明。
tsInputModeMenuIconFileKey 必須 入力モードに対するアイコン
tsInputModeAlternateMenuIconFileKey 必須 メニューバーでクリックされたときのアイコン
tsInputModeDefaultStateKey 必須 設定画面で言語を選んだときに自動で選択されるか。(と書いてあるがよくわからない)
tsInputModeKeyEquivalentKey 任意 この入力モードに切り替えるために使うキー
tsInputModeKeyEquivalentModifiersKey 任意 この入力モードに切り替えるために使う修飾キー
tsInputModeJISKeyboardShortcutKey 任意 この入力モードに切り替えるために使うキー。(JISキーボード用)(0=none,1=hiragana,2=katakana,3=eisu)

これらをふまえてEmojiIMでは以下のように定義した。

<key>ComponentInputModeDict</key>
<dict>
         <key>tsInputModeListKey</key>
          <dict>
                 <key>jp.mzp.inputmethod.EmojiIM</key>
                  <dict>
                         <key>TISInputSourceID</key>
                         <string>jp.mzp.inputmethod.EmojiIM.emoji</string>
                         <key>TISIntendedLanguage</key>
                         <string>en</string>
                         <key>tsInputModeScriptKey</key>
                         <string>smUnicode</string>
                         <key>tsInputModeMenuIconFileKey</key>
                         <string>InputMethodIcon.tiff</string>
                         <key>tsInputModeAlternateMenuIconFileKey</key>
                         <string>InputMethodIcon.tiff</string>
                         <key>tsInputModeDefaultStateKey</key>
                         <true/>
                 </dict>
                 <key>com.apple.inputmethod.Roman</key>
                 <dict>
                          <key>TISInputSourceID</key>
                          <string>jp.mzp.inputmethod.EmojiIM.roman</string>
                          <key>TISIntendedLanguage</key>
                          <string>en</string>
                          <key>tsInputModeScriptKey</key>
                          <string>smRoman</string>
                          <key>tsInputModeMenuIconFileKey</key>
                          <string>InputMethodIcon.tiff</string>
                          <key>tsInputModeAlternateMenuIconFileKey</key>
                          <string>InputMethodIcon.tiff</string>
                          <key>tsInputModeDefaultStateKey</key>
                          <true/>
                  </dict>
          </dict>
          <key>tsVisibleInputModeOrderedArrayKey</key>
          <array>
                 <string>jp.mzp.inputmethod.EmojiIM</string>
                 <string>com.apple.inputmethod.Roman</string>
         </array>
</dict>

これで設定画面より入力モードの追加・削除ができる。

f:id:mzp:20171026083736p:plain

追加後はメニューバーから入力モードが切り替えられる。


f:id:mzp:20171026083804p:plain

🌐 入力モードの名前

設定画面に入力モードIDがそのまま表示されるのは分かりづらいので、分かりやすい名前をつける。

これはローカライゼーションの仕組みを用いて行なう。XcodeでInfoPlist.stringsを追加し以下のように定義する。

CFBundleName = "Emoji IM";

com.apple.inputmethod.Roman = "Emoji(Alphabet)";

jp.mzp.inputmethod.EmojiIM = "Emoji";

これで入力モードの名前を指定できる。

f:id:mzp:20171026082536p:plain

🔀入力コントローラでのモード切り替え

入力モードを切り替えた場合は、入力コントローラの setValue:forTag:client: が呼ばれる。

アルファベット入力モードかどうかを示すdirectMode変数の値を変更するようにした。

class EmojiInputController: IMKInputController {
    private var directMode: Bool = false
    override func setValue(_ value: Any, forTag tag: Int, client sender: Any) {
        // valueに入力モードIDが渡されるので、動作を切り替える
        guard let value = value as? NSString else {
            return
        }
        directMode = value == "com.apple.inputmethod.Roman"
    }
}

キー入力のハンドラでは、directMode変数に基づいて処理を決める。

class EmojiInputController: IMKInputController {
    override func handle(_ event: NSEvent, client sender: Any) -> Bool {
        NSLog("%@", "\(#function)((\(event), client: \(sender))")
        if directMode {
            // directModeが真ならfalseを返し、通常の処理を行なうようにする
            return false
        }

        // 通常の処理
    }
}