| ... | @@ -21,6 +21,8 @@ const BACKEND_ENTRY = path.join(ROOT_DIR, "apps/server/dist/index.mjs"); | ... | @@ -21,6 +21,8 @@ const BACKEND_ENTRY = path.join(ROOT_DIR, "apps/server/dist/index.mjs"); |
| 21 | const WEB_ENTRY = path.join(ROOT_DIR, "apps/web/dist/index.html"); | 21 | const WEB_ENTRY = path.join(ROOT_DIR, "apps/web/dist/index.html"); |
| 22 | const STATE_DIR = path.join(os.homedir(), ".t3", "userdata"); | 22 | const STATE_DIR = path.join(os.homedir(), ".t3", "userdata"); |
| 23 | const isDevelopment = Boolean(process.env.VITE_DEV_SERVER_URL); | 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 | let mainWindow: BrowserWindow | null = null; | 27 | let mainWindow: BrowserWindow | null = null; |
| 26 | let backendProcess: ChildProcess | null = null; | 28 | let backendProcess: ChildProcess | null = null; |
| ... | @@ -31,6 +33,41 @@ let restartAttempt = 0; | ... | @@ -31,6 +33,41 @@ let restartAttempt = 0; |
| 31 | let restartTimer: ReturnType<typeof setTimeout> | null = null; | 33 | let restartTimer: ReturnType<typeof setTimeout> | null = null; |
| 32 | let isQuitting = false; | 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 | async function reserveLoopbackPort(): Promise<number> { | 71 | async function reserveLoopbackPort(): Promise<number> { |
| 35 | return new Promise((resolve, reject) => { | 72 | return new Promise((resolve, reject) => { |
| 36 | const probe = net.createServer(); | 73 | const probe = net.createServer(); |
| ... | @@ -207,7 +244,8 @@ function registerIpcHandlers(): void { | ... | @@ -207,7 +244,8 @@ function registerIpcHandlers(): void { |
| 207 | function getIconOption(): { icon: string } | Record<string, never> { | 244 | function getIconOption(): { icon: string } | Record<string, never> { |
| 208 | if (process.platform === "darwin") return {}; // macOS uses .icns from app bundle | 245 | if (process.platform === "darwin") return {}; // macOS uses .icns from app bundle |
| 209 | const ext = process.platform === "win32" ? "ico" : "png"; | 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 | function createWindow(): BrowserWindow { | 251 | function createWindow(): BrowserWindow { |
| ... | @@ -219,6 +257,7 @@ function createWindow(): BrowserWindow { | ... | @@ -219,6 +257,7 @@ function createWindow(): BrowserWindow { |
| 219 | show: false, | 257 | show: false, |
| 220 | autoHideMenuBar: true, | 258 | autoHideMenuBar: true, |
| 221 | ...getIconOption(), | 259 | ...getIconOption(), |
| | 260 | title: APP_DISPLAY_NAME, |
| 222 | titleBarStyle: "hiddenInset", | 261 | titleBarStyle: "hiddenInset", |
| 223 | trafficLightPosition: { x: 16, y: 18 }, | 262 | trafficLightPosition: { x: 16, y: 18 }, |
| 224 | webPreferences: { | 263 | webPreferences: { |
| ... | @@ -253,6 +292,8 @@ function createWindow(): BrowserWindow { | ... | @@ -253,6 +292,8 @@ function createWindow(): BrowserWindow { |
| 253 | return window; | 292 | return window; |
| 254 | } | 293 | } |
| 255 | | 294 | |
| | 295 | configureAppIdentity(); |
| | 296 | |
| 256 | async function bootstrap(): Promise<void> { | 297 | async function bootstrap(): Promise<void> { |
| 257 | backendPort = await reserveLoopbackPort(); | 298 | backendPort = await reserveLoopbackPort(); |
| 258 | backendAuthToken = randomBytes(24).toString("hex"); | 299 | backendAuthToken = randomBytes(24).toString("hex"); |