はじめに
macOS で、バックグラウンドのプロセスから TISSelectInputSource を使って Google 日本語入力に切り替えると、メニューバーは「ひらがな」になるのに、実際に入力されるのは英字のまま、という現象に遭遇しました。自作の入力ソース切り替えツールに届いたバグ報告(GitHub issue #4)を調べた記録です。
わかっていること、測ったこと、解釈にとどまることを分けて書きます。
症状
- 入力ソースを ABC から Google 日本語入力に
TISSelectInputSourceで切り替える。 - システムが返すカレントの入力ソースは
com.google.inputmethod.Japanese.base、メニューバーの表示もひらがな。 - それでもフォーカス中のアプリで打つと英字が出る。
条件として重要だったのは「フォーカス中のアプリで、ABC のまま実際に文字を入力したあと」であることです。アプリが一度も日本語入力から離れていない状態で試すと成功してしまい、初期の「再現しない」はこれが原因でした。
また、呼び出し元には依存しませんでした。ツール本体、同梱の CLI(keyboardctl switch)、TISSelectInputSource を呼ぶだけの素のテストプロセス、いずれも同じように失敗します。
再現と計測の方法
環境は macOS 27.0、Mac 1 台です。
- テキストエディット(TextEdit)を前面にし、ABC で 1 文字実際に入力した状態から始める。
- 実際のトリガー(修飾キーの単打、またはショートカット)で切り替える。
- 切り替えから 300 ms 後に
aiuを入力する。 - 入力されたテキストを Accessibility API で読み戻し、かなになっていれば成功と数える。
これを 1 ラウンドとして繰り返しました。
修正前の結果:
| 入力メソッド | 成功数 |
|---|---|
| Google 日本語入力(修飾キー単打) | 16 回中 1 回 |
| azooKey | 12 回中 12 回 |
| 微信输入法(WeType Pinyin) | 9 回中 9 回 |
同じ経路で azooKey と微信输入法は問題が出ないので、Google 日本語入力に固有の現象です。
なぜ TISSelectInputSource だけでは英数モードのままなのか
わかっていること(ソースを読んで確認)
mozc の macOS 向けコントローラー(src/mac/mozc_imk_input_controller.mm)は DIRECT モードで始まり、そこから抜けるのは次のどちらかのときだけです。
- システムからモード変更(
setValue:forTag:client:)が届いたとき - 「かな」キーそのものを受け取ったとき
アクティベートされただけではモードは設定されません。
観察と、そこからの解釈
観察:報告者のスクリーンショット(issue #4)では、失敗時のメニューの「Google 日本語入力」セクションがグレーアウトして点の列になっており、成功時には「再変換」「環境設定」などが並んでいました。
解釈:バックグラウンドからの TISSelectInputSource のあとは、入力メソッドがフォーカス中のアプリに接続されておらず、上のモード変更も mozc に届いていない、と考えています。
ただし、なぜ Google 日本語入力だけがこの状態になるのかはわかっていません。
できないこと
フォーカス中のアプリで入力メソッドが実際にアクティブかどうかを読む公開 API は macOS にありません。ツール側は入力ソース ID で切り替えを確認していましたが、ID は正しく変わっているので、この失敗は検出できません。直すとしたら「どう切り替えるか」の側です。
対処:「かな」キーを先に送り、60 ms 後に選択する
「かな」キー(kVK_JIS_Kana、キーコード 104)はシステム自身の切り替え経路を通るので、入力メソッドがアプリに接続されます。ただし「かな」キーで入るのは最後に使った日本語入力ソースなので、日本語入力メソッドが複数入っている環境では狙ったものにならないことがあります。そこで、そのあと目的の入力ソースを改めて選択します。
判定部分(Sources/KeyboardSwitcherCore/SwitchActivationPolicy.swift より):
public static let builtInRecipes = [
ActivationRecipe(sourceIDPrefix: "com.google.inputmethod.Japanese", strategy: .kanaThenSelect),
]
/// `kVK_JIS_Kana`. Recognised on every keyboard, not only JIS ones.
public static let kanaKeyCode = 104
/// Time the Kana switch needs before the slot's source is selected. 20 ms lost one switch
/// in six on azooKey; 60 ms lost none on Google Japanese Input or azooKey.
public static let kanaToSelectDelay: TimeInterval = 0.06
public static func needsKanaPrelude(target: InputSourceInfo, current: InputSourceInfo?, userRecipes: [ActivationRecipe] = []) -> Bool {
// Kana only switches to Japanese sources; a recipe naming anything else is ignored.
guard strategy(for: target, userRecipes: userRecipes) == .kanaThenSelect, target.primaryLanguage == "ja" else { return false }
// Inside Japanese the system no longer switches on Kana; the app would get the key.
return current?.primaryLanguage != "ja"
}
「かな」キーの送出(Sources/KeyboardSwitcherCore/EventTapMonitor.swift より):
static func postKanaKeyEvent() {
for isDown in [true, false] {
let event = CGEvent(keyboardEventSource: nil, virtualKey: CGKeyCode(SwitchActivationPolicy.kanaKeyCode), keyDown: isDown)
event?.flags = []
event?.setIntegerValueField(.eventSourceUserData, value: EventTapMonitor.syntheticEventMarker)
event?.post(tap: .cghidEventTap)
}
}
ポイントは次のとおりです。
- 対象は、組み込みのレシピ(ID が
com.google.inputmethod.Japaneseで始まる入力ソース)か、ユーザーが~/.config/cmd-ime/activation-recipes.jsonに書いたレシピに一致する入力ソースだけ。それ以外の入力メソッドは従来どおりTISSelectInputSourceだけで、遅延も増えません。 - さらに、目的の入力ソースが日本語で、かつ現在の入力ソースが日本語でないときだけ。日本語の中ではシステムが「かな」キーで切り替えなくなり、キーがそのままアプリに渡ってしまうためです。
- 送出したイベントには
eventSourceUserDataに目印を付け、ツール自身のイベントタップが自分の「かな」キーを素通しできるようにしています。 - 待ち時間はレシピの
delayMsで上書きでき、0〜500 ms に丸められます。
うまくいかなかった方法
| 方法 | 結果 |
|---|---|
| 先に選択し、そのあと「かな」キー | 6 回中 1 回。しかも制御文字が 1 つドキュメントに入る |
| 「かな」キーのあと 20 ms で選択 | 6 回中 1 回失敗(azooKey) |
| 「かな」キーのあと 60 ms で選択 | Google 日本語入力 6 回中 6 回、azooKey 6 回中 6 回 |
順序が逆だとだめな理由:先に選択すると、システムはすでに日本語入力ソースがカレントだと見なすので、「かな」キーを普通のキーとしてアプリに渡します。これが紛れ込んだ制御文字の正体です。
60 ms の行は、修正を入れる前の検証段階の数字です。日本語入力メソッドを 2 つ入れた環境でも、最終的にスロットで指定した入力ソースになることを確認しました。
計測結果(リリース版 0.6.2)
修正を含むリリースビルドで、先ほどと同じ手順(実際のトリガー、切り替え 300 ms 後に入力、各ラウンド ABC で 1 文字入力してから開始)で測り直しました。
| 対象 | 修正前 | 0.6.2 |
|---|---|---|
| Google 日本語入力、修飾キー単打 | 16 回中 1 回 | 12 回中 12 回 |
| Google 日本語入力、Option+J(Option を押したまま) | 未計測 | 12 回中 12 回 |
| azooKey | 12 回中 12 回 | 12 回中 12 回 |
| 微信输入法(WeType Pinyin) | 9 回中 9 回 | 12 回中 12 回 |
報告者からも、0.6.2 で直ったと確認をもらっています。
制限事項
- 検証は Mac 1 台(macOS 27.0)だけです。
- 「かな」キーは本物のキーイベントとして送出されます。リモートデスクトップや仮想マシンのウィンドウが前面にあると、リモート側に届く可能性がありますが、これは未検証です。
- 設定画面の「切り替え」ボタンと
keyboardctl switchは、今も直接選択するだけで、この処理を通りません。 - 同じ症状が出る別の日本語入力メソッドがあれば、
activation-recipes.jsonにレシピを書けばリリースを待たずに同じ処理を適用できます。ただしkanaThenSelectは日本語の入力ソースにしか効きません。
{ "recipes": [
{ "sourceIDPrefix": "com.example.inputmethod", "strategy": "kanaThenSelect", "delayMs": 60 }
] }
おわりに:CmdIME について
この修正は、入力ソースごとに専用のキーを割り当てる macOS 用ツール CmdIME(MIT ライセンス、Swift 製)に入っています。Control+Space のように巡回するのではなく、たとえば左 ⌘ で英語、右 ⌘ で中国語、右 Shift で日本語へ直接切り替えます。
- リポジトリ:https://github.com/ShunmeiCho/cmd-ime (現在 0.7.0)
- プレビュー版です。署名済みですが公証されていません。インストールは次の 1 行が確実です。
curl -fsSL https://raw.githubusercontent.com/ShunmeiCho/cmd-ime/main/script/install.sh | bash
ブラウザーで zip をダウンロードした場合は「壊れている」と表示されることがあります。アプリは壊れておらず、隔離属性が原因なので xattr -dr com.apple.quarantine /Applications/CmdIME.app で開けるようになります。
お使いの入力メソッドで同じような症状が出たら、入力メソッド名を添えて issue をいただけると助かります。
