Files
orca/src/main/index.ts
T
NeilandClaude Opus 4.6 4db7643a6b Fix update checker 406 error and add menu feedback dialogs
The GitHub /releases/latest endpoint returns 406 when electron-updater
requests it with Accept: application/json. Setting allowPrerelease=true
bypasses this by parsing the version from the atom feed directly.

The "Check for Updates" menu item now shows native dialogs so users get
feedback without needing to open Settings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 20:47:21 -07:00

231 lines
6.9 KiB
TypeScript

import { app, shell, BrowserWindow, Menu, nativeImage, ipcMain } from 'electron'
import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'
import devIcon from '../../resources/icon-dev.png?asset'
import { Store } from './persistence'
import { registerRepoHandlers } from './ipc/repos'
import { registerWorktreeHandlers } from './ipc/worktrees'
import { registerPtyHandlers, killAllPty } from './ipc/pty'
import { registerGitHubHandlers } from './ipc/github'
import { registerSettingsHandlers } from './ipc/settings'
import { registerShellHandlers } from './ipc/shell'
import { registerSessionHandlers } from './ipc/session'
import { registerUIHandlers } from './ipc/ui'
import { warmSystemFontFamilies } from './system-fonts'
import {
setupAutoUpdater,
checkForUpdates,
checkForUpdatesFromMenu,
getUpdateStatus,
quitAndInstall
} from './updater'
let mainWindow: BrowserWindow | null = null
let store: Store | null = null
// Enable WebGPU in Electron
app.commandLine.appendSwitch('enable-features', 'Vulkan,UseSkiaGraphite')
app.commandLine.appendSwitch('enable-unsafe-webgpu')
// ---------------------------------------------------------------------------
// Window creation
// ---------------------------------------------------------------------------
function createWindow(): BrowserWindow {
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 600,
minHeight: 400,
show: false,
autoHideMenuBar: true,
titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : undefined,
...(process.platform === 'darwin' ? { trafficLightPosition: { x: 16, y: 12 } } : {}),
icon: is.dev ? devIcon : icon,
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false
}
})
// Restore persisted zoom before the window is shown. dom-ready fires
// before ready-to-show, so the user never sees the wrong zoom level.
// Always set explicitly to override Chromium's own zoom cache.
mainWindow.webContents.on('dom-ready', () => {
mainWindow.webContents.setZoomLevel(store?.getUI().uiZoomLevel ?? 0)
})
mainWindow.on('ready-to-show', () => {
mainWindow.maximize()
mainWindow.show()
})
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
// Handle zoom shortcuts reliably via before-input-event
mainWindow.webContents.on('before-input-event', (_event, input) => {
if (input.type !== 'keyDown') return
const mod = process.platform === 'darwin' ? input.meta : input.control
if (!mod || input.alt) return
if (input.key === '=' || input.key === '+') {
_event.preventDefault()
mainWindow.webContents.send('terminal:zoom', 'in')
} else if (input.key === '-') {
_event.preventDefault()
mainWindow.webContents.send('terminal:zoom', 'out')
} else if (input.key === '0' && !input.shift) {
_event.preventDefault()
mainWindow.webContents.send('terminal:zoom', 'reset')
}
})
// HMR for renderer base on electron-vite cli.
// Load the remote URL for development or the local html file for production.
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
}
return mainWindow
}
// ---------------------------------------------------------------------------
// App lifecycle
// ---------------------------------------------------------------------------
app.whenReady().then(() => {
electronApp.setAppUserModelId('com.stablyai.orca')
app.setName('Orca')
if (process.platform === 'darwin') {
const dockIcon = nativeImage.createFromPath(is.dev ? devIcon : icon)
app.dock?.setIcon(dockIcon)
}
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window)
})
// Override default menu to prevent Cmd+W from closing the window.
// The renderer handles Cmd+W to close terminal panes instead.
const template: Electron.MenuItemConstructorOptions[] = [
{
label: app.name,
submenu: [
{ role: 'about' },
{
label: 'Check for Updates...',
click: () => checkForUpdatesFromMenu()
},
{
label: 'Settings',
accelerator: 'CmdOrCtrl+,',
click: () => {
mainWindow?.webContents.send('ui:openSettings')
}
},
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideOthers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' }
]
},
{
label: 'Edit',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
{ role: 'selectAll' }
]
},
{
label: 'View',
submenu: [
{ role: 'reload' },
{ role: 'forceReload' },
{ role: 'toggleDevTools' },
{ type: 'separator' },
{
label: 'Actual Size',
accelerator: 'CmdOrCtrl+0',
registerAccelerator: false
},
{
label: 'Zoom In',
accelerator: 'CmdOrCtrl+=',
registerAccelerator: false
},
{
label: 'Zoom Out',
accelerator: 'CmdOrCtrl+-',
registerAccelerator: false
},
{ type: 'separator' },
{ role: 'togglefullscreen' }
]
},
{
label: 'Window',
submenu: [{ role: 'minimize' }, { role: 'zoom' }]
}
]
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
// Initialize persistence
store = new Store()
// Create window
mainWindow = createWindow()
// Register all IPC handlers
registerRepoHandlers(mainWindow, store)
registerWorktreeHandlers(mainWindow, store)
registerPtyHandlers(mainWindow)
registerGitHubHandlers()
registerSettingsHandlers(store)
registerShellHandlers()
registerSessionHandlers(store)
registerUIHandlers(store)
warmSystemFontFamilies()
setupAutoUpdater(mainWindow)
// Updater IPC
ipcMain.handle('updater:getStatus', () => getUpdateStatus())
ipcMain.handle('updater:check', () => checkForUpdates())
ipcMain.handle('updater:quitAndInstall', () => quitAndInstall())
// macOS re-activate
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) {
mainWindow = createWindow()
registerPtyHandlers(mainWindow)
}
})
})
// ---------------------------------------------------------------------------
// Cleanup
// ---------------------------------------------------------------------------
app.on('before-quit', () => {
killAllPty()
store?.flush()
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})