diff --git a/claude-notify.md b/claude-notify.md index d86b517..98ab51b 100644 --- a/claude-notify.md +++ b/claude-notify.md @@ -18,8 +18,10 @@ the terminal is in the background. 2. The hook runs `~/bin/claude-notify`, a bash script that calls Windows PowerShell directly from WSL via `/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe`. 3. PowerShell creates a `NotifyIcon` (system tray icon) and shows a balloon tip for 5 s. -4. The script sleeps 6 s to keep the PowerShell process alive long enough for the - balloon to display, then disposes the icon and exits. +4. A WinForms message loop keeps the process alive until the balloon is dismissed or clicked. +5. **Clicking the balloon** restores the Windows Terminal window (if minimised) and brings + it to the foreground via `ShowWindow` + `SetForegroundWindow`. +6. On dismiss or click, the message loop exits, the icon is disposed, and the process ends. --- @@ -45,12 +47,17 @@ public class Win32 { public static extern IntPtr GetForegroundWindow(); [DllImport(\"user32.dll\")] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); + [DllImport(\"user32.dll\")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool SetForegroundWindow(IntPtr hWnd); + [DllImport(\"user32.dll\")] + public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); } '@ \$hwnd = [Win32]::GetForegroundWindow() -\$pid = 0 -[Win32]::GetWindowThreadProcessId(\$hwnd, [ref]\$pid) | Out-Null -\$proc = Get-Process -Id \$pid -ErrorAction SilentlyContinue +\$winPid = 0 +[Win32]::GetWindowThreadProcessId(\$hwnd, [ref]\$winPid) | Out-Null +\$proc = Get-Process -Id \$winPid -ErrorAction SilentlyContinue if (\$proc -and \$proc.Name -eq 'WindowsTerminal') { exit 0 } Add-Type -AssemblyName System.Windows.Forms @@ -60,8 +67,21 @@ Add-Type -AssemblyName System.Drawing \$notification.BalloonTipTitle = '$title' \$notification.BalloonTipText = '$message' \$notification.Visible = \$true + +\$notification.add_BalloonTipClicked({ + \$wt = Get-Process -Name 'WindowsTerminal' -ErrorAction SilentlyContinue | Select-Object -First 1 + if (\$wt -and \$wt.MainWindowHandle -ne [IntPtr]::Zero) { + [Win32]::ShowWindow(\$wt.MainWindowHandle, 9) | Out-Null + [Win32]::SetForegroundWindow(\$wt.MainWindowHandle) | Out-Null + } + [System.Windows.Forms.Application]::Exit() +}) +\$notification.add_BalloonTipClosed({ + [System.Windows.Forms.Application]::Exit() +}) + \$notification.ShowBalloonTip(5000) -Start-Sleep -Seconds 6 +[System.Windows.Forms.Application]::Run() \$notification.Dispose() " ``` @@ -109,7 +129,8 @@ Restart Claude Code for the hook to take effect. When Claude Code hits a permission prompt and is waiting for your approval, a Windows balloon tip appears in the system tray with the title **Claude Code** and the message -**Needs your input!** +**Needs your input!** Clicking the balloon restores and focuses Windows Terminal so you +can approve or deny immediately without manually switching windows. --- @@ -126,5 +147,12 @@ balloon tip appears in the system tray with the title **Claude Code** and the me - Ensure the command in `settings.json` is wrapped as `bash -c '... &'`. **Balloon tip flashes and disappears instantly** -- The `Start-Sleep -Seconds 6` in the script keeps the PowerShell process alive so the - notification stays visible. If you removed or reduced it, restore it to at least 6 s. +- The WinForms message loop (`Application.Run()`) keeps the PowerShell process alive until + the balloon is clicked or times out. If the process exits immediately, check that both + `add_BalloonTipClicked` and `add_BalloonTipClosed` handlers call `Application.Exit()`. + +**Click does not focus Windows Terminal** +- Windows restricts `SetForegroundWindow` to prevent background processes from stealing focus. + The balloon-click event fires in the context of the notification click, which satisfies the + restriction in most cases. If it still doesn't work, try clicking the taskbar button instead. +- Make sure Windows Terminal is running (not just WSL in another host).