Mac音声入力をFnキーで扱うための設定検証レポート
目的
Macの標準「音声入力」を、Fnキーで快適に起動・停止したい。
具体的には次の2つを両立したい、という要件だった。
Fn単押し: 音声入力ショートカットを1回送ってトグル
Fn長押し: 押している間だけ音声入力する擬似 push-to-talk
前提仕様
macOS標準の音声入力は、開始と停止が別コマンドではなく同じショートカットのトグルになっている。ショートカットはカスタマイズ可能で、終了も同じショートカット、または Esc / マイクキーで行う。加えて、30秒間無音だと自動停止する仕様がある。 (Appleサポート)
Fn / Globeキーは macOS 側で役割を持っており、入力ソース切替 / 絵文字と記号 / Dictation開始 / 何もしない を選べる。なお、Start Dictation は「Fnを2回押す」動作であり、単押しや長押しの自由な挙動にはならない。 (Appleサポート)
Karabiner-Elements では、次の挙動を定義できる。
to_if_alone: キーを単独で押して離した時に発火
to_if_held_down: 一定時間以上押した時に発火
to_after_key_up: キーを離した時に発火
to_delayed_action: 一定時間後に発火、またはそれまでに条件が崩れたらキャンセル側を発火
また to_if_alone は、内部的には key_down と key_up を同時に送る。そのため、EventViewer上では「押した直後にすぐ離した」ように見える。これは異常ではなく仕様。 (Karabiner-Elements)
実機検証で確認できたこと
Karabinerの変更を止めた状態で EventViewer を確認したところ、Fnキーは
apple_vendor_top_case_key_code: "keyboard_fn"
として取得できた。
つまり、この環境では FnそのものをKarabinerの入力トリガーとして使える ことが確認できた。
一方、Karabiner有効時のログでは ⌃⌥⌘D が送られており、macOSに対して音声入力ショートカット自体は正しく届いていた。
問題の原因
最初の実装では、Fnに対して
to_if_alone
to_if_held_down
to_after_key_up
を同時に使っていた。
この構成自体は理論上可能だが、単押し経路と長押し経路が明確に排他になっていないと、同じFn操作に対して複数の送出経路が重なりやすい。Karabinerの仕様上、to_if_alone は「離した時」に評価され、to_if_held_down は保持時間を超えた時に発火し、to_after_key_up はさらに離した時に発火するため、設計が曖昧だと意図しない二重送出や状態ズレが起きやすい。これは Karabiner の各アクション仕様から説明できる。 (Karabiner-Elements)
加えて、Fnに関する旧ルールが残っていると、同じFnに複数のmanipulatorが当たって再現性の低い誤動作になりやすい。
今回の症状は、実際には macOSのDictationそのものより、Karabiner側のルーティング設計が原因 だった。
この判断根拠は、手動で ⌃⌥⌘D を連打した時には問題が再現しなかったため。
誤解しやすかった点
EventViewerで
d down
直後に d up
が出ていたため、「勝手に即離しが発生しているのでは」と見えたが、これはKarabinerの仕様どおり。
to_if_alone 系のイベントは 押しっぱなしではなく “1回のキー送信” として down/up を連続で出す。したがって、⌃⌥⌘D の送出ログで down → up がすぐ続くこと自体は正常。 (Karabiner-Elements)
最終的に安定した解決策
macOS側
Fn / Globe の既定動作を無効化するため、
システム設定 > キーボード > Press fn/Globe key to = Do Nothing
に設定する。Appleの設定項目として正式に存在する。 (Appleサポート)
Dictationショートカットは、たとえば
⌃⌥⌘D
のような専用ショートカットにする。AppleはDictationショートカットのカスタマイズをサポートしている。 (Appleサポート)
Karabiner側
to_if_alone と to_if_held_down をそのまま併用するのではなく、to_delayed_action を使って tap と hold を排他的に分岐させる構成に変更した。Karabinerは to_delayed_action を「一定時間後に発火する処理」として定義しており、to_if_invoked と to_if_canceled で明確に分岐できる。 (Karabiner-Elements)
この設計にすると、Fn操作は次のように整理できる。
短押し
一定時間未満で離した場合だけ、⌃⌥⌘D を1回送る
長押し
一定時間を超えた時点で ⌃⌥⌘D を1回送る
長押し後に離す
離した時に ⌃⌥⌘D をもう1回送る
これにより、単押しルートと長押しルートが同じFn押下で重ならない構成になった。
実装後の最終挙動
今回直った状態では、実質的に次の動作になっている。
Fn単押し
音声入力をトグル
Fn長押し
押し始めで音声入力開始
Fnを離す
音声入力停止
期待どおり、単押しは1回だけ, 長押しは開始時1回 + 離した時1回 の送出に整理された。
運用上の注意
1. Fn系ルールは1本だけ有効にする
同じFnを扱う古いルールが残ると再発しやすい。
2. macOS側のFn役割は Do Nothing のままにする
入力ソース切替や「Fn2回でDictation開始」が有効だと競合する。 (Appleサポート)
3. Dictationはトグル式であること自体は変わらない
ただし今回の修正により、Karabiner側の分岐が安定したため、実用上のズレは解消した。AppleのDictation自体は同じショートカットで開始・停止する仕様。 (Appleサポート)
結論
今回の問題は、macOS標準の音声入力の限界というより、Karabiner側で tap と hold の経路を排他的に設計できていなかったことが主因だった。
Fnの生イベント取得自体は可能であり、macOS側のFn役割を無効化し、Karabinerで to_delayed_action を使って分岐を整理することで、Fn単押しトグル + Fn長押し擬似 push-to-talk は安定して実現できた。
必要なら次に、この内容を 社内共有向けの短い版 か 自分用メモ向けの技術メモ版 に整形します。