improve shortcut 'open with' handling
This commit is contained in:
parent
59ff0b1088
commit
6565ded4d5
2 changed files with 73 additions and 34 deletions
|
|
@ -188,7 +188,15 @@ handlers in src/ipc/. Keep new APIs in that pattern.
|
|||
when it sits inside the prefix's drive_c, `Z:\...` otherwise. Giving a file
|
||||
to an app that is ALREADY running is a handoff (runGame `handoff: true`):
|
||||
an extra untracked process, no log/playtime writes, because the app's own
|
||||
single-instance logic is what actually opens the file.
|
||||
single-instance logic is what actually opens the file. The second-instance
|
||||
handoff must never read a value from the token next to its option: Electron
|
||||
RE-SERIALIZES that argv (all switches first, their values after, the dev app
|
||||
path among them), so `--launch <id> --file <path>` arrives as
|
||||
`--launch --file --allow-file-access-from-files <id> <path>`. argv.js pairs
|
||||
options with positionals IN ORDER (inline `--name=value` wins, the dev app
|
||||
path is skipped); reading the next token used to take the launch id as the
|
||||
opened file, so an app started without its image.
|
||||
`electron test/argv-handoff.test.js` proves the real serialization.
|
||||
- A sender hashing a big unit is minutes of network silence: it pings
|
||||
`/plwl/ping` (+token) every HEARTBEAT_MS for the whole sync, the receiver's
|
||||
handler re-arms its popup watchdog (stage 'wait' changes nothing on screen),
|
||||
|
|
|
|||
97
src/argv.js
97
src/argv.js
|
|
@ -10,49 +10,79 @@ const fs = require('fs')
|
|||
//
|
||||
// Supported forms:
|
||||
// --launch <id> | --launch=<id> auto-start that library app
|
||||
// --file <path> hand this file to the --launch app
|
||||
// --file <path> | --file=<path> hand this file to the --launch app
|
||||
// --open <path> | --open=<path> open an .exe with the wine picker
|
||||
// a bare existing-file path same as --open (plumbing for Exec %f)
|
||||
//
|
||||
// Electron hands the second instance's argv over RE-SERIALIZED: every switch
|
||||
// comes first and its value follows as a positional argument, e.g. a shortcut
|
||||
// `--launch <id> --file <path>` arrives as
|
||||
// `--launch --file --allow-file-access-from-files <id> <path>`. Taking the
|
||||
// token next to an option therefore picks the wrong value (it used to take
|
||||
// the launch id as the opened file, so an app started without its image).
|
||||
// Options and positionals are matched IN ORDER here instead.
|
||||
|
||||
function firstNonFlagAfter(args, i) {
|
||||
// Chromium injects its own flags into the second instance's argv and they
|
||||
// can land BETWEEN the option and its 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]
|
||||
const VALUE_OPTIONS = ['--launch', '--file', '--open']
|
||||
|
||||
// The options that carry a value, the positional arguments that follow the
|
||||
// switches, and the values written inline (`--name=value`, which keeps its
|
||||
// value no matter how the rest is ordered).
|
||||
//
|
||||
// Tokens before the first switch are the executable - never values. In dev
|
||||
// mode (`electron <app> ...`) Electron re-serializes the app path ALONG WITH
|
||||
// the values, so it is matched against the running app's own path and skipped:
|
||||
// it would otherwise be paired with the first option and shift every value.
|
||||
function splitArgv(argv) {
|
||||
const options = []
|
||||
const positional = []
|
||||
const inline = new Map()
|
||||
const appPath = process.defaultApp && process.argv[1] ? path.resolve(process.argv[1]) : null
|
||||
let switched = false
|
||||
for (const arg of Array.isArray(argv) ? argv : []) {
|
||||
if (typeof arg !== 'string' || !arg) continue
|
||||
if (arg.startsWith('-')) {
|
||||
switched = true
|
||||
const eq = arg.indexOf('=')
|
||||
const name = eq === -1 ? arg : arg.slice(0, eq)
|
||||
if (!VALUE_OPTIONS.includes(name)) continue
|
||||
if (eq === -1) options.push(name)
|
||||
else inline.set(name, arg.slice(eq + 1))
|
||||
continue
|
||||
}
|
||||
// absolute in the handed-over argv, possibly relative here
|
||||
if (appPath && path.resolve(arg) === appPath) continue
|
||||
if (switched) positional.push(arg)
|
||||
}
|
||||
return null
|
||||
return { options, positional, inline }
|
||||
}
|
||||
|
||||
function findValue(args, names) {
|
||||
for (const name of names) {
|
||||
const i = args.indexOf(name)
|
||||
if (i !== -1) return firstNonFlagAfter(args, i)
|
||||
const eq = args.find(a => a.startsWith(name + '='))
|
||||
if (eq) return eq.slice(name.length + 1)
|
||||
}
|
||||
return null
|
||||
// The value of an option: inline when it was written as `--name=value`,
|
||||
// otherwise the positional argument with the SAME position as the option
|
||||
// among our value options (Chromium pushes all switches before all values, so
|
||||
// any other pairing would read another option's value).
|
||||
function optionValue(parsed, name) {
|
||||
if (parsed.inline.has(name)) return parsed.inline.get(name)
|
||||
const index = parsed.options.indexOf(name)
|
||||
if (index === -1) return null
|
||||
const value = parsed.positional[index]
|
||||
return value === undefined ? null : value
|
||||
}
|
||||
|
||||
// PLWL's own options must not swallow Chromium's flags (--flag=... style),
|
||||
// so anything after a flag that itself carries a value is skipped here by
|
||||
// looking only at the KNOWN option names above.
|
||||
// The file an app shortcut was asked to open: `--file <path>` when the
|
||||
// generated shortcut passes it explicitly, or the path a file manager appends
|
||||
// on its own to an Exec line without a file field (KDE does that). Only an
|
||||
// existing absolute FILE counts in the fallback, so Chromium's injected flags
|
||||
// and the launcher's own path can never be mistaken for the opened document.
|
||||
// absolute path counts in either place: the launch id, Chromium's injected
|
||||
// flags and the launcher's own path can never be mistaken for the document.
|
||||
const FIELD_CODE_RE = /^%[fFuUdDnNickvm]?$/
|
||||
function launchFileOf(args, launchId) {
|
||||
const explicit = findValue(args, ['--file'])
|
||||
function launchFileOf(parsed, launchId) {
|
||||
const explicit = optionValue(parsed, '--file')
|
||||
// a desktop environment that does not expand field codes would hand over
|
||||
// the literal token - never treat that as a file path
|
||||
if (explicit && !FIELD_CODE_RE.test(explicit)) return explicit
|
||||
if (explicit && !FIELD_CODE_RE.test(explicit) && path.isAbsolute(explicit)) return explicit
|
||||
if (!launchId) return null
|
||||
for (let i = args.length - 1; i > 1; i--) {
|
||||
const arg = args[i]
|
||||
if (typeof arg !== 'string' || arg.startsWith('-') || !path.isAbsolute(arg)) continue
|
||||
for (let i = parsed.positional.length - 1; i >= 0; i--) {
|
||||
const arg = parsed.positional[i]
|
||||
if (!path.isAbsolute(arg)) continue
|
||||
try {
|
||||
if (fs.statSync(arg).isFile()) return arg
|
||||
} catch {}
|
||||
|
|
@ -62,9 +92,10 @@ function launchFileOf(args, launchId) {
|
|||
|
||||
function parseArgv(argv) {
|
||||
const args = Array.isArray(argv) ? argv : process.argv
|
||||
const launchId = findValue(args, ['--launch'])
|
||||
const launchFile = launchFileOf(args, launchId)
|
||||
let openPath = findValue(args, ['--open'])
|
||||
const parsed = splitArgv(args)
|
||||
const launchId = optionValue(parsed, '--launch')
|
||||
const launchFile = launchFileOf(parsed, launchId)
|
||||
let openPath = optionValue(parsed, '--open')
|
||||
if (!openPath && !launchId) {
|
||||
// "Open with" plumbing (Exec %f): the file path arrives as the only
|
||||
// non-flag argument - never pick a bare token when a real flag carries
|
||||
|
|
@ -74,11 +105,11 @@ function parseArgv(argv) {
|
|||
a =>
|
||||
!a.startsWith('-') &&
|
||||
/\.(exe|msi|bat|cmd|lnk)$/i.test(a) &&
|
||||
!/(electron|olcxja\.plwl)(\.exe)?$/i.test(a)
|
||||
!/\b(electron|olcxja\.plwl)(\.exe)?$/i.test(a)
|
||||
)
|
||||
if (bare) openPath = bare
|
||||
}
|
||||
return { launchId, openPath, launchFile }
|
||||
}
|
||||
|
||||
module.exports = { parseArgv, findValue }
|
||||
module.exports = { parseArgv }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue