When several processes each open a GPUI window at the same time, only some of the
windows come to the front. The rest are created and shown, but they sit directly
behind whatever app is frontmost, all at the same screen position. Closing the front
window reveals the next one, so it looks as if the windows are opening one at a time.
- macOS 26.6.2 (arm64)
@gpuix/native 0.7.0
- Node 24.14.1
Repro
// node repro.js spawns four copies of itself, each opening one window
// node repro.js <title> opens a single window
import { spawn } from 'node:child_process';
import { GpuixRenderer } from '@gpuix/native';
const title = process.argv[2];
if (!title) {
for (const t of ['one', 'two', 'three', 'four']) {
spawn(process.execPath, [process.argv[1], t], { stdio: 'inherit' });
}
} else {
const r = new GpuixRenderer(() => {});
r.init({ title, width: 400, height: 300 });
r.applyBatch(
JSON.stringify([
['createElement', 1, 'div'],
['setStyle', 1, { display: 'flex', width: '100%', height: '100%', alignItems: 'center', justifyContent: 'center', fontSize: 48 }],
['createElement', 2, 'text'],
['setText', 2, title],
['appendChild', 1, 2],
['setRoot', 1]
])
);
r.commitMutations?.();
const tick = () => {
if (r.tick() !== false) setTimeout(tick, 8);
};
tick();
}
Run node repro.js from a terminal. Expected: four windows on top. Actual: between
zero and three of them are on top, the count varies from run to run, and the others
are hidden behind the terminal (or the browser, or whichever app was active). All
four report as on-screen in CGWindowListCopyWindowInfo, and all four have the same
bounds. Close the front one and the next appears. Cmd-Tab or the Dock also brings the
hidden ones forward, so they are not broken, just never ordered in front.
One run, window list front to back:
1 node three
2 node one
3 node two
4 Code (the terminal that launched them)
5 node four
Cause
After open_window, init calls cx.activate(true) (packages/native/src/renderer.rs,
around line 1001 at the 0.7.0 tag). On macOS that is
NSApplication.activateIgnoringOtherApps: (crates/gpui_macos/src/platform.rs:827), and
the window is shown with makeKeyAndOrderFront: / orderFront:
(crates/gpui_macos/src/window.rs:1214).
Since macOS 14 activation is cooperative: activateIgnoringOtherApps: is deprecated and
the "ignoring" part is ignored. A process only becomes active if the current app yields
to it or the user launched it, so sibling processes that all ask at once race for the
grant. makeKeyAndOrderFront: from a process that lost only orders the window front
among that process's own windows, which is why the losers land right behind the active
app. activateWindow() has the same limitation, so a caller cannot recover from JS.
Zed never sees this because it is a bundled app the user launches, so its activation is
always granted. Unbundled node processes opening windows are the gpuix case.
Suggested fix
When show is true and the activation request is not granted, call
orderFrontRegardless on the NSWindow, which puts it in front without the app having
to become active. Adopting the macOS 14 NSApplication.activate() /
NSRunningApplication API instead of the deprecated call would be the fuller fix.
Related but separate: WindowOptions has no x/y, so every window opens centred and
any launcher that opens several ends up with a stack even when they all make it to the
front.
When several processes each open a GPUI window at the same time, only some of the
windows come to the front. The rest are created and shown, but they sit directly
behind whatever app is frontmost, all at the same screen position. Closing the front
window reveals the next one, so it looks as if the windows are opening one at a time.
@gpuix/native0.7.0Repro
Run
node repro.jsfrom a terminal. Expected: four windows on top. Actual: betweenzero and three of them are on top, the count varies from run to run, and the others
are hidden behind the terminal (or the browser, or whichever app was active). All
four report as on-screen in
CGWindowListCopyWindowInfo, and all four have the samebounds. Close the front one and the next appears. Cmd-Tab or the Dock also brings the
hidden ones forward, so they are not broken, just never ordered in front.
One run, window list front to back:
Cause
After
open_window,initcallscx.activate(true)(packages/native/src/renderer.rs,around line 1001 at the 0.7.0 tag). On macOS that is
NSApplication.activateIgnoringOtherApps:(crates/gpui_macos/src/platform.rs:827), andthe window is shown with
makeKeyAndOrderFront:/orderFront:(
crates/gpui_macos/src/window.rs:1214).Since macOS 14 activation is cooperative:
activateIgnoringOtherApps:is deprecated andthe "ignoring" part is ignored. A process only becomes active if the current app yields
to it or the user launched it, so sibling processes that all ask at once race for the
grant.
makeKeyAndOrderFront:from a process that lost only orders the window frontamong that process's own windows, which is why the losers land right behind the active
app.
activateWindow()has the same limitation, so a caller cannot recover from JS.Zed never sees this because it is a bundled app the user launches, so its activation is
always granted. Unbundled
nodeprocesses opening windows are the gpuix case.Suggested fix
When
showis true and the activation request is not granted, callorderFrontRegardlesson theNSWindow, which puts it in front without the app havingto become active. Adopting the macOS 14
NSApplication.activate()/NSRunningApplicationAPI instead of the deprecated call would be the fuller fix.Related but separate:
WindowOptionshas nox/y, so every window opens centred andany launcher that opens several ends up with a stack even when they all make it to the
front.