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