77 loginUrl ,
88 getAlternativeWine ,
99 isLoggedIn ,
10- icon ,
1110 legendaryConfigPath ,
1211 userInfo ,
1312 writeDefaultconfig ,
@@ -19,7 +18,10 @@ import {
1918 checkForUpdates ,
2019 showAboutWindow ,
2120 kofiURL ,
21+ handleExit ,
2222 heroicGithubURL ,
23+ iconDark ,
24+ iconLight ,
2325} from './utils'
2426
2527import byteSize from 'byte-size'
@@ -33,6 +35,7 @@ import {
3335 writeFile ,
3436 existsSync ,
3537 mkdirSync ,
38+ unlinkSync ,
3639} from 'fs'
3740import { promisify } from 'util'
3841import axios from 'axios'
@@ -47,15 +50,17 @@ import {
4750 Notification ,
4851 Menu ,
4952 Tray ,
53+ nativeTheme ,
5054 dialog ,
5155} from 'electron'
5256import { AppSettings , Game , InstalledInfo , KeyImage } from './types.js'
5357
5458const showMessageBox = dialog . showMessageBox
59+ let mainWindow : BrowserWindow = null
5560
5661function createWindow ( ) {
5762 // Create the browser window.
58- const win = new BrowserWindow ( {
63+ mainWindow = new BrowserWindow ( {
5964 width : isDev ? 1800 : 1280 ,
6065 height : isDev ? 1200 : 720 ,
6166 minHeight : 600 ,
@@ -83,83 +88,114 @@ function createWindow() {
8388 console . log ( 'An error occurred: ' , err )
8489 } )
8590 } )
86- win . loadURL ( 'http://localhost:3000' )
91+ mainWindow . loadURL ( 'http://localhost:3000' )
8792 // Open the DevTools.
88- win . webContents . openDevTools ( )
89- win . on ( 'close' , async ( e ) => {
93+ mainWindow . webContents . openDevTools ( )
94+ mainWindow . on ( 'close' , async ( e ) => {
9095 e . preventDefault ( )
91- win . hide ( )
96+ const { exitToTray } = JSON . parse (
97+ // @ts -ignore
98+ readFileSync ( heroicConfigPath )
99+ ) . defaultSettings as AppSettings
100+
101+ if ( exitToTray ) {
102+ return mainWindow . hide ( )
103+ }
104+ return handleExit ( )
92105 } )
93106 } else {
94- win . on ( 'close' , async ( e ) => {
107+ mainWindow . on ( 'close' , async ( e ) => {
95108 e . preventDefault ( )
96- win . hide ( )
109+ const { exitToTray } = JSON . parse (
110+ // @ts -ignore
111+ readFileSync ( heroicConfigPath )
112+ ) . defaultSettings as AppSettings
113+
114+ if ( exitToTray ) {
115+ return mainWindow . hide ( )
116+ }
117+ return handleExit ( )
97118 } )
98- win . loadURL ( `file://${ path . join ( __dirname , '../build/index.html' ) } ` )
99- win . setMenu ( null )
119+ mainWindow . loadURL ( `file://${ path . join ( __dirname , '../build/index.html' ) } ` )
120+ mainWindow . setMenu ( null )
100121 }
101122}
102123
103124// This method will be called when Electron has finished
104125// initialization and is ready to create browser windows.
105126// Some APIs can only be used after this event occurs.
106127let appIcon : Tray = null
107- app . whenReady ( ) . then ( ( ) => {
108- createWindow ( )
109-
110- appIcon = new Tray ( icon )
111- const currentWindow : BrowserWindow = BrowserWindow . getAllWindows ( ) [ 0 ]
112-
113- const contextMenu = Menu . buildFromTemplate ( [
114- {
115- label : 'Show Heroic' ,
116- click : function ( ) {
117- currentWindow . show ( )
128+ let window = null
129+ const gotTheLock = app . requestSingleInstanceLock ( )
130+
131+ if ( ! gotTheLock ) {
132+ app . quit ( )
133+ } else {
134+ app . on ( 'second-instance' , ( ) => {
135+ // Someone tried to run a second instance, we should focus our window.
136+ if ( window ) {
137+ if ( window . isMinimized ( ) ) {
138+ window . restore ( )
139+ window . focus ( )
140+ }
141+ }
142+ } )
143+ app . whenReady ( ) . then ( ( ) => {
144+ window = createWindow ( )
145+ const trayIcon = nativeTheme . shouldUseDarkColors ? iconDark : iconLight
146+ appIcon = new Tray ( trayIcon )
147+
148+ const contextMenu = Menu . buildFromTemplate ( [
149+ {
150+ label : 'Show Heroic' ,
151+ click : function ( ) {
152+ mainWindow . show ( )
153+ } ,
118154 } ,
119- } ,
120- {
121- label : 'About' ,
122- click : function ( ) {
123- showAboutWindow ( )
155+ {
156+ label : 'About' ,
157+ click : function ( ) {
158+ showAboutWindow ( )
159+ } ,
124160 } ,
125- } ,
126- {
127- label : 'Github' ,
128- click : function ( ) {
129- exec ( `xdg-open ${ heroicGithubURL } ` )
161+ {
162+ label : 'Github' ,
163+ click : function ( ) {
164+ exec ( `xdg-open ${ heroicGithubURL } ` )
165+ } ,
130166 } ,
131- } ,
132- {
133- label : 'Support Us' ,
134- click : function ( ) {
135- exec ( `xdg-open ${ kofiURL } ` )
167+ {
168+ label : 'Support Us' ,
169+ click : function ( ) {
170+ exec ( `xdg-open ${ kofiURL } ` )
171+ } ,
136172 } ,
137- } ,
138- {
139- label : 'Quit' ,
140- click : function ( ) {
141- app . exit ( )
173+ {
174+ label : 'Quit' ,
175+ click : function ( ) {
176+ handleExit ( )
177+ } ,
142178 } ,
143- } ,
144- ] )
179+ ] )
145180
146- appIcon . setContextMenu ( contextMenu )
147- appIcon . setToolTip ( 'Heroic' )
148- return
149- } )
181+ appIcon . setContextMenu ( contextMenu )
182+ appIcon . setToolTip ( 'Heroic' )
183+ return
184+ } )
185+ }
150186
151187ipcMain . on ( 'Notify' , ( event , args ) => {
152- const currentWindow : BrowserWindow = BrowserWindow . getAllWindows ( ) [ 0 ]
153-
154188 const notify = new Notification ( {
155189 title : args [ 0 ] ,
156190 body : args [ 1 ] ,
157191 } )
158192
159- notify . on ( 'click' , ( ) => currentWindow . show ( ) )
193+ notify . on ( 'click' , ( ) => mainWindow . show ( ) )
160194 notify . show ( )
161195} )
162196
197+ ipcMain . on ( 'openSupportPage' , ( ) => exec ( `xdg-open ${ kofiURL } ` ) )
198+
163199ipcMain . handle ( 'writeFile' , ( event , args ) => {
164200 const app = args [ 0 ]
165201 const config = args [ 1 ]
@@ -177,6 +213,18 @@ ipcMain.handle('writeFile', (event, args) => {
177213 )
178214} )
179215
216+ ipcMain . on ( 'lock' , ( ) =>
217+ writeFile ( `${ heroicGamesConfigPath } /lock` , '' , ( ) => 'done' )
218+ )
219+
220+ ipcMain . on ( 'unlock' , ( ) => {
221+ if ( existsSync ( `${ heroicGamesConfigPath } /lock` ) ) {
222+ unlinkSync ( `${ heroicGamesConfigPath } /lock` )
223+ }
224+ } )
225+
226+ ipcMain . on ( 'quit' , ( ) => handleExit ( ) )
227+
180228ipcMain . handle ( 'getGameInfo' , async ( event , game ) => {
181229 const epicUrl = `https://store-content.ak.epicgames.com/api/en-US/content/products/${ game } `
182230 try {
@@ -258,7 +306,7 @@ ipcMain.handle('repair', async (event, game) => {
258306ipcMain . handle ( 'importGame' , async ( event , args ) => {
259307 const { appName : game , path } = args
260308 const command = `${ legendaryBin } import-game ${ game } '${ path } '`
261- const { stderr, stdout } = await execAsync ( command )
309+ const { stderr, stdout } = await execAsync ( command , { shell : '/bin/bash' } )
262310 console . log ( `${ stdout } - ${ stderr } ` )
263311 return
264312} )
@@ -268,13 +316,16 @@ ipcMain.handle('updateGame', (e, appName) => updateGame(appName))
268316ipcMain . on ( 'requestGameProgress' , ( event , appName ) => {
269317 const logPath = `${ heroicGamesConfigPath } ${ appName } .log`
270318 exec (
271- `tail ${ logPath } | grep 'Progress: ' | awk '{print $5 $6}'` ,
319+ `tail ${ logPath } | grep 'Progress: ' | awk '{print $5 $6 $11 }'` ,
272320 ( error , stdout ) => {
273321 const status = `${ stdout . split ( '\n' ) [ 0 ] } ` . split ( '(' )
274322 const percent = status [ 0 ]
275- const bytes = status [ 1 ] ? status [ 1 ] . replace ( '),' , 'MB' ) : ''
276- const progress = { percent, bytes }
277- console . log ( `Progress: ${ appName } ${ progress . percent } /${ progress . bytes } /` )
323+ const eta = status [ 1 ] ? status [ 1 ] . split ( ',' ) [ 1 ] : ''
324+ const bytes = status [ 1 ] ? status [ 1 ] . split ( ',' ) [ 0 ] . replace ( ')' , 'MB' ) : ''
325+ const progress = { percent, bytes, eta }
326+ console . log (
327+ `Progress: ${ appName } ${ progress . percent } /${ progress . bytes } /${ eta } `
328+ )
278329 event . reply ( `${ appName } -progress` , progress )
279330 }
280331 )
@@ -333,6 +384,7 @@ ipcMain.on('requestSettings', (event, appName) => {
333384ipcMain . handle ( 'isLoggedIn' , ( ) => isLoggedIn ( ) )
334385
335386ipcMain . on ( 'openLoginPage' , ( ) => spawn ( 'xdg-open' , [ loginUrl ] ) )
387+
336388ipcMain . on ( 'openSidInfoPage' , ( ) => spawn ( 'xdg-open' , [ sidInfoUrl ] ) )
337389
338390ipcMain . on ( 'getLog' , ( event , appName ) =>
0 commit comments