import { app, BrowserWindow, ipcMain, Menu, nativeImage, screen, systemPreferences, Tray } from 'electron' import { readFileSync, writeFileSync } from 'node:fs' import os from 'node:os' import path from 'node:path' import { fileURLToPath } from 'node:url' import { UsageReader } from './usage-reader.js' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const SINGLE_TILE_WIDTH = 120 const DOUBLE_TILE_WIDTH = 236 const WINDOW_HEIGHT = 60 const DOCK_WIDTH = 12 const DOCK_TRIGGER_DISTANCE = 16 const DOCK_ANIMATION_DURATION = 210 const screenshotArgument = process.argv.find((argument) => argument.startsWith('--screenshot=')) const screenshotPath = screenshotArgument?.slice('--screenshot='.length) if (screenshotPath) { app.setPath('userData', path.join(os.tmpdir(), `codex-halo-preview-${process.pid}`)) } let mainWindow let tray let usageReader let isQuitting = false let saveBoundsTimer let dragState = null let normalWindowWidth = SINGLE_TILE_WIDTH let isDocked = false let isDockCollapsed = false let dockTimer let dockAnimationTimer function settingsPath() { return path.join(app.getPath('userData'), 'window-state.json') } function readWindowState() { try { return JSON.parse(readFileSync(settingsPath(), 'utf8')) } catch { return null } } function isVisibleOnAnyDisplay(bounds) { return screen.getAllDisplays().some(({ workArea }) => { const horizontal = bounds.x < workArea.x + workArea.width && bounds.x + bounds.width > workArea.x const vertical = bounds.y < workArea.y + workArea.height && bounds.y + bounds.height > workArea.y return horizontal && vertical }) } function initialBounds() { const saved = readWindowState() if (saved && isVisibleOnAnyDisplay(saved)) { return { width: SINGLE_TILE_WIDTH, height: WINDOW_HEIGHT, x: saved.x + Math.max(0, saved.width - SINGLE_TILE_WIDTH), y: saved.y, } } const { workArea } = screen.getPrimaryDisplay() return { width: SINGLE_TILE_WIDTH, height: WINDOW_HEIGHT, x: workArea.x + workArea.width - SINGLE_TILE_WIDTH - 28, y: workArea.y + 28, } } function resizeForSnapshot(snapshot) { if (!mainWindow || mainWindow.isDestroyed()) return const now = Date.now() const hasShortWindow = snapshot.shortWindow?.resetsAt > now const hasWeekWindow = snapshot.weekWindow?.resetsAt > now const width = hasShortWindow && hasWeekWindow ? DOUBLE_TILE_WIDTH : SINGLE_TILE_WIDTH const bounds = mainWindow.getBounds() normalWindowWidth = width if (isDocked) { clearInterval(dockAnimationTimer) const { workArea } = screen.getDisplayMatching(bounds) const dockWidth = isDockCollapsed ? DOCK_WIDTH : normalWindowWidth mainWindow.setBounds({ x: workArea.x + workArea.width - dockWidth, y: bounds.y, width: dockWidth, height: WINDOW_HEIGHT, }) return } if (bounds.width !== width || bounds.height !== WINDOW_HEIGHT) { mainWindow.setBounds({ x: bounds.x + bounds.width - width, y: bounds.y, width, height: WINDOW_HEIGHT, }) } } function saveWindowState() { if (!mainWindow || mainWindow.isDestroyed() || isDocked) return clearTimeout(saveBoundsTimer) saveBoundsTimer = setTimeout(() => { if (isDocked) return writeFileSync(settingsPath(), JSON.stringify(mainWindow.getBounds())) }, 250) } function setDockCollapsed(collapsed, animated = true) { if (!isDocked || !mainWindow || mainWindow.isDestroyed()) return const bounds = mainWindow.getBounds() const { workArea } = screen.getDisplayMatching(bounds) const targetWidth = collapsed ? DOCK_WIDTH : normalWindowWidth const rightEdge = workArea.x + workArea.width const y = Math.max( workArea.y, Math.min(bounds.y, workArea.y + workArea.height - WINDOW_HEIGHT), ) isDockCollapsed = collapsed clearInterval(dockAnimationTimer) if (!animated || systemPreferences.getAnimationSettings().prefersReducedMotion) { mainWindow.setBounds({ x: rightEdge - targetWidth, y, width: targetWidth, height: WINDOW_HEIGHT }) return } const startWidth = bounds.width const startedAt = Date.now() dockAnimationTimer = setInterval(() => { if (!mainWindow || mainWindow.isDestroyed()) { clearInterval(dockAnimationTimer) return } const elapsed = Math.min(1, (Date.now() - startedAt) / DOCK_ANIMATION_DURATION) const eased = 1 - Math.pow(1 - elapsed, 3) const width = Math.round(startWidth + (targetWidth - startWidth) * eased) mainWindow.setBounds({ x: rightEdge - width, y, width, height: WINDOW_HEIGHT }) if (elapsed === 1) clearInterval(dockAnimationTimer) }, 16) } function dockAtRightEdge() { const bounds = mainWindow.getBounds() const { workArea } = screen.getDisplayMatching(bounds) const rightEdge = workArea.x + workArea.width if (bounds.x + bounds.width < rightEdge - DOCK_TRIGGER_DISTANCE) return isDocked = true setDockCollapsed(true) } function createTrayImage() { const size = 40 const bitmap = Buffer.alloc(size * size * 4) function isInsideRoundedRect(x, y, left, top, right, bottom, radius) { const nearestX = Math.max(left + radius, Math.min(x, right - radius)) const nearestY = Math.max(top + radius, Math.min(y, bottom - radius)) return Math.hypot(x - nearestX, y - nearestY) <= radius } for (let y = 0; y < size; y += 1) { for (let x = 0; x < size; x += 1) { const offset = (y * size + x) * 4 const isTile = isInsideRoundedRect(x + 0.5, y + 0.5, 4, 4, 36, 36, 9) const isTallBar = isInsideRoundedRect(x + 0.5, y + 0.5, 13, 12, 18, 29, 2.5) const isShortBar = isInsideRoundedRect(x + 0.5, y + 0.5, 23, 19, 28, 29, 2.5) if (isTile) { // Bitmap pixels use BGRA order. This matches the widget's cobalt-blue identity. bitmap[offset] = 246 bitmap[offset + 1] = 87 bitmap[offset + 2] = 49 bitmap[offset + 3] = 255 } if (isTallBar || isShortBar) { bitmap[offset] = 255 bitmap[offset + 1] = 255 bitmap[offset + 2] = 255 bitmap[offset + 3] = 255 } } } return nativeImage .createFromBitmap(bitmap, { width: size, height: size, scaleFactor: 1 }) .resize({ width: 20, height: 20, quality: 'best' }) } function showWindow() { if (!mainWindow) return mainWindow.showInactive() mainWindow.moveTop() } function trayMenu() { return Menu.buildFromTemplate([ { label: '重新读取', click: () => usageReader.refresh() }, { label: '始终置顶', type: 'checkbox', checked: mainWindow?.isAlwaysOnTop() ?? true, click: ({ checked }) => mainWindow?.setAlwaysOnTop(checked, 'floating'), }, { type: 'separator' }, { label: '退出', click: () => { isQuitting = true app.quit() }, }, ]) } function createWindow() { mainWindow = new BrowserWindow({ ...initialBounds(), frame: false, transparent: true, backgroundColor: '#00000000', alwaysOnTop: true, skipTaskbar: true, show: false, resizable: false, hasShadow: false, webPreferences: { preload: path.join(__dirname, 'preload.cjs'), contextIsolation: true, nodeIntegration: false, sandbox: true, }, }) mainWindow.setAlwaysOnTop(true, 'floating') mainWindow.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: false }) if (process.env.VITE_DEV_SERVER_URL) mainWindow.loadURL(process.env.VITE_DEV_SERVER_URL) else mainWindow.loadFile(path.join(__dirname, '..', 'dist', 'index.html')) mainWindow.once('ready-to-show', () => { showWindow() usageReader.refresh() }) mainWindow.webContents.once('did-finish-load', async () => { if (screenshotPath) { showWindow() await new Promise((resolve) => setTimeout(resolve, 1200)) mainWindow.webContents.invalidate() const image = await mainWindow.webContents.capturePage() writeFileSync(path.resolve(screenshotPath), image.toPNG()) isQuitting = true app.quit() } }) mainWindow.on('move', saveWindowState) mainWindow.on('close', (event) => { if (!isQuitting) { event.preventDefault() mainWindow.hide() } }) mainWindow.webContents.on('context-menu', () => trayMenu().popup({ window: mainWindow })) } function createTray() { tray = new Tray(createTrayImage()) tray.setToolTip('Codex Halo · 本地额度仪表') tray.setContextMenu(trayMenu()) tray.on('double-click', showWindow) } const hasSingleInstanceLock = screenshotPath || app.requestSingleInstanceLock() if (!hasSingleInstanceLock) app.quit() app.on('second-instance', showWindow) app.on('before-quit', () => { isQuitting = true clearInterval(dockAnimationTimer) usageReader?.stop() }) app.whenReady().then(() => { const sessionsPath = path.join(os.homedir(), '.codex', 'sessions') usageReader = new UsageReader(sessionsPath) usageReader.on('snapshot', (snapshot) => { if (mainWindow && !mainWindow.isDestroyed()) { resizeForSnapshot(snapshot) mainWindow.webContents.send('usage:snapshot', snapshot) } }) usageReader.start() ipcMain.handle('usage:get', () => usageReader.readLatest()) ipcMain.handle('usage:refresh', () => usageReader.refresh()) ipcMain.on('window:drag-start', (event, point) => { if (event.sender !== mainWindow?.webContents) return clearTimeout(dockTimer) if (isDocked) setDockCollapsed(false, false) isDocked = false isDockCollapsed = false const bounds = mainWindow.getBounds() dragState = { pointerX: point.x, pointerY: point.y, windowX: bounds.x, windowY: bounds.y } }) ipcMain.on('window:drag-move', (event, point) => { if (event.sender !== mainWindow?.webContents || !dragState) return mainWindow.setPosition( Math.round(dragState.windowX + point.x - dragState.pointerX), Math.round(dragState.windowY + point.y - dragState.pointerY), ) }) ipcMain.on('window:drag-end', () => { dragState = null dockAtRightEdge() }) ipcMain.on('window:dock-expand', (event) => { if (event.sender !== mainWindow?.webContents || !isDocked) return clearTimeout(dockTimer) setDockCollapsed(false) }) ipcMain.on('window:dock-collapse', (event) => { if (event.sender !== mainWindow?.webContents || !isDocked || dragState) return clearTimeout(dockTimer) dockTimer = setTimeout(() => setDockCollapsed(true), 180) }) createWindow() if (!screenshotPath) createTray() }) app.on('window-all-closed', () => { // The tray owns the application lifecycle on Windows. })