Add --no-activate flag: launch the Windows console without stealing focus - #56
Add --no-activate flag: launch the Windows console without stealing focus#56NoiZzz3R wants to merge 1 commit into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughChangesNo-activate launch option
Estimated code review effort: 2 (Simple) | ~10 minutes Merge Risk: ⚪ Minimal · up to The optional flag prevents launched Windows consoles from taking focus while preserving existing behavior by default. No actionable merge-blocking risk remains beyond normal checks and review. Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant ProgramMain as Program.Main
participant PwshLauncherShared
participant PwshLauncherWindows
participant CreateProcessW
ProgramMain->>PwshLauncherShared: Set SuppressWindowActivation
PwshLauncherWindows->>PwshLauncherShared: BuildWindowsShowWindow
PwshLauncherShared-->>PwshLauncherWindows: Return startup flags
PwshLauncherWindows->>CreateProcessW: Pass STARTUPINFOW
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 3 files. (1 skipped: 1 unsupported.)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
… focus The Windows launcher calls CreateProcessW with CREATE_NEW_CONSOLE and a STARTUPINFOW whose dwFlags/wShowWindow are never set. Windows then grants the new console foreground privilege, so it takes focus the moment it appears. For a short-lived console that is the right behaviour: you asked the AI to run something and you want to see it. For a long-lived shared console it is not -- the console is spawned once, but the user keeps working in other applications while the AI drives it, and the window pulls focus mid-keystroke. ForegroundLockTimeout does not help here: it only blocks explicit SetForegroundWindow calls from background processes, not the foreground privilege granted at process creation. The fix has to be in STARTUPINFOW. This adds an opt-in `--no-activate` proxy flag, mirroring the shape of `--no-profile` (yotsuda#49): off by default, so nothing changes unless the operator asks for it. When set, the console is created with STARTF_USESHOWWINDOW + SW_SHOWNOACTIVATE. The window still appears and stays fully interactive -- Read-Host, Get-Credential and the elevation prompt all keep working -- it simply does not take focus. Windows only; on macOS/Linux the terminal emulator owns activation, so the flag is a no-op there. The flag mapping is extracted into PwshLauncherShared.BuildWindowsShowWindow so it can be unit-tested without spawning a console. Because wShowWindow is ignored unless STARTF_USESHOWWINDOW is present, the default path leaves both fields zeroed and is bit-for-bit the previous behaviour. Verified on Windows 11 / PowerShell 7.5.5 with a harness calling the real PwshLauncherWindows.LaunchPwsh twice: noActivate=false -> foreground changed (steals focus, as before) noActivate=true -> foreground unchanged (does not steal focus) dotnet build: 0 errors. Unit tests: 406 passed. One pre-existing failure in PipelineHelperTests.FormatBusyStatus_NullStatusLine_BuildsFromParameters, which reproduces on an unmodified checkout of main and is unrelated to this change.
|
Thanks — nicely scoped, and the The focus behaviour does not reproduce here, though. The flag was in effect: pwsh 25624's parent is 8236, whose command line is It looks like console handoff. My default terminal is "Let Windows decide" ( Which default terminal was active when your harness printed |
Summary
The Windows launcher calls
CreateProcessWwithCREATE_NEW_CONSOLEand aSTARTUPINFOWwhosedwFlags/wShowWindoware never set. Windows then grants the new console foreground privilege, so it takes focus the moment it appears.For a short-lived console that is the right behaviour — you asked the AI to run something and you want to see it. For a long-lived shared console it is not: the console is spawned once, but the user keeps working in other applications while the AI drives it, and the window pulls focus mid-keystroke.
This adds an opt-in
--no-activateproxy flag that creates the console withSTARTF_USESHOWWINDOW+SW_SHOWNOACTIVATE.Why not ForegroundLockTimeout
Worth recording, because it looks like the obvious fix and is not. I measured it first:
HKCU\Control Panel\DesktopSPI_GETFOREGROUNDLOCKTIMEOUT02147483647The registry value was stale; the lock was already at maximum. It still did not help — that lock only blocks explicit
SetForegroundWindowcalls from background processes, not the foreground privilege granted at process creation. The fix has to be inSTARTUPINFOW.Switching the default terminal from Windows Terminal to conhost does not help either. It does give the console its own top-level window instead of a tab (
pwsh.MainWindowHandlegoes from0to a real HWND, which is a nice side effect), but the newly created window still takes focus.Design
Deliberately shaped after
--no-profile(#49): off by default, so nothing changes for anyone who does not ask for it. I read your reasoning there — that these are real human-facing shells and defaults should respect that — and it applies here too. A console that appears in front is usually what you want; this is for the case where it is not.The window still appears and stays fully visible and interactive.
Read-Host,Get-Credentialand the elevation confirmation prompt all keep working —SW_SHOWNOACTIVATEonly withholds activation, it does not hide anything. That mattered to me specifically because of the elevation interceptor: hiding the window would deadlock it, whereas not activating the window does not.Windows only. On macOS/Linux the terminal emulator owns activation, so the flag is a no-op there — same asymmetry
--no-profilealready has between its interactive and headless paths.The flag mapping is extracted into
PwshLauncherShared.BuildWindowsShowWindowso it is unit-testable without spawning a console. SincewShowWindowis ignored unlessSTARTF_USESHOWWINDOWis present, the default path leaves both fields zeroed and is bit-for-bit the previous behaviour.Validation
E2E harness calling the real
PwshLauncherWindows.LaunchPwshtwice, measuringGetForegroundWindow()before and after each spawn:dotnet build PowerShell.MCP.Proxy -c Release— 0 errorsdotnet test Tests/PowerShell.MCP.Tests.csproj -c Release -f net9.0— 406 passed, 1 failedThe one failure is
PipelineHelperTests.FormatBusyStatus_NullStatusLine_BuildsFromParameters. It reproduces on an unmodified checkout ofmain(verified viagit stash), so it is pre-existing and unrelated to this change.Environment: Windows 11 26100, PowerShell 7.5.5, .NET SDK 9.0.317.
Usage
README updated with a
Background consolessection alongside the existing--no-profiledocs.Summary by CodeRabbit
New Features
--no-activatesetting to prevent the Windows PowerShell console from taking focus when launched.Documentation
Tests