開発claude-code-tools
Windows Terminal 自動フォーカス実装ガイド
概要
Claude Code の notification フックが発火したとき、Windows Terminal を自動的に最前面に持ってきて、該当ペインにフォーカスを当てる仕組みを実装する。
ゴール
- 別アプリ(Chrome等)を操作中でも、通知が来たらWindows Terminalが自動で最前面に来る
- 4分割ペインのうち、通知を出したペインに自動フォーカスが当たる
- あとはEnterを押すだけで操作を続行できる
前提環境
- Windows Terminal(4分割ペイン: 左上・右上・左下・右下)
- Claude Code セッションを各ペインで非同期実行
- 既存の音声通知フックが動作済み
- PowerShell 7.5+
アーキテクチャ
Claude Code (notification hook)
├── 既存: 音声通知スクリプト (そのまま)
└── 新規: auto-focus.ps1
├── 環境変数 $env:PANE_POS で自分の位置を判定
├── Win32 API (SetForegroundWindow) でターミナルを最前面化
└── SendKeys (Alt+矢印) で該当ペインにフォーカス移動
実装手順
Step 1: 自動フォーカススクリプトの作成
以下のファイルを作成する。
ファイルパス: ~/.claude/scripts/auto-focus.ps1
# auto-focus.ps1
# Claude Code notification フックから呼び出される
# Windows Terminal を最前面にし、該当ペインにフォーカスを当てる
# --- Win32 API 定義 ---
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class Win32Focus {
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);
[DllImport("user32.dll")]
public static extern bool IsIconic(IntPtr hWnd);
}
"@
# --- 定数 ---
$SW_RESTORE = 9
$KEYEVENTF_KEYUP = 0x0002
$VK_MENU = 0x12 # Alt キー
# --- Windows Terminal のウィンドウハンドルを取得 ---
$wtProcess = Get-Process -Name "WindowsTerminal" -ErrorAction SilentlyContinue |
Select-Object -First 1
if (-not $wtProcess) {
Write-Error "Windows Terminal が見つかりません"
exit 1
}
$hwnd = $wtProcess.MainWindowHandle
if ($hwnd -eq [IntPtr]::Zero) {
Write-Error "Windows Terminal のウィンドウハンドルを取得できません"
exit 1
}
# --- 最小化されていたら復元 ---
if ([Win32Focus]::IsIconic($hwnd)) {
[Win32Focus]::ShowWindow($hwnd, $SW_RESTORE)
}
# --- フォアグラウンドロック回避 ---
# バックグラウンドプロセスから SetForegroundWindow を呼ぶには
# Alt キーをシミュレートしてロックを解除する必要がある
[Win32Focus]::keybd_event($VK_MENU, 0, 0, [UIntPtr]::Zero) # Alt down
[Win32Focus]::keybd_event($VK_MENU, 0, $KEYEVENTF_KEYUP, [UIntPtr]::Zero) # Alt up
# --- Windows Terminal を最前面に ---
[Win32Focus]::SetForegroundWindow($hwnd)
# 少し待ってフォーカスが安定するのを待つ
Start-Sleep -Milliseconds 200
# --- ペインフォーカス移動 ---
# 環境変数 PANE_POS に基づいて対象ペインにフォーカスを移す
# 戦略: まず確実に左上に移動(←↑連打)してから目的のペインへ移動
$panePos = $env:PANE_POS
if (-not $panePos) {
# PANE_POS 未設定の場合はウィンドウ最前面化のみで終了
Write-Host "PANE_POS が未設定のため、ウィンドウ最前面化のみ実行しました"
exit 0
}
# WScript.Shell の SendKeys を使って Alt+矢印 を送信
$wsh = New-Object -ComObject WScript.Shell
# まず左上にリセット(最大2回ずつ送れば確実に左上に到達する)
Start-Sleep -Milliseconds 100
$wsh.SendKeys("%{LEFT}") # Alt+Left
Start-Sleep -Milliseconds 50
$wsh.SendKeys("%{LEFT}") # Alt+Left
Start-Sleep -Milliseconds 50
$wsh.SendKeys("%{UP}") # Alt+Up
Start-Sleep -Milliseconds 50
$wsh.SendKeys("%{UP}") # Alt+Up
Start-Sleep -Milliseconds 50
# 目的のペインに移動
switch ($panePos) {
"top-left" {
# 既に左上にいるので何もしない
}
"top-right" {
$wsh.SendKeys("%{RIGHT}") # Alt+Right
}
"bottom-left" {
$wsh.SendKeys("%{DOWN}") # Alt+Down
}
"bottom-right" {
$wsh.SendKeys("%{RIGHT}") # Alt+Right
Start-Sleep -Milliseconds 50
$wsh.SendKeys("%{DOWN}") # Alt+Down
}
default {
Write-Warning "不明な PANE_POS: $panePos (top-left / top-right / bottom-left / bottom-right を指定してください)"
}
}
Write-Host "フォーカス移動完了: $panePos"
Step 2: notification フックの設定
.claude/hooks.json (グローバル設定)に以下を追加する。既存の音声通知フックとは別エントリで追加すること。
既存の hooks.json に追記するパターン:
{
"hooks": {
"notification": [
// ... 既存の音声通知フックはそのまま残す ...
{
"command": "pwsh -NoProfile -File \"$HOME/.claude/scripts/auto-focus.ps1\"",
"description": "通知時にWindows Terminalを最前面にし、該当ペインにフォーカスを当てる"
}
]
}
}
注意:
hooks.jsonの正確な書式は Claude Code のバージョンに依存する。/hooksコマンドで既存の設定を確認し、それに合わせて追記すること。
Step 3: 各ペインで環境変数を設定
各ペインの起動時に、ペイン位置を示す環境変数をセットする。
方法A: 手動設定(シンプル)
各ペインで Claude Code を起動する前に以下を実行:
# 左上ペイン
$env:PANE_POS = "top-left"
# 右上ペイン
$env:PANE_POS = "top-right"
# 左下ペイン
$env:PANE_POS = "bottom-left"
# 右下ペイン
$env:PANE_POS = "bottom-right"
方法B: 起動スクリプトで自動化
以下のスクリプトで4ペインを一括起動し、環境変数も自動セットする。
ファイルパス: ~/.claude/scripts/launch-4pane.ps1
# launch-4pane.ps1
# Windows Terminal で4分割ペインを立ち上げ、各ペインに PANE_POS を設定する
#
# 使い方: pwsh -File ~/.claude/scripts/launch-4pane.ps1
#
# 必要に応じてプロジェクトパスを変更すること
param(
[string]$Project1 = ".",
[string]$Project2 = ".",
[string]$Project3 = ".",
[string]$Project4 = "."
)
# Windows Terminal の `wt` コマンドで4ペイン起動
# セミコロン区切りで split-pane を連結する
wt new-tab --title "CC:top-left" `
pwsh -NoExit -Command "`$env:PANE_POS='top-left'; cd '$Project1'" `; `
split-pane --horizontal --title "CC:bottom-left" `
pwsh -NoExit -Command "`$env:PANE_POS='bottom-left'; cd '$Project3'" `; `
move-focus up `; `
split-pane --vertical --title "CC:top-right" `
pwsh -NoExit -Command "`$env:PANE_POS='top-right'; cd '$Project2'" `; `
move-focus down `; `
split-pane --vertical --title "CC:bottom-right" `
pwsh -NoExit -Command "`$env:PANE_POS='bottom-right'; cd '$Project4'"
Write-Host "4ペイン起動完了。各ペインで claude を起動してください。"
使用例:
# 4つのプロジェクトを指定して起動
pwsh -File ~/.claude/scripts/launch-4pane.ps1 `
-Project1 "C:\Users\numbe\Git_repo\mdx-playground" `
-Project2 "C:\Users\numbe\Git_repo\chrome-extension-MF" `
-Project3 "C:\Users\numbe\Git_repo\project3" `
-Project4 "C:\Users\numbe\Git_repo\project4"
動作確認手順
1. スクリプト単体テスト
# 左上ペインにいると仮定してテスト
$env:PANE_POS = "top-left"
# Chrome など別ウィンドウにフォーカスを移してから実行
Start-Sleep -Seconds 3 # 3秒以内に別ウィンドウをクリック
pwsh -NoProfile -File "$HOME/.claude/scripts/auto-focus.ps1"
# → Windows Terminal が最前面に来て左上ペインにフォーカスが当たれば成功
2. フック経由テスト
# Claude Code セッション内で長時間かかるタスクを依頼し、
# 別ウィンドウに切り替えて待機する
# → タスク完了時に音声通知と同時にターミナルが最前面に来れば成功
トラブルシューティング
Windows Terminal が最前面に来ない
原因: Windows のフォアグラウンドロック制約。Alt キーシミュレートが効いていない可能性。
対策:
- スクリプト内の
Start-Sleepの値を増やす(200ms → 500ms) - PowerShell を管理者権限で実行してみる
- それでもダメな場合は
FlashWindowEx(タスクバー点滅)にフォールバックする:
# auto-focus.ps1 の SetForegroundWindow 後に追加
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class FlashHelper {
[StructLayout(LayoutKind.Sequential)]
public struct FLASHWINFO {
public uint cbSize;
public IntPtr hwnd;
public uint dwFlags;
public uint uCount;
public uint dwTimeout;
}
[DllImport("user32.dll")]
public static extern bool FlashWindowEx(ref FLASHWINFO pwfi);
}
"@
$flash = New-Object FlashHelper+FLASHWINFO
$flash.cbSize = [System.Runtime.InteropServices.Marshal]::SizeOf($flash)
$flash.hwnd = $hwnd
$flash.dwFlags = 0x0003 # FLASHW_ALL (タスクバー + タイトルバー)
$flash.uCount = 5
$flash.dwTimeout = 0
[FlashHelper]::FlashWindowEx([ref]$flash)
ペインフォーカスが正しく移動しない
原因: Windows Terminal のペインレイアウトがスクリプトの想定と異なる。
対策:
Alt+矢印のデフォルトキーバインドが変更されていないか確認- Windows Terminal の
settings.jsonで以下が設定されているか確認:
{
"actions": [
{ "command": { "action": "moveFocus", "direction": "left" }, "keys": "alt+left" },
{ "command": { "action": "moveFocus", "direction": "right" }, "keys": "alt+right" },
{ "command": { "action": "moveFocus", "direction": "up" }, "keys": "alt+up" },
{ "command": { "action": "moveFocus", "direction": "down" }, "keys": "alt+down" }
]
}
PANE_POS が引き継がれない
原因: Claude Code がサブシェルで起動し、環境変数が引き継がれていない。
対策:
claudeコマンド起動前に同じシェルで$env:PANE_POSをセットしているか確認- 起動スクリプト(方法B)を使う場合は
-NoExitフラグが付いているか確認
ファイル構成
~/.claude/
├── hooks.json # フック設定(既存に追記)
└── scripts/
├── auto-focus.ps1 # 自動フォーカススクリプト(新規)
└── launch-4pane.ps1 # 4ペイン一括起動スクリプト(任意)
補足: 今後の拡張案
- Stream Deck 連携: 各ボタンに
Alt+矢印キーストロークを割り当て、手動でのペイン切り替えも高速化 - トースト通知との併用:
BurntToastPowerShell モジュールを使い、どのプロジェクトで完了したかをWindowsトースト通知で表示する(自動フォーカスが不要な場面用) - 完了ペインの視覚的ハイライト: Windows Terminal の
SetTabColor等で、完了したペインのタブ色を変更する