[linux apps] fix drag-n-drop & opening files

This commit is contained in:
olcxja 2026-10-08 13:59:08 +02:00
commit f4e770a7db
3 changed files with 80 additions and 5 deletions

View file

@ -61,6 +61,72 @@ function linuxUser() {
}
}
// The names the app owns in its home: the redirected XDG folders and the
// launcher's own folder. The host home never gets to shadow them.
const OWNED_HOME_NAMES = new Set([PLWL_DIR])
// The host home's own entries - what the desktop can hand an app (a drop from
// the file manager, a file picked in a dialog). A link resolving nowhere is
// not a path anything can be given, so it is skipped too.
function hostHomeEntries() {
const home = os.homedir()
let names
try {
names = fs.readdirSync(home)
} catch {
return []
}
return names.filter(name => !OWNED_HOME_NAMES.has(name) && fs.existsSync(path.join(home, name)))
}
// The launcher's own links are the ones pointing into the relaxed view.
function isFsRootLink(file) {
let target
try {
target = fs.readlinkSync(file)
} catch {
return false
}
return target.startsWith(FS_ROOT + path.sep)
}
function clearHomeRedirects(root) {
const dir = path.join(root, 'home')
let names
try {
names = fs.readdirSync(dir)
} catch {
return
}
for (const name of names) {
if (!isFsRootLink(path.join(dir, name))) continue
try {
fs.rmSync(path.join(dir, name), { force: true })
} catch {}
}
}
// Relaxed is the one level where the real filesystem is reachable (through
// FS_ROOT) - and there the app's home is mounted over the real one, so a path
// the desktop hands over names a folder the app cannot see. A link into that
// view gives exactly those names back, at their real paths; the app's own
// entries are never touched. No other level has a view to point at, so the
// links come off again there - they would only dangle.
function syncHomeRedirects(root, { relaxed }) {
clearHomeRedirects(root)
if (!relaxed) return []
const made = []
for (const name of hostHomeEntries()) {
const file = path.join(root, 'home', name)
if (fs.existsSync(file)) continue
try {
fs.symlinkSync(path.join(FS_ROOT, os.homedir(), name), file)
made.push(name)
} catch {}
}
return made
}
function copyFromHost(src, dst, { refresh = false } = {}) {
try {
if (!refresh && fs.existsSync(dst)) return false
@ -350,6 +416,7 @@ function buildLinuxEnv({ game, backend = 'env', fsRoot = false }) {
const prefixName = prefixNameForGame(game)
const root = ensureLinuxPfx(prefixName)
syncDesktopConfig(root)
const homeRedirects = syncHomeRedirects(root, { relaxed: backend === 'bwrap' && fsRoot })
const sandboxed = backend === 'bwrap'
const home = sandboxed ? `/home/${linuxUser()}` : path.join(root, 'home')
const env = { ...process.env }
@ -367,7 +434,7 @@ function buildLinuxEnv({ game, backend = 'env', fsRoot = false }) {
// the app's own environment comes last: it overrides everything above
for (const [k, v] of Object.entries(game.env || {})) env[k] = v
// an opened link or document leaves for the host desktop (see openrelay.js)
return { env, root, home, prefixName, prefixDir: prefixPath(prefixName), routing: urlRouting({ root, home, sandboxed, fsRoot }) }
return { env, root, home, prefixName, prefixDir: prefixPath(prefixName), homeRedirects, routing: urlRouting({ root, home, sandboxed, fsRoot }) }
}
// Where a handed document goes inside the app's own home - a live bind, so a
@ -475,12 +542,13 @@ async function runLinuxGame({ game, sandbox, onExit, openFile, handoff = false,
const exe = game.exePath
const workdir = game.workdir || path.dirname(exe)
const { env, root, home, routing } = buildLinuxEnv({ game, backend: chosen.backend, fsRoot: chosen.level === 'relaxed' })
const { env, root, home, homeRedirects, routing } = buildLinuxEnv({ game, backend: chosen.backend, fsRoot: chosen.level === 'relaxed' })
const argList = parseArgs(game.args)
const handed = await handedPathFor({ openFile, level: chosen.level, root, home, onProgress })
if (handed) argList.push(handed)
const header = [isolationLine(chosen)]
if (homeRedirects.length) header.push(`[plwl] home redirects: ${homeRedirects.length} entries through ${FS_ROOT}`)
if (game.forceX11) header.push('[plwl] x11 forced (XWayland)')
let cmd = exe
@ -497,6 +565,9 @@ async function runLinuxGame({ game, sandbox, onExit, openFile, handoff = false,
module.exports = {
DESKTOP_SEEDS,
hostHomeEntries,
syncHomeRedirects,
clearHomeRedirects,
HANDOFF_DIR,
STAGE_NOTICE_BYTES,
SHIM_REL,