add support for directly launching windows exes from file exporer using .desktop shortcut

This commit is contained in:
olcxja 2026-09-30 17:32:30 +02:00
commit 5e217472aa
15 changed files with 440 additions and 28 deletions

View file

@ -7,6 +7,8 @@ const wines = require('./wines')
const { registerIpc } = require('./ipc')
const { createMainWindow } = require('./window')
const { setAppIcon } = require('./appicon')
const { parseArgv } = require('./argv')
const openwith = require('./openwith')
const path = require('path')
const fs = require('fs')
@ -23,6 +25,7 @@ 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 autoOpenPath = null // file path from --open ("Open with PLWL")
let autoQuitWhenDone = false // headless boot: quit after that game exits
const getWindow = () => win
@ -50,9 +53,10 @@ if (!gotLock) {
} else {
app.on('second-instance', (e, argv) => {
trace(`second-instance argv=${argv.join(' ')}`)
const id = parseLaunchId(argv)
const { launchId, openPath } = parseArgv(argv)
lastHandoffAt = Date.now()
if (id) launchGame(id)
if (openPath) openWith(openPath)
else if (launchId) launchGame(launchId)
else showMainWindow()
})
}
@ -62,24 +66,20 @@ if (!gotLock) {
// 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) {
// Chromium injects its own flags into the second instance's argv and they
// can land BETWEEN --launch and the value (e.g. `--launch
// --allow-file-access-from-files 1790414564627`), so skip anything that
// looks like a flag and take the first non-flag token.
for (let j = i + 1; j < args.length; j++) {
if (!args[j].startsWith('--')) return args[j]
}
return null
}
const eq = args.find(a => a.startsWith('--launch='))
return eq ? eq.slice('--launch='.length) : null
// --- "Open with PLWL" (Exec %f from the file manager) --------------------------
// Hands the file to the window (renderer shows the wine/prefix picker popup);
// headless boots open the window for it - a file cannot be launched without
// the user choosing how.
async function openWith(filePath) {
trace(`openWith ${filePath} serviceReady=${!!gamesService}`)
showMainWindow()
if (gamesService) gamesService.openExternally(filePath)
else openwith.setPending(filePath)
}
autoLaunchId = parseLaunchId(process.argv)
const auto = parseArgv(process.argv)
autoLaunchId = auto.launchId
autoOpenPath = auto.openPath
headless = !!autoLaunchId
autoQuitWhenDone = headless // booting just to run one game -> exit when it ends
@ -241,6 +241,11 @@ app.whenReady().then(async () => {
} else {
win = createMainWindow()
wireWindow(win)
// file open handed over while the service was still booting (or queued
// by a second instance racing the first boot)
if (autoOpenPath || openwith.hasPending()) {
openWith(autoOpenPath || openwith.getPending())
}
}
})