[{"data":1,"prerenderedAt":449},["ShallowReactive",2],{"content-/stream-deck-voice-input-toggle":3,"all-pages-for-dir":447,"og-image-/stream-deck-voice-input-toggle":448},{"id":4,"title":5,"body":6,"category":428,"description":429,"extension":430,"meta":431,"navigation":110,"path":432,"project_name":433,"published":434,"publishedAt":435,"seo":436,"stem":437,"tags":438,"todo":444,"updatedAt":445,"__hash__":446},"pages/2026-04/2026-04-07/stream-deck-voice-input-toggle.md","Stream Deck × AutoHotkeyでClaude Codeの音声入力をトグル化する",{"type":7,"value":8,"toc":417},"minimark",[9,13,17,20,25,28,31,33,37,45,68,75,78,80,84,90,227,230,271,274,291,294,296,300,303,307,310,318,321,327,376,379,381,384,390,392,395,404,410,413],[10,11,5],"h1",{"id":12},"stream-deck-autohotkeyでclaude-codeの音声入力をトグル化する",[14,15,16],"p",{},"Claude Codeの音声入力はスペースキーを長押しする。押している間だけ認識が走り、指を離すと止まる。短いプロンプトなら問題ないが、長文を口述するとき、スペースキーを左手で押さえ続けるのが地味につらい。Stream Deckのボタンを1回押すだけでトグルできないか――そう考えて試行錯誤が始まった。",[18,19],"hr",{},[21,22,24],"h2",{"id":23},"stream-deckのホットキーアクションでは解決しない","Stream Deckの「ホットキー」アクションでは解決しない",[14,26,27],{},"最初に思いつくのはStream Deck標準の「ホットキー」アクションだ。しかしこれは「ボタンを物理的に押している間だけキーを送る」仕組みで、指を離すとキーも離れる。Claude Codeの音声入力が求めるのは「長押しし続ける」状態の維持なので、標準機能ではトグルにできない。",[14,29,30],{},"ここでAutoHotkeyの出番になる。",[18,32],{},[21,34,36],{"id":35},"最初の試み-space-downを1回送る-失敗","最初の試み: Space downを1回送る → 失敗",[14,38,39,40,44],{},"AutoHotkeyで ",[41,42,43],"code",{},"Send \"{Space down}\""," を1回送れば、OSがキーを押しっぱなしと認識してくれるだろう――そう考えてスクリプトを書いた。",[46,47,52],"pre",{"className":48,"code":49,"language":50,"meta":51,"style":51},"language-ahk shiki shiki-themes vitesse-light vitesse-light","; 最初の案（動かない）\nSend \"{Space down}\"\n","ahk","",[41,53,54,62],{"__ignoreMap":51},[55,56,59],"span",{"class":57,"line":58},"line",1,[55,60,61],{},"; 最初の案（動かない）\n",[55,63,65],{"class":57,"line":64},2,[55,66,67],{},"Send \"{Space down}\"\n",[14,69,70,71,74],{},"実行するとClaude Codeの音声入力アイコンが一瞬点灯して、すぐ消える。",[41,72,73],{},"Space down"," を1回送っただけでは、物理キーのような連続的なキーリピートイベントが発生しない。OSは「一瞬押されて離された」と解釈してしまう。",[14,76,77],{},"ターミナルのカーソルが微動だにしない時点で、この方式は無理だと悟った。",[18,79],{},[21,81,83],{"id":82},"解決策-ループでspace-downを送り続ける","解決策: ループでSpace downを送り続ける",[14,85,86,87,89],{},"物理キーの長押しを模倣するには、スクリプトが終了せずにループで ",[41,88,73],{}," を送り続ける必要がある。",[46,91,93],{"className":48,"code":92,"language":50,"meta":51,"style":51},"; toggle-voice.ahk の核心部分\n#Requires AutoHotkey v2.0\n\nstateFile := A_Temp \"\\voice-input-pane1.lock\"\n\n; 既に動いているインスタンスがあれば、状態ファイルを消して停止させる\nif FileExist(stateFile) {\n    FileDelete stateFile\n    ExitApp\n}\n\n; 状態ファイルを作成して「録音中」を示す\nFileAppend(\"\", stateFile)\n\n; 状態ファイルが存在する限り、Space downを送り続ける\nwhile FileExist(stateFile) {\n    Send \"{Space down}\"\n    Sleep 50\n}\n\n; ループを抜けたらキーを離して終了\nSend \"{Space up}\"\nExitApp\n",[41,94,95,100,105,112,118,123,129,135,141,147,153,158,164,170,175,181,187,193,199,204,209,215,221],{"__ignoreMap":51},[55,96,97],{"class":57,"line":58},[55,98,99],{},"; toggle-voice.ahk の核心部分\n",[55,101,102],{"class":57,"line":64},[55,103,104],{},"#Requires AutoHotkey v2.0\n",[55,106,108],{"class":57,"line":107},3,[55,109,111],{"emptyLinePlaceholder":110},true,"\n",[55,113,115],{"class":57,"line":114},4,[55,116,117],{},"stateFile := A_Temp \"\\voice-input-pane1.lock\"\n",[55,119,121],{"class":57,"line":120},5,[55,122,111],{"emptyLinePlaceholder":110},[55,124,126],{"class":57,"line":125},6,[55,127,128],{},"; 既に動いているインスタンスがあれば、状態ファイルを消して停止させる\n",[55,130,132],{"class":57,"line":131},7,[55,133,134],{},"if FileExist(stateFile) {\n",[55,136,138],{"class":57,"line":137},8,[55,139,140],{},"    FileDelete stateFile\n",[55,142,144],{"class":57,"line":143},9,[55,145,146],{},"    ExitApp\n",[55,148,150],{"class":57,"line":149},10,[55,151,152],{},"}\n",[55,154,156],{"class":57,"line":155},11,[55,157,111],{"emptyLinePlaceholder":110},[55,159,161],{"class":57,"line":160},12,[55,162,163],{},"; 状態ファイルを作成して「録音中」を示す\n",[55,165,167],{"class":57,"line":166},13,[55,168,169],{},"FileAppend(\"\", stateFile)\n",[55,171,173],{"class":57,"line":172},14,[55,174,111],{"emptyLinePlaceholder":110},[55,176,178],{"class":57,"line":177},15,[55,179,180],{},"; 状態ファイルが存在する限り、Space downを送り続ける\n",[55,182,184],{"class":57,"line":183},16,[55,185,186],{},"while FileExist(stateFile) {\n",[55,188,190],{"class":57,"line":189},17,[55,191,192],{},"    Send \"{Space down}\"\n",[55,194,196],{"class":57,"line":195},18,[55,197,198],{},"    Sleep 50\n",[55,200,202],{"class":57,"line":201},19,[55,203,152],{},[55,205,207],{"class":57,"line":206},20,[55,208,111],{"emptyLinePlaceholder":110},[55,210,212],{"class":57,"line":211},21,[55,213,214],{},"; ループを抜けたらキーを離して終了\n",[55,216,218],{"class":57,"line":217},22,[55,219,220],{},"Send \"{Space up}\"\n",[55,222,224],{"class":57,"line":223},23,[55,225,226],{},"ExitApp\n",[14,228,229],{},"仕組みはシンプルだ。",[231,232,233,248,254],"ol",{},[234,235,236,240,241,244,245,247],"li",{},[237,238,239],"strong",{},"1回目の呼び出し",": 状態ファイル（",[41,242,243],{},".lock","）を作成し、50msごとに ",[41,246,73],{}," を送るループに入る",[234,249,250,253],{},[237,251,252],{},"2回目の呼び出し",": 別のインスタンスが起動し、状態ファイルを削除して即終了する",[234,255,256,259,260,263,264,266,267,270],{},[237,257,258],{},"1回目のインスタンス",": ループの ",[41,261,262],{},"FileExist"," チェックで ",[41,265,243],{}," が消えたことを検知し、",[41,268,269],{},"Space up"," を送って終了する",[14,272,273],{},"Stream Deckのボタンには、このAHKスクリプトを呼ぶbatファイルを割り当てる。既存のStream Deck → bat → AHKというパターンをそのまま踏襲した。",[46,275,279],{"className":276,"code":277,"language":278,"meta":51,"style":51},"language-bat shiki shiki-themes vitesse-light vitesse-light","@echo off\nstart \"\" \"C:\\tools\\AutoHotkey\\v2\\AutoHotkey64.exe\" \"C:\\tools\\scripts\\toggle-voice.ahk\" pane1\n","bat",[41,280,281,286],{"__ignoreMap":51},[55,282,283],{"class":57,"line":58},[55,284,285],{},"@echo off\n",[55,287,288],{"class":57,"line":64},[55,289,290],{},"start \"\" \"C:\\tools\\AutoHotkey\\v2\\AutoHotkey64.exe\" \"C:\\tools\\scripts\\toggle-voice.ahk\" pane1\n",[14,292,293],{},"ボタンを押す。Claude Codeの音声入力アイコンが点灯し、マイクが拾い始める。もう一度押す。アイコンが消え、入力が確定される。手がキーボードから離れた状態で音声入力のON/OFFを切り替えられるようになった。",[18,295],{},[21,297,299],{"id":298},"ペイン間の自動切り替え-4分割画面での運用","ペイン間の自動切り替え: 4分割画面での運用",[14,301,302],{},"4分割画面で複数のClaude Codeインスタンスを動かしている場合、ペインごとにStream Deckのボタンを割り当てたい。ペイン1で音声入力中にペイン2のボタンを押したら、ペイン1が止まってペイン2が始まる――という挙動が理想だ。",[304,305,306],"h3",{"id":306},"状態ファイルをペインごとに分ける",[14,308,309],{},"状態ファイルの名前にペイン番号を含める。",[46,311,316],{"className":312,"code":314,"language":315},[313],"language-text","voice-input-pane1.lock\nvoice-input-pane2.lock\nvoice-input-pane3.lock\nvoice-input-pane4.lock\n","text",[41,317,314],{"__ignoreMap":51},[304,319,320],{"id":320},"他ペインの状態ファイルを先に消す",[14,322,323,324,326],{},"あるペインのボタンが押されたとき、まず他の全ペインの ",[41,325,243],{}," ファイルを削除する。これで他ペインで走っているインスタンスが次のループチェックで自動停止する。",[46,328,330],{"className":48,"code":329,"language":50,"meta":51,"style":51},"; 自分以外のペインの状態ファイルを全て消す\npanes := [\"pane1\", \"pane2\", \"pane3\", \"pane4\"]\nfor _, p in panes {\n    if (p != myPane) {\n        otherFile := A_Temp \"\\voice-input-\" p \".lock\"\n        if FileExist(otherFile)\n            FileDelete otherFile\n    }\n}\n",[41,331,332,337,342,347,352,357,362,367,372],{"__ignoreMap":51},[55,333,334],{"class":57,"line":58},[55,335,336],{},"; 自分以外のペインの状態ファイルを全て消す\n",[55,338,339],{"class":57,"line":64},[55,340,341],{},"panes := [\"pane1\", \"pane2\", \"pane3\", \"pane4\"]\n",[55,343,344],{"class":57,"line":107},[55,345,346],{},"for _, p in panes {\n",[55,348,349],{"class":57,"line":114},[55,350,351],{},"    if (p != myPane) {\n",[55,353,354],{"class":57,"line":120},[55,355,356],{},"        otherFile := A_Temp \"\\voice-input-\" p \".lock\"\n",[55,358,359],{"class":57,"line":125},[55,360,361],{},"        if FileExist(otherFile)\n",[55,363,364],{"class":57,"line":131},[55,365,366],{},"            FileDelete otherFile\n",[55,368,369],{"class":57,"line":137},[55,370,371],{},"    }\n",[55,373,374],{"class":57,"line":143},[55,375,152],{},[14,377,378],{},"この仕組みにより、ペイン間の切り替えがボタン1回で完結する。ペイン1のボタンを押して口述を始め、途中でペイン3に切り替えたくなったらペイン3のボタンを押すだけだ。ペイン1の音声入力は自動で止まり、ペイン3が即座に始まる。",[18,380],{},[21,382,383],{"id":383},"動作の流れまとめ",[46,385,388],{"className":386,"code":387,"language":315},[313],"[Stream Deck] ボタン押下\n    ↓\n[bat] AHKスクリプト起動（ペイン番号を引数で渡す）\n    ↓\n[AHK] 他ペインの .lock を削除（排他制御）\n    ↓\n[AHK] 自ペインの .lock が存在する？\n    ├─ Yes → .lock を削除して終了（= 停止トグル）\n    └─ No  → .lock を作成してループ開始（= 開始トグル）\n    ↓\n[AHK] ループ: 50msごとに Space down を送信\n    ↓\n[AHK] .lock が消えたらループ脱出 → Space up → 終了\n",[41,389,387],{"__ignoreMap":51},[18,391],{},[21,393,394],{"id":394},"振り返り",[14,396,397,399,400,403],{},[41,398,73],{}," を1回送って沈黙するターミナルを眺めた時間が一番長かった。物理キーの長押しとソフトウェアからのキーイベント送信は、見た目は同じでもOSの扱いが違う。キーリピートは物理キーボードのコントローラーが生成するもので、",[41,401,402],{},"SendInput"," で1回送っただけでは再現されない。",[14,405,406,407,409],{},"状態ファイルによるプロセス間通信は原始的だが、AutoHotkeyのようにプロセス間のメッセージングが面倒な環境では手堅く動く。ファイルの有無だけで状態を管理するので、デバッグも ",[41,408,243],{}," ファイルをエクスプローラーで確認するだけで済む。",[14,411,412],{},"Stream Deckに4つのペインボタンが並んでいる画面を見ると、音声入力のハードルが目に見えて下がった実感がある。キーボードに手を伸ばさずに口述を切り替えられるのは、思った以上に作業のリズムを変える。",[414,415,416],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":51,"searchDepth":64,"depth":64,"links":418},[419,420,421,422,426,427],{"id":23,"depth":64,"text":24},{"id":35,"depth":64,"text":36},{"id":82,"depth":64,"text":83},{"id":298,"depth":64,"text":299,"children":423},[424,425],{"id":306,"depth":107,"text":306},{"id":320,"depth":107,"text":320},{"id":383,"depth":64,"text":383},{"id":394,"depth":64,"text":394},"dev","Claude Codeの音声入力（スペースキー長押し）をStream Deckのボタン1回で開始/停止する仕組みをAutoHotkeyで実装。キーリピート問題の解決とペイン間自動切り替えまで","md",{},"/stream-deck-voice-input-toggle","misc-dev",false,"2026-04-07T00:00:00.000Z",{"title":5,"description":429},"2026-04/2026-04-07/stream-deck-voice-input-toggle",[439,440,441,442,443],"Stream Deck","AutoHotkey","Claude Code","音声入力","自動化","memo",null,"1sxTntMNsqGrymrhQT1ZfEANyPzGW0xQv96N2d_gpu4",[],"https://log.eurekapu.com/favicon.svg",1775602359999]