firefox: add handlers.json configuration
Adds support for configuring Firefox's handlers.json file to manage MIME type and URL scheme handlers declaratively (at `programs.firefox.profiles.<profile>.handlers`). Handlers control how Firefox opens files and protocols (e.g., PDF viewers, `mailto:` handlers).
This commit is contained in:
parent
c9507a9aa5
commit
a913ae61bf
6 changed files with 364 additions and 0 deletions
18
modules/misc/news/2025/12/2025-12-10_04-15-59.nix
Normal file
18
modules/misc/news/2025/12/2025-12-10_04-15-59.nix
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{ config, ... }:
|
||||
{
|
||||
time = "2025-12-10T07:15:59+00:00";
|
||||
condition = config.programs.firefox.enable;
|
||||
message = ''
|
||||
The Firefox module now provides a
|
||||
'programs.firefox.profiles.<name>.handlers' option.
|
||||
|
||||
It allows declarative configuration of MIME type and URL scheme handlers
|
||||
through Firefox's handlers.json file, controlling how Firefox opens files
|
||||
and protocols (e.g., PDF viewers, mailto handlers).
|
||||
|
||||
Configure handlers with:
|
||||
|
||||
programs.firefox.profiles.<name>.handlers.mimeTypes
|
||||
programs.firefox.profiles.<name>.handlers.schemes
|
||||
'';
|
||||
}
|
||||
|
|
@ -548,6 +548,25 @@ in
|
|||
description = "Declarative search engine configuration.";
|
||||
};
|
||||
|
||||
handlers = mkOption {
|
||||
type = types.submodule (
|
||||
args:
|
||||
import ./profiles/handlers.nix {
|
||||
inherit (args) config;
|
||||
inherit lib pkgs appName;
|
||||
package = cfg.finalPackage;
|
||||
modulePath = modulePath ++ [
|
||||
"profiles"
|
||||
name
|
||||
"handlers"
|
||||
];
|
||||
profilePath = config.path;
|
||||
}
|
||||
);
|
||||
default = { };
|
||||
description = "Declarative handlers configuration for MIME types and URL schemes.";
|
||||
};
|
||||
|
||||
containersForce = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
|
|
@ -1048,6 +1067,11 @@ in
|
|||
source = profile.search.file;
|
||||
};
|
||||
|
||||
"${cfg.profilesPath}/${profile.path}/handlers.json" = mkIf (profile.handlers.enable) {
|
||||
source = profile.handlers.configFile;
|
||||
force = profile.handlers.force;
|
||||
};
|
||||
|
||||
"${cfg.profilesPath}/${profile.path}/extensions" = mkIf (profile.extensions.packages != [ ]) {
|
||||
source =
|
||||
let
|
||||
|
|
|
|||
209
modules/programs/firefox/profiles/handlers.nix
Normal file
209
modules/programs/firefox/profiles/handlers.nix
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
appName,
|
||||
...
|
||||
}:
|
||||
let
|
||||
jsonFormat = pkgs.formats.json { };
|
||||
|
||||
# Process configuration, remove null values and empty handlers arrays.
|
||||
genCfg =
|
||||
cfg:
|
||||
lib.mapAttrs (
|
||||
_: item:
|
||||
(removeAttrs item [ "handlers" ])
|
||||
// (lib.optionalAttrs (item.handlers != [ ]) {
|
||||
handlers = map (handler: lib.filterAttrsRecursive (_: v: v != null) handler) item.handlers;
|
||||
})
|
||||
) cfg;
|
||||
|
||||
# Common options shared between mimeTypes and schemes
|
||||
commonHandlerOptions = {
|
||||
action = lib.mkOption {
|
||||
type = lib.types.enum [
|
||||
0
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
];
|
||||
default = 1;
|
||||
description = ''
|
||||
The action to take for this MIME type / URL scheme. Possible values:
|
||||
- 0: Save file
|
||||
- 1: Always ask
|
||||
- 2: Use helper app
|
||||
- 3: Open in ${appName}
|
||||
- 4: Use system default
|
||||
'';
|
||||
};
|
||||
|
||||
ask = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
If true, the user is asked what they want to do with the file.
|
||||
If false, the action is taken without user intervention.
|
||||
'';
|
||||
};
|
||||
|
||||
handlers = lib.mkOption {
|
||||
type = lib.types.listOf (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Display name of the handler.
|
||||
'';
|
||||
};
|
||||
|
||||
path = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Path to the executable to be used.
|
||||
|
||||
Only one of 'path' or 'uriTemplate' should be set.
|
||||
'';
|
||||
};
|
||||
|
||||
uriTemplate = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
URI for the application handler.
|
||||
|
||||
Only one of 'path' or 'uriTemplate' should be set.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
default = [ ];
|
||||
description = ''
|
||||
An array of handlers with the first one being the default.
|
||||
If you don't want to have a default handler, use an empty object for the first handler.
|
||||
Only valid when action is set to 2 (Use helper app).
|
||||
'';
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
imports = [ (pkgs.path + "/nixos/modules/misc/meta.nix") ];
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ kugland ];
|
||||
|
||||
options = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.schemes != { } || config.mimeTypes != { };
|
||||
internal = true;
|
||||
};
|
||||
|
||||
force = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to force replace the existing handlers configuration.
|
||||
'';
|
||||
};
|
||||
|
||||
mimeTypes = lib.mkOption {
|
||||
type = lib.types.attrsOf (
|
||||
lib.types.submodule {
|
||||
options = commonHandlerOptions // {
|
||||
extensions = lib.mkOption {
|
||||
type = lib.types.listOf (lib.types.strMatching "^[^\\.].+$");
|
||||
default = [ ];
|
||||
example = [
|
||||
"jpg"
|
||||
"jpeg"
|
||||
];
|
||||
description = ''
|
||||
List of file extensions associated with this MIME type.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
default = { };
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
"application/pdf" = {
|
||||
action = 2;
|
||||
ask = false;
|
||||
handlers = [
|
||||
{
|
||||
name = "Okular";
|
||||
path = "''${pkgs.okular}/bin/okular";
|
||||
}
|
||||
];
|
||||
extensions = [ "pdf" ];
|
||||
};
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Attribute set mapping MIME types to their handler configurations.
|
||||
|
||||
For a configuration example, see [this file on Firefox’s source code](https://github.com/mozilla-firefox/firefox/blob/c3797cdebac1316dd7168e995e3468c5a597e8d1/uriloader/exthandler/tests/unit/handlers.json).
|
||||
'';
|
||||
};
|
||||
|
||||
schemes = lib.mkOption {
|
||||
type = lib.types.attrsOf (
|
||||
lib.types.submodule {
|
||||
options = commonHandlerOptions;
|
||||
}
|
||||
);
|
||||
default = { };
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
mailto = {
|
||||
action = 2;
|
||||
ask = false;
|
||||
handlers = [
|
||||
{
|
||||
name = "Gmail";
|
||||
uriTemplate = "https://mail.google.com/mail/?extsrc=mailto&url=%s";
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Attribute set mapping URL schemes to their handler configurations.
|
||||
|
||||
For a configuration example, see [this file on Firefox’s source code](https://github.com/mozilla-firefox/firefox/blob/c3797cdebac1316dd7168e995e3468c5a597e8d1/uriloader/exthandler/tests/unit/handlers.json).
|
||||
'';
|
||||
};
|
||||
|
||||
finalSettings = lib.mkOption {
|
||||
type = jsonFormat.type;
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
default = {
|
||||
defaultHandlersVersion = { };
|
||||
isDownloadsImprovementsAlreadyMigrated = false;
|
||||
mimeTypes = genCfg config.mimeTypes;
|
||||
schemes = genCfg config.schemes;
|
||||
};
|
||||
description = ''
|
||||
Resulting handlers.json settings.
|
||||
'';
|
||||
};
|
||||
|
||||
configFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
default = jsonFormat.generate "handlers.json" config.finalSettings;
|
||||
description = ''
|
||||
JSON representation of the handlers configuration.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ builtins.mapAttrs
|
|||
"${name}-profiles-extensions-assertions" = ./profiles/extensions/assertions.nix;
|
||||
"${name}-profiles-extensions-exhaustive" = ./profiles/extensions/exhaustive.nix;
|
||||
"${name}-profiles-extensions-exact" = ./profiles/extensions/exact.nix;
|
||||
"${name}-profiles-handlers" = ./profiles/handlers;
|
||||
"${name}-profiles-overwrite" = ./profiles/overwrite;
|
||||
"${name}-profiles-search" = ./profiles/search;
|
||||
"${name}-profiles-settings" = ./profiles/settings;
|
||||
|
|
|
|||
69
tests/modules/programs/firefox/profiles/handlers/default.nix
Normal file
69
tests/modules/programs/firefox/profiles/handlers/default.nix
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
modulePath:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = lib.getAttrFromPath modulePath config;
|
||||
firefoxMockOverlay = import ../../setup-firefox-mock-overlay.nix modulePath;
|
||||
in
|
||||
{
|
||||
imports = [ firefoxMockOverlay ];
|
||||
|
||||
config = lib.mkIf config.test.enableBig (
|
||||
lib.setAttrByPath modulePath {
|
||||
enable = true;
|
||||
profiles.handlers = {
|
||||
id = 0;
|
||||
handlers = {
|
||||
mimeTypes = {
|
||||
"application/pdf" = {
|
||||
action = 2;
|
||||
ask = false;
|
||||
handlers = [
|
||||
{
|
||||
name = "Hello App";
|
||||
path = "${pkgs.hello}/bin/hello";
|
||||
}
|
||||
];
|
||||
extensions = [ "pdf" ];
|
||||
};
|
||||
"text/html" = {
|
||||
action = 4;
|
||||
ask = true;
|
||||
extensions = [
|
||||
"html"
|
||||
"htm"
|
||||
];
|
||||
};
|
||||
};
|
||||
schemes = {
|
||||
mailto = {
|
||||
action = 2;
|
||||
ask = false;
|
||||
handlers = [
|
||||
{
|
||||
name = "Gmail";
|
||||
uriTemplate = "https://mail.google.com/mail/?extsrc=mailto&url=%s";
|
||||
}
|
||||
];
|
||||
};
|
||||
http = {
|
||||
action = 3;
|
||||
ask = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
// {
|
||||
nmt.script = ''
|
||||
assertFileContent \
|
||||
home-files/${cfg.configPath}/handlers/handlers.json \
|
||||
${./expected-handlers.json}
|
||||
'';
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"defaultHandlersVersion": {},
|
||||
"isDownloadsImprovementsAlreadyMigrated": false,
|
||||
"mimeTypes": {
|
||||
"application/pdf": {
|
||||
"action": 2,
|
||||
"ask": false,
|
||||
"extensions": [
|
||||
"pdf"
|
||||
],
|
||||
"handlers": [
|
||||
{
|
||||
"name": "Hello App",
|
||||
"path": "@hello@/bin/hello"
|
||||
}
|
||||
]
|
||||
},
|
||||
"text/html": {
|
||||
"action": 4,
|
||||
"ask": true,
|
||||
"extensions": [
|
||||
"html",
|
||||
"htm"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schemes": {
|
||||
"http": {
|
||||
"action": 3,
|
||||
"ask": true
|
||||
},
|
||||
"mailto": {
|
||||
"action": 2,
|
||||
"ask": false,
|
||||
"handlers": [
|
||||
{
|
||||
"name": "Gmail",
|
||||
"uriTemplate": "https://mail.google.com/mail/?extsrc=mailto&url=%s"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue