11/* 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' ;
33import * as path from 'path' ;
44import * as fs from 'fs' ;
55import * as os from 'os' ;
@@ -8,11 +8,151 @@ import { execSync } from 'child_process';
88
99// Main window reference
1010let 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
1117
1218// Check if app is running in development mode
1319const args = process . argv . slice ( 1 ) ;
1420const serve = args . some ( val => val === '--serve' ) ;
1521
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+
16156/**
17157 * Creates the main application window
18158 */
@@ -29,8 +169,8 @@ function createWindow(): BrowserWindow {
29169 frame : false ,
30170 fullscreenable : false ,
31171 autoHideMenuBar : true ,
32- minWidth : 600 ,
33- minHeight : 600 ,
172+ minWidth : 800 ,
173+ minHeight : 700 ,
34174 webPreferences : {
35175 preload : path . join ( __dirname , 'preload.js' ) ,
36176 contextIsolation : true ,
@@ -180,14 +320,67 @@ function createWindow(): BrowserWindow {
180320
181321 // Close application
182322 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+ }
184330 } ) ;
185331
186332 // Open URL in default browser
187333 ipcMain . on ( 'open-url' , ( event , url ) => {
188334 shell . openExternal ( url ) ;
189335 } ) ;
190336
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+
191384 // Disable page refresh in production
192385 if ( process . env . NODE_ENV === 'production' ) {
193386 win . webContents . on ( 'before-input-event' , ( event , input ) => {
@@ -290,10 +483,36 @@ function getCPUName() {
290483try {
291484 app . commandLine . appendSwitch ( 'class' , 'tensorblock-desktop' ) ;
292485
486+ // Set force quit flag when app is about to quit
487+ app . on ( 'before-quit' , ( ) => {
488+ forceQuit = true ;
489+ } ) ;
490+
293491 // Initialize app when Electron is ready
294492 // Added delay to fix black background issue with transparent windows
295493 // 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+ } ) ;
297516
298517 // Quit when all windows are closed
299518 app . on ( 'window-all-closed' , ( ) => {
@@ -303,7 +522,10 @@ try {
303522 // Re-create window if activated and no windows exist
304523 app . on ( 'activate' , ( ) => {
305524 if ( win === null ) {
306- createWindow ( ) ;
525+ win = createWindow ( ) ;
526+ } else {
527+ win . show ( ) ;
528+ win . setSkipTaskbar ( false ) ; // Show in taskbar
307529 }
308530 } ) ;
309531} catch ( _e ) {
0 commit comments