| ... | ... | @@ -21,6 +21,8 @@ const BACKEND_ENTRY = path.join(ROOT_DIR, "apps/server/dist/index.mjs"); |
| 21 | 21 | const WEB_ENTRY = path.join(ROOT_DIR, "apps/web/dist/index.html"); |
| 22 | 22 | const STATE_DIR = path.join(os.homedir(), ".t3", "userdata"); |
| 23 | 23 | const isDevelopment = Boolean(process.env.VITE_DEV_SERVER_URL); |
| 24 | const APP_DISPLAY_NAME = "T3 Code"; |
| 25 | const APP_USER_MODEL_ID = "com.t3tools.t3code"; |
| 24 | 26 | |
| 25 | 27 | let mainWindow: BrowserWindow | null = null; |
| 26 | 28 | let backendProcess: ChildProcess | null = null; |
| ... | ... | @@ -31,6 +33,41 @@ let restartAttempt = 0; |
| 31 | 33 | let restartTimer: ReturnType<typeof setTimeout> | null = null; |
| 32 | 34 | let isQuitting = false; |
| 33 | 35 | |
| 36 | function resolveResourcePath(fileName: string): string | null { |
| 37 | const candidates = [ |
| 38 | path.join(__dirname, "../resources", fileName), |
| 39 | path.join(process.resourcesPath, "resources", fileName), |
| 40 | path.join(process.resourcesPath, fileName), |
| 41 | ]; |
| 42 | |
| 43 | for (const candidate of candidates) { |
| 44 | if (fs.existsSync(candidate)) { |
| 45 | return candidate; |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | return null; |
| 50 | } |
| 51 | |
| 52 | function resolveIconPath(ext: "ico" | "icns" | "png"): string | null { |
| 53 | return resolveResourcePath(`icon.${ext}`); |
| 54 | } |
| 55 | |
| 56 | function configureAppIdentity(): void { |
| 57 | app.setName(APP_DISPLAY_NAME); |
| 58 | |
| 59 | if (process.platform === "win32") { |
| 60 | app.setAppUserModelId(APP_USER_MODEL_ID); |
| 61 | } |
| 62 | |
| 63 | if (process.platform === "darwin" && app.dock) { |
| 64 | const iconPath = resolveIconPath("png"); |
| 65 | if (iconPath) { |
| 66 | app.dock.setIcon(iconPath); |
| 67 | } |
| 68 | } |
| 69 | } |
| 70 | |
| 34 | 71 | async function reserveLoopbackPort(): Promise<number> { |
| 35 | 72 | return new Promise((resolve, reject) => { |
| 36 | 73 | const probe = net.createServer(); |
| ... | ... | @@ -207,7 +244,8 @@ function registerIpcHandlers(): void { |
| 207 | 244 | function getIconOption(): { icon: string } | Record<string, never> { |
| 208 | 245 | if (process.platform === "darwin") return {}; // macOS uses .icns from app bundle |
| 209 | 246 | const ext = process.platform === "win32" ? "ico" : "png"; |
| 210 | | return { icon: path.join(__dirname, `../resources/icon.${ext}`) }; |
| 247 | const iconPath = resolveIconPath(ext); |
| 248 | return iconPath ? { icon: iconPath } : {}; |
| 211 | 249 | } |
| 212 | 250 | |
| 213 | 251 | function createWindow(): BrowserWindow { |
| ... | ... | @@ -219,6 +257,7 @@ function createWindow(): BrowserWindow { |
| 219 | 257 | show: false, |
| 220 | 258 | autoHideMenuBar: true, |
| 221 | 259 | ...getIconOption(), |
| 260 | title: APP_DISPLAY_NAME, |
| 222 | 261 | titleBarStyle: "hiddenInset", |
| 223 | 262 | trafficLightPosition: { x: 16, y: 18 }, |
| 224 | 263 | webPreferences: { |
| ... | ... | @@ -253,6 +292,8 @@ function createWindow(): BrowserWindow { |
| 253 | 292 | return window; |
| 254 | 293 | } |
| 255 | 294 | |
| 295 | configureAppIdentity(); |
| 296 | |
| 256 | 297 | async function bootstrap(): Promise<void> { |
| 257 | 298 | backendPort = await reserveLoopbackPort(); |
| 258 | 299 | backendAuthToken = randomBytes(24).toString("hex"); |