Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 55 additions & 2 deletions src/desktop-electron/app.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,78 @@
import { app } from "electron";
import path from "node:path";
import { registerElectronDesktopBridge } from "./main";
import { resolveDesktopStartUrl } from "./startup";
import { findRepoRoot } from "../installer-core/repo";

export interface DesktopStartupWindow {
show(): void;
focus(): void;
}

export interface DesktopStartupApp {
dock?: {
show(): void;
};
focus(options?: { steal?: boolean }): void;
}

export function focusDesktopWindow(window: DesktopStartupWindow, desktopApp: DesktopStartupApp = app): void {
try {
desktopApp.dock?.show();
} catch {
// Ignore dock activation failures on non-macOS runtimes.
}

window.show();
window.focus();

try {
desktopApp.focus({ steal: true });
} catch {
desktopApp.focus();
}
}

export function shouldAutoStartDesktopApp(
argv: string[] = process.argv,
filename: string = __filename,
electronRuntime: string | undefined = process.versions?.electron,
): boolean {
if (!electronRuntime) {
return false;
}

const entryArg = argv[1];
if (!entryArg) {
return false;
}

return path.resolve(entryArg) === path.resolve(filename);
}

export async function startElectronDesktopApp(): Promise<void> {
await app.whenReady();
const repoRoot = findRepoRoot(__dirname);
const startUrl = resolveDesktopStartUrl(repoRoot, process.env);
const desktopBridge = await registerElectronDesktopBridge({
repoRoot,
startUrl,
});

await desktopBridge.createWindow();
const window = await desktopBridge.createWindow();
focusDesktopWindow(window);

app.on("window-all-closed", async () => {
await desktopBridge.dispose();
app.quit();
});

app.on("activate", () => {
focusDesktopWindow(window);
});
}

if (require.main === module) {
if (shouldAutoStartDesktopApp()) {
void startElectronDesktopApp().catch((error) => {
console.error("[desktop] Electron startup failed.", error);
app.quit();
Expand Down
6 changes: 6 additions & 0 deletions src/types/electron.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ declare module "electron" {
export class BrowserWindow {
constructor(options?: BrowserWindowConstructorOptions);
loadURL(url: string): Promise<void>;
show(): void;
focus(): void;
webContents: WebContents;
}

Expand All @@ -52,6 +54,10 @@ declare module "electron" {
on(event: "window-all-closed" | "activate", listener: () => void): void;
getVersion(): string;
isPackaged: boolean;
focus(options?: { steal?: boolean }): void;
dock?: {
show(): void;
};
quit(): void;
};

Expand Down
35 changes: 35 additions & 0 deletions tests/installer/desktop-preview-startup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { installDesktopLoadDiagnostics, resolveDesktopStartUrl } from "../../src/desktop-electron/startup";
import { focusDesktopWindow, shouldAutoStartDesktopApp } from "../../src/desktop-electron/app";

interface FakeWebContents {
on(
Expand Down Expand Up @@ -84,3 +85,37 @@ test("desktop load diagnostics present a readable fallback when renderer load fa
assert.match(decoded, /file:\/\/\/broken\/index\.html/);
assert.match(errors.join("\n"), /ERR_FILE_NOT_FOUND/);
});

test("desktop startup explicitly surfaces the first Electron window", () => {
const calls: string[] = [];
const fakeWindow = {
show() {
calls.push("window.show");
},
focus() {
calls.push("window.focus");
},
};
const fakeApp = {
dock: {
show() {
calls.push("dock.show");
},
},
focus(options?: { steal?: boolean }) {
calls.push(`app.focus:${options?.steal === true ? "steal" : "default"}`);
},
};

focusDesktopWindow(fakeWindow, fakeApp);

assert.deepEqual(calls, ["dock.show", "window.show", "window.focus", "app.focus:steal"]);
});

test("desktop app entrypoint auto-starts only for Electron script execution", () => {
const filename = "/workspace/dist/src/desktop-electron/app.js";

assert.equal(shouldAutoStartDesktopApp(["/electron", filename], filename, "35.1.4"), true);
assert.equal(shouldAutoStartDesktopApp(["/node", "/workspace/test-runner.js"], filename, undefined), false);
assert.equal(shouldAutoStartDesktopApp(["/electron"], filename, "35.1.4"), false);
});
Loading