Files
myagents/desktop/electron/main.ts
T
Zhengshou Lai 2ac1c9750e refactor(desktop): code review fixes
- Shortcut: Cmd+Shift+C → Cmd+Shift+M (avoid Chrome DevTools conflict)
- Preload: onData/onExit now return cleanup functions (fix memory leak)
- PTY: resolve shell path at construction time, throw on missing claude CLI
- Terminal: remove allowProposedApi, dedupe resize listeners, add cleanup
- Main: debounce window move config writes (500ms), restart PTY on activate
- CSS: remove unused orbGlow keyframes
- Package: fix appId to com.apaam.myclaude, skip code signing, remove unused deps
2026-05-27 11:23:14 +08:00

199 lines
4.9 KiB
TypeScript

import { app, BrowserWindow, ipcMain, screen, globalShortcut } from 'electron'
import * as path from 'path'
import { PTYManager } from './pty'
import { loadConfig, saveConfig } from './config'
const ptyManager = new PTYManager()
let mainWindow: BrowserWindow | null = null
let moveTimeout: NodeJS.Timeout | null = null
const COLLAPSED_SIZE = { width: 80, height: 80 }
const EXPANDED_SIZE = { width: 900, height: 600 }
function createWindow() {
const config = loadConfig()
const display = screen.getPrimaryDisplay()
const { width: screenWidth, height: screenHeight } = display.workAreaSize
const x = config.windowPosition?.x ?? screenWidth - COLLAPSED_SIZE.width - 20
const y = config.windowPosition?.y ?? screenHeight - COLLAPSED_SIZE.height - 20
mainWindow = new BrowserWindow({
width: COLLAPSED_SIZE.width,
height: COLLAPSED_SIZE.height,
x,
y,
frame: false,
transparent: true,
backgroundColor: '#00000000',
alwaysOnTop: true,
skipTaskbar: true,
hasShadow: false,
resizable: false,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
},
})
if (app.dock) {
app.dock.hide()
}
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.on('closed', () => {
mainWindow = null
})
mainWindow.on('moved', () => {
if (!mainWindow) return
if (moveTimeout) clearTimeout(moveTimeout)
moveTimeout = setTimeout(() => {
if (!mainWindow) return
const [x, y] = mainWindow.getPosition()
const config = loadConfig()
saveConfig({ ...config, windowPosition: { x, y } })
}, 500)
})
}
function expandWindow() {
if (!mainWindow) return
const display = screen.getPrimaryDisplay()
const { width: screenWidth, height: screenHeight } = display.workAreaSize
const currentPos = mainWindow.getPosition()
let x = currentPos[0]
let y = currentPos[1]
if (x + EXPANDED_SIZE.width > screenWidth) {
x = screenWidth - EXPANDED_SIZE.width - 20
}
if (y + EXPANDED_SIZE.height > screenHeight) {
y = screenHeight - EXPANDED_SIZE.height - 20
}
mainWindow.setResizable(true)
mainWindow.setMinimumSize(EXPANDED_SIZE.width, EXPANDED_SIZE.height)
mainWindow.setSize(EXPANDED_SIZE.width, EXPANDED_SIZE.height)
mainWindow.setPosition(x, y)
mainWindow.setAlwaysOnTop(true)
mainWindow.setIgnoreMouseEvents(false)
mainWindow.setHasShadow(true)
const config = loadConfig()
saveConfig({ ...config, collapsed: false })
}
function collapseWindow() {
if (!mainWindow) return
const currentPos = mainWindow.getPosition()
mainWindow.setResizable(false)
mainWindow.setSize(COLLAPSED_SIZE.width, COLLAPSED_SIZE.height)
mainWindow.setPosition(currentPos[0], currentPos[1])
mainWindow.setAlwaysOnTop(true)
mainWindow.setIgnoreMouseEvents(true, { forward: true })
mainWindow.setHasShadow(false)
const config = loadConfig()
saveConfig({ ...config, collapsed: true, windowPosition: { x: currentPos[0], y: currentPos[1] } })
}
app.whenReady().then(() => {
createWindow()
ptyManager.start('claude')
mainWindow?.webContents.on('did-finish-load', () => {
collapseWindow()
})
const ret = globalShortcut.register('CommandOrControl+Shift+M', () => {
if (!mainWindow) {
createWindow()
return
}
if (mainWindow.isVisible()) {
mainWindow.hide()
} else {
mainWindow.show()
mainWindow.focus()
}
})
if (!ret) {
console.log('Global shortcut registration failed')
}
ipcMain.on('pty:write', (_event, data: string) => {
ptyManager.write(data)
})
ipcMain.on('pty:resize', (_event, cols: number, rows: number) => {
ptyManager.resize(cols, rows)
})
ipcMain.on('window:expand', () => {
expandWindow()
})
ipcMain.on('window:collapse', () => {
collapseWindow()
})
ipcMain.on('window:set-position', (_event, x: number, y: number) => {
mainWindow?.setPosition(x, y)
})
ipcMain.on('window:set-ignore-mouse', (_event, ignore: boolean) => {
mainWindow?.setIgnoreMouseEvents(ignore, { forward: true })
})
ipcMain.on('window:close', () => {
mainWindow?.close()
})
ipcMain.on('window:minimize', () => {
mainWindow?.minimize()
})
ptyManager.on('data', (data: string) => {
mainWindow?.webContents.send('pty:data', data)
})
ptyManager.on('exit', (code: number) => {
mainWindow?.webContents.send('pty:exit', code)
})
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
ptyManager.start('claude')
}
})
})
app.on('window-all-closed', () => {
ptyManager.kill()
globalShortcut.unregisterAll()
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('before-quit', () => {
ptyManager.kill()
globalShortcut.unregisterAll()
})
app.on('will-quit', () => {
globalShortcut.unregisterAll()
})