improvemets & fixes:

- added 'open with' handling to desktop shortcuts
- move logs button to app configuration
- fix sync progress disappearing after long hashing
- fix sync double file counter
- merge wine and proton prefix folders via symlink
This commit is contained in:
olcxja 2026-10-04 19:12:33 +02:00
commit 59ff0b1088
15 changed files with 520 additions and 113 deletions

View file

@ -221,6 +221,25 @@ function parseArgs(args) {
return out
}
// A file handed over from the Linux file manager ("Open with" on an app
// shortcut) has to reach the Windows app as a WINDOWS path, the way it would
// on Windows: C:\ for anything inside the prefix's C: drive, and wine's Z:\
// mapping (the filesystem root) for everything else.
function windowsPathFor(file, prefixDir) {
let abs = String(file || '')
try {
abs = fs.realpathSync(abs)
} catch {}
try {
const driveC = fs.realpathSync(path.join(prefixDir, 'drive_c'))
const rel = path.relative(driveC, abs)
if (rel && !rel.startsWith('..') && !path.isAbsolute(rel)) {
return 'C:\\' + rel.split(path.sep).join('\\')
}
} catch {}
return `Z:${abs.split(path.sep).join('\\')}`
}
const GRACE_MS = 60000
// Live playtime: while the game runs, update ONLY the tiny playtime file
@ -250,13 +269,15 @@ function openLatestLog(gameId) {
return fs.createWriteStream(logFile, { flags: 'w' })
}
async function runGame({ game, wine, systemWine, onExit }) {
async function runGame({ game, wine, systemWine, onExit, openFile, handoff = false }) {
await healPrefixOnce(game)
const isProton = wine && wine.type === 'proton'
const { env } = buildEnv({ game, wine })
const { env, prefixDir } = buildEnv({ game, wine })
const exe = game.exePath
const workdir = game.workdir || path.dirname(exe)
const argList = parseArgs(game.args)
// an opened file is an argument to the exe, exactly like Windows' "Open with"
if (openFile) argList.push(windowsPathFor(openFile, prefixDir))
let cmd
let base
@ -275,7 +296,10 @@ async function runGame({ game, wine, systemWine, onExit }) {
child = spawn(cmd, base, {
cwd: workdir,
env,
stdio: ['ignore', 'pipe', 'pipe'],
// a handoff (a file given to an already running app) is not tracked:
// its pipes are left unread, so the output goes nowhere instead of
// filling up and blocking the app
stdio: handoff ? 'ignore' : ['ignore', 'pipe', 'pipe'],
detached: true
})
} catch (err) {
@ -293,6 +317,12 @@ async function runGame({ game, wine, systemWine, onExit }) {
})
child.once('spawn', () => {
started = true
// a handoff never writes the app's log or playtime: the tracked run owns
// both, and truncating the live log would destroy what it is writing
if (handoff) {
child.unref()
return resolve({ pid: child.pid, logFile: null })
}
const logFile = latestLogPath(game.id)
const ws = openLatestLog(game.id)
// a launch line of our own before the raw wine output: when a game
@ -365,4 +395,4 @@ function runAdHoc({ openPath, wine, systemWine, prefix, onExit }) {
return runGame({ game, wine, systemWine, onExit })
}
module.exports = { buildEnv, runGame, runTool, runAdHoc, parseArgs, latestLogPath }
module.exports = { buildEnv, runGame, runTool, runAdHoc, parseArgs, latestLogPath, windowsPathFor }