diff --git a/flake.nix b/flake.nix index 6d44301..7b4e905 100644 --- a/flake.nix +++ b/flake.nix @@ -56,10 +56,11 @@ }) self.packages; # Tests of the VR keyboard (text model, corrector, swipe decoder on the - # default German + English dictionary), the Jellyfin mpv shim and - # install.sh cleanup: nix flake check + # default German + English dictionary), the Jellyfin mpv shim, the + # Firefox launcher and install.sh cleanup: nix flake check checks = forSystems (pkgs: { cleanup = import ./modules/cleanup/check.nix { inherit pkgs; }; + firefox = import ./modules/firefox/check.nix { inherit pkgs; }; jellyfin = import ./modules/jellyfin/check.nix { inherit pkgs; }; vr-keyboard = (import ./modules/vr-keyboard/build.nix { inherit pkgs; diff --git a/install.sh b/install.sh index 369e458..fcd0610 100755 --- a/install.sh +++ b/install.sh @@ -74,8 +74,7 @@ Commands: or before removing it. --orphans what the configuration no longer uses (run by the Home Manager module on every switch); keeps saved choices. - --keep still in use (with --orphans): debugger, - firefox-desktop-userjs= + --keep still in use (with --orphans): debugger --dry-run only print what would be done --quiet print only actions, deferrals and warnings SteamVR's VRWebHelper.DebuggerEnabled can't be changed while SteamVR @@ -396,10 +395,12 @@ EOF # icons links hicolor/scalable/apps/.svg -> Breeze in the store # (the icon-fallbacks script of 2026-09; manifest # $SFN_STATE/icon-fallbacks). -# firefox user.js in Firefox profiles: links to the desktop profile's -# /app/etc/firefox/steam-frame-nix-desktop-user.js, older links -# to *-firefox-*user.js and copies starting with FF_MARKER, and -# the values they left in prefs.js (only with Firefox closed). +# firefox user.js in Firefox profiles: links to +# /app/etc/firefox/steam-frame-nix-desktop-user.js (the +# launcher's, while the desktop profile runs; left alone while +# the profile is in use), older links to *-firefox-*user.js and +# copies starting with FF_MARKER, and the values they left in +# prefs.js (only with Firefox closed). # jellyfin the hwdec shim entries in the Jellyfin Flatpak's user override # (nix-flatpak), an empty override file, and the shim copy of # earlier versions (marker $SFN_STATE/jellyfin-hwdec-shim). @@ -714,8 +715,8 @@ clean_icons() { ff_keys() { sed -n 's/^[[:space:]]*user_pref(\("[^"]*"\),.*/\1/p' "$1" 2>/dev/null || true; } ff_in_use() { find /proc/[0-9]*/fd -lname "$1/.parentlock" -print -quit 2>/dev/null | grep -q .; } -clean_firefox() { # keep_profile ('' = none) - local keep=$1 prof name u t kind keys pats left +clean_firefox() { # all + local all=$1 prof name u t kind keys pats left [[ -d $FF_DIR ]] || return 0 for prof in "$FF_DIR"/*/; do prof=${prof%/}; name=${prof##*/}; u=$prof/user.js @@ -725,7 +726,11 @@ clean_firefox() { # keep_profile ('' = none) t="$(readlink "$u")" case $t in "$FF_DESKTOP_JS") - [[ $name == "$keep" ]] && continue + # The launcher's, while Firefox runs in the desktop profile. + if ff_in_use "$prof"; then + (( all )) && c_defer "$u: Firefox is using profile $name; close it and run this again" + continue + fi kind="desktop profile user.js link"; keys='"full-screen-api.ignore-widgets"' ;; /nix/store/*-firefox-user.js|/nix/store/*-firefox-desktop-user.js) kind="user.js link of an older version"; keys="$(ff_keys "$u")" @@ -846,7 +851,7 @@ clean_ui_state() { # all } cmd_cleanup() { - local mode='' keep_debugger=0 keep_ff='' keeps=0 k + local mode='' keep_debugger=0 keeps=0 k while (( $# )); do case $1 in --all) mode=all; shift ;; @@ -858,8 +863,7 @@ cmd_cleanup() { k=$2; shift 2; keeps=$((keeps + 1)) case $k in debugger) keep_debugger=1 ;; - firefox-desktop-userjs=?*) keep_ff=${k#*=} ;; - *) die "cleanup: unknown artifact '$k' for --keep (debugger, firefox-desktop-userjs=)" ;; + *) die "cleanup: unknown artifact '$k' for --keep (debugger)" ;; esac ;; -h|--help) usage; exit 0 ;; *) die "cleanup: unknown option '$1' (see --help)" ;; @@ -875,7 +879,7 @@ cmd_cleanup() { if (( ! CLEAN_QUIET )); then step "$CLEAN_HEADER"; CLEAN_HEADER=''; fi clean_debugger "$keep_debugger" clean_icons - clean_firefox "$keep_ff" + clean_firefox "$([[ $mode == all ]] && echo 1 || echo 0)" clean_jellyfin clean_ui_state "$([[ $mode == all ]] && echo 1 || echo 0)" c_rmdir "$SFN_STATE" diff --git a/modules/cleanup/check.nix b/modules/cleanup/check.nix index 534ce9a..c8b881d 100644 --- a/modules/cleanup/check.nix +++ b/modules/cleanup/check.nix @@ -156,6 +156,7 @@ pkgs.runCommand "cleanup-check" { nativeBuildInputs = [ cleanup pkgs.jq ]; } '' echo absent > $S/steamvr-debugger.armed mkdir -p $F/desktop $F/x.default ln -s /app/etc/firefox/steam-frame-nix-desktop-user.js $F/desktop/user.js + touch $F/desktop/.parentlock; exec 8< $F/desktop/.parentlock # the launcher's, Firefox running ln -s /app/etc/firefox/steam-frame-nix-desktop-user.js $F/x.default/user.js printf '[Context]\ndevices=all;\nfilesystems=/nix/store/00000000000000000000000000000000-mpv-hwdec-shim:ro;\n\n[Environment]\nLD_PRELOAD=/nix/store/00000000000000000000000000000000-mpv-hwdec-shim/lib/mpv-hwdec-shim.so\nSFN_MPV_HWDEC=x\n' > $O/org.jellyfin.JellyfinDesktop mkdir -p $HOME/.var/app/org.jellyfin.JellyfinDesktop @@ -164,25 +165,28 @@ pkgs.runCommand "cleanup-check" { nativeBuildInputs = [ cleanup pkgs.jq ]; } '' echo '{' > $S/ui-patches/x.json.tmp; touch -d '-1 hour' $S/ui-patches/x.json.tmp echo '{' > $S/ui-patches/y.json.tmp # being written: kept echo active > $STUB/state - res=$(steam-frame-nix-cleanup --orphans --keep debugger --keep firefox-desktop-userjs=desktop); echo "$res" + res=$(steam-frame-nix-cleanup --orphans --keep debugger); echo "$res" hasnt "$res" "deferred" # kept: the next start re-arms it there $root/run/systemd/user/steamvr.service.d/50-steam-frame-nix-debugger.conf $S/steamvr-debugger.armed there $F/desktop/user.js $S/ui-patches/frame-controls.json $S/ui-patches/vr-cat.json $S/ui-patches/y.json.tmp gone $F/x.default/user.js $O/org.jellyfin.JellyfinDesktop $S/ui-patches/x.json.tmp there $HOME/.var/app/org.jellyfin.JellyfinDesktop/mpv-hwdec-shim.so has "$res" "left alone: $HOME/.var/app/org.jellyfin.JellyfinDesktop/mpv-hwdec-shim.so" - noop --orphans --keep debugger --keep firefox-desktop-userjs=desktop + noop --orphans --keep debugger # SteamVR stopped (e.g. after a power loss): the key is restored, hook kept echo inactive > $STUB/state - res=$(steam-frame-nix-cleanup --orphans --keep debugger --keep firefox-desktop-userjs=desktop) + res=$(steam-frame-nix-cleanup --orphans --keep debugger) [ "$(jq -c . $V)" = '{}' ] || fail "orphans restore: $(cat $V)" gone $S/steamvr-debugger.armed there $root/run/systemd/user/steamvr.service.d/50-steam-frame-nix-debugger.conf # debugger no longer kept: runtime pieces go - res=$(steam-frame-nix-cleanup --orphans --keep firefox-desktop-userjs=desktop) + res=$(steam-frame-nix-cleanup --orphans) gone $root/run/steam-frame-nix $root/run/systemd - noop --orphans --keep firefox-desktop-userjs=desktop + noop --orphans there $S/ui-patches/frame-controls.json $HOME/.local/share/icons/hicolor/scalable/apps # not ours + exec 8<&- # Firefox closed + res=$(steam-frame-nix-cleanup --orphans) + gone $F/desktop/user.js ! steam-frame-nix-cleanup --all --keep debugger 2>/dev/null || fail "--all --keep accepted" ! steam-frame-nix-cleanup --orphans --keep bogus 2>/dev/null || fail "unknown --keep accepted" echo "C ok" diff --git a/modules/firefox.nix b/modules/firefox.nix index 705c69e..920f28e 100644 --- a/modules/firefox.nix +++ b/modules/firefox.nix @@ -12,14 +12,14 @@ # fullscreen inside the window. # - desktopProfile: the sessions have separate buses/displays, so a second # Firefox can't reach the running one and hits the profile lock; the nested -# desktop gets its own profile. Fullscreen works there, so the fix is undone -# by a user.js in that profile: a link to the extension's -# steam-frame-nix-desktop-user.js (a sandbox path; dangling on the host). -# - profileSync (on switch and before each launch) keeps those links and -# removes what older versions wrote: user.js copies starting with `marker` -# and links to *-firefox-*user.js store files, whose user_pref values -# Firefox had stored in prefs.js. They are taken out of prefs.js too, which -# needs the profile closed; profiles in use are left for the next run. +# desktop gets its own profile (a normal Firefox profile: browser data). +# Fullscreen works there, so the launcher undoes the fix in that profile +# only while its Firefox runs: a user.js link to the extension's +# steam-frame-nix-desktop-user.js (a sandbox path; dangling on the host), +# made right before Firefox starts and removed, with the value Firefox +# stored from it in prefs.js, once it has exited (firefox/launcher.nix). +# Leftovers (a crash) and what older versions wrote into profiles (user.js +# copies and links) are removed by steam-frame-nix-cleanup (on switch). # The entry shadows the Flatpak's (same ID), keeping MIME associations, and is # seen by the "+" menu (which reads only ~/.local/share/applications). { config, pkgs, lib, ... }: @@ -32,7 +32,7 @@ let // lib.optionalAttrs cfg.vrFullscreenFix { "full-screen-api.ignore-widgets" = true; } // cfg.prefs; desktopFix = cfg.enable && cfg.vrFullscreenFix && cfg.desktopProfile != null; - # Only ever this one pref (profileSync relies on it). + # Only ever this one pref (steam-frame-nix-cleanup relies on it). desktopKeys = [ "full-screen-api.ignore-widgets" ]; desktopUserJsName = "steam-frame-nix-desktop-user.js"; @@ -46,78 +46,11 @@ let (k: "user_pref(${builtins.toJSON k}, false);\n") desktopKeys)} $out/${desktopUserJsName} ''); - # Written by versions that copied user.js into every profile. - marker = "// Managed by steam-frame-nix (steamFrame.firefox); rewritten on switch."; - - profileSync = pkgs.writeShellScript "firefox-profile-sync" '' - PATH=${lib.makeBinPath (with pkgs; [ coreutils diffutils findutils gnugrep gnused ])} - ffDir="${ffDir}" - desktopJs=/app/etc/firefox/${desktopUserJsName} - [ -d "$ffDir" ] || exit 0 - - # Firefox holds .parentlock open while it uses a profile (the sandbox - # sees the profile at the same path). - inUse() { find /proc/[0-9]*/fd -lname "$1/.parentlock" -print -quit 2>/dev/null | grep -q .; } - keysOf() { sed -n 's/^[[:space:]]*user_pref(\("[^"]*"\),.*/\1/p' "$1" 2>/dev/null; } - - for prof in "$ffDir"/*/; do - prof=''${prof%/}; name=''${prof##*/} - [ -f "$prof/prefs.js" ] || [ "$name" = ${lib.escapeShellArg (toString cfg.desktopProfile)} ] || continue - want= wantKeys= - ${lib.optionalString desktopFix '' - [ "$name" = ${lib.escapeShellArg cfg.desktopProfile} ] && - want=$desktopJs wantKeys=${lib.escapeShellArg (lib.concatMapStrings (k: builtins.toJSON k + "\n") desktopKeys)} - ''} - userJs=$prof/user.js - if [ -L "$userJs" ]; then - case $(readlink "$userJs") in - "$want") continue ;; - "$desktopJs") oldKeys=${lib.escapeShellArg (lib.concatMapStrings (k: builtins.toJSON k + "\n") desktopKeys)} ;; - /nix/store/*-firefox-user.js|/nix/store/*-firefox-desktop-user.js) oldKeys=$(keysOf "$userJs") ;; - *) oldKeys=foreign ;; - esac - elif [ -f "$userJs" ]; then - if [ "$(head -n1 "$userJs")" = ${lib.escapeShellArg marker} ]; then oldKeys=$(keysOf "$userJs"); else oldKeys=foreign; fi - elif [ -e "$userJs" ]; then oldKeys=foreign - else oldKeys= - fi - if [ "$oldKeys" = foreign ]; then - [ -z "$want" ] || echo "firefox: skipping $userJs (not managed by steam-frame-nix, move it away to adopt)" - continue - fi - - if [ -e "$userJs" ] || [ -L "$userJs" ]; then - # Values our user.js set, no longer set by one: out of prefs.js. - keys=$(printf '%s\n' "$oldKeys" | grep -vxF -f <(printf '%s\n' "$wantKeys") | grep .) - if [ -n "$keys" ]; then - if inUse "$prof"; then - echo "firefox: $name is in use; its old user.js stays until the next switch or launch with Firefox closed" - continue - fi - grep -vF "$(sed 's/.*/user_pref(&,/' <<< "$keys")" "$prof/prefs.js" > "$prof/prefs.js.sfn" || true - if cmp -s "$prof/prefs.js" "$prof/prefs.js.sfn"; then rm "$prof/prefs.js.sfn" - else cat "$prof/prefs.js.sfn" > "$prof/prefs.js"; rm "$prof/prefs.js.sfn"; echo "firefox: $name: removed from prefs.js:" $keys - fi - fi - rm -f "$userJs" - fi - if [ -n "$want" ]; then ln -s "$want" "$userJs"; fi - done - ''; - - firefox = pkgs.writeShellScript "firefox-launcher" ('' - profile=() - '' + lib.optionalString (cfg.desktopProfile != null) '' - if [ "$XDG_CURRENT_DESKTOP" = KDE ]; then - # Firefox exits (status 1) if the --profile dir doesn't exist yet. - mkdir -p "${profileDir}" - profile=(--profile "${profileDir}") - fi - '' + '' - ${profileSync} >&2 || true - exec /usr/bin/flatpak run --branch=stable --arch=aarch64 --command=firefox \ - --file-forwarding org.mozilla.firefox "''${profile[@]}" "$@" - ''); + firefox = pkgs.callPackage ./firefox/launcher.nix { + profileDir = if cfg.desktopProfile == null then null else profileDir; + inherit desktopFix desktopKeys; + desktopJs = "/app/etc/firefox/${desktopUserJsName}"; + }; in { imports = [ ./cleanup.nix ]; @@ -158,49 +91,43 @@ in { type = lib.types.nullOr lib.types.str; default = "desktop"; description = '' - Profile used in the nested desktop, so both sessions can run Firefox at - once; null = default profile in both. + Profile (directory name under the Flatpak's + ~/.var/app/org.mozilla.firefox/config/mozilla/firefox) used in the + nested desktop, so both sessions can run Firefox at once; created on + first use, a normal profile with its own browser data. null = the + default profile in both sessions. ''; }; }; - config = lib.mkMerge [ - { - # Always, so disabling removes our user.js links (and old copies). - home.activation.firefoxProfiles = - lib.hm.dag.entryAfter [ "writeBoundary" ] "run ${profileSync}\n"; - # The desktop profile's user.js link (steam-frame-nix-cleanup). - steamFrame.cleanup.keep = lib.optional desktopFix "firefox-desktop-userjs=${cfg.desktopProfile}"; - } - (lib.mkIf cfg.enable { - # stable: the branch the launcher runs (the extension point has no - # version, so it takes the app's branch). - xdg.dataFile = lib.optionalAttrs (defaultPrefs != { } || desktopFix) { - "flatpak/extension/org.mozilla.firefox.systemconfig/aarch64/stable".source = sysconfig; - } // { - "applications/org.mozilla.firefox.desktop".text = '' - [Desktop Entry] - Type=Application - Name=Firefox - GenericName=Web Browser - Icon=org.mozilla.firefox - Exec=${firefox} @@u %u @@ - StartupWMClass=firefox - StartupNotify=true - Terminal=false - Categories=Network;WebBrowser; - MimeType=application/json;application/pdf;application/rdf+xml;application/rss+xml;application/x-xpinstall;application/xhtml+xml;application/xml;audio/flac;audio/ogg;audio/webm;image/avif;image/gif;image/jpeg;image/png;image/svg+xml;image/webp;text/html;text/xml;video/ogg;video/webm;x-scheme-handler/chrome;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/mailto; - Actions=new-window;new-private-window; + config = lib.mkIf cfg.enable { + # stable: the branch the launcher runs (the extension point has no + # version, so it takes the app's branch). + xdg.dataFile = lib.optionalAttrs (defaultPrefs != { } || desktopFix) { + "flatpak/extension/org.mozilla.firefox.systemconfig/aarch64/stable".source = sysconfig; + } // { + "applications/org.mozilla.firefox.desktop".text = '' + [Desktop Entry] + Type=Application + Name=Firefox + GenericName=Web Browser + Icon=org.mozilla.firefox + Exec=${firefox} @@u %u @@ + StartupWMClass=firefox + StartupNotify=true + Terminal=false + Categories=Network;WebBrowser; + MimeType=application/json;application/pdf;application/rdf+xml;application/rss+xml;application/x-xpinstall;application/xhtml+xml;application/xml;audio/flac;audio/ogg;audio/webm;image/avif;image/gif;image/jpeg;image/png;image/svg+xml;image/webp;text/html;text/xml;video/ogg;video/webm;x-scheme-handler/chrome;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/mailto; + Actions=new-window;new-private-window; - [Desktop Action new-window] - Name=New Window - Exec=${firefox} --new-window @@u %u @@ + [Desktop Action new-window] + Name=New Window + Exec=${firefox} --new-window @@u %u @@ - [Desktop Action new-private-window] - Name=New Private Window - Exec=${firefox} --private-window @@u %u @@ - ''; - }; - }) - ]; + [Desktop Action new-private-window] + Name=New Private Window + Exec=${firefox} --private-window @@u %u @@ + ''; + }; + }; } diff --git a/modules/firefox/check.nix b/modules/firefox/check.nix new file mode 100644 index 0000000..808f20e --- /dev/null +++ b/modules/firefox/check.nix @@ -0,0 +1,69 @@ +# Checks of the Firefox launcher's desktop profile user.js (launcher.nix) +# against a fake flatpak: the link exists only while Firefox runs, a second +# launch that hands over to the running Firefox doesn't remove it, the value +# Firefox stored from it leaves prefs.js once the profile is unlocked, and a +# user.js of the user's own is never touched. +{ pkgs }: +let + # Fake `flatpak run … org.mozilla.firefox --profile DIR …`: with the + # profile locked by another process it "forwards" and returns; otherwise it + # holds .parentlock, records user.js and stores the pref like Firefox does, + # and runs until $FAKE/release exists. + fakeFlatpak = pkgs.writeShellScript "flatpak" '' + while [ $# -gt 0 ] && [ "$1" != --profile ]; do shift; done + [ $# -gt 0 ] || { echo "default profile" >> $FAKE/log; exit 0; } + prof=$2 + if find /proc/[0-9]*/fd -lname "$prof/.parentlock" -print -quit 2>/dev/null | grep -q .; then + echo forwarded >> $FAKE/log; exit 0 + fi + touch "$prof/.parentlock"; exec 9< "$prof/.parentlock" + echo "started $(readlink "$prof/user.js" || echo none)" >> $FAKE/log + [ -L "$prof/user.js" ] && echo 'user_pref("full-screen-api.ignore-widgets", false);' >> "$prof/prefs.js" + while [ ! -e $FAKE/release ]; do sleep 0.1; done + exit 3 + ''; + launcher = pkgs.callPackage ./launcher.nix { + flatpakBin = fakeFlatpak; + profileDir = "$HOME/ff/desktop"; + desktopFix = true; + }; +in +pkgs.runCommand "firefox-check" { nativeBuildInputs = [ pkgs.findutils pkgs.gnugrep ]; } '' + set -euo pipefail + fail() { echo "FAIL: $*" >&2; cat $FAKE/log >&2 || true; exit 1; } + export HOME=$PWD/home FAKE=$PWD/fake XDG_CURRENT_DESKTOP=KDE + mkdir -p $HOME $FAKE; : > $FAKE/log + p=$HOME/ff/desktop + + # first launch: link made, Firefox runs + ${launcher} https://example.org & first=$! + for i in $(seq 100); do grep -q started $FAKE/log && break; sleep 0.1; done + grep -qx 'started /app/etc/firefox/steam-frame-nix-desktop-user.js' $FAKE/log || fail "not linked at start" + echo 'user_pref("other", 1);' >> $p/prefs.js + # second launch: hands over, returns, cleans nothing + ${launcher} https://example.com + grep -qx forwarded $FAKE/log || fail "no forward" + [ -L $p/user.js ] || fail "second launch removed the link" + # Firefox exits: link and value gone, exit status passed on + touch $FAKE/release + status=0; wait $first || status=$? + [ $status = 3 ] || fail "status $status" + [ ! -e $p/user.js ] && [ ! -L $p/user.js ] || fail "link left" + [ "$(cat $p/prefs.js)" = 'user_pref("other", 1);' ] || fail "prefs.js: $(cat $p/prefs.js)" + + # a user.js of the user's own: untouched + echo 'user_pref("mine", 1);' > $p/user.js + echo 'user_pref("full-screen-api.ignore-widgets", false);' >> $p/prefs.js + ${launcher} || true + [ "$(cat $p/user.js)" = 'user_pref("mine", 1);' ] || fail "own user.js changed" + grep -q ignore-widgets $p/prefs.js || fail "prefs.js changed with an own user.js" + rm $p/user.js + + # Steam session: default profile, no link + : > $FAKE/log + XDG_CURRENT_DESKTOP=gamescope ${launcher} || true + grep -qx 'default profile' $FAKE/log || fail "used --profile in the Steam session" + [ ! -e $p/user.js ] || fail "linked in the Steam session" + echo ok + touch $out +'' diff --git a/modules/firefox/launcher.nix b/modules/firefox/launcher.nix new file mode 100644 index 0000000..8436440 --- /dev/null +++ b/modules/firefox/launcher.nix @@ -0,0 +1,53 @@ +# The Firefox Flatpak launcher (firefox.nix). In the nested desktop (KDE) it +# uses the desktop profile; with the fullscreen fix it links that profile's +# user.js to the sandbox path desktopJs (undoing the fix there) right before +# Firefox starts, and once Firefox has exited and the profile is no longer in +# use (a second launch that only hands its URL to the running Firefox returns +# at once) removes the link and the value Firefox stored from it in +# prefs.js. A user.js of the user's own is never touched. After a crash the +# next launch or steam-frame-nix-cleanup finishes the removal. +{ lib, writeShellScript, coreutils, findutils, gnugrep +, flatpakBin ? "/usr/bin/flatpak" # (not `flatpak`: callPackage would pass pkgs.flatpak) +, profileDir ? null # shell word, e.g. "$HOME/.var/app/…/desktop"; null: no desktop profile +, desktopFix ? false +, desktopJs ? "/app/etc/firefox/steam-frame-nix-desktop-user.js" +, desktopKeys ? [ "full-screen-api.ignore-widgets" ] +}: +writeShellScript "firefox-launcher" ('' + PATH=${lib.makeBinPath [ coreutils findutils gnugrep ]}:$PATH + run=(${flatpakBin} run --branch=stable --arch=aarch64 --command=firefox --file-forwarding org.mozilla.firefox) +'' + (if profileDir == null then '' + exec "''${run[@]}" "$@" +'' else '' + if [ "$XDG_CURRENT_DESKTOP" != KDE ]; then exec "''${run[@]}" "$@"; fi + prof="${profileDir}" + # Firefox exits (status 1) if the --profile dir doesn't exist yet. + mkdir -p "$prof" +'' + lib.optionalString desktopFix '' + js=${lib.escapeShellArg desktopJs} + u=$prof/user.js + # Firefox holds .parentlock open while it uses a profile (the sandbox sees + # the profile at the same path). + inUse() { find /proc/[0-9]*/fd -lname "$prof/.parentlock" -print -quit 2>/dev/null | grep -q .; } + ours() { [ -L "$u" ] && [ "$(readlink "$u")" = "$js" ]; } + sfn_unlink() { + ours && ! inUse || return 0 + if [ -f "$prof/prefs.js" ] && grep -qF -f <(printf '%s\n' ${lib.escapeShellArgs (map (k: "user_pref(${builtins.toJSON k},") desktopKeys)}) "$prof/prefs.js"; then + grep -vF -f <(printf '%s\n' ${lib.escapeShellArgs (map (k: "user_pref(${builtins.toJSON k},") desktopKeys)}) "$prof/prefs.js" > "$prof/prefs.js.sfn" || true + cat "$prof/prefs.js.sfn" > "$prof/prefs.js" + rm -f "$prof/prefs.js.sfn" + fi + rm -f "$u" + } + if [ -e "$u" ] || [ -L "$u" ]; then + ours || echo "firefox: $u is not steam-frame-nix's; fullscreen stays inside the window in this profile" >&2 + else + ln -s "$js" "$u" + fi + "''${run[@]}" --profile "$prof" "$@" + status=$? + sfn_unlink + exit $status +'' + lib.optionalString (!desktopFix) '' + exec "''${run[@]}" --profile "$prof" "$@" +''))