diff --git a/src/main.ts b/src/main.ts index c270a22..de26315 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,4 @@ -import { app, BrowserWindow, ipcMain, nativeImage, shell, Tray } from 'electron'; +import { app, BrowserWindow, ipcMain, Menu, MenuItemConstructorOptions, nativeImage, shell, Tray } from 'electron'; import path from 'node:path'; import started from 'electron-squirrel-startup'; import { APP_NAME, DEFAULT_STATUS, GATEWAY_HOST, MANAGEMENT_URL, POLL_INTERVAL_MS, WINDOW_HEIGHT, WINDOW_WIDTH } from './config'; @@ -10,10 +10,29 @@ if (started) { app.quit(); } -// This method will be called when Electron has finished -// initialization and is ready to create browser windows. -// Some APIs can only be used after this event occurs. -app.on('ready', createWindow); +const singleInstanceLock = app.requestSingleInstanceLock() + +if (!singleInstanceLock) app.quit(); +else { + app.on('second-instance', () => { + if (window) { + if (window.isMinimized()) window.restore(); + window.focus(); + } + }); + app.on('before-quit', disconnect); +} + +app.whenReady().then(() => { + createWindow(); + createTray(); + refreshStatus(); + pollTimer = setInterval(refreshStatus, POLL_INTERVAL_MS); +}); + +app.on('before-quit', () => { + if (pollTimer) clearInterval(pollTimer); +}); // Quit when all windows are closed, except on macOS. There, it's common // for applications and their menu bar to stay active until the user quits @@ -65,6 +84,25 @@ function updateTray() { if (!tray) return; const label = cachedStatus.state === 'connected' ? `${APP_NAME}: Connected` : APP_NAME; tray.setToolTip(label); + + const template: MenuItemConstructorOptions[] = []; + const connected = cachedStatus.state === "connected"; + const busy = ['connecting', 'disconnecting'].includes(cachedStatus.state); + + template.push({ + label: connected ? 'Disconnect' : 'Connect', + click: connected ? disconnect : connect, + enabled: !busy + }); + + template.push( + { + label: 'Logout', + click: logout + }, + { role: 'quit' } + ); + tray.setContextMenu(Menu.buildFromTemplate(template)); } function setStatus(status: Record) { @@ -187,21 +225,10 @@ function showWindow() { function createTray() { const icon = nativeImage.createFromPath(resourcePath('VynteIcon.png')).resize({ width: 18, height: 18 }); tray = new Tray(icon); - tray.setToolTip(APP_NAME); + updateTray(); tray.on('click', showWindow); } -app.whenReady().then(() => { - createWindow(); - createTray(); - refreshStatus(); - pollTimer = setInterval(refreshStatus, POLL_INTERVAL_MS); -}); - -app.on('before-quit', () => { - if (pollTimer) clearInterval(pollTimer); -}); - ipcMain.handle('status:get', () => withAutoConnect(cachedStatus)); ipcMain.handle('status:refresh', refreshStatus); ipcMain.handle('vpn:connect', connect);