feat(serve): keep macOS serve out of the Dock and opt in to the GPU on Linux (#33878)

* feat(serve): keep macOS serve out of the Dock and opt in to the GPU on Linux

On macOS, orca serve now runs as an accessory app with no Dock icon or
Cmd-Tab entry, and gets them back only while it hosts a desktop window.
On Linux, ORCA_SERVE_GPU=1 keeps hardware acceleration on for hosts that
expose a GPU to the server.

* fix(serve): restore the Dock only for real launches, after window services resolve

Gate the restore on any background or E2E launch, since dock.show() activates
the app, and run it just before the window is created so a services failure
cannot leave a windowless Dock tile. Say in the docs what the GPU opt-in can't
do on Orca's own Xvfb.
This commit is contained in:
Neil
2026-10-26 02:53:57 -07:00
committed by GitHub
parent 23c25ab55c
commit 33ddab70b7
7 changed files with 123 additions and 13 deletions
+12
View File
@@ -151,6 +151,7 @@ orca-ide serve --pairing-address 100.64.1.20
The command:
- starts the Orca runtime without opening the desktop window;
- on macOS, stays out of the Dock and the Cmd-Tab switcher until you open a desktop window from it;
- runs in the foreground until you press `Ctrl-C`;
- prints the bound endpoint and a runtime pairing URL;
- uses `--pairing-address` only for the address clients should dial.
@@ -186,6 +187,17 @@ orca-ide serve --pairing-address 100.64.1.20
- Orca reads them only when its terminal host process starts. That process keeps running when `orca serve` restarts, so terminals keep their old limits until it restarts too, for example after the server reboots.
- They have no effect on macOS or Windows.
### Use the GPU on a Linux server
On Linux, `orca serve` turns off hardware acceleration by default, because most headless hosts have no GPU and a failing GPU process can stop the server from starting. Browser panes then render in software. If the host gives the Orca process a GPU (for example a render node such as `/dev/dri/renderD128`), set `ORCA_SERVE_GPU=1` in the environment that starts `orca serve` (or in its service unit) to keep hardware acceleration on:
```bash
export ORCA_SERVE_GPU=1
orca-ide serve --pairing-address 100.64.1.20
```
This only stops Orca from turning the GPU off. Chromium may still render in software on Orca's own Xvfb, which has no GPU of its own; open `chrome://gpu` in a browser pane to check, and point `DISPLAY` at a GPU-backed X server if it reports software rendering. If you installed the server with `orca serve service install`, add the variable to the service unit yourself. If the server stops starting or browser panes crash with it set, unset it to go back to software rendering.
### Mobile from a headless server
For the Orca mobile app, request a mobile-scoped QR code and link:
@@ -124,6 +124,23 @@ describe('ensureVirtualDisplayForHeadlessServe', () => {
expect(appMock.commandLine.appendSwitch).toHaveBeenCalledWith('disable-gpu')
})
it('keeps hardware acceleration when ORCA_SERVE_GPU=1 opts in', async () => {
setPlatform('linux')
process.env.DISPLAY = ':0'
vi.stubEnv('ORCA_SERVE_GPU', '1')
mockLiveXDisplay()
const { ensureVirtualDisplayForHeadlessServe } = await import('./ensure-virtual-display')
try {
expect(ensureVirtualDisplayForHeadlessServe({ isServeMode: true })).toBe(true)
} finally {
vi.unstubAllEnvs()
}
expect(appMock.disableHardwareAcceleration).not.toHaveBeenCalled()
expect(appMock.commandLine.appendSwitch).toHaveBeenCalledWith('disable-dev-shm-usage')
expect(appMock.commandLine.appendSwitch).not.toHaveBeenCalledWith('disable-gpu')
})
it('reports unsupported when Xvfb cannot be launched', async () => {
setPlatform('linux')
statSyncMock.mockImplementation(() => {
@@ -27,6 +27,10 @@ let xvfbProcess: ChildProcess | null = null
function configureHeadlessServeChromiumFlags(): void {
// Small sandbox /dev/shm mounts can exhaust and crash Chromium utility services; use disk-backed temp.
app.commandLine.appendSwitch('disable-dev-shm-usage')
// Opt-in for hosts that expose a GPU to this process (#22450).
if (process.env.ORCA_SERVE_GPU === '1') {
return
}
// External displays may also be Xvfb; GPU forks can trap before serve readiness.
app.disableHardwareAcceleration()
app.commandLine.appendSwitch('disable-gpu')
@@ -60,8 +60,11 @@ import { reportProfileStateSaveDelay } from './profile-state-save-delay'
export async function initializeReadyFoundation(): Promise<void> {
logStartupMilestone('app-ready')
// Background automation must not claim the macOS Dock/menu bar.
applyBackgroundActivationPolicy({ warn: console.warn })
// Background automation and a windowless serve must not claim the macOS Dock/menu bar.
applyBackgroundActivationPolicy({
serveWithoutDesktopWindow: state.isServeMode,
warn: console.warn
})
installElectronProxyRequestGuard(session.defaultSession)
app.on('login', (event, webContents, details, authInfo, callback) => {
handleElectronProxyLogin(
@@ -40,6 +40,10 @@ import { requireMainWindowServices } from './main-window-service-readiness'
import { recordRendererLaunchFailureProbe } from '../window/renderer-launch-failure-probe'
import { isResourceWindowVisible } from '../window/resource-view-admission'
import {
applyBackgroundActivationPolicy,
applyServeDesktopWindowActivationPolicy
} from '../window/foreground-activation-policy'
const TRAY_CREATE_FALLBACK_MS = 12_000
const AGENT_STATE_CRASH_BREADCRUMB_MIN_INTERVAL_MS = 30_000
@@ -87,6 +91,9 @@ export function openMainWindow(options: { revealOnDidFinishLoad?: boolean } = {}
noteWindowsInstallDirAclProbePending()
}
}
if (state.isServeMode) {
applyServeDesktopWindowActivationPolicy({ warn: console.warn })
}
const window = createMainWindow(store, {
getIsQuitting: () => state.isQuitting,
onQuitAborted: () => {
@@ -220,6 +227,9 @@ export function openMainWindow(options: { revealOnDidFinishLoad?: boolean } = {}
window.on('closed', () => {
if (state.mainWindow === window) {
state.mainWindow = null
if (state.isServeMode) {
applyBackgroundActivationPolicy({ serveWithoutDesktopWindow: true, warn: console.warn })
}
}
clearExpectedRendererReload(rendererWebContentsId)
state.automations?.setWebContents(null)
@@ -5,6 +5,7 @@ vi.mock('electron', () => ({ app: { dock: { hide: vi.fn() }, setActivationPolicy
const {
applyBackgroundActivationPolicy,
applyServeDesktopWindowActivationPolicy,
isBackgroundLaunch,
isOrcadSidecarLaunch,
isWindowlessLaunch,
@@ -104,7 +105,7 @@ describe('showWindowWithoutStealingFocus', () => {
describe('applyBackgroundActivationPolicy', () => {
function makeApp() {
return {
dock: { hide: vi.fn(() => {}) },
dock: { hide: vi.fn(() => {}), show: vi.fn(async () => {}) },
setActivationPolicy: vi.fn((_policy: 'accessory' | 'prohibited' | 'regular') => {})
}
}
@@ -139,6 +140,42 @@ describe('applyBackgroundActivationPolicy', () => {
expect(user.setActivationPolicy).not.toHaveBeenCalled()
})
it('keeps a windowless macOS serve out of the Dock until it hosts a desktop window', () => {
const app = makeApp()
expect(
applyBackgroundActivationPolicy({
app,
env: {},
platform: 'darwin',
serveWithoutDesktopWindow: true
})
).toBe(true)
expect(app.setActivationPolicy).toHaveBeenLastCalledWith('accessory')
expect(applyServeDesktopWindowActivationPolicy({ app, env: {}, platform: 'darwin' })).toBe(true)
expect(app.setActivationPolicy).toHaveBeenLastCalledWith('regular')
expect(app.dock.show).toHaveBeenCalledOnce()
})
it('never gives a background serve its Dock tile back', () => {
const app = makeApp()
expect(
applyServeDesktopWindowActivationPolicy({
app,
env: { ORCA_BACKGROUND_LAUNCH: '1' },
platform: 'darwin'
})
).toBe(false)
expect(
applyServeDesktopWindowActivationPolicy({
app,
env: { ORCA_E2E_HEADFUL: '1' },
platform: 'darwin'
})
).toBe(false)
expect(app.setActivationPolicy).not.toHaveBeenCalled()
})
it('is a no-op off macOS', () => {
const app = makeApp()
expect(
+37 -10
View File
@@ -3,7 +3,7 @@ import { app as electronApp, type BrowserWindow } from 'electron'
/** Automation must not steal OS foreground. Background/sidecar requests override E2E_FOREGROUND; the hosted-Xvfb benchmark may present explicitly after startup (tests/AGENTS.md), while automatic reveals stay suppressed. */
type ActivationPolicyApp = {
dock?: { hide: () => void }
dock?: { hide: () => void; show?: () => Promise<void> }
setActivationPolicy: (policy: 'accessory' | 'prohibited' | 'regular') => void
}
@@ -37,17 +37,22 @@ export function isWindowlessLaunch(env: PolicyEnv = process.env): boolean {
)
}
type ActivationPolicyOptions = {
app?: ActivationPolicyApp
env?: PolicyEnv
platform?: NodeJS.Platform
/** `orca serve` with no desktop window open: a server, not an app to switch to or quit (#16061). */
serveWithoutDesktopWindow?: boolean
warn?: (message: string, error: unknown) => void
}
/** macOS accessory policy hides Dock/menu-bar claims during windowless launch/exit while retaining programmatic activation. */
export function applyBackgroundActivationPolicy(
options: {
app?: ActivationPolicyApp
env?: PolicyEnv
platform?: NodeJS.Platform
warn?: (message: string, error: unknown) => void
} = {}
): boolean {
export function applyBackgroundActivationPolicy(options: ActivationPolicyOptions = {}): boolean {
const platform = options.platform ?? process.platform
if (platform !== 'darwin' || !isWindowlessLaunch(options.env ?? process.env)) {
if (
platform !== 'darwin' ||
!(options.serveWithoutDesktopWindow || isWindowlessLaunch(options.env ?? process.env))
) {
return false
}
try {
@@ -61,6 +66,28 @@ export function applyBackgroundActivationPolicy(
}
}
/** Gives a serve process its Dock tile and menu bar back while it hosts a desktop window. */
export function applyServeDesktopWindowActivationPolicy(
options: Omit<ActivationPolicyOptions, 'serveWithoutDesktopWindow'> = {}
): boolean {
const platform = options.platform ?? process.platform
// dock.show() activates the app, so automated launches keep the accessory policy.
if (platform !== 'darwin' || isBackgroundLaunch(options.env ?? process.env)) {
return false
}
try {
const app = options.app ?? electronApp
app.setActivationPolicy('regular')
void app.dock?.show?.().catch((error: unknown) => {
options.warn?.('[window] Failed to show the Dock icon', error)
})
return true
} catch (error) {
options.warn?.('[window] Failed to apply foreground activation policy', error)
return false
}
}
/**
* Reveal a window without taking the foreground: hidden entirely when windowless,
* `showInactive()` for explicitly headful E2E runs.