-
Notifications
You must be signed in to change notification settings - Fork 0
Tray and Window Lifecycle
The main window is created in createWindow() in src/main/main.ts:
mainWindow = new BrowserWindow({
width: 1200, height: 760, minWidth: 900, minHeight: 600,
frame: false,
backgroundColor: '#08090d',
icon: getIconImage(),
show: false, // shown in ready-to-show to avoid white flash
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
sandbox: false,
},
})frame: false removes the OS title bar entirely. TitleBar.tsx renders a custom replacement with drag region and window controls.
mainWindow.once('ready-to-show', () => {
if (isHidden || (settings.startMinimized && !IS_DEV))
mainWindow?.hide()
else
mainWindow?.show()
})isHidden is true when the app is launched with --hidden (e.g. via autostart). In dev mode startMinimized is always ignored so the window appears immediately.
TitleBar.tsx applies CSS regions via inline style:
// Draggable region — the whole title bar
style={{ WebkitAppRegion: 'drag' }}
// Control buttons — must be non-draggable
style={{ WebkitAppRegion: 'no-drag' }}Without the no-drag override, clicks on buttons are intercepted by the drag handler and never fire.
The close event on the window is intercepted:
mainWindow.on('close', (e) => {
if (forceQuit) return
if (getSettings().minimizeToTray) {
e.preventDefault()
mainWindow?.hide()
}
})- If
minimizeToTrayis enabled, the window is hidden and the app stays alive - If disabled, the window closes and the app quits
-
forceQuitis set totruebefore any deliberateapp.quit()call so the handler does not suppress it
The window:close IPC handler follows the same logic from the custom close button in TitleBar.
The tray is created once in createTray() and never destroyed while the app is running.
getIconImage() tries icon.ico then icon.png from the resources/ directory. In dev, resources are resolved relative to the project root. In production, they are inside process.resourcesPath. Falls back to a 1×1 transparent PNG if neither file exists.
Tray icon is resized to 16×16 for display in the notification area:
tray = new Tray(getIconImage().resize({ width: 16, height: 16 }))updateTrayMenu() rebuilds the context menu whenever process state changes:
Open Java Runner Client
─────────────────────
ProfileName (PID 12345) ← one entry per running process
...
─────────────────────
Quit
When no processes are running, a disabled "No processes running" item is shown instead.
| Interaction | Behaviour |
|---|---|
| Double-click |
mainWindow.show() + focus()
|
| Right-click | Shows context menu |
| "Open" item |
mainWindow.show() + focus()
|
| "Quit" item | Sets forceQuit = true, calls app.quit()
|
app.on('window-all-closed', () => { /* intentionally empty — keep alive in tray */ })
app.on('before-quit', () => { forceQuit = true })
app.on('activate', () => { mainWindow?.show() }) // macOS dock clickProcesses spawned by JRC are children of the main process. They are not automatically killed when JRC quits — this is by design so JARs can outlive the manager UI. Use the Stop button or Utilities → Process Scanner to terminate them explicitly.
The launchOnStartup setting is stored and exposed in the settings UI, but OS-level registration via app.setLoginItemSettings() is not yet wired in main.ts. This is a planned implementation gap. When implemented, the setting should call:
app.setLoginItemSettings({
openAtLogin: settings.launchOnStartup,
args: settings.startMinimized ? ['--hidden'] : [],
})