1
1
/* eslint-disable @typescript-eslint/no-unused-vars */
2
- import { app , BrowserWindow , ipcMain , shell , dialog } from 'electron' ;
2
+ import { app , BrowserWindow , ipcMain , shell , dialog , Tray , Menu , nativeImage } from 'electron' ;
3
3
import * as path from 'path' ;
4
4
import * as fs from 'fs' ;
5
5
import * as os from 'os' ;
@@ -8,11 +8,147 @@ import { execSync } from 'child_process';
8
8
9
9
// Main window reference
10
10
let win : BrowserWindow | null = null ;
11
+ // Tray reference
12
+ let tray : Tray | null = null ;
13
+
14
+ // Settings
15
+ let closeToTray = true ;
11
16
12
17
// Check if app is running in development mode
13
18
const args = process . argv . slice ( 1 ) ;
14
19
const serve = args . some ( val => val === '--serve' ) ;
15
20
21
+ /**
22
+ * Create the system tray
23
+ */
24
+ function createTray ( ) {
25
+ if ( tray ) {
26
+ return ;
27
+ }
28
+
29
+ // Get appropriate icon based on platform
30
+ const iconPath = path . join ( __dirname , '..' , 'dist' , 'logos' , 'favicon.256x256.png' ) ;
31
+ const icon = nativeImage . createFromPath ( iconPath ) ;
32
+
33
+ tray = new Tray ( icon ) ;
34
+ tray . setToolTip ( 'TensorBlock Desktop' ) ;
35
+
36
+ const contextMenu = Menu . buildFromTemplate ( [
37
+ { label : 'Open TensorBlock' , click : ( ) => {
38
+ win ?. show ( ) ;
39
+ win ?. setSkipTaskbar ( false ) ; // Show in taskbar
40
+ } } ,
41
+ { type : 'separator' } ,
42
+ { label : 'Quit' , click : ( ) => { app . quit ( ) ; } }
43
+ ] ) ;
44
+
45
+ tray . setContextMenu ( contextMenu ) ;
46
+
47
+ tray . on ( 'click' , ( ) => {
48
+ if ( win ) {
49
+ if ( win . isVisible ( ) ) {
50
+ win . hide ( ) ;
51
+ win . setSkipTaskbar ( true ) ; // Hide from taskbar
52
+ } else {
53
+ win . show ( ) ;
54
+ win . setSkipTaskbar ( false ) ; // Show in taskbar
55
+ }
56
+ }
57
+ } ) ;
58
+ }
59
+
60
+ /**
61
+ * Set or remove auto launch on system startup
62
+ */
63
+ function setAutoLaunch ( enable : boolean ) : boolean {
64
+ try {
65
+ if ( process . platform === 'win32' ) {
66
+ const appPath = app . getPath ( 'exe' ) ;
67
+ const regKey = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' ;
68
+ const appName = app . getName ( ) ;
69
+
70
+ if ( enable ) {
71
+ // Add to registry to enable auto launch
72
+ execSync ( `reg add ${ regKey } /v ${ appName } /t REG_SZ /d "${ appPath } " /f` ) ;
73
+ } else {
74
+ // Remove from registry to disable auto launch
75
+ execSync ( `reg delete ${ regKey } /v ${ appName } /f` ) ;
76
+ }
77
+ return true ;
78
+ } else if ( process . platform === 'darwin' ) {
79
+ const appPath = app . getPath ( 'exe' ) ;
80
+ const loginItemSettings = app . getLoginItemSettings ( ) ;
81
+
82
+ // Set login item settings for macOS
83
+ app . setLoginItemSettings ( {
84
+ openAtLogin : enable ,
85
+ path : appPath
86
+ } ) ;
87
+
88
+ return app . getLoginItemSettings ( ) . openAtLogin === enable ;
89
+ } else if ( process . platform === 'linux' ) {
90
+ // For Linux, create or remove a .desktop file in autostart directory
91
+ const desktopFilePath = path . join ( os . homedir ( ) , '.config' , 'autostart' , `${ app . getName ( ) } .desktop` ) ;
92
+
93
+ if ( enable ) {
94
+ // Create directory if it doesn't exist
95
+ const autoStartDir = path . dirname ( desktopFilePath ) ;
96
+ if ( ! fs . existsSync ( autoStartDir ) ) {
97
+ fs . mkdirSync ( autoStartDir , { recursive : true } ) ;
98
+ }
99
+
100
+ // Create .desktop file
101
+ const desktopFileContent = `
102
+ [Desktop Entry]
103
+ Type=Application
104
+ Exec=${ app . getPath ( 'exe' ) }
105
+ Hidden=false
106
+ NoDisplay=false
107
+ X-GNOME-Autostart-enabled=true
108
+ Name=${ app . getName ( ) }
109
+ Comment=${ app . getName ( ) } startup script
110
+ ` ;
111
+ fs . writeFileSync ( desktopFilePath , desktopFileContent ) ;
112
+ } else if ( fs . existsSync ( desktopFilePath ) ) {
113
+ // Remove .desktop file
114
+ fs . unlinkSync ( desktopFilePath ) ;
115
+ }
116
+
117
+ return true ;
118
+ }
119
+
120
+ return false ;
121
+ } catch ( error ) {
122
+ console . error ( 'Error setting auto launch:' , error ) ;
123
+ return false ;
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Check if app is set to auto launch on system startup
129
+ */
130
+ function getAutoLaunchEnabled ( ) : boolean {
131
+ try {
132
+ if ( process . platform === 'win32' ) {
133
+ const regKey = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' ;
134
+ const appName = app . getName ( ) ;
135
+
136
+ const output = execSync ( `reg query ${ regKey } /v ${ appName } 2>nul` ) . toString ( ) ;
137
+ return output . includes ( appName ) ;
138
+ } else if ( process . platform === 'darwin' ) {
139
+ return app . getLoginItemSettings ( ) . openAtLogin ;
140
+ } else if ( process . platform === 'linux' ) {
141
+ const desktopFilePath = path . join ( os . homedir ( ) , '.config' , 'autostart' , `${ app . getName ( ) } .desktop` ) ;
142
+ return fs . existsSync ( desktopFilePath ) ;
143
+ }
144
+
145
+ return false ;
146
+ } catch ( error ) {
147
+ // If command fails (e.g., key doesn't exist), auto launch is not enabled
148
+ return false ;
149
+ }
150
+ }
151
+
16
152
/**
17
153
* Creates the main application window
18
154
*/
@@ -29,8 +165,8 @@ function createWindow(): BrowserWindow {
29
165
frame : false ,
30
166
fullscreenable : false ,
31
167
autoHideMenuBar : true ,
32
- minWidth : 700 ,
33
- minHeight : 1000 ,
168
+ minWidth : 800 ,
169
+ minHeight : 700 ,
34
170
webPreferences : {
35
171
preload : path . join ( __dirname , 'preload.js' ) ,
36
172
contextIsolation : true ,
@@ -180,14 +316,66 @@ function createWindow(): BrowserWindow {
180
316
181
317
// Close application
182
318
ipcMain . on ( 'close-app' , ( ) => {
183
- app . quit ( ) ;
319
+ if ( closeToTray ) {
320
+ win ?. hide ( ) ;
321
+ win ?. setSkipTaskbar ( true ) ;
322
+ } else {
323
+ app . quit ( ) ;
324
+ }
184
325
} ) ;
185
326
186
327
// Open URL in default browser
187
328
ipcMain . on ( 'open-url' , ( event , url ) => {
188
329
shell . openExternal ( url ) ;
189
330
} ) ;
190
331
332
+ // Auto-startup handlers
333
+ ipcMain . handle ( 'get-auto-launch' , ( ) => {
334
+ return getAutoLaunchEnabled ( ) ;
335
+ } ) ;
336
+
337
+ ipcMain . handle ( 'set-auto-launch' , ( event , enable ) => {
338
+ return setAutoLaunch ( enable ) ;
339
+ } ) ;
340
+
341
+ // Tray handlers
342
+ ipcMain . handle ( 'set-close-to-tray' , ( event , enable ) => {
343
+ closeToTray = enable ;
344
+ return true ;
345
+ } ) ;
346
+
347
+ ipcMain . handle ( 'get-close-to-tray' , ( ) => {
348
+ return closeToTray ;
349
+ } ) ;
350
+
351
+ ipcMain . handle ( 'set-startup-to-tray' , ( event , enable ) => {
352
+ // Store this preference for the next app start
353
+ try {
354
+ const configPath = path . join ( app . getPath ( 'userData' ) , 'config.json' ) ;
355
+ let config = { } ;
356
+
357
+ if ( fs . existsSync ( configPath ) ) {
358
+ config = JSON . parse ( fs . readFileSync ( configPath , 'utf8' ) ) ;
359
+ }
360
+
361
+ config = { ...config , startupToTray : enable } ;
362
+ fs . writeFileSync ( configPath , JSON . stringify ( config ) ) ;
363
+ return true ;
364
+ } catch ( error ) {
365
+ console . error ( 'Error saving startup to tray setting:' , error ) ;
366
+ return false ;
367
+ }
368
+ } ) ;
369
+
370
+ // Listen for window close event
371
+ win . on ( 'close' , ( e ) => {
372
+ if ( closeToTray ) {
373
+ e . preventDefault ( ) ;
374
+ win ?. hide ( ) ;
375
+ win ?. setSkipTaskbar ( true ) ;
376
+ }
377
+ } ) ;
378
+
191
379
// Disable page refresh in production
192
380
if ( process . env . NODE_ENV === 'production' ) {
193
381
win . webContents . on ( 'before-input-event' , ( event , input ) => {
@@ -293,7 +481,28 @@ try {
293
481
// Initialize app when Electron is ready
294
482
// Added delay to fix black background issue with transparent windows
295
483
// See: https://github.com/electron/electron/issues/15947
296
- app . on ( 'ready' , ( ) => setTimeout ( createWindow , 400 ) ) ;
484
+ app . on ( 'ready' , ( ) => {
485
+ setTimeout ( ( ) => {
486
+ // Create window
487
+ win = createWindow ( ) ;
488
+
489
+ // Create tray
490
+ createTray ( ) ;
491
+
492
+ // Check if we should start minimized to tray
493
+ try {
494
+ const configPath = path . join ( app . getPath ( 'userData' ) , 'config.json' ) ;
495
+ if ( fs . existsSync ( configPath ) ) {
496
+ const config = JSON . parse ( fs . readFileSync ( configPath , 'utf8' ) ) ;
497
+ if ( config . startupToTray ) {
498
+ win . hide ( ) ;
499
+ }
500
+ }
501
+ } catch ( error ) {
502
+ console . error ( 'Error reading config file:' , error ) ;
503
+ }
504
+ } , 400 ) ;
505
+ } ) ;
297
506
298
507
// Quit when all windows are closed
299
508
app . on ( 'window-all-closed' , ( ) => {
@@ -303,7 +512,10 @@ try {
303
512
// Re-create window if activated and no windows exist
304
513
app . on ( 'activate' , ( ) => {
305
514
if ( win === null ) {
306
- createWindow ( ) ;
515
+ win = createWindow ( ) ;
516
+ } else {
517
+ win . show ( ) ;
518
+ win . setSkipTaskbar ( false ) ; // Show in taskbar
307
519
}
308
520
} ) ;
309
521
} catch ( _e ) {
0 commit comments