@@ -20,8 +20,14 @@ app.commandLine.appendSwitch("enable-features", "OverlayScrollbar,FluentOverlayS
2020fs . mkdirSync ( electronUserDataDir ( ) , { recursive : true } ) ;
2121app . setPath ( "userData" , electronUserDataDir ( ) ) ;
2222
23+ const gotSingleInstanceLock = app . requestSingleInstanceLock ( ) ;
24+ if ( ! gotSingleInstanceLock ) {
25+ app . quit ( ) ;
26+ }
27+
2328let mainWindow = null ;
2429let tray = null ;
30+ let trayContextMenu = null ;
2531let runningProcess = null ;
2632let quitting = false ;
2733const THEME_STORAGE_KEY = "clovapi-theme" ;
@@ -105,6 +111,7 @@ function showMainWindow(options = {}) {
105111 mainWindow . show ( ) ;
106112 if ( process . platform === "darwin" ) app . dock ?. show ( ) ;
107113 mainWindow . focus ( ) ;
114+ void updateTrayMenu ( ) ;
108115 if ( ! eventPayload ) return ;
109116 const sendAppEvent = ( ) => dispatchRendererEvent ( eventPayload ) ;
110117 if ( created || mainWindow . webContents . isLoading ( ) ) {
@@ -117,14 +124,7 @@ function showMainWindow(options = {}) {
117124function hideMainWindow ( ) {
118125 if ( ! mainWindow || mainWindow . isDestroyed ( ) ) return ;
119126 mainWindow . hide ( ) ;
120- }
121-
122- function toggleMainWindow ( ) {
123- if ( ! mainWindow || mainWindow . isDestroyed ( ) || ! mainWindow . isVisible ( ) ) {
124- showMainWindow ( ) ;
125- return ;
126- }
127- hideMainWindow ( ) ;
127+ void updateTrayMenu ( ) ;
128128}
129129
130130async function readTrayProxyState ( ) {
@@ -214,7 +214,6 @@ async function updateTrayMenu() {
214214 if ( ! tray ) return ;
215215 const [ state , desktop ] = await Promise . all ( [ readTrayProxyState ( ) , readTrayDesktopState ( ) ] ) ;
216216 const model = buildTrayMenuModel ( {
217- visible : Boolean ( mainWindow && ! mainWindow . isDestroyed ( ) && mainWindow . isVisible ( ) ) ,
218217 running : state . running ,
219218 port : state . port ,
220219 external : state . external ,
@@ -227,7 +226,7 @@ async function updateTrayMenu() {
227226 const template = [
228227 {
229228 label : model . windowLabel ,
230- click : ( ) => toggleMainWindow ( ) ,
229+ click : ( ) => showMainWindow ( ) ,
231230 } ,
232231 {
233232 label : model . profilesLabel ,
@@ -300,13 +299,29 @@ async function updateTrayMenu() {
300299 } ,
301300 } ,
302301 ] ;
303- tray . setContextMenu ( Menu . buildFromTemplate ( template ) ) ;
302+ trayContextMenu = Menu . buildFromTemplate ( template ) ;
303+ if ( process . platform === "darwin" ) {
304+ tray . setContextMenu ( trayContextMenu ) ;
305+ }
306+ }
307+
308+ function openTrayContextMenu ( ) {
309+ if ( ! tray || ! trayContextMenu ) return ;
310+ tray . popUpContextMenu ( trayContextMenu ) ;
304311}
305312
306313function createTray ( ) {
307314 if ( tray ) return tray ;
308315 tray = new Tray ( createTrayImage ( ) ) ;
309- tray . on ( "click" , ( ) => toggleMainWindow ( ) ) ;
316+ tray . on ( "click" , ( ) => showMainWindow ( ) ) ;
317+ if ( process . platform !== "darwin" ) {
318+ tray . on ( "right-click" , ( ) => {
319+ void ( async ( ) => {
320+ await updateTrayMenu ( ) ;
321+ openTrayContextMenu ( ) ;
322+ } ) ( ) ;
323+ } ) ;
324+ }
310325 void updateTrayMenu ( ) ;
311326 return tray ;
312327}
@@ -1019,25 +1034,31 @@ ipcMain.handle("cli:tool-status", async () => {
10191034 return { ok : false , available : false , source : "none" , path : "" , error : "No bundled or system clovapi found" } ;
10201035} ) ;
10211036
1022- app . whenReady ( ) . then ( async ( ) => {
1023- nativeTheme . themeSource = "light" ;
1024- createWindow ( ) ;
1025- createTray ( ) ;
1026- watchCoreDevBinary ( ) ;
1027- try {
1028- await proxyManager . autostartIfAllowed ( ) ;
1029- } catch {
1030- // Non-fatal on startup
1031- }
1032- await updateTrayMenu ( ) ;
1037+ if ( gotSingleInstanceLock ) {
1038+ app . on ( "second-instance" , ( ) => {
1039+ showMainWindow ( ) ;
1040+ } ) ;
10331041
1034- app . on ( "activate" , ( ) => {
1035- if ( BrowserWindow . getAllWindows ( ) . length === 0 ) {
1036- createWindow ( ) ;
1042+ app . whenReady ( ) . then ( async ( ) => {
1043+ nativeTheme . themeSource = "light" ;
1044+ createWindow ( ) ;
1045+ createTray ( ) ;
1046+ watchCoreDevBinary ( ) ;
1047+ try {
1048+ await proxyManager . autostartIfAllowed ( ) ;
1049+ } catch {
1050+ // Non-fatal on startup
10371051 }
1038- showMainWindow ( ) ;
1052+ await updateTrayMenu ( ) ;
1053+
1054+ app . on ( "activate" , ( ) => {
1055+ if ( BrowserWindow . getAllWindows ( ) . length === 0 ) {
1056+ createWindow ( ) ;
1057+ }
1058+ showMainWindow ( ) ;
1059+ } ) ;
10391060 } ) ;
1040- } ) ;
1061+ }
10411062
10421063app . on ( "before-quit" , ( ) => {
10431064 quitting = true ;
0 commit comments