mirror of
https://github.com/lhns/steam-frame-nix.git
synced 2026-10-06 01:00:13 +02:00
prefs and vrFullscreenFix become default prefs (pref()) in defaults/pref/steam-frame-nix.js of /app/etc/firefox, the mount point of the org.mozilla.firefox.systemconfig extension point. home-manager provides the extension as a user "unmaintained extension" link ($XDG_DATA_HOME/flatpak/extension/<id>/aarch64/stable) to a store dir, which Flatpak mounts itself: the sandbox doesn't see /nix. Default prefs are never written to prefs.js, so removing one leaves nothing behind. The desktop profile undoes the fullscreen fix with a user.js linked to /app/etc/firefox/steam-frame-nix-desktop-user.js. The sync (on switch, and before each launch) removes the user.js copies and store links of older versions and takes their values out of prefs.js, for each profile not in use (Firefox holds .parentlock open).
194 lines
8.9 KiB
Nix
194 lines
8.9 KiB
Nix
# Firefox Flatpak (org.mozilla.firefox) launcher.
|
|
# - prefs and vrFullscreenFix are default prefs (pref(), the default branch:
|
|
# never written to prefs.js, so removing one leaves nothing behind). Firefox
|
|
# reads defaults/pref/*.js from its system config dir, /app/etc/firefox in
|
|
# the Flatpak, the mount point of the org.mozilla.firefox.systemconfig
|
|
# extension. The user installation's "unmaintained extension" dir
|
|
# ($XDG_DATA_HOME/flatpak/extension/<id>/<arch>/<branch>) provides it: a
|
|
# home-manager link to a store dir, which Flatpak mounts itself (the
|
|
# sandbox needn't see /nix).
|
|
# - vrFullscreenFix: in the Steam session gamescope focuses a fullscreen X11
|
|
# window but never shows it (Firefox looks frozen). ignore-widgets keeps
|
|
# 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.
|
|
# 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, ... }:
|
|
let
|
|
cfg = config.steamFrame.firefox;
|
|
ffDir = "$HOME/.var/app/org.mozilla.firefox/config/mozilla/firefox";
|
|
profileDir = "${ffDir}/${cfg.desktopProfile}";
|
|
|
|
defaultPrefs = cfg.prefs // lib.optionalAttrs cfg.vrFullscreenFix { "full-screen-api.ignore-widgets" = true; };
|
|
desktopFix = cfg.enable && cfg.vrFullscreenFix && cfg.desktopProfile != null;
|
|
# Only ever this one pref (profileSync relies on it).
|
|
desktopKeys = [ "full-screen-api.ignore-widgets" ];
|
|
desktopUserJsName = "steam-frame-nix-desktop-user.js";
|
|
|
|
sysconfig = pkgs.runCommand "steam-frame-nix-firefox-systemconfig" { } (''
|
|
mkdir -p $out/defaults/pref
|
|
cp ${pkgs.writeText "steam-frame-nix.js" (lib.concatStrings (lib.mapAttrsToList
|
|
(k: v: "pref(${builtins.toJSON k}, ${builtins.toJSON v});\n") defaultPrefs))} \
|
|
$out/defaults/pref/steam-frame-nix.js
|
|
'' + lib.optionalString desktopFix ''
|
|
cp ${pkgs.writeText desktopUserJsName (lib.concatMapStrings
|
|
(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[@]}" "$@"
|
|
'');
|
|
in {
|
|
options.steamFrame.firefox = {
|
|
enable = lib.mkEnableOption ''
|
|
the Firefox Flatpak (org.mozilla.firefox) launcher with Steam Frame
|
|
fixes'';
|
|
vrFullscreenFix = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = ''
|
|
Default full-screen-api.ignore-widgets to true (except in the desktop
|
|
profile), so fullscreen fills the window instead of freezing in the
|
|
Steam session.
|
|
'';
|
|
};
|
|
prefs = lib.mkOption {
|
|
type = with lib.types; attrsOf (oneOf [ bool int str ]);
|
|
default = { };
|
|
example = { "media.av1.enabled" = false; };
|
|
description = ''
|
|
about:config preferences for every profile (desktop one included),
|
|
as default values: about:config can still change them for a profile,
|
|
and removing one here leaves nothing behind. E.g. media.av1.enabled =
|
|
false: the Frame's decoder has no AV1, so sites like YouTube fall back
|
|
to VP9/H.264, which it decodes in hardware. Take effect at the next
|
|
start of Firefox.
|
|
'';
|
|
};
|
|
desktopProfile = lib.mkOption {
|
|
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.
|
|
'';
|
|
};
|
|
};
|
|
|
|
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";
|
|
}
|
|
(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-private-window]
|
|
Name=New Private Window
|
|
Exec=${firefox} --private-window @@u %u @@
|
|
'';
|
|
};
|
|
})
|
|
];
|
|
}
|