Closed bug report

KiCad windows grab the foreground on every open and are only bounced afterwards: the user sees every one (30 in one battery)

John Lauer · ·edited ·closed by Colby Knox
parked

Parked (kicad-lane, 2026-10-29): KiCad raises its own frames when they open. The bridge already starts KiCad with SW_SHOWNOACTIVATE. The only counter, the background guard (#114, closed into this), is off by default because it fought the user. See the latest comment for today's numbers and what a guard needs before it comes back.

What John saw (2026-09-21)

While the verb battery ran on AdomLapper, KiCad kept coming to the front of a YouTube video. John: "why do you always have to push kicad to the foreground? its really disrupting", and "make your kicad-bridge code such that kicad stays in the background and only in the worst most desperate cases, foreground it".

What the bridge's own log says

kicad_state {includeImages:false}.focusEvents after one battery:

Box Foreground grabs that were bounced
AdomLapper (John watching a video) 30
arav-rog 40
winvm (nobody at the console) 16 after a handful of verbs

Every KiCad window kind does it: PCB Editor, Symbol Editor, Footprint Editor, 3D Viewer, Schematic Editor, and the progress dialogs (Load PCB, Loading, Fill All Zones). Both new processes and second frames inside a process that is already running.

Why nobody caught it

The design is REACTIVE: spawn with SW_SHOWNOACTIVATE, then a WinEvent hook and a 120 ms sentinel bounce a window once after it reaches the foreground. The bounce lands within milliseconds, so the verb battery, which looks at the foreground window one second after each verb, reported "zero steals" on every box for weeks. The user still sees every grab: a flash over their window, keyboard focus gone for a moment, a fullscreen video interrupted.

Fixed today in the runner: it now counts every steal event in the focus log for the run and the run is RED while that count is above zero.

What does not explain it

HKCU\Control Panel\Desktop\ForegroundLockTimeout is 0 on AdomLapper, ConfRoomROG and arav-rog (Windows' focus-stealing protection switched off; something installed on long-lived Adom boxes does this) and 200000, the Windows default, on the fresh winvm. It matters, but it is not the fix: Windows lifts the lock once the user has been idle longer than the timeout, and someone watching a video produces no input. winvm grabbed 16 times with the lock on.

Options for a PREVENTIVE guard

  1. WS_EX_NOACTIVATE on bridge-owned KiCad windows while a verb is in flight, cleared the moment the verb ends or the user clicks the window. Most direct. John banned it on 0.9.181 ("NEVER set WS_EX_NOACTIVATE. It blocks the user's own click") because the old hold lasted seconds after the verb. Needs John to lift the ban for the in-flight window only.
  2. Hold the user's window on top for the length of a verb (HWND_TOPMOST without activating, removed at verb end, ledger so a crash cannot leave it stuck). KiCad may still take keyboard focus for a few milliseconds but is never drawn over the user's window. Touches a window that is not ours.
  3. A thread-scoped WH_CBT hook inside the KiCad processes the bridge spawned, refusing HCBT_ACTIVATE. Exact, but it is SetWindowsHookEx plus a DLL loaded into KiCad, and the 0.9.184 rule is "no SetWindowsHookEx, ever".
  4. A separate desktop for bridge-owned KiCad. Nothing can ever reach the user's screen, but KiCad's OpenGL canvases are unlikely to render there, and "show it to me" would need a relaunch.

Owner's recommendation: 1, scoped to the verb in flight, with the click rule clearing it at once, and 2 as the fallback if John keeps the ban.

Acceptance

A full verb battery on a box where someone is watching a fullscreen video: zero steal events in the focus log, and the video is never interrupted.

4 Replies

John Lauer ·

Two more observations from John on winvm (2026-09-21), while a verb battery was running there over a remote session.

1. His own taskbar clicks are bounced, sometimes. "When I keep clicking on your KiCad windows' taskbar icons, sometimes you let me bring up the KiCad windows but others you don't." The focus log agrees: at 02:23:01 UTC, 0.11 s after user input, steal-during-guard (bounced) on Interface_USB:CH224K — Symbol Editor, with guards:2 because a battery verb was in flight. The rule "a click on the window or the shell outranks every policy" depends on the cursor being over the window or the taskbar within 1.2 s of the click. Over a remote session on a slow box the window arrives later than that, so the click is judged stale and the user's own window is sent away. With no verb in flight the idle rule leaves him alone, which is the "sometimes".

2. Explorer died and restarted. Most likely caused by 1.0.38, which retried a refused push. The log shows the same PCB Editor window pushed four times in five seconds, and every push also calls bring_to_front on the user's previous window. When that window is the taskbar he just clicked, the bridge is forcing Explorer's taskbar to the foreground several times a second. 1.0.38 is reverted in 1.0.39 and is off winvm and arav-rog. I have not proven Explorer cannot die on 1.0.37 and earlier, which share the single-shot bring_to_front on the user's window.

What this adds to the design question

  • The restore step must never target a shell window (taskbar, Start, desktop). Restoring "the user's window" is only safe for an ordinary application window.
  • A click on a KiCad taskbar button has to win even when it lands mid-verb and the window shows up seconds later. A time window measured from the click is the wrong test on a slow or remote box; the test should be "did user input reach the shell since this window was last behind".
  • Any retry of a refused push must touch only the KiCad window, never the user's.
Colby Knox ·

kicad-lane: parking this, with today's numbers. It is labelled parked, with a note at the top of the body (the wiki has no way to edit an issue title from the CLI or the API).

Where it stands (kicad-bridge 1.0.85, KiCad 10.0.7, guard off by default since 1.0.50):

  • The bridge starts KiCad with STARTF_USESHOWWINDOW / SW_SHOWNOACTIVATE, so a new KiCad process opens without taking focus.

  • What still comes to the front are KiCad's own frames, raising themselves after they open:

    • a second editor inside a running KiCad (Symbol Editor, Footprint Editor, 3D viewer);
    • the progress dialogs.

    Windows' launch flags don't reach those; only a guard that pushes them back can.

  • Today's full test run on DESKTOP-BAR6S0F (guard off) logged 14 times KiCad came to the front. Twice it was in front of another thread's window (Altium Designer Develop).

Why parked: the only fix that worked was the guard (#114, now closed into this one). John turned it off because it fought the user: it bounced his taskbar clicks and pushed Explorer's taskbar around. A guard that comes back needs the rules in John's comment above:

  • never restore a shell window;
  • a click on a KiCad taskbar button wins even mid-verb;
  • a retry touches only the KiCad window.

Nobody has asked for it back yet. Reopen the work by un-parking this.

Colby Knox ·

kicad-lane: a stopgap for the part that hurts most: KiCad windows rising while another thread's test run drives the same machine.

The KiCad test run now refuses to overlap another bridge's test run (altium-lane's, fusion-lane's) on the same --target:

  • it finds the other run's process on the machine that launches both;
  • it waits up to --wait-minutes (30 by default), then refuses.

Tested against altium-lane's run on DESKTOP-BAR6S0F: it refused before touching anything. The window behaviour itself stays parked, as above.

Colby Knox ·

kicad-lane: closing this. An open issue should mean live work, and nobody is working on this one.

Where it stands (kicad-bridge 1.0.87):

  • The bridge starts KiCad with SW_SHOWNOACTIVATE.
  • What still comes to the front are KiCad's own frames raising themselves: a second editor inside a running KiCad, and the progress dialogs.
  • The only counter is the background guard. It is still there, opt-in: kicad_set_settings {"backgroundGuard": true}, or "Keep KiCad in the background" on the dashboard. It is off by default since 1.0.50, because it fought the user (John's taskbar clicks, Explorer's taskbar).
  • The part that hurt most, KiCad rising while another thread's test run drives the same machine, is handled: the verb test run never overlaps another bridge's test run on the same --target (PR #47).

Reopen this if the guard should be on by default again. The design notes above are the starting point:

  • never restore a shell window;
  • a taskbar click wins even when it lands mid-verb;
  • a retry touches only the KiCad window.
Colby Knox closed this ·

Log in to reply.