Skip to content

Commit 7be851a

Browse files
committed
Merge branch 'main' of github.com:flavioislima/HeroicGamesLauncher
2 parents fcde212 + 2f5bd6a commit 7be851a

16 files changed

Lines changed: 272 additions & 124 deletions

File tree

electron/main.ts

Lines changed: 107 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
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

2527
import byteSize from 'byte-size'
@@ -33,6 +35,7 @@ import {
3335
writeFile,
3436
existsSync,
3537
mkdirSync,
38+
unlinkSync,
3639
} from 'fs'
3740
import { promisify } from 'util'
3841
import axios from 'axios'
@@ -47,15 +50,17 @@ import {
4750
Notification,
4851
Menu,
4952
Tray,
53+
nativeTheme,
5054
dialog,
5155
} from 'electron'
5256
import { AppSettings, Game, InstalledInfo, KeyImage } from './types.js'
5357

5458
const showMessageBox = dialog.showMessageBox
59+
let mainWindow: BrowserWindow = null
5560

5661
function 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.
106127
let 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

151187
ipcMain.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+
163199
ipcMain.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+
180228
ipcMain.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) => {
258306
ipcMain.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))
268316
ipcMain.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) => {
333384
ipcMain.handle('isLoggedIn', () => isLoggedIn())
334385

335386
ipcMain.on('openLoginPage', () => spawn('xdg-open', [loginUrl]))
387+
336388
ipcMain.on('openSidInfoPage', () => spawn('xdg-open', [sidInfoUrl]))
337389

338390
ipcMain.on('getLog', (event, appName) =>

electron/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export interface AppSettings {
2828
egsLinkedPath: string
2929
savesPath: string
3030
autoSyncSaves: boolean
31+
exitToTray: boolean
3132
defaultInstallPath: string
3233
}
3334

electron/utils.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ const userInfo = `${legendaryConfigPath}/user.json`
2727
const heroicInstallPath = `${home}/Games/Heroic`
2828
const legendaryBin = fixPathForAsarUnpack(join(__dirname, '/bin/legendary'))
2929
const icon = fixPathForAsarUnpack(join(__dirname, '/icon.png'))
30+
const iconDark = fixPathForAsarUnpack(join(__dirname, '/icon-dark.png'))
31+
const iconLight = fixPathForAsarUnpack(join(__dirname, '/icon-light.png'))
3032
const loginUrl =
3133
'https://www.epicgames.com/id/login?redirectUrl=https%3A%2F%2Fwww.epicgames.com%2Fid%2Fapi%2Fredirect'
3234
const sidInfoUrl =
@@ -130,7 +132,7 @@ const launchGame = async (appName: any) => {
130132
let prefix = `--wine-prefix ${winePrefix}`
131133

132134
envVars = otherOptions
133-
const isProton = wineVersion.name.startsWith('Steam')
135+
const isProton = wineVersion.name.startsWith('Proton')
134136
prefix = isProton ? '' : `--wine-prefix ${winePrefix}`
135137

136138
if (isProton) {
@@ -161,7 +163,7 @@ const launchGame = async (appName: any) => {
161163
const runWithGameMode = useGameMode && gameMode ? gameMode : ''
162164
const dxvkFps = showFps ? 'DXVK_HUD=fps' : ''
163165

164-
const command = `${envVars} ${dxvkFps}${runWithGameMode} ${legendaryBin} launch ${appName} ${wine} ${prefix}`
166+
const command = `${envVars} ${dxvkFps} ${runWithGameMode} ${legendaryBin} launch ${appName} ${wine} ${prefix}`
165167
console.log('\n Launch Command:', command)
166168

167169
if (isProton && !existsSync(`'${winePrefix}'`)) {
@@ -372,13 +374,30 @@ const showAboutWindow = () => {
372374
return app.showAboutPanel()
373375
}
374376

377+
const handleExit = async () => {
378+
if (existsSync(`${heroicGamesConfigPath}/lock`)) {
379+
const { response } = await showMessageBox({
380+
title: 'Exit',
381+
message: 'Games are being download, are you sure?',
382+
buttons: ['NO', 'YES'],
383+
})
384+
385+
if (response === 0) {
386+
return
387+
}
388+
return app.exit()
389+
}
390+
app.exit()
391+
}
392+
375393
export {
376394
getAlternativeWine,
377395
isLoggedIn,
378396
launchGame,
379397
writeDefaultconfig,
380398
writeGameconfig,
381399
checkForUpdates,
400+
handleExit,
382401
userInfo,
383402
getLatestDxvk,
384403
installDxvk,
@@ -389,6 +408,8 @@ export {
389408
legendaryBin,
390409
showAboutWindow,
391410
icon,
411+
iconDark,
412+
iconLight,
392413
home,
393414
loginUrl,
394415
sidInfoUrl,

package.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
22
"name": "heroic",
3-
"version": "1.2.0",
3+
"version": "1.2.1",
44
"private": true,
55
"main": "public/main.js",
66
"homepage": "./",
77
"license": "GPL-3.0-only",
8-
"description": "A native launcher for Epic Games for Linux based on Legendary",
8+
"description": "A Native Epic Games Launcher for Linux",
99
"repository": {
1010
"type": "Github",
1111
"url": "https://github.com/flavioislima/HeroicGamesLauncher"
@@ -22,7 +22,9 @@
2222
],
2323
"asarUnpack": [
2424
"build/bin/legendary",
25-
"build/icon.png"
25+
"build/icon.png",
26+
"build/icon-dark.png",
27+
"build/icon-light.png"
2628
],
2729
"directories": {
2830
"buildResources": "public"
@@ -31,7 +33,7 @@
3133
"linux": {
3234
"category": "Game",
3335
"icon": "build/icon.icns",
34-
"description": "Native Epic Games Launcher alternative for Linux based on Legendary",
36+
"description": "A Native Epic Games Launcher for Linux",
3537
"desktop": "Name=Heroic Exec=heroic Icon=heroic Type=Application Categories=games"
3638
},
3739
"deb": {

public/icon-dark.png

1.13 KB
Loading

public/icon-light.png

1.15 KB
Loading

0 commit comments

Comments
 (0)