1 { stdenv, lib, makeDesktopItem, makeWrapper, makeBinaryWrapper, lndir, config
3 , jq, xdg-utils, writeText
5 ## various stuff that can be plugged in
6 , ffmpeg, xorg, alsa-lib, libpulseaudio, libcanberra-gtk3, libglvnd, libnotify, opensc
8 , browserpass, gnome-browser-connector, uget-integrator, plasma5Packages, bukubrow, pipewire
24 ## configurability of the wrapper itself
30 { applicationName ? browser.binaryName or (lib.getName browser)
31 , pname ? applicationName
32 , version ? lib.getVersion browser
33 , desktopName ? # applicationName with first letter capitalized
34 (lib.toUpper (lib.substring 0 1 applicationName) + lib.substring 1 (-1) applicationName)
36 , icon ? applicationName
37 , wmClass ? applicationName
38 , nativeMessagingHosts ? []
39 , extraNativeMessagingHosts ? []
42 , cfg ? config.${applicationName} or {}
44 ## Following options are needed for extra prefs & policies
45 # For more information about anti tracking (german website)
46 # visit https://wiki.kairaven.de/open/app/firefox
48 , extraPrefsFiles ? []
49 # For more information about policies visit
50 # https://mozilla.github.io/policy-templates/
52 , extraPoliciesFiles ? []
53 , libName ? browser.libName or applicationName # Important for tor package or the like
54 , nixExtensions ? null
55 , hasMozSystemDirPatch ? (lib.hasPrefix "firefox" pname && !lib.hasSuffix "-bin" pname)
59 ffmpegSupport = browser.ffmpegSupport or false;
60 gssSupport = browser.gssSupport or false;
61 alsaSupport = browser.alsaSupport or false;
62 pipewireSupport = browser.pipewireSupport or false;
63 sndioSupport = browser.sndioSupport or false;
64 jackSupport = browser.jackSupport or false;
65 # PCSC-Lite daemon (services.pcscd) also must be enabled for firefox to access smartcards
66 smartcardSupport = cfg.smartcardSupport or false;
68 deprecatedNativeMessagingHost = option: pkg:
69 if (cfg.${option} or false)
71 lib.warn "The cfg.${option} argument for `firefox.override` is deprecated, please add `pkgs.${pkg.pname}` to `nativeMessagingHosts` instead"
75 allNativeMessagingHosts = builtins.map lib.getBin (
77 ++ deprecatedNativeMessagingHost "enableBrowserpass" browserpass
78 ++ deprecatedNativeMessagingHost "enableBukubrow" bukubrow
79 ++ deprecatedNativeMessagingHost "enableTridactylNative" tridactyl-native
80 ++ deprecatedNativeMessagingHost "enableGnomeExtensions" gnome-browser-connector
81 ++ deprecatedNativeMessagingHost "enableUgetIntegrator" uget-integrator
82 ++ deprecatedNativeMessagingHost "enablePlasmaBrowserIntegration" plasma5Packages.plasma-browser-integration
83 ++ deprecatedNativeMessagingHost "enableFXCastBridge" fx-cast-bridge
84 ++ deprecatedNativeMessagingHost "enableKeePassXC" keepassxc
85 ++ (if extraNativeMessagingHosts != []
86 then lib.warn "The extraNativeMessagingHosts argument for the Firefox wrapper is deprecated, please use `nativeMessagingHosts`" extraNativeMessagingHosts
90 libs = lib.optionals stdenv.hostPlatform.isLinux (
91 [ udev libva libgbm libnotify xorg.libXScrnSaver cups pciutils vulkan-loader ]
92 ++ lib.optional (cfg.speechSynthesisSupport or true) speechd-minimal
94 ++ lib.optional pipewireSupport pipewire
95 ++ lib.optional ffmpegSupport ffmpeg
96 ++ lib.optional gssSupport libkrb5
97 ++ lib.optional useGlvnd libglvnd
98 ++ lib.optionals (cfg.enableQuakeLive or false)
99 (with xorg; [ stdenv.cc libX11 libXxf86dga libXxf86vm libXext libXt alsa-lib zlib ])
100 ++ lib.optional (config.pulseaudio or true) libpulseaudio
101 ++ lib.optional alsaSupport alsa-lib
102 ++ lib.optional sndioSupport sndio
103 ++ lib.optional jackSupport libjack2
104 ++ lib.optional smartcardSupport opensc
107 gtk_modules = [ libcanberra-gtk3 ];
109 launcherName = "${applicationName}${nameSuffix}";
111 #########################
113 # EXTRA PREF CHANGES #
115 #########################
116 policiesJson = writeText "policies.json" (builtins.toJSON enterprisePolicies);
118 usesNixExtensions = nixExtensions != null;
120 nameArray = builtins.map(a: a.name) (lib.optionals usesNixExtensions nixExtensions);
122 # Check that every extension has a unqiue .name attribute
123 # and an extid attribute
124 extensions = if nameArray != (lib.unique nameArray) then
125 throw "Firefox addon name needs to be unique"
126 else if browser.requireSigning || !browser.allowAddonSideload then
127 throw "Nix addons are only supported with signature enforcement disabled and addon sideloading enabled (eg. LibreWolf)"
128 else builtins.map (a:
129 if ! (builtins.hasAttr "extid" a) then
130 throw "nixExtensions has an invalid entry. Missing extid attribute. Please use fetchFirefoxAddon"
133 ) (lib.optionals usesNixExtensions nixExtensions);
138 DisableAppUpdate = true;
140 lib.optionalAttrs usesNixExtensions {
141 ExtensionSettings = {
143 blocked_install_message = "You can't have manual extension mixed with nix extensions";
144 installation_mode = "blocked";
146 } // lib.foldr (e: ret:
149 installation_mode = "allowed";
155 Install = lib.foldr (e: ret:
156 ret ++ [ "${e.outPath}/${e.extid}.xpi" ]
159 } // lib.optionalAttrs smartcardSupport {
161 "OpenSC PKCS#11 Module" = "opensc-pkcs11.so";
168 // First line must be a comment
170 // Disables addon signature checking
171 // to be able to install addons that do not have an extid
172 // Security is maintained because only user whitelisted addons
173 // with a checksum can be installed
174 ${ lib.optionalString usesNixExtensions ''lockPref("xpinstall.signatures.required", false)'' };
177 #############################
179 # END EXTRA PREF CHANGES #
181 #############################
183 in stdenv.mkDerivation (finalAttrs: {
184 __structuredAttrs = true;
185 inherit pname version;
187 desktopItem = makeDesktopItem ({
189 exec = "${launcherName} --name ${wmClass} %U";
192 startupNotify = true;
193 startupWMClass = wmClass;
195 } // (if libName == "thunderbird"
197 genericName = "Email Client";
198 comment = "Read and write e-mails or RSS feeds, or manage tasks on calendars.";
200 "Network" "Chat" "Email" "Feed" "GTK" "News"
203 "mail" "email" "e-mail" "messages" "rss" "calendar"
204 "address book" "addressbook" "chat"
208 "x-scheme-handler/mailto"
213 profile-manager-window = {
214 name = "Profile Manager";
215 exec = "${launcherName} --ProfileManager";
220 genericName = "Web Browser";
221 categories = [ "Network" "WebBrowser" ];
225 "application/xhtml+xml"
226 "application/vnd.mozilla.xul+xml"
227 "x-scheme-handler/http"
228 "x-scheme-handler/https"
233 exec = "${launcherName} --new-window %U";
235 new-private-window = {
236 name = "New Private Window";
237 exec = "${launcherName} --private-window %U";
239 profile-manager-window = {
240 name = "Profile Manager";
241 exec = "${launcherName} --ProfileManager";
246 nativeBuildInputs = [ makeWrapper lndir jq ];
247 buildInputs = [ browser.gtk3 ];
258 "${lib.concatStringsSep ":" finalAttrs.gtk_modules}"
262 "${placeholder "out"}/bin"
269 "MOZ_LEGACY_PROFILES"
273 "MOZ_ALLOW_DOWNGRADE"
279 "${adwaita-icon-theme}/share"
285 ] ++ lib.optionals (!xdg-utils.meta.broken) [
286 # make xdg-open overrideable at runtime
290 "${lib.makeBinPath [ xdg-utils ]}"
292 ] ++ lib.optionals hasMozSystemDirPatch [
295 "${placeholder "out"}/lib/mozilla"
297 ] ++ lib.optionals (!hasMozSystemDirPatch && allNativeMessagingHosts != [ ]) [
299 ''mkdir -p ''${MOZ_HOME:-~/.mozilla}/native-messaging-hosts''
301 ] ++ lib.optionals (!hasMozSystemDirPatch) (lib.concatMap (ext: [
303 ''ln -sfLt ''${MOZ_HOME:-~/.mozilla}/native-messaging-hosts ${ext}/lib/mozilla/native-messaging-hosts/*''
304 ]) allNativeMessagingHosts);
307 if [ ! -x "${browser}/bin/${applicationName}" ]
309 echo "cannot find executable file \`${browser}/bin/${applicationName}'"
313 #########################
315 # EXTRA PREF CHANGES #
317 #########################
318 # Link the runtime. The executable itself has to be copied,
319 # because it will resolve paths relative to its true location.
320 # Any symbolic links have to be replicated as well.
322 find . -type d -exec mkdir -p "$out"/{} \;
324 find . -type f \( -not -name "${applicationName}" \) -exec ln -sT "${browser}"/{} "$out"/{} \;
326 find . -type f \( -name "${applicationName}" -o -name "${applicationName}-bin" \) -print0 | while read -d $'\0' f; do
327 cp -P --no-preserve=mode,ownership --remove-destination "${browser}/$f" "$out/$f"
328 chmod a+rwx "$out/$f"
331 # fix links and absolute references
333 find . -type l -print0 | while read -d $'\0' l; do
334 target="$(readlink "$l")"
335 target=''${target/#"${browser}"/"$out"}
336 ln -sfT "$target" "$out/$l"
343 executablePrefix="$out/bin"
344 executablePath="$executablePrefix/${applicationName}"
347 if [[ -L $executablePath ]]; then
348 # Symbolic link: wrap the link's target.
349 oldExe="$(readlink -v --canonicalize-existing "$executablePath")"
351 elif wrapperCmd=$(${buildPackages.makeBinaryWrapper.extractCmd} "$executablePath"); [[ $wrapperCmd ]]; then
352 # If the executable is a binary wrapper, we need to update its target to
353 # point to $out, but we can't just edit the binary in-place because of length
354 # issues. So we extract the command used to create the wrapper and add the
355 # arguments to our wrapper.
356 parseMakeCWrapperCall() {
359 oldWrapperArgs=("$@")
361 eval "parseMakeCWrapperCall ''${wrapperCmd//"${browser}"/"$out"}"
364 if read -rn2 shebang < "$executablePath" && [[ $shebang == '#!' ]]; then
365 # Shell wrapper: patch in place to point to $out.
366 sed -i "s@${browser}@$out@g" "$executablePath"
368 # Suffix the executable with -old, because -wrapped might already be used by the old wrapper.
369 oldExe="$executablePrefix/.${applicationName}"-old
370 mv "$executablePath" "$oldExe"
373 appendToVar makeWrapperArgs --prefix XDG_DATA_DIRS : "$GSETTINGS_SCHEMAS_PATH"
374 concatTo makeWrapperArgs oldWrapperArgs
375 makeWrapper "$oldExe" "''${executablePath}${nameSuffix}" ''${makeWrapperArgs[@]}
376 #############################
378 # END EXTRA PREF CHANGES #
380 #############################
382 if [ -e "${browser}/share/icons" ]; then
383 mkdir -p "$out/share"
384 ln -s "${browser}/share/icons" "$out/share/icons"
386 for res in 16 32 48 64 128; do
387 mkdir -p "$out/share/icons/hicolor/''${res}x''${res}/apps"
388 icon=$( find "${browser}/lib/" -name "default''${res}.png" )
389 if [ -e "$icon" ]; then ln -s "$icon" \
390 "$out/share/icons/hicolor/''${res}x''${res}/apps/${icon}.png"
395 install -D -t $out/share/applications $desktopItem/share/applications/*
397 '' + lib.optionalString hasMozSystemDirPatch ''
398 mkdir -p $out/lib/mozilla/native-messaging-hosts
399 for ext in ${toString allNativeMessagingHosts}; do
400 ln -sLt $out/lib/mozilla/native-messaging-hosts $ext/lib/mozilla/native-messaging-hosts/*
404 mkdir -p $out/lib/mozilla/pkcs11-modules
405 for ext in ${toString pkcs11Modules}; do
406 ln -sLt $out/lib/mozilla/pkcs11-modules $ext/lib/mozilla/pkcs11-modules/*
410 #########################
412 # EXTRA PREF CHANGES #
414 #########################
416 mkdir -p $out/lib/${libName}
418 # creating policies.json
419 mkdir -p "$out/lib/${libName}/distribution"
421 POL_PATH="$out/lib/${libName}/distribution/policies.json"
423 cat ${policiesJson} >> "$POL_PATH"
425 extraPoliciesFiles=(${builtins.toString extraPoliciesFiles})
426 for extraPoliciesFile in "''${extraPoliciesFiles[@]}"; do
427 jq -s '.[0] * .[1]' "$POL_PATH" $extraPoliciesFile > .tmp.json
428 mv .tmp.json "$POL_PATH"
431 # preparing for autoconfig
432 mkdir -p "$out/lib/${libName}/defaults/pref"
434 echo 'pref("general.config.filename", "mozilla.cfg");' > "$out/lib/${libName}/defaults/pref/autoconfig.js"
435 echo 'pref("general.config.obscure_value", 0);' >> "$out/lib/${libName}/defaults/pref/autoconfig.js"
437 cat > "$out/lib/${libName}/mozilla.cfg" << EOF
441 extraPrefsFiles=(${builtins.toString extraPrefsFiles})
442 for extraPrefsFile in "''${extraPrefsFiles[@]}"; do
443 cat "$extraPrefsFile" >> "$out/lib/${libName}/mozilla.cfg"
446 cat >> "$out/lib/${libName}/mozilla.cfg" << EOF
450 mkdir -p $out/lib/${libName}/distribution/extensions
452 #############################
454 # END EXTRA PREF CHANGES #
456 #############################
459 preferLocalBuild = true;
461 libs = lib.makeLibraryPath libs + ":" + lib.makeSearchPathOutput "lib" "lib64" libs;
462 gtk_modules = map (x: x + x.gtkModule) gtk_modules;
464 passthru = { unwrapped = browser; };
466 disallowedRequisites = [ stdenv.cc ];
467 meta = browser.meta // {
468 inherit (browser.meta) description;
469 mainProgram = launcherName;
471 priority = (browser.meta.priority or lib.meta.defaultPriority) - 1; # prefer wrapper over the package
474 in lib.makeOverridable wrapper