{ config, lib, pkgs, ... }: let inherit (lib) mkOption mkAliasOptionModule types mapAttrsToList attrNames filterAttrs replaceStrings getExe toLower ; inherit (types) str strMatching package path nullOr listOf attrsOf submodule either ; browser = getExe config.apps.browser; webAppLauncher = pkgs.writeShellScript "web-app-launcher" '' browser="${browser}" browser_exec="" for path in ~/.local ~/.nix-profile /usr /nix/var/nix/profiles/system/etc/profiles/per-user/$USER; do if [ -f "$path/share/applications/$browser.desktop" ]; then browser_exec=$(sed -n 's/^Exec=\([^ ]*\).*/\1/p' "$path/share/applications/$browser.desktop" 2>/dev/null | head -1) break fi done if [ -z "$browser_exec" ]; then browser_exec="${browser}" fi url="$1" app_name="''${2:-$(basename "$0")}" shift 2 exec setsid "$browser_exec" \ --app="$url" \ --user-data-dir="$HOME/.local/share/web-apps/$app_name" \ --no-first-run \ --no-default-browser-check \ --disable-background-timer-throttling \ --disable-backgrounding-occluded-windows \ --disable-renderer-backgrounding \ --enable-quic \ --quic-version=h3-29 \ --enable-features=UseOzonePlatform,WaylandWindowDecorations,WaylandPerWindowScaling,WaylandTextInputV3,WebRTCPipeWireCapturer \ --disable-features=WebRtcAllowInputVolumeAdjustment \ --ozone-platform=wayland \ --gtk-version=4 \ --enable-experimental-web-platform-features \ "$@" ''; # Create web app package mkWebApp = { name, url, icon, description, categories, }: let cleanName = replaceStrings [ " " "\n" "\t" ] [ "-" "-" "-" ] name |> toLower; pname = "${cleanName}-webapp"; in pkgs.stdenv.mkDerivation { inherit pname; version = "1.0"; dontUnpack = true; nativeBuildInputs = [ pkgs.copyDesktopItems pkgs.makeWrapper ]; installPhase = '' runHook preInstall makeWrapper ${webAppLauncher} $out/bin/${pname} \ --add-flags "${url}" \ --add-flags "${pname}" runHook postInstall ''; desktopItems = [ (pkgs.makeDesktopItem ( { name = pname; exec = "${pname} %U"; desktopName = name; startupNotify = true; startupWMClass = pname; } // filterAttrs (_: v: v != null) { inherit icon categories; comment = description; } )) ]; meta.mainProgram = pname; }; innerOpts = { options = { name = mkOption { type = str; }; url = mkOption { type = strMatching "[hH][tT][tT][pP][sS]?://[^\n\r[:space:]]+"; }; icon = mkOption { type = nullOr (either str path); default = null; }; description = mkOption { type = nullOr str; default = null; }; categories = mkOption { type = nullOr (listOf str); default = null; }; }; }; pwaModule = submodule ( { config, ... }: { imports = let names = innerOpts.options |> attrNames; in map (optName: mkAliasOptionModule [ optName ] [ "settings" optName ]) names; options = { settings = mkOption { type = submodule innerOpts; default = { }; }; package = mkOption { type = package; readOnly = true; default = mkWebApp config.settings; }; }; } ); in { options = { programs.pwas = mkOption { description = "PWA Applications"; type = attrsOf pwaModule; default = { }; }; }; config = { environment.systemPackages = mapAttrsToList (_: v: v.package) config.programs.pwas; }; }