diff --git a/src/linuxrunner.js b/src/linuxrunner.js index 1311a52..a60aece 100644 --- a/src/linuxrunner.js +++ b/src/linuxrunner.js @@ -217,6 +217,7 @@ function writeIfChanged(file, text, mode) { // what the seeded desktop entry starts. A scheme goes to the host's portal // (chooser included), a path to the request file the launcher watches - the // portal's answer is unreadable (development.md), the call is all we can trust. +// Mounted over the app's own xdg-open as well: see bundleOpenMounts. function handOffScript({ hostHome, fsRoot, request }) { const cases = [ `file://"$HOME") arg="file://${hostHome}" ;;`, @@ -234,8 +235,23 @@ function handOffScript({ hostHome, fsRoot, request }) { return `#!/bin/sh # Written by PLWL: what an app opens belongs to the host desktop. arg="$1" + +# the bundle's own xdg-open answers to gio-launch-desktop too (its bin links the +# two names to one file) and there the argument is not something to open but a +# program to start, so that name goes straight back to the command line +if [ "\${0##*/}" = "gio-launch-desktop" ]; then + export GIO_LAUNCHED_DESKTOP_FILE_PID=$$ + exec "$@" +fi + [ -n "$arg" ] || exit 1 +# a flag is not something to open: Java probes for xdg-open by running it with +# one, and handing that over would open garbage on the host desktop +case "$arg" in + -*) exit 2 ;; +esac + # a link is the host portal's from here on; anything else falls back open_portal() { command -v gdbus >/dev/null 2>&1 || return 1 @@ -296,15 +312,29 @@ function writeMimeSeed(root, home) { return writeIfChanged(path.join(root, MIME_REL), mime, 0o644) } +// A bundle ships an xdg-open wrapper in its own bin/ (sharun's) and that bin is +// first in the app's PATH, so the wrapper - not /usr/bin - is what an app's +// opens really reach. It dies before delegating whenever the caller closes the +// child's streams, which is what Minecraft does right after Runtime.exec +// (measured: SIGPIPE, and no hand-off at all), so the launcher's shim is +// mounted over the bundle's own path as well. Names the bundle links to that +// file (its gio-launch-desktop) are answered by the shim itself - bwrap will +// not mount over a symlink, so they cannot be given a script of their own. +function bundleOpenMounts({ appDir, shim }) { + if (!appDir || !shim) return [] + const wrapper = path.join(appDir, 'bin', SHIM_NAME) + return fs.existsSync(wrapper) ? [{ source: shim, target: wrapper }] : [] +} + // Everything an opened thing needs: the hand-off script, the association naming // it the default, and - in a sandbox - xdg-open replaced by the same script. -function urlRouting({ root, home, sandboxed, fsRoot = false }) { +function urlRouting({ root, home, sandboxed, fsRoot = false, appDir = '' }) { const target = OPEN_BINS.find(bin => fs.existsSync(bin)) || '' const opts = { hostHome: path.join(root, 'home'), fsRoot, request: requestPath(home) } if (!writeUrlOpener(root, opts)) return null writeMimeSeed(root, home) const shim = sandboxed && target ? writeUrlShim(root, opts) : '' - return { target, shim } + return { target, shim, mounts: bundleOpenMounts({ appDir, shim }) } } function hashOf(file) { @@ -434,7 +464,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), homeRedirects, routing: urlRouting({ root, home, sandboxed, fsRoot }) } + return { env, root, home, prefixName, prefixDir: prefixPath(prefixName), homeRedirects, routing: urlRouting({ root, home, sandboxed, fsRoot, appDir: writableAppDir(game) }) } } // Where a handed document goes inside the app's own home - a live bind, so a diff --git a/src/sandbox.js b/src/sandbox.js index 49890c4..396f38f 100644 --- a/src/sandbox.js +++ b/src/sandbox.js @@ -160,6 +160,10 @@ function buildBwrapArgs({ // and the home bind above would otherwise mount over and hide it - bwrap // then fails with "execvp : No such file or directory". ...(appDir ? ['--bind', appDir, appDir] : []), + // the bundle's own bin/ faces the shim too (see linuxrunner.js): its own + // xdg-open is what an app's PATH reaches first, so the hand-off script has + // to sit there as well. After the app bind, so the bundle is in place first. + ...(urlOpener && urlOpener.shim ? (urlOpener.mounts || []).flatMap(mount => ['--ro-bind', mount.source, mount.target]) : []), // --- environment --- '--setenv', 'HOME', home, '--setenv', 'XDG_CONFIG_HOME', xdgDir('.config'),