みずぴー日記

人間の再起動ボタンはハワイのビーチにある

🍣入力メソッド

すべてのキー入力に対して🍣を入力する入力メソッドを作った。

コード

https://github.com/mzp/EmojiIM

入力メソッドの構成

入力メソッドによるテキスト入力は、各アプリケーションと入力メソッドが通信することで実現されている。アプリケーションはキー入力を入力メソッドに送信し、入力メソッドがアプリケーションに対してテキスト入力を行なう。

この通信のために入力メソッドはIMKServerクラスを用いてサーバーを起動する。そして各アプリケーションはクライアントとなり、サーバーと通信する。この通信を入力セッション(input session)と呼ぶ。

サーバーはクライアントごとに入力コントローラーを生成する。この入力コントローラーがキー入力からテキスト入力を生成する。

f:id:mzp:20170917183745p:plain

プロジェクトの作成

Xcodeでプロジェクトを作成する。Cocoa Appテンプレートを選ぶ。 Product nameはなんでもいいが、Bundle identifierにinputmethodという文字列を含める必要がある。

f:id:mzp:20170917183932p:plain

起動時の処理

入力メソッドが起動時に各アプリケーションと通信するための IMKServer を起動する。このとき、クライアントがサーバーに接続する際に使う名前を指定する。

import Cocoa
import InputMethodKit

private var server: IMKServer?

@NSApplicationMain
internal class AppDelegate: NSObject, NSApplicationDelegate {

    @IBOutlet private weak var window: NSWindow!

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        server = IMKServer(name: "EmojiInputSession", bundleIdentifier: Bundle.main.bundleIdentifier)
    }
}

入力コントローラ

入力コントローラとして使うクラスを定義する。このクラスは IMKInputController の派生クラスにする必要がある。

このクラスの名前をInfo.plistで指定す。そのため、objc 属性を用いて、Swiftコンパイラによってマングリングされないようにする。

@objc(EmojiInputController)
open class EmojiInputController: IMKInputController {

キー入力時に inputText メソッドが呼ばれる。キー入力を処理したかどうかを真偽値で返す。

func inputText(_ string: String!, client sender: Any!) -> Bool

テキスト入力をしたいアプリケーションは client 引数として渡される。 ただし、 Any 型のままではテキスト入力を行なえないのでIMKTextInput プロトコルにキャストする。 キャストに失敗した場合は、入力メソッドが処理しなかったことを示すために偽を返す。

guard let client = sender as? IMKTextInput else {
  return false
}

IMKTextInputinsertText メソッドを用いて、入力したい文字列を挿入する。

client.insertText("🍣", replacementRange: NSRange(location: NSNotFound, length: NSNotFound))

Info.plist

Info.plistで入力セッションに使う名前や入力コントローラとして使うクラスの名前を指定する。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>$(DEVELOPMENT_LANGUAGE)</string>
    <key>CFBundleExecutable</key>
    <string>$(EXECUTABLE_NAME)</string>
    <key>CFBundleIconFile</key>
    <string></string>
    <key>CFBundleIdentifier</key>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>$(PRODUCT_NAME)</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>1.0</string>
    <key>CFBundleVersion</key>
    <string>1</string>
    <key>LSBackgroundOnly</key>
    <string>YES</string>
    <key>LSHasLocalizedDisplayName</key>
    <false/>
    <key>LSMinimumSystemVersion</key>
    <string>$(MACOSX_DEPLOYMENT_TARGET)</string>
    <key>NSHumanReadableCopyright</key>
    <string>Copyright © 2017 mzp. All rights reserved.</string>
    <key>NSMainNibFile</key>
    <string>MainMenu</string>
    <key>NSPrincipalClass</key>
    <string>NSApplication</string>
    <!-- 入力セッションに使う名前 -->
    <key>InputMethodConnectionName</key>
    <string>EmojiInputSession</string>
    <!-- 入力コントローラのクラス名 -->
    <key>InputMethodServerControllerClass</key>
    <string>EmojiInputController</string>
    <!-- 入力メソッドのアイコンとして使う画像 -->
    <key>tsInputMethodIconFileKey</key>
    <string>InputMethodIcon.tiff</string>
    <!-- 入力ソース(入力ソースの詳細はよくわからん) -->
    <key>TISIntendedLanguage</key>
    <string>ja</string>
   <!-- 入力ソースに対する識別子 -->
    <key>TISInputSourceID</key>
    <string>jp.mzp.inputmethod.EmojiIM</string>
    <!-- 入力メソッドが対応している言語のリスト -->
    <key>tsInputMethodCharacterRepertoireKey</key>
    <array>
        <string>Hira</string>
        <string>Kana</string>
        <string>Latn</string>
    </array>
</dict>
</plist>

インストール

ビルドして生成された EmojiIM.app~/Library/Input Methods にコピーする。Workspace settingsからDerivedDataをworkspace relativeにしておくと楽。 こんな感じでコピーする。

cp -r DerivedData/EmojiIM/Build/Products/Debug/EmojiIM.app ~/Library/Input\\ Methods

System PreferencesのInput Sourcesから追加できる。うまく反映されない場合は、いったんログアウトするとよい。

f:id:mzp:20170917185308p:plain