Stream Deck × AutoHotkeyでClaude Codeの音声入力をトグル化する
Claude Codeの音声入力はスペースキーを長押しする。押している間だけ認識が走り、指を離すと止まる。短いプロンプトなら問題ないが、長文を口述するとき、スペースキーを左手で押さえ続けるのが地味につらい。Stream Deckのボタンを1回押すだけでトグルできないか――そう考えて試行錯誤が始まった。
Stream Deckの「ホットキー」アクションでは解決しない
最初に思いつくのはStream Deck標準の「ホットキー」アクションだ。しかしこれは「ボタンを物理的に押している間だけキーを送る」仕組みで、指を離すとキーも離れる。Claude Codeの音声入力が求めるのは「長押しし続ける」状態の維持なので、標準機能ではトグルにできない。
ここでAutoHotkeyの出番になる。
最初の試み: Space downを1回送る → 失敗
AutoHotkeyで Send "{Space down}" を1回送れば、OSがキーを押しっぱなしと認識してくれるだろう――そう考えてスクリプトを書いた。
; 最初の案(動かない)
Send "{Space down}"
実行するとClaude Codeの音声入力アイコンが一瞬点灯して、すぐ消える。Space down を1回送っただけでは、物理キーのような連続的なキーリピートイベントが発生しない。OSは「一瞬押されて離された」と解釈してしまう。
ターミナルのカーソルが微動だにしない時点で、この方式は無理だと悟った。
解決策: ループでSpace downを送り続ける
物理キーの長押しを模倣するには、スクリプトが終了せずにループで Space down を送り続ける必要がある。
; toggle-voice.ahk の核心部分
#Requires AutoHotkey v2.0
stateFile := A_Temp "\voice-input-pane1.lock"
; 既に動いているインスタンスがあれば、状態ファイルを消して停止させる
if FileExist(stateFile) {
FileDelete stateFile
ExitApp
}
; 状態ファイルを作成して「録音中」を示す
FileAppend("", stateFile)
; 状態ファイルが存在する限り、Space downを送り続ける
while FileExist(stateFile) {
Send "{Space down}"
Sleep 50
}
; ループを抜けたらキーを離して終了
Send "{Space up}"
ExitApp
仕組みはシンプルだ。
- 1回目の呼び出し: 状態ファイル(
.lock)を作成し、50msごとにSpace downを送るループに入る - 2回目の呼び出し: 別のインスタンスが起動し、状態ファイルを削除して即終了する
- 1回目のインスタンス: ループの
FileExistチェックで.lockが消えたことを検知し、Space upを送って終了する
Stream Deckのボタンには、このAHKスクリプトを呼ぶbatファイルを割り当てる。既存のStream Deck → bat → AHKというパターンをそのまま踏襲した。
@echo off
start "" "C:\tools\AutoHotkey\v2\AutoHotkey64.exe" "C:\tools\scripts\toggle-voice.ahk" pane1
ボタンを押す。Claude Codeの音声入力アイコンが点灯し、マイクが拾い始める。もう一度押す。アイコンが消え、入力が確定される。手がキーボードから離れた状態で音声入力のON/OFFを切り替えられるようになった。
ペイン間の自動切り替え: 4分割画面での運用
4分割画面で複数のClaude Codeインスタンスを動かしている場合、ペインごとにStream Deckのボタンを割り当てたい。ペイン1で音声入力中にペイン2のボタンを押したら、ペイン1が止まってペイン2が始まる――という挙動が理想だ。
状態ファイルをペインごとに分ける
状態ファイルの名前にペイン番号を含める。
voice-input-pane1.lock
voice-input-pane2.lock
voice-input-pane3.lock
voice-input-pane4.lock
他ペインの状態ファイルを先に消す
あるペインのボタンが押されたとき、まず他の全ペインの .lock ファイルを削除する。これで他ペインで走っているインスタンスが次のループチェックで自動停止する。
; 自分以外のペインの状態ファイルを全て消す
panes := ["pane1", "pane2", "pane3", "pane4"]
for _, p in panes {
if (p != myPane) {
otherFile := A_Temp "\voice-input-" p ".lock"
if FileExist(otherFile)
FileDelete otherFile
}
}
この仕組みにより、ペイン間の切り替えがボタン1回で完結する。ペイン1のボタンを押して口述を始め、途中でペイン3に切り替えたくなったらペイン3のボタンを押すだけだ。ペイン1の音声入力は自動で止まり、ペイン3が即座に始まる。
動作の流れまとめ
[Stream Deck] ボタン押下
↓
[bat] AHKスクリプト起動(ペイン番号を引数で渡す)
↓
[AHK] 他ペインの .lock を削除(排他制御)
↓
[AHK] 自ペインの .lock が存在する?
├─ Yes → .lock を削除して終了(= 停止トグル)
└─ No → .lock を作成してループ開始(= 開始トグル)
↓
[AHK] ループ: 50msごとに Space down を送信
↓
[AHK] .lock が消えたらループ脱出 → Space up → 終了
振り返り
Space down を1回送って沈黙するターミナルを眺めた時間が一番長かった。物理キーの長押しとソフトウェアからのキーイベント送信は、見た目は同じでもOSの扱いが違う。キーリピートは物理キーボードのコントローラーが生成するもので、SendInput で1回送っただけでは再現されない。
状態ファイルによるプロセス間通信は原始的だが、AutoHotkeyのようにプロセス間のメッセージングが面倒な環境では手堅く動く。ファイルの有無だけで状態を管理するので、デバッグも .lock ファイルをエクスプローラーで確認するだけで済む。
Stream Deckに4つのペインボタンが並んでいる画面を見ると、音声入力のハードルが目に見えて下がった実感がある。キーボードに手を伸ばさずに口述を切り替えられるのは、思った以上に作業のリズムを変える。