Skip to content

Windows opened by sibling processes end up behind the active app (macOS) #44

Description

@khromov

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions