add support for directly launching windows exes from file exporer using .desktop shortcut
This commit is contained in:
parent
66f42f30cc
commit
5e217472aa
15 changed files with 440 additions and 28 deletions
108
src/openwith.js
Normal file
108
src/openwith.js
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
'use strict'
|
||||
|
||||
// "Open with PLWL" (file manager -> Open With -> PLWL): a file handed in via
|
||||
// `--open <path>` (or the Exec %f plumbing) is parked here until the renderer
|
||||
// picks it up, then launched ad hoc with the wine version + prefix chosen in
|
||||
// the picker popup. Nothing is imported into the library.
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
let pending = null // file path waiting for the picker (service not ready yet)
|
||||
|
||||
function setPending(p) {
|
||||
pending = p
|
||||
}
|
||||
|
||||
function getPending() {
|
||||
return pending
|
||||
}
|
||||
|
||||
function hasPending() {
|
||||
return !!pending
|
||||
}
|
||||
|
||||
function clearPending() {
|
||||
pending = null
|
||||
}
|
||||
|
||||
// The launcher's own translations may not cover openwith texts in older
|
||||
// installs - fall back to English keys so the UI never shows raw key names.
|
||||
function textsFor(code) {
|
||||
let texts = {}
|
||||
try {
|
||||
texts = require('./i18n').getLang(code).texts || {}
|
||||
} catch {}
|
||||
const fallback = {
|
||||
'openwith.title': 'Open with Wine',
|
||||
'openwith.file': 'File',
|
||||
'openwith.fileMissing': 'This file no longer exists.',
|
||||
'openwith.wine': 'Wine / Proton version',
|
||||
'openwith.wineDefault': 'Default ({0})',
|
||||
'openwith.prefix': 'Wine prefix',
|
||||
'openwith.prefixDefault': 'Default prefix ({0})',
|
||||
'openwith.run': 'Run',
|
||||
'openwith.startFailed': 'Could not start {0}: {1}',
|
||||
'openwith.notWindows': 'This is not a Windows executable (exe/msi/bat/cmd/lnk).'
|
||||
}
|
||||
return { ...fallback, ...texts }
|
||||
}
|
||||
|
||||
function resolveWine(id) {
|
||||
const system = { id: 'system', type: 'system', label: 'System Wine' }
|
||||
const wanted = id === 'default' ? require('./library').loadSettings().defaultWine || 'system' : id
|
||||
if (!wanted || wanted === 'system') return system
|
||||
return require('./wines').getWine(wanted) || system
|
||||
}
|
||||
|
||||
function resolvePrefix(name) {
|
||||
const defs = require('./prefixes').listPrefixes()
|
||||
const known = name && defs.prefixes.some(p => p.name === name) ? name : ''
|
||||
return known || defs.default || 'Main'
|
||||
}
|
||||
|
||||
// Ad-hoc run target: shaped exactly like a library game (runner.buildEnv and
|
||||
// the playtime/log paths expect those fields) but nothing is persisted.
|
||||
function makeTarget(openPath, wineId, prefixName) {
|
||||
const dir = path.dirname(openPath)
|
||||
return {
|
||||
id: 'openwith',
|
||||
name: path.basename(openPath),
|
||||
mode: 'open',
|
||||
path: dir,
|
||||
exePath: openPath,
|
||||
workdir: dir,
|
||||
wine: wineId || 'default',
|
||||
prefix: prefixName || '',
|
||||
args: '',
|
||||
dllOverrides: [],
|
||||
env: {},
|
||||
iconSource: '',
|
||||
playSeconds: 0,
|
||||
created: 0,
|
||||
lastPlayed: 0
|
||||
}
|
||||
}
|
||||
|
||||
// Launch without importing: resolves the chosen wine/prefix, builds the ad
|
||||
// hoc target and runs it exactly like a library app (same env, same logging,
|
||||
// same detached process group).
|
||||
async function run({ openPath, wine: wineId, prefix: prefixName0, lang, onStarted }) {
|
||||
if (!openPath || !fs.existsSync(openPath)) return { ok: false, error: 'file-missing' }
|
||||
if (!/\.(exe|msi|bat|cmd|lnk)$/i.test(openPath)) return { ok: false, error: 'not-windows' }
|
||||
const wine = resolveWine(wineId)
|
||||
const prefixName = resolvePrefix(prefixName0)
|
||||
const runner = require('./runner')
|
||||
try {
|
||||
// runAdHoc builds the synthetic target internally (shared `openwith` id,
|
||||
// so logs/playtime never collide with a library app id); the resolved
|
||||
// prefix is passed through so buildEnv uses the chosen one
|
||||
const res = await runner.runAdHoc({ openPath, wine, systemWine: wine.type === 'system', prefix: prefixName })
|
||||
if (onStarted) onStarted({ name: path.basename(openPath), pid: res.pid })
|
||||
return { ok: true, pid: res.pid || null, logFile: res.logFile || null }
|
||||
} catch (err) {
|
||||
return { ok: false, error: String((err && err.message) || err) }
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { setPending, getPending, hasPending, clearPending, run, makeTarget, resolveWine, resolvePrefix, textsFor }
|
||||
Loading…
Add table
Add a link
Reference in a new issue