| 1 | import { lstatSync } from "node:fs"; |
| 2 | import { win32 } from "node:path"; |
| 3 | import type { AppDetailsOptions, BrowserWindow } from "electron"; |
| 4 | |
| 5 | // Share the launcher's identity without grouping with old or current Studio. |
| 6 | // internal/appidentity/electron_identity_test.go guards the Go/JS contract. |
| 7 | |
| 8 | export const APP_USER_MODEL_ID = "io.reasonix.desktop"; |
| 9 | |
| 10 | // Applied at module load: the ID must be in place before the first window |
| 11 | // exists, and Windows ignores the call on other platforms. |
| 12 | export function applyAppUserModelId(target: Pick<typeof import("electron").app, "setAppUserModelId">, platform: NodeJS.Platform): void { |
| 13 | if (platform === "win32") target.setAppUserModelId(APP_USER_MODEL_ID); |
| 14 | } |
| 15 | |
| 16 | interface WindowCreationEvents { |
| 17 | on(event: "browser-window-created", listener: (event: unknown, window: Pick<BrowserWindow, "setAppDetails">) => void): unknown; |
| 18 | } |
| 19 | |
| 20 | function isRegularFile(path: string): boolean { |
| 21 | try { return lstatSync(path).isFile(); } catch { return false; } |
| 22 | } |
| 23 | |
| 24 | export function windowsTaskbarDetails(executable: string, isFile = isRegularFile): AppDetailsOptions | undefined { |
| 25 | if (!win32.isAbsolute(executable)) return undefined; |
| 26 | const appDir = win32.dirname(executable); |
| 27 | if (win32.basename(appDir).toLowerCase() !== "app") return undefined; |
| 28 | const releaseDir = win32.dirname(appDir); |
| 29 | const versioned = win32.basename(win32.dirname(releaseDir)).toLowerCase() === "versions" |
| 30 | && /^v[0-9]+(?:\.[0-9]+){1,3}(?:-[0-9A-Za-z.-]+)?$/.test(win32.basename(releaseDir)); |
| 31 | const root = versioned ? win32.dirname(win32.dirname(releaseDir)) : releaseDir; |
| 32 | const launcher = ["Reasonix.exe", "reasonix-launcher.exe"].map(name => win32.join(root, name)).find(isFile); |
| 33 | if (!launcher) return undefined; |
| 34 | return { |
| 35 | appId: APP_USER_MODEL_ID, |
| 36 | appIconPath: launcher, |
| 37 | appIconIndex: 0, |
| 38 | relaunchCommand: `"${launcher}"`, |
| 39 | relaunchDisplayName: "Reasonix", |
| 40 | }; |
| 41 | } |
| 42 | |
| 43 | // An AppID alone makes Explorer pin the versioned Electron executable. Stamp |
| 44 | // every window before it is shown so new pins also survive version retention. |
| 45 | export function registerTaskbarRelaunch(target: WindowCreationEvents, platform: NodeJS.Platform, executable: string, packaged: boolean, isFile = isRegularFile): void { |
| 46 | if (platform !== "win32" || !packaged) return; |
| 47 | const details = windowsTaskbarDetails(executable, isFile); |
| 48 | if (details) target.on("browser-window-created", (_event, window) => window.setAppDetails(details)); |
| 49 | } |
| 50 |