add tray & creating shortcuts
This commit is contained in:
parent
81f5eadc76
commit
1097a1f441
11 changed files with 452 additions and 22 deletions
202
src/main.js
202
src/main.js
|
|
@ -7,28 +7,216 @@ const wines = require('./wines')
|
|||
const { registerIpc } = require('./ipc')
|
||||
const { createMainWindow } = require('./window')
|
||||
const { setAppIcon } = require('./appicon')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
app.setName('PLWL')
|
||||
|
||||
// The launcher keeps running with only a tray icon, so closing the window
|
||||
// must not quit the app (playtime keeps counting while a game runs).
|
||||
app.on('window-all-closed', e => {
|
||||
// no-op on purpose: tray Quit / menu Quit is the way out
|
||||
})
|
||||
|
||||
let win = null
|
||||
let tray = null
|
||||
let quitting = false
|
||||
let headless = false // started via --launch: no window until the user asks
|
||||
let autoLaunchId = null // game id from --launch <id>
|
||||
let autoQuitWhenDone = false // headless boot: quit after that game exits
|
||||
const getWindow = () => win
|
||||
|
||||
// --- single instance ---------------------------------------------------------
|
||||
// A second launcher invocation (e.g. a desktop shortcut with --launch <id>)
|
||||
// hands its arguments to the running instance and exits immediately.
|
||||
const gotLock = app.requestSingleInstanceLock()
|
||||
if (!gotLock) {
|
||||
app.quit()
|
||||
} else {
|
||||
app.on('second-instance', (e, argv) => {
|
||||
const id = parseLaunchId(argv)
|
||||
lastHandoffAt = Date.now()
|
||||
if (id) launchGame(id)
|
||||
else showMainWindow()
|
||||
})
|
||||
}
|
||||
|
||||
// set on every second-instance handoff: the auto-quit check skips itself for
|
||||
// a moment afterwards, so "one game exits" racing "a shortcut just started
|
||||
// another game" can never kill the launcher mid-handoff
|
||||
let lastHandoffAt = 0
|
||||
|
||||
function parseLaunchId(argv) {
|
||||
const args = Array.isArray(argv) ? argv : process.argv
|
||||
const i = args.indexOf('--launch')
|
||||
if (i !== -1 && args[i + 1]) return args[i + 1]
|
||||
const eq = args.find(a => a.startsWith('--launch='))
|
||||
return eq ? eq.slice('--launch='.length) : null
|
||||
}
|
||||
|
||||
autoLaunchId = parseLaunchId(process.argv)
|
||||
headless = !!autoLaunchId
|
||||
autoQuitWhenDone = headless // booting just to run one game -> exit when it ends
|
||||
|
||||
// --- tray ---------------------------------------------------------------------
|
||||
// Lives as long as the app does: shows "active in background" even when the
|
||||
// window is closed, and offers running games + Quit.
|
||||
function trayIconPath() {
|
||||
const png = path.resolve(__dirname, '..', 'renderer', 'plwl.png')
|
||||
return fs.existsSync(png) ? png : undefined
|
||||
}
|
||||
|
||||
function createTray() {
|
||||
const { Tray, Menu } = require('electron')
|
||||
try {
|
||||
tray = new Tray(trayIconPath())
|
||||
} catch {
|
||||
return // no icon available; window still works normally
|
||||
}
|
||||
tray.setToolTip('PLWL')
|
||||
rebuildTrayMenu()
|
||||
}
|
||||
|
||||
function rebuildTrayMenu() {
|
||||
if (!tray) return
|
||||
const { Menu } = require('electron')
|
||||
const lang = loadLangSafe()
|
||||
const gamesSvc = gamesService
|
||||
const running = gamesSvc ? gamesSvc.listRunning() : []
|
||||
const items = []
|
||||
|
||||
items.push({
|
||||
label: i18n.translate(lang, 'tray.open'),
|
||||
click: () => showMainWindow()
|
||||
})
|
||||
|
||||
if (running.length) {
|
||||
items.push({ type: 'separator' })
|
||||
items.push({ label: i18n.translate(lang, 'tray.running'), enabled: false })
|
||||
for (const id of running) {
|
||||
const game = require('./library').getGame(id)
|
||||
if (!game) continue
|
||||
items.push({
|
||||
label: `• ${game.name}`,
|
||||
click: () => {
|
||||
// click toggles: stop the running game
|
||||
gamesSvc.kill(id)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
items.push({ type: 'separator' })
|
||||
items.push({
|
||||
label: i18n.translate(lang, 'tray.quit'),
|
||||
click: () => {
|
||||
quitting = true
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
tray.setContextMenu(Menu.buildFromTemplate(items))
|
||||
const tip = running.length
|
||||
? `PLWL - ${i18n.translate(lang, 'tray.tooltipRunning', running.length)}`
|
||||
: 'PLWL'
|
||||
tray.setToolTip(tip)
|
||||
}
|
||||
|
||||
function loadLangSafe() {
|
||||
try {
|
||||
return require('./library').loadSettings().language || 'en-us'
|
||||
} catch {
|
||||
return 'en-us'
|
||||
}
|
||||
}
|
||||
|
||||
function showMainWindow() {
|
||||
// the user is present now - headless auto-quit must never fire again
|
||||
headless = false
|
||||
autoQuitWhenDone = false
|
||||
if (gamesService) gamesService.setOnGameExit(null)
|
||||
if (win && !win.isDestroyed()) {
|
||||
win.show()
|
||||
win.focus()
|
||||
return
|
||||
}
|
||||
win = createMainWindow()
|
||||
wireWindow(win)
|
||||
}
|
||||
|
||||
function wireWindow(w) {
|
||||
// closing the window hides it to the tray (playtime keeps counting in the
|
||||
// background) unless the user disabled that in settings - then it quits
|
||||
w.on('close', e => {
|
||||
if (quitting) return
|
||||
let closeToTray = true
|
||||
try {
|
||||
closeToTray = require('./library').loadSettings().closeToTray !== false
|
||||
} catch {}
|
||||
if (closeToTray) {
|
||||
e.preventDefault()
|
||||
w.hide()
|
||||
} else {
|
||||
quitting = true
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
w.on('closed', () => {
|
||||
win = null
|
||||
})
|
||||
}
|
||||
|
||||
// --- game auto-launch (shortcuts) -----------------------------------------------
|
||||
let gamesService = null
|
||||
|
||||
async function launchGame(id) {
|
||||
if (!gamesService) return
|
||||
const res = await gamesService.start(getWindow(), id)
|
||||
if (res.ok) {
|
||||
rebuildTrayMenu()
|
||||
// headless boot: when the auto-started game finishes and nothing else is
|
||||
// running, quit the launcher
|
||||
if (autoQuitWhenDone) {
|
||||
gamesService.setOnGameExit(() => {
|
||||
// fires for EVERY game exit; quit only when the last one just ended,
|
||||
// the user never opened the window and no shortcut handed a new game
|
||||
// over in the last few seconds (start/exit race guard)
|
||||
if (Date.now() - lastHandoffAt < 5000) return
|
||||
if (!gamesService.listRunning().length) {
|
||||
quitting = true
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
}
|
||||
} else if (headless && autoQuitWhenDone) {
|
||||
// nothing to run (bad shortcut target) -> do not linger in the tray
|
||||
quitting = true
|
||||
app.quit()
|
||||
}
|
||||
}
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
if (process.platform === 'linux') {
|
||||
try {
|
||||
setAppIcon(app)
|
||||
} catch {}
|
||||
}
|
||||
registerIpc({ getWindow })
|
||||
ensureDirs()
|
||||
await i18n.init()
|
||||
await wines.refreshWines()
|
||||
win = createMainWindow()
|
||||
win.on('closed', () => {
|
||||
win = null
|
||||
})
|
||||
const { games } = registerIpc({ getWindow, onGamesChanged: rebuildTrayMenu })
|
||||
gamesService = games
|
||||
|
||||
createTray()
|
||||
|
||||
if (headless) {
|
||||
// shortcut boot: run the game without opening the launcher window
|
||||
launchGame(autoLaunchId)
|
||||
} else {
|
||||
win = createMainWindow()
|
||||
wireWindow(win)
|
||||
}
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
app.quit()
|
||||
app.on('before-quit', () => {
|
||||
quitting = true
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue