1
0
Fork 0
mirror of https://github.com/nix-community/home-manager.git synced 2025-11-08 19:46:05 +01:00

treewide: reformat nixfmt-rfc-style

Reformat repository using new nixfmt-rfc-style.
This commit is contained in:
Austin Horstman 2025-04-07 16:11:29 -05:00
parent 5df48c4255
commit cba2f9ce95
1051 changed files with 37028 additions and 26594 deletions

View file

@ -1,4 +1,6 @@
{ pkgs ? import <nixpkgs> { } }: {
pkgs ? import <nixpkgs> { },
}:
let let
path = builtins.path { path = builtins.path {
@ -6,22 +8,26 @@ let
name = "home-manager-source"; name = "home-manager-source";
}; };
in rec { in
docs = let releaseInfo = pkgs.lib.importJSON ./release.json; rec {
in with import ./docs { docs =
inherit pkgs; let
inherit (releaseInfo) release isReleaseBranch; releaseInfo = pkgs.lib.importJSON ./release.json;
}; { in
with import ./docs {
inherit pkgs;
inherit (releaseInfo) release isReleaseBranch;
};
{
inherit manPages jsonModuleMaintainers; inherit manPages jsonModuleMaintainers;
inherit (manual) html htmlOpenTool; inherit (manual) html htmlOpenTool;
inherit (options) json; inherit (options) json;
}; };
home-manager = pkgs.callPackage ./home-manager { inherit path; }; home-manager = pkgs.callPackage ./home-manager { inherit path; };
install = install = pkgs.callPackage ./home-manager/install.nix { inherit home-manager; };
pkgs.callPackage ./home-manager/install.nix { inherit home-manager; };
nixos = import ./nixos; nixos = import ./nixos;
lib = import ./lib { inherit (pkgs) lib; }; lib = import ./lib { inherit (pkgs) lib; };

View file

@ -1,9 +1,12 @@
{ pkgs {
pkgs,
# Note, this should be "the standard library" + HM extensions. # Note, this should be "the standard library" + HM extensions.
, lib ? import ../modules/lib/stdlib-extended.nix pkgs.lib lib ? import ../modules/lib/stdlib-extended.nix pkgs.lib,
, release, isReleaseBranch }: release,
isReleaseBranch,
}:
let let
@ -19,86 +22,124 @@ let
# Caveat: even if the package is reached by a different means, the # Caveat: even if the package is reached by a different means, the
# path above will be shown and not e.g. # path above will be shown and not e.g.
# `${config.services.foo.package}`. # `${config.services.foo.package}`.
scrubDerivations = prefixPath: attrs: scrubDerivations =
prefixPath: attrs:
let let
scrubDerivation = name: value: scrubDerivation =
let pkgAttrName = prefixPath + "." + name; name: value:
in if lib.isAttrs value then let
pkgAttrName = prefixPath + "." + name;
in
if lib.isAttrs value then
scrubDerivations pkgAttrName value scrubDerivations pkgAttrName value
// lib.optionalAttrs (lib.isDerivation value) { // lib.optionalAttrs (lib.isDerivation value) {
outPath = "\${${pkgAttrName}}"; outPath = "\${${pkgAttrName}}";
} }
else else
value; value;
in lib.mapAttrs scrubDerivation attrs; in
lib.mapAttrs scrubDerivation attrs;
# Make sure the used package is scrubbed to avoid actually # Make sure the used package is scrubbed to avoid actually
# instantiating derivations. # instantiating derivations.
scrubbedPkgsModule = { scrubbedPkgsModule = {
imports = [{ imports = [
_module.args = { {
pkgs = lib.mkForce (scrubDerivations "pkgs" pkgs); _module.args = {
pkgs_i686 = lib.mkForce { }; pkgs = lib.mkForce (scrubDerivations "pkgs" pkgs);
}; pkgs_i686 = lib.mkForce { };
}]; };
}
];
}; };
dontCheckDefinitions = { _module.check = false; }; dontCheckDefinitions = {
_module.check = false;
};
gitHubDeclaration = user: repo: subpath: gitHubDeclaration =
let urlRef = if isReleaseBranch then "release-${release}" else "master"; user: repo: subpath:
in { let
urlRef = if isReleaseBranch then "release-${release}" else "master";
in
{
url = "https://github.com/${user}/${repo}/blob/${urlRef}/${subpath}"; url = "https://github.com/${user}/${repo}/blob/${urlRef}/${subpath}";
name = "<${repo}/${subpath}>"; name = "<${repo}/${subpath}>";
}; };
hmPath = toString ./..; hmPath = toString ./..;
buildOptionsDocs = args@{ modules, includeModuleSystemOptions ? true, ... }: buildOptionsDocs =
args@{
modules,
includeModuleSystemOptions ? true,
...
}:
let let
options = (lib.evalModules { options =
inherit modules; (lib.evalModules {
class = "homeManager"; inherit modules;
}).options; class = "homeManager";
in pkgs.buildPackages.nixosOptionsDoc ({ }).options;
options = if includeModuleSystemOptions then in
options pkgs.buildPackages.nixosOptionsDoc (
else {
builtins.removeAttrs options [ "_module" ]; options =
transformOptions = opt: if includeModuleSystemOptions then options else builtins.removeAttrs options [ "_module" ];
opt // { transformOptions =
# Clean up declaration sites to not refer to the Home Manager opt:
# source tree. opt
declarations = map (decl: // {
if lib.hasPrefix hmPath (toString decl) then # Clean up declaration sites to not refer to the Home Manager
gitHubDeclaration "nix-community" "home-manager" # source tree.
(lib.removePrefix "/" (lib.removePrefix hmPath (toString decl))) declarations = map (
else if decl == "lib/modules.nix" then decl:
# TODO: handle this in a better way (may require upstream if lib.hasPrefix hmPath (toString decl) then
# changes to nixpkgs) gitHubDeclaration "nix-community" "home-manager" (
gitHubDeclaration "NixOS" "nixpkgs" decl lib.removePrefix "/" (lib.removePrefix hmPath (toString decl))
else )
decl) opt.declarations; else if decl == "lib/modules.nix" then
}; # TODO: handle this in a better way (may require upstream
} // builtins.removeAttrs args [ "modules" "includeModuleSystemOptions" ]); # changes to nixpkgs)
gitHubDeclaration "NixOS" "nixpkgs" decl
else
decl
) opt.declarations;
};
}
// builtins.removeAttrs args [
"modules"
"includeModuleSystemOptions"
]
);
hmOptionsDocs = buildOptionsDocs { hmOptionsDocs = buildOptionsDocs {
modules = import ../modules/modules.nix { modules =
inherit lib pkgs; import ../modules/modules.nix {
check = false; inherit lib pkgs;
} ++ [ scrubbedPkgsModule ]; check = false;
}
++ [ scrubbedPkgsModule ];
variablelistId = "home-manager-options"; variablelistId = "home-manager-options";
}; };
nixosOptionsDocs = buildOptionsDocs { nixosOptionsDocs = buildOptionsDocs {
modules = [ ../nixos scrubbedPkgsModule dontCheckDefinitions ]; modules = [
../nixos
scrubbedPkgsModule
dontCheckDefinitions
];
includeModuleSystemOptions = false; includeModuleSystemOptions = false;
variablelistId = "nixos-options"; variablelistId = "nixos-options";
optionIdPrefix = "nixos-opt-"; optionIdPrefix = "nixos-opt-";
}; };
nixDarwinOptionsDocs = buildOptionsDocs { nixDarwinOptionsDocs = buildOptionsDocs {
modules = [ ../nix-darwin scrubbedPkgsModule dontCheckDefinitions ]; modules = [
../nix-darwin
scrubbedPkgsModule
dontCheckDefinitions
];
includeModuleSystemOptions = false; includeModuleSystemOptions = false;
variablelistId = "nix-darwin-options"; variablelistId = "nix-darwin-options";
optionIdPrefix = "nix-darwin-opt-"; optionIdPrefix = "nix-darwin-opt-";
@ -108,22 +149,26 @@ let
revision = "release-${release-config.release}"; revision = "release-${release-config.release}";
# Generate the `man home-configuration.nix` package # Generate the `man home-configuration.nix` package
home-configuration-manual = home-configuration-manual =
pkgs.runCommand "home-configuration-reference-manpage" { pkgs.runCommand "home-configuration-reference-manpage"
nativeBuildInputs = {
[ pkgs.buildPackages.installShellFiles pkgs.nixos-render-docs ]; nativeBuildInputs = [
allowedReferences = [ "out" ]; pkgs.buildPackages.installShellFiles
} '' pkgs.nixos-render-docs
# Generate manpages. ];
mkdir -p $out/share/man/man5 allowedReferences = [ "out" ];
mkdir -p $out/share/man/man1 }
nixos-render-docs -j $NIX_BUILD_CORES options manpage \ ''
--revision ${revision} \ # Generate manpages.
--header ${./home-configuration-nix-header.5} \ mkdir -p $out/share/man/man5
--footer ${./home-configuration-nix-footer.5} \ mkdir -p $out/share/man/man1
${hmOptionsDocs.optionsJSON}/share/doc/nixos/options.json \ nixos-render-docs -j $NIX_BUILD_CORES options manpage \
$out/share/man/man5/home-configuration.nix.5 --revision ${revision} \
cp ${./home-manager.1} $out/share/man/man1/home-manager.1 --header ${./home-configuration-nix-header.5} \
''; --footer ${./home-configuration-nix-footer.5} \
${hmOptionsDocs.optionsJSON}/share/doc/nixos/options.json \
$out/share/man/man5/home-configuration.nix.5
cp ${./home-manager.1} $out/share/man/man1/home-manager.1
'';
# Generate the HTML manual pages # Generate the HTML manual pages
home-manager-manual = pkgs.callPackage ./home-manager-manual.nix { home-manager-manual = pkgs.callPackage ./home-manager-manual.nix {
home-manager-options = { home-manager-options = {
@ -135,22 +180,26 @@ let
}; };
html = home-manager-manual; html = home-manager-manual;
htmlOpenTool = pkgs.callPackage ./html-open-tool.nix { } { inherit html; }; htmlOpenTool = pkgs.callPackage ./html-open-tool.nix { } { inherit html; };
in { in
{
options = { options = {
# TODO: Use `hmOptionsDocs.optionsJSON` directly once upstream # TODO: Use `hmOptionsDocs.optionsJSON` directly once upstream
# `nixosOptionsDoc` is more customizable. # `nixosOptionsDoc` is more customizable.
json = pkgs.runCommand "options.json" { json =
meta.description = "List of Home Manager options in JSON format"; pkgs.runCommand "options.json"
} '' {
mkdir -p $out/{share/doc,nix-support} meta.description = "List of Home Manager options in JSON format";
cp -a ${hmOptionsDocs.optionsJSON}/share/doc/nixos $out/share/doc/home-manager }
substitute \ ''
${hmOptionsDocs.optionsJSON}/nix-support/hydra-build-products \ mkdir -p $out/{share/doc,nix-support}
$out/nix-support/hydra-build-products \ cp -a ${hmOptionsDocs.optionsJSON}/share/doc/nixos $out/share/doc/home-manager
--replace-fail \ substitute \
'${hmOptionsDocs.optionsJSON}/share/doc/nixos' \ ${hmOptionsDocs.optionsJSON}/nix-support/hydra-build-products \
"$out/share/doc/home-manager" $out/nix-support/hydra-build-products \
''; --replace-fail \
'${hmOptionsDocs.optionsJSON}/share/doc/nixos' \
"$out/share/doc/home-manager"
'';
}; };
manPages = home-configuration-manual; manPages = home-configuration-manual;
@ -158,13 +207,18 @@ in {
manual = { inherit html htmlOpenTool; }; manual = { inherit html htmlOpenTool; };
# Unstable, mainly for CI. # Unstable, mainly for CI.
jsonModuleMaintainers = pkgs.writeText "hm-module-maintainers.json" (let jsonModuleMaintainers = pkgs.writeText "hm-module-maintainers.json" (
result = lib.evalModules { let
modules = import ../modules/modules.nix { result = lib.evalModules {
inherit lib pkgs; modules =
check = false; import ../modules/modules.nix {
} ++ [ scrubbedPkgsModule ]; inherit lib pkgs;
class = "homeManager"; check = false;
}; }
in builtins.toJSON result.config.meta.maintainers); ++ [ scrubbedPkgsModule ];
class = "homeManager";
};
in
builtins.toJSON result.config.meta.maintainers
);
} }

View file

@ -9,7 +9,12 @@
}; };
}; };
outputs = { self, nixpkgs, scss-reset }: outputs =
{
self,
nixpkgs,
scss-reset,
}:
let let
supportedSystems = [ supportedSystems = [
"aarch64-darwin" "aarch64-darwin"
@ -28,7 +33,12 @@
p-build = pkgs.writeShellScriptBin "p-build" '' p-build = pkgs.writeShellScriptBin "p-build" ''
set -euo pipefail set -euo pipefail
export PATH=${lib.makeBinPath [ pkgs.coreutils pkgs.rsass ]} export PATH=${
lib.makeBinPath [
pkgs.coreutils
pkgs.rsass
]
}
tmpfile=$(mktemp -d) tmpfile=$(mktemp -d)
trap "rm -r $tmpfile" EXIT trap "rm -r $tmpfile" EXIT
@ -42,20 +52,25 @@
}; };
releaseInfo = lib.importJSON ../release.json; releaseInfo = lib.importJSON ../release.json;
in { in
devShells = forAllSystems (system: {
devShells = forAllSystems (
system:
let let
pkgs = nixpkgs.legacyPackages.${system}; pkgs = nixpkgs.legacyPackages.${system};
fpkgs = flakePkgs pkgs; fpkgs = flakePkgs pkgs;
in { in
{
default = pkgs.mkShell { default = pkgs.mkShell {
name = "hm-docs"; name = "hm-docs";
packages = [ fpkgs.p-build ]; packages = [ fpkgs.p-build ];
}; };
}); }
);
# Expose the docs outputs # Expose the docs outputs
packages = forAllSystems (system: packages = forAllSystems (
system:
let let
pkgs = nixpkgs.legacyPackages.${system}; pkgs = nixpkgs.legacyPackages.${system};
docs = import ./default.nix { docs = import ./default.nix {
@ -63,10 +78,12 @@
release = releaseInfo.release; release = releaseInfo.release;
isReleaseBranch = releaseInfo.isReleaseBranch; isReleaseBranch = releaseInfo.isReleaseBranch;
}; };
in { in
{
inherit (docs) manPages jsonModuleMaintainers; inherit (docs) manPages jsonModuleMaintainers;
inherit (docs.manual) html htmlOpenTool; inherit (docs.manual) html htmlOpenTool;
inherit (docs.options) json; inherit (docs.options) json;
}); }
);
}; };
} }

View file

@ -1,7 +1,15 @@
{ stdenv, lib, documentation-highlighter, revision, home-manager-options {
, nixos-render-docs }: stdenv,
let outputPath = "share/doc/home-manager"; lib,
in stdenv.mkDerivation { documentation-highlighter,
revision,
home-manager-options,
nixos-render-docs,
}:
let
outputPath = "share/doc/home-manager";
in
stdenv.mkDerivation {
name = "home-manager-manual"; name = "home-manager-manual";
nativeBuildInputs = [ nixos-render-docs ]; nativeBuildInputs = [ nixos-render-docs ];
@ -61,5 +69,7 @@ in stdenv.mkDerivation {
passthru = { inherit home-manager-options; }; passthru = { inherit home-manager-options; };
meta = { maintainers = [ lib.maintainers.considerate ]; }; meta = {
maintainers = [ lib.maintainers.considerate ];
};
} }

View file

@ -1,6 +1,14 @@
{ writeShellScriptBin, makeDesktopItem, symlinkJoin }: {
{ html, pathName ? "home-manager", projectName ? pathName writeShellScriptBin,
, name ? "${pathName}-help" }: makeDesktopItem,
symlinkJoin,
}:
{
html,
pathName ? "home-manager",
projectName ? pathName,
name ? "${pathName}-help",
}:
let let
helpScript = writeShellScriptBin name '' helpScript = writeShellScriptBin name ''
set -euo pipefail set -euo pipefail
@ -30,7 +38,11 @@ let
exec = "${helpScript}/bin/${name}"; exec = "${helpScript}/bin/${name}";
categories = [ "System" ]; categories = [ "System" ];
}; };
in symlinkJoin { in
symlinkJoin {
inherit name; inherit name;
paths = [ helpScript desktopItem ]; paths = [
helpScript
desktopItem
];
} }

View file

@ -1,6 +1,18 @@
{ lib, flake-parts-lib, moduleLocation, ... }: {
let inherit (lib) toString mapAttrs mkOption types; lib,
in { flake-parts-lib,
moduleLocation,
...
}:
let
inherit (lib)
toString
mapAttrs
mkOption
types
;
in
{
options = { options = {
flake = flake-parts-lib.mkSubmoduleOptions { flake = flake-parts-lib.mkSubmoduleOptions {
homeConfigurations = mkOption { homeConfigurations = mkOption {
@ -17,11 +29,13 @@ in {
homeModules = mkOption { homeModules = mkOption {
type = types.lazyAttrsOf types.deferredModule; type = types.lazyAttrsOf types.deferredModule;
default = { }; default = { };
apply = mapAttrs (k: v: { apply = mapAttrs (
_class = "homeManager"; k: v: {
_file = "${toString moduleLocation}#homeModules.${k}"; _class = "homeManager";
imports = [ v ]; _file = "${toString moduleLocation}#homeModules.${k}";
}); imports = [ v ];
}
);
description = '' description = ''
Home Manager modules. Home Manager modules.

View file

@ -10,7 +10,13 @@
}; };
}; };
outputs = { self, nixpkgs, treefmt-nix, ... }: outputs =
{
self,
nixpkgs,
treefmt-nix,
...
}:
{ {
nixosModules = rec { nixosModules = rec {
home-manager = ./nixos; home-manager = ./nixos;
@ -44,42 +50,50 @@
}; };
lib = import ./lib { inherit (nixpkgs) lib; }; lib = import ./lib { inherit (nixpkgs) lib; };
} // (let }
forAllSystems = nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed; // (
let
forAllSystems = nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed;
treefmtEval = forAllSystems ( treefmtEval = forAllSystems (
system: system:
treefmt-nix.lib.evalModule nixpkgs.legacyPackages.${system} { treefmt-nix.lib.evalModule nixpkgs.legacyPackages.${system} {
# Formatting configuration # Formatting configuration
programs = { programs = {
nixfmt.enable = true; nixfmt.enable = true;
}; };
}
);
in
{
checks = forAllSystems (system: {
formatting = treefmtEval.${system}.config.build.check self;
}); });
in {
checks = forAllSystems (system: {
formatting = treefmtEval.${system}.config.build.check self;
});
formatter = forAllSystems (system: treefmtEval.${system}.config.build.wrapper); formatter = forAllSystems (system: treefmtEval.${system}.config.build.wrapper);
packages = forAllSystems (system: packages = forAllSystems (
let system:
pkgs = nixpkgs.legacyPackages.${system}; let
releaseInfo = nixpkgs.lib.importJSON ./release.json; pkgs = nixpkgs.legacyPackages.${system};
docs = import ./docs { releaseInfo = nixpkgs.lib.importJSON ./release.json;
inherit pkgs; docs = import ./docs {
inherit (releaseInfo) release isReleaseBranch; inherit pkgs;
}; inherit (releaseInfo) release isReleaseBranch;
hmPkg = pkgs.callPackage ./home-manager { path = "${self}"; }; };
in { hmPkg = pkgs.callPackage ./home-manager { path = "${self}"; };
default = hmPkg; in
home-manager = hmPkg; {
default = hmPkg;
home-manager = hmPkg;
docs-html = docs.manual.html; docs-html = docs.manual.html;
docs-htmlOpenTool = docs.manual.htmlOpenTool; docs-htmlOpenTool = docs.manual.htmlOpenTool;
docs-json = docs.options.json; docs-json = docs.options.json;
docs-jsonModuleMaintainers = docs.jsonModuleMaintainers; docs-jsonModuleMaintainers = docs.jsonModuleMaintainers;
docs-manpages = docs.manPages; docs-manpages = docs.manPages;
}); }
}); );
}
);
} }

View file

@ -2,46 +2,70 @@
# file is considered internal and the exported fields may change without # file is considered internal and the exported fields may change without
# warning. # warning.
{ newsJsonFile, newsReadIdsFile ? null }: {
newsJsonFile,
newsReadIdsFile ? null,
}:
let let
inherit (builtins) inherit (builtins)
concatStringsSep filter hasAttr isString length readFile replaceStrings sort concatStringsSep
split; filter
hasAttr
isString
length
readFile
replaceStrings
sort
split
;
newsJson = builtins.fromJSON (builtins.readFile newsJsonFile); newsJson = builtins.fromJSON (builtins.readFile newsJsonFile);
# Sorted and relevant entries. # Sorted and relevant entries.
relevantEntries = relevantEntries = sort (a: b: a.time > b.time) (filter (e: e.condition) newsJson.entries);
sort (a: b: a.time > b.time) (filter (e: e.condition) newsJson.entries);
newsReadIds = if newsReadIdsFile == null then newsReadIds =
{ } if newsReadIdsFile == null then
else { }
let ids = filter isString (split "\n" (readFile newsReadIdsFile)); else
in builtins.listToAttrs (map (id: { let
name = id; ids = filter isString (split "\n" (readFile newsReadIdsFile));
value = null; in
}) ids); builtins.listToAttrs (
map (id: {
name = id;
value = null;
}) ids
);
newsIsRead = entry: hasAttr entry.id newsReadIds; newsIsRead = entry: hasAttr entry.id newsReadIds;
newsUnread = let pred = entry: entry.condition && !newsIsRead entry; newsUnread =
in filter pred relevantEntries; let
pred = entry: entry.condition && !newsIsRead entry;
in
filter pred relevantEntries;
prettyTime = t: replaceStrings [ "T" "+00:00" ] [ " " "" ] t; prettyTime = t: replaceStrings [ "T" "+00:00" ] [ " " "" ] t;
layoutNews = entries: layoutNews =
entries:
let let
mkTextEntry = entry: mkTextEntry =
let flag = if newsIsRead entry then "read" else "unread"; entry:
in '' let
flag = if newsIsRead entry then "read" else "unread";
in
''
* ${prettyTime entry.time} [${flag}] * ${prettyTime entry.time} [${flag}]
${replaceStrings [ "\n" ] [ "\n " ] entry.message} ${replaceStrings [ "\n" ] [ "\n " ] entry.message}
''; '';
in concatStringsSep "\n\n" (map mkTextEntry entries); in
in { concatStringsSep "\n\n" (map mkTextEntry entries);
in
{
meta = { meta = {
numUnread = length newsUnread; numUnread = length newsUnread;
display = newsJson.display; display = newsJson.display;

View file

@ -1,64 +1,79 @@
{ runCommand, lib, bash, callPackage, coreutils, findutils, gettext, gnused, jq {
, less, ncurses, inetutils runCommand,
# used for pkgs.path for nixos-option lib,
, pkgs bash,
callPackage,
coreutils,
findutils,
gettext,
gnused,
jq,
less,
ncurses,
inetutils,
# used for pkgs.path for nixos-option
pkgs,
# Path to use as the Home Manager channel. # Path to use as the Home Manager channel.
, path ? null }: path ? null,
}:
let let
pathStr = if path == null then "" else path; pathStr = if path == null then "" else path;
nixos-option = pkgs.nixos-option or (callPackage nixos-option =
(pkgs.path + "/nixos/modules/installer/tools/nixos-option") { }); pkgs.nixos-option or (callPackage (pkgs.path + "/nixos/modules/installer/tools/nixos-option") { });
in runCommand "home-manager" { in
preferLocalBuild = true; runCommand "home-manager"
nativeBuildInputs = [ gettext ]; {
meta = { preferLocalBuild = true;
mainProgram = "home-manager"; nativeBuildInputs = [ gettext ];
description = "A user environment configurator"; meta = {
maintainers = [ lib.maintainers.rycee ]; mainProgram = "home-manager";
platforms = lib.platforms.unix; description = "A user environment configurator";
license = lib.licenses.mit; maintainers = [ lib.maintainers.rycee ];
}; platforms = lib.platforms.unix;
} '' license = lib.licenses.mit;
install -v -D -m755 ${./home-manager} $out/bin/home-manager };
}
''
install -v -D -m755 ${./home-manager} $out/bin/home-manager
substituteInPlace $out/bin/home-manager \ substituteInPlace $out/bin/home-manager \
--subst-var-by bash "${bash}" \ --subst-var-by bash "${bash}" \
--subst-var-by DEP_PATH "${ --subst-var-by DEP_PATH "${
lib.makeBinPath [ lib.makeBinPath [
coreutils coreutils
findutils findutils
gettext gettext
gnused gnused
jq jq
less less
ncurses ncurses
nixos-option nixos-option
inetutils # for `hostname` inetutils # for `hostname`
] ]
}" \ }" \
--subst-var-by HOME_MANAGER_LIB '${../lib/bash/home-manager.sh}' \ --subst-var-by HOME_MANAGER_LIB '${../lib/bash/home-manager.sh}' \
--subst-var-by HOME_MANAGER_PATH '${pathStr}' \ --subst-var-by HOME_MANAGER_PATH '${pathStr}' \
--subst-var-by OUT "$out" --subst-var-by OUT "$out"
install -D -m755 ${./completion.bash} \ install -D -m755 ${./completion.bash} \
$out/share/bash-completion/completions/home-manager $out/share/bash-completion/completions/home-manager
install -D -m755 ${./completion.zsh} \ install -D -m755 ${./completion.zsh} \
$out/share/zsh/site-functions/_home-manager $out/share/zsh/site-functions/_home-manager
install -D -m755 ${./completion.fish} \ install -D -m755 ${./completion.fish} \
$out/share/fish/vendor_completions.d/home-manager.fish $out/share/fish/vendor_completions.d/home-manager.fish
install -D -m755 ${../lib/bash/home-manager.sh} \ install -D -m755 ${../lib/bash/home-manager.sh} \
"$out/share/bash/home-manager.sh" "$out/share/bash/home-manager.sh"
for path in ${./po}/*.po; do for path in ${./po}/*.po; do
lang="''${path##*/}" lang="''${path##*/}"
lang="''${lang%%.*}" lang="''${lang%%.*}"
mkdir -p "$out/share/locale/$lang/LC_MESSAGES" mkdir -p "$out/share/locale/$lang/LC_MESSAGES"
msgfmt -o "$out/share/locale/$lang/LC_MESSAGES/home-manager.mo" "$path" msgfmt -o "$out/share/locale/$lang/LC_MESSAGES/home-manager.mo" "$path"
done done
'' ''

View file

@ -1,18 +1,31 @@
{ pkgs ? import <nixpkgs> { }, confPath, confAttr ? null, check ? true {
, newsReadIdsFile ? null }: pkgs ? import <nixpkgs> { },
confPath,
confAttr ? null,
check ? true,
newsReadIdsFile ? null,
}:
let let
inherit (pkgs.lib) inherit (pkgs.lib)
concatMapStringsSep fileContents filter length optionalString removeSuffix concatMapStringsSep
replaceStrings splitString; fileContents
filter
length
optionalString
removeSuffix
replaceStrings
splitString
;
env = import ../modules { env = import ../modules {
configuration = if confAttr == "" || confAttr == null then configuration =
confPath if confAttr == "" || confAttr == null then confPath else (import confPath).${confAttr};
else
(import confPath).${confAttr};
pkgs = pkgs; pkgs = pkgs;
check = check; check = check;
}; };
in { inherit (env) activationPackage config; } in
{
inherit (env) activationPackage config;
}

View file

@ -8,13 +8,16 @@ let
source ${home-manager}/share/bash/home-manager.sh source ${home-manager}/share/bash/home-manager.sh
''; '';
in runCommand "home-manager-install" { in
propagatedBuildInputs = [ home-manager ]; runCommand "home-manager-install"
preferLocalBuild = true; {
shellHookOnly = true; propagatedBuildInputs = [ home-manager ];
shellHook = "exec ${home-manager}/bin/home-manager init --switch --no-flake"; preferLocalBuild = true;
} '' shellHookOnly = true;
${hmBashLibInit} shellHook = "exec ${home-manager}/bin/home-manager init --switch --no-flake";
_iError 'This derivation is not buildable, please run it using nix-shell.' }
exit 1 ''
'' ${hmBashLibInit}
_iError 'This derivation is not buildable, please run it using nix-shell.'
exit 1
''

View file

@ -1,10 +1,21 @@
{ lib }: { { lib }:
{
hm = (import ../modules/lib/stdlib-extended.nix lib).hm; hm = (import ../modules/lib/stdlib-extended.nix lib).hm;
homeManagerConfiguration = { modules ? [ ], pkgs, lib ? pkgs.lib homeManagerConfiguration =
, extraSpecialArgs ? { }, check ? true {
modules ? [ ],
pkgs,
lib ? pkgs.lib,
extraSpecialArgs ? { },
check ? true,
# Deprecated: # Deprecated:
, configuration ? null, extraModules ? null, stateVersion ? null configuration ? null,
, username ? null, homeDirectory ? null, system ? null }@args: extraModules ? null,
stateVersion ? null,
username ? null,
homeDirectory ? null,
system ? null,
}@args:
let let
msgForRemovedArg = '' msgForRemovedArg = ''
The 'homeManagerConfiguration' arguments The 'homeManagerConfiguration' arguments
@ -20,7 +31,8 @@
'modules'. See the 22.11 release notes for more: https://nix-community.github.io/home-manager/release-notes.xhtml#sec-release-22.11-highlights 'modules'. See the 22.11 release notes for more: https://nix-community.github.io/home-manager/release-notes.xhtml#sec-release-22.11-highlights
''; '';
throwForRemovedArgs = v: throwForRemovedArgs =
v:
let let
used = builtins.filter (n: (args.${n} or null) != null) [ used = builtins.filter (n: (args.${n} or null) != null) [
"configuration" "configuration"
@ -30,20 +42,34 @@
"extraModules" "extraModules"
"system" "system"
]; ];
msg = msgForRemovedArg + '' msg =
msgForRemovedArg
+ ''
Deprecated args passed: '' + builtins.concatStringsSep " " used; Deprecated args passed: ''
in lib.throwIf (used != [ ]) msg v; + builtins.concatStringsSep " " used;
in
lib.throwIf (used != [ ]) msg v;
in throwForRemovedArgs (import ../modules { in
inherit pkgs lib check extraSpecialArgs; throwForRemovedArgs (
configuration = { ... }: { import ../modules {
imports = modules ++ [{ programs.home-manager.path = "${../.}"; }]; inherit
nixpkgs = { pkgs
config = lib.mkDefault pkgs.config; lib
inherit (pkgs) overlays; check
}; extraSpecialArgs
}; ;
}); configuration =
{ ... }:
{
imports = modules ++ [ { programs.home-manager.path = "${../.}"; } ];
nixpkgs = {
config = lib.mkDefault pkgs.config;
inherit (pkgs) overlays;
};
};
}
);
} }

View file

@ -4,19 +4,22 @@ let
cfg = config.accounts.calendar; cfg = config.accounts.calendar;
localModule = name: localModule =
name:
types.submodule { types.submodule {
options = { options = {
path = mkOption { path = mkOption {
type = types.str; type = types.str;
default = "${cfg.basePath}/${name}"; default = "${cfg.basePath}/${name}";
defaultText = defaultText = lib.literalExpression "accounts.calendar.basePath/name";
lib.literalExpression "accounts.calendar.basePath/name";
description = "The path of the storage."; description = "The path of the storage.";
}; };
type = mkOption { type = mkOption {
type = types.enum [ "filesystem" "singlefile" ]; type = types.enum [
"filesystem"
"singlefile"
];
default = "filesystem"; default = "filesystem";
description = "The type of the storage."; description = "The type of the storage.";
}; };
@ -41,7 +44,11 @@ let
remoteModule = types.submodule { remoteModule = types.submodule {
options = { options = {
type = mkOption { type = mkOption {
type = types.enum [ "caldav" "http" "google_calendar" ]; type = types.enum [
"caldav"
"http"
"google_calendar"
];
description = "The type of the storage."; description = "The type of the storage.";
}; };
@ -60,7 +67,10 @@ let
passwordCommand = mkOption { passwordCommand = mkOption {
type = types.nullOr (types.listOf types.str); type = types.nullOr (types.listOf types.str);
default = null; default = null;
example = [ "pass" "caldav" ]; example = [
"pass"
"caldav"
];
description = '' description = ''
A command that prints the password to standard output. A command that prints the password to standard output.
''; '';
@ -68,62 +78,66 @@ let
}; };
}; };
calendarOpts = { name, ... }: { calendarOpts =
options = { { name, ... }:
name = mkOption { {
type = types.str; options = {
readOnly = true; name = mkOption {
description = '' type = types.str;
Unique identifier of the calendar. This is set to the readOnly = true;
attribute name of the calendar configuration. description = ''
''; Unique identifier of the calendar. This is set to the
attribute name of the calendar configuration.
'';
};
primary = mkOption {
type = types.bool;
default = false;
description = ''
Whether this is the primary account. Only one account may be
set as primary.
'';
};
primaryCollection = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
The primary collection of the account. Required when an
account has multiple collections.
'';
};
local = mkOption {
type = localModule name;
default = { };
description = ''
Local configuration for the calendar.
'';
};
remote = mkOption {
type = types.nullOr remoteModule;
default = null;
description = ''
Remote configuration for the calendar.
'';
};
}; };
primary = mkOption { config = {
type = types.bool; name = name;
default = false;
description = ''
Whether this is the primary account. Only one account may be
set as primary.
'';
};
primaryCollection = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
The primary collection of the account. Required when an
account has multiple collections.
'';
};
local = mkOption {
type = localModule name;
default = { };
description = ''
Local configuration for the calendar.
'';
};
remote = mkOption {
type = types.nullOr remoteModule;
default = null;
description = ''
Remote configuration for the calendar.
'';
}; };
}; };
config = { name = name; }; in
}; {
in {
options.accounts.calendar = { options.accounts.calendar = {
basePath = mkOption { basePath = mkOption {
type = types.str; type = types.str;
example = ".calendar"; example = ".calendar";
apply = p: apply = p: if lib.hasPrefix "/" p then p else "${config.home.homeDirectory}/${p}";
if lib.hasPrefix "/" p then p else "${config.home.homeDirectory}/${p}";
description = '' description = ''
The base directory in which to save calendars. May be a The base directory in which to save calendars. May be a
relative path, in which case it is relative the home relative path, in which case it is relative the home
@ -132,25 +146,32 @@ in {
}; };
accounts = mkOption { accounts = mkOption {
type = types.attrsOf (types.submodule [ type = types.attrsOf (
calendarOpts types.submodule [
(import ../programs/vdirsyncer-accounts.nix) calendarOpts
(import ../programs/khal-accounts.nix) (import ../programs/vdirsyncer-accounts.nix)
(import ../programs/khal-calendar-accounts.nix) (import ../programs/khal-accounts.nix)
]); (import ../programs/khal-calendar-accounts.nix)
]
);
default = { }; default = { };
description = "List of calendars."; description = "List of calendars.";
}; };
}; };
config = lib.mkIf (cfg.accounts != { }) { config = lib.mkIf (cfg.accounts != { }) {
assertions = let assertions =
primaries = lib.catAttrs "name" let
(lib.filter (a: a.primary) (lib.attrValues cfg.accounts)); primaries = lib.catAttrs "name" (lib.filter (a: a.primary) (lib.attrValues cfg.accounts));
in [{ in
assertion = lib.length primaries <= 1; [
message = "Must have at most one primary calendar account but found " {
+ toString (lib.length primaries) + ", namely " assertion = lib.length primaries <= 1;
+ lib.concatStringsSep ", " primaries; message =
}]; "Must have at most one primary calendar account but found "
+ toString (lib.length primaries)
+ ", namely "
+ lib.concatStringsSep ", " primaries;
}
];
}; };
} }

View file

@ -5,19 +5,22 @@ let
cfg = config.accounts.contact; cfg = config.accounts.contact;
localModule = name: localModule =
name:
types.submodule { types.submodule {
options = { options = {
path = mkOption { path = mkOption {
type = types.str; type = types.str;
default = "${cfg.basePath}/${name}"; default = "${cfg.basePath}/${name}";
defaultText = defaultText = lib.literalExpression "accounts.contact.basePath/name";
lib.literalExpression "accounts.contact.basePath/name";
description = "The path of the storage."; description = "The path of the storage.";
}; };
type = mkOption { type = mkOption {
type = types.enum [ "filesystem" "singlefile" ]; type = types.enum [
"filesystem"
"singlefile"
];
description = "The type of the storage."; description = "The type of the storage.";
}; };
@ -41,7 +44,11 @@ let
remoteModule = types.submodule { remoteModule = types.submodule {
options = { options = {
type = mkOption { type = mkOption {
type = types.enum [ "carddav" "http" "google_contacts" ]; type = types.enum [
"carddav"
"http"
"google_contacts"
];
description = "The type of the storage."; description = "The type of the storage.";
}; };
@ -69,7 +76,10 @@ let
passwordCommand = mkOption { passwordCommand = mkOption {
type = types.nullOr (types.listOf types.str); type = types.nullOr (types.listOf types.str);
default = null; default = null;
example = [ "pass" "caldav" ]; example = [
"pass"
"caldav"
];
description = '' description = ''
A command that prints the password to standard output. A command that prints the password to standard output.
''; '';
@ -77,43 +87,47 @@ let
}; };
}; };
contactOpts = { name, ... }: { contactOpts =
options = { { name, ... }:
name = mkOption { {
type = types.str; options = {
readOnly = true; name = mkOption {
description = '' type = types.str;
Unique identifier of the contact account. This is set to the readOnly = true;
attribute name of the contact configuration. description = ''
''; Unique identifier of the contact account. This is set to the
attribute name of the contact configuration.
'';
};
local = mkOption {
type = types.nullOr (localModule name);
default = null;
description = ''
Local configuration for the contacts.
'';
};
remote = mkOption {
type = types.nullOr remoteModule;
default = null;
description = ''
Remote configuration for the contacts.
'';
};
}; };
local = mkOption { config = {
type = types.nullOr (localModule name); name = name;
default = null;
description = ''
Local configuration for the contacts.
'';
};
remote = mkOption {
type = types.nullOr remoteModule;
default = null;
description = ''
Remote configuration for the contacts.
'';
}; };
}; };
config = { name = name; }; in
}; {
in {
options.accounts.contact = { options.accounts.contact = {
basePath = mkOption { basePath = mkOption {
type = types.str; type = types.str;
apply = p: apply = p: if lib.hasPrefix "/" p then p else "${config.home.homeDirectory}/${p}";
if lib.hasPrefix "/" p then p else "${config.home.homeDirectory}/${p}";
description = '' description = ''
The base directory in which to save contacts. May be a The base directory in which to save contacts. May be a
relative path, in which case it is relative the home relative path, in which case it is relative the home
@ -122,12 +136,14 @@ in {
}; };
accounts = mkOption { accounts = mkOption {
type = types.attrsOf (types.submodule [ type = types.attrsOf (
contactOpts types.submodule [
(import ../programs/vdirsyncer-accounts.nix) contactOpts
(import ../programs/khal-accounts.nix) (import ../programs/vdirsyncer-accounts.nix)
(import ../programs/khal-contact-accounts.nix) (import ../programs/khal-accounts.nix)
]); (import ../programs/khal-contact-accounts.nix)
]
);
default = { }; default = { };
description = "List of contacts."; description = "List of contacts.";
}; };

View file

@ -1,7 +1,12 @@
{ config, lib, ... }: { config, lib, ... }:
let let
inherit (lib) mkDefault mkIf mkOption types; inherit (lib)
mkDefault
mkIf
mkOption
types
;
cfg = config.accounts.email; cfg = config.accounts.email;
@ -66,7 +71,11 @@ let
}; };
showSignature = mkOption { showSignature = mkOption {
type = types.enum [ "append" "attach" "none" ]; type = types.enum [
"append"
"attach"
"none"
];
default = "none"; default = "none";
description = "Method to communicate the signature."; description = "Method to communicate the signature.";
}; };
@ -195,320 +204,333 @@ let
}; };
}; };
maildirModule = types.submodule ({ config, ... }: { maildirModule = types.submodule (
options = { { config, ... }:
path = mkOption { {
type = types.str; options = {
description = '' path = mkOption {
Path to maildir directory where mail for this account is type = types.str;
stored. This is relative to the base maildir path. description = ''
''; Path to maildir directory where mail for this account is
stored. This is relative to the base maildir path.
'';
};
absPath = mkOption {
type = types.path;
readOnly = true;
internal = true;
default = "${cfg.maildirBasePath}/${config.path}";
description = ''
A convenience option whose value is the absolute path of
this maildir.
'';
};
}; };
}
);
absPath = mkOption { mailAccountOpts =
type = types.path; { name, config, ... }:
readOnly = true; {
internal = true; options = {
default = "${cfg.maildirBasePath}/${config.path}"; name = mkOption {
description = '' type = types.str;
A convenience option whose value is the absolute path of readOnly = true;
this maildir. description = ''
''; Unique identifier of the account. This is set to the
}; attribute name of the account configuration.
}; '';
}); };
mailAccountOpts = { name, config, ... }: { primary = mkOption {
options = { type = types.bool;
name = mkOption { default = false;
type = types.str; description = ''
readOnly = true; Whether this is the primary account. Only one account may be
description = '' set as primary.
Unique identifier of the account. This is set to the '';
attribute name of the account configuration. };
'';
};
primary = mkOption { flavor = mkOption {
type = types.bool; type = types.enum [
default = false; "plain"
description = '' "gmail.com"
Whether this is the primary account. Only one account may be "runbox.com"
set as primary. "fastmail.com"
''; "yandex.com"
}; "outlook.office365.com"
"migadu.com"
];
default = "plain";
description = ''
Some email providers have peculiar behavior that require
special treatment. This option is therefore intended to
indicate the nature of the provider.
flavor = mkOption { When this indicates a specific provider then, for example,
type = types.enum [ the IMAP, SMTP, and JMAP server configuration may be set
"plain" automatically.
"gmail.com" '';
"runbox.com" };
"fastmail.com"
"yandex.com"
"outlook.office365.com"
"migadu.com"
];
default = "plain";
description = ''
Some email providers have peculiar behavior that require
special treatment. This option is therefore intended to
indicate the nature of the provider.
When this indicates a specific provider then, for example, address = mkOption {
the IMAP, SMTP, and JMAP server configuration may be set type = types.strMatching ".*@.*";
automatically. example = "jane.doe@example.org";
''; description = "The email address of this account.";
}; };
address = mkOption { aliases = mkOption {
type = types.strMatching ".*@.*"; description = "Alternative identities of this account.";
example = "jane.doe@example.org"; default = [ ];
description = "The email address of this account."; example = [
}; "webmaster@example.org"
"admin@example.org"
];
type = types.listOf (
types.oneOf [
(types.strMatching ".*@.*")
(types.submodule {
options = {
realName = mkOption {
type = types.str;
example = "Jane Doe";
description = "Name displayed when sending mails.";
};
address = mkOption {
type = types.strMatching ".*@.*";
example = "jane.doe@example.org";
description = "The email address of this identity.";
};
};
})
]
);
};
aliases = mkOption { realName = mkOption {
description = "Alternative identities of this account."; type = types.str;
default = [ ]; example = "Jane Doe";
example = [ "webmaster@example.org" "admin@example.org" ]; description = "Name displayed when sending mails.";
type = types.listOf (types.oneOf [ };
(types.strMatching ".*@.*")
(types.submodule { userName = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
The server username of this account. This will be used as
the SMTP, IMAP, and JMAP user name.
'';
};
passwordCommand = mkOption {
type = types.nullOr (types.either types.str (types.listOf types.str));
default = null;
apply = p: if lib.isString p then lib.splitString " " p else p;
example = "secret-tool lookup email me@example.org";
description = ''
A command, which when run writes the account password on
standard output.
'';
};
folders = mkOption {
type = types.submodule {
options = { options = {
realName = mkOption { inbox = mkOption {
type = types.str; type = types.str;
example = "Jane Doe"; default = "Inbox";
description = "Name displayed when sending mails."; description = ''
Relative path of the inbox mail.
'';
}; };
address = mkOption {
type = types.strMatching ".*@.*"; sent = mkOption {
example = "jane.doe@example.org"; type = types.nullOr types.str;
description = "The email address of this identity."; default = "Sent";
description = ''
Relative path of the sent mail folder.
'';
}; };
};
})
]);
};
realName = mkOption { drafts = mkOption {
type = types.str; type = types.nullOr types.str;
example = "Jane Doe"; default = "Drafts";
description = "Name displayed when sending mails."; description = ''
}; Relative path of the drafts mail folder.
'';
};
userName = mkOption { trash = mkOption {
type = types.nullOr types.str; type = types.str;
default = null; default = "Trash";
description = '' description = ''
The server username of this account. This will be used as Relative path of the deleted mail folder.
the SMTP, IMAP, and JMAP user name. '';
''; };
};
passwordCommand = mkOption {
type = types.nullOr (types.either types.str (types.listOf types.str));
default = null;
apply = p: if lib.isString p then lib.splitString " " p else p;
example = "secret-tool lookup email me@example.org";
description = ''
A command, which when run writes the account password on
standard output.
'';
};
folders = mkOption {
type = types.submodule {
options = {
inbox = mkOption {
type = types.str;
default = "Inbox";
description = ''
Relative path of the inbox mail.
'';
};
sent = mkOption {
type = types.nullOr types.str;
default = "Sent";
description = ''
Relative path of the sent mail folder.
'';
};
drafts = mkOption {
type = types.nullOr types.str;
default = "Drafts";
description = ''
Relative path of the drafts mail folder.
'';
};
trash = mkOption {
type = types.str;
default = "Trash";
description = ''
Relative path of the deleted mail folder.
'';
}; };
}; };
default = { };
description = ''
Standard email folders.
'';
};
imap = mkOption {
type = types.nullOr imapModule;
default = null;
description = ''
The IMAP configuration to use for this account.
'';
};
jmap = mkOption {
type = types.nullOr jmapModule;
default = null;
description = ''
The JMAP configuration to use for this account.
'';
};
signature = mkOption {
type = signatureModule;
default = { };
description = ''
Signature configuration.
'';
};
gpg = mkOption {
type = types.nullOr gpgModule;
default = null;
description = ''
GPG configuration.
'';
};
smtp = mkOption {
type = types.nullOr smtpModule;
default = null;
description = ''
The SMTP configuration to use for this account.
'';
};
maildir = mkOption {
type = types.nullOr maildirModule;
defaultText = {
path = "\${name}";
};
description = ''
Maildir configuration for this account.
'';
}; };
default = { };
description = ''
Standard email folders.
'';
}; };
imap = mkOption { config = lib.mkMerge [
type = types.nullOr imapModule; {
default = null; name = name;
description = '' maildir = lib.mkOptionDefault { path = "${name}"; };
The IMAP configuration to use for this account. }
'';
};
jmap = mkOption { (mkIf (config.flavor == "yandex.com") {
type = types.nullOr jmapModule; userName = mkDefault config.address;
default = null;
description = ''
The JMAP configuration to use for this account.
'';
};
signature = mkOption { imap = {
type = signatureModule; host = "imap.yandex.com";
default = { }; port = 993;
description = '' tls.enable = true;
Signature configuration. };
'';
};
gpg = mkOption { smtp = {
type = types.nullOr gpgModule; host = "smtp.yandex.com";
default = null; port = 465;
description = '' tls.enable = true;
GPG configuration. };
''; })
};
smtp = mkOption { (mkIf (config.flavor == "outlook.office365.com") {
type = types.nullOr smtpModule; userName = mkDefault config.address;
default = null;
description = ''
The SMTP configuration to use for this account.
'';
};
maildir = mkOption { imap = {
type = types.nullOr maildirModule; host = "outlook.office365.com";
defaultText = { path = "\${name}"; }; port = 993;
description = '' tls.enable = true;
Maildir configuration for this account. };
'';
}; smtp = {
host = "smtp.office365.com";
port = 587;
tls = {
enable = true;
useStartTls = true;
};
};
})
(mkIf (config.flavor == "fastmail.com") {
userName = mkDefault config.address;
imap = {
host = "imap.fastmail.com";
port = 993;
};
smtp = {
host = "smtp.fastmail.com";
port = if config.smtp.tls.useStartTls then 587 else 465;
};
jmap = {
host = "fastmail.com";
sessionUrl = "https://jmap.fastmail.com/.well-known/jmap";
};
})
(mkIf (config.flavor == "migadu.com") {
userName = mkDefault config.address;
imap = {
host = "imap.migadu.com";
port = 993;
};
smtp = {
host = "smtp.migadu.com";
port = 465;
};
})
(mkIf (config.flavor == "gmail.com") {
userName = mkDefault config.address;
imap = {
host = "imap.gmail.com";
port = 993;
};
smtp = {
host = "smtp.gmail.com";
port = if config.smtp.tls.useStartTls then 587 else 465;
};
})
(mkIf (config.flavor == "runbox.com") {
imap = {
host = "mail.runbox.com";
port = 993;
};
smtp = {
host = "mail.runbox.com";
port = if config.smtp.tls.useStartTls then 587 else 465;
};
})
];
}; };
config = lib.mkMerge [ in
{ {
name = name;
maildir = lib.mkOptionDefault { path = "${name}"; };
}
(mkIf (config.flavor == "yandex.com") {
userName = mkDefault config.address;
imap = {
host = "imap.yandex.com";
port = 993;
tls.enable = true;
};
smtp = {
host = "smtp.yandex.com";
port = 465;
tls.enable = true;
};
})
(mkIf (config.flavor == "outlook.office365.com") {
userName = mkDefault config.address;
imap = {
host = "outlook.office365.com";
port = 993;
tls.enable = true;
};
smtp = {
host = "smtp.office365.com";
port = 587;
tls = {
enable = true;
useStartTls = true;
};
};
})
(mkIf (config.flavor == "fastmail.com") {
userName = mkDefault config.address;
imap = {
host = "imap.fastmail.com";
port = 993;
};
smtp = {
host = "smtp.fastmail.com";
port = if config.smtp.tls.useStartTls then 587 else 465;
};
jmap = {
host = "fastmail.com";
sessionUrl = "https://jmap.fastmail.com/.well-known/jmap";
};
})
(mkIf (config.flavor == "migadu.com") {
userName = mkDefault config.address;
imap = {
host = "imap.migadu.com";
port = 993;
};
smtp = {
host = "smtp.migadu.com";
port = 465;
};
})
(mkIf (config.flavor == "gmail.com") {
userName = mkDefault config.address;
imap = {
host = "imap.gmail.com";
port = 993;
};
smtp = {
host = "smtp.gmail.com";
port = if config.smtp.tls.useStartTls then 587 else 465;
};
})
(mkIf (config.flavor == "runbox.com") {
imap = {
host = "mail.runbox.com";
port = 993;
};
smtp = {
host = "mail.runbox.com";
port = if config.smtp.tls.useStartTls then 587 else 465;
};
})
];
};
in {
options.accounts.email = { options.accounts.email = {
certificatesFile = mkOption { certificatesFile = mkOption {
type = types.nullOr types.path; type = types.nullOr types.path;
@ -524,8 +546,7 @@ in {
type = types.str; type = types.str;
default = "${config.home.homeDirectory}/Maildir"; default = "${config.home.homeDirectory}/Maildir";
defaultText = "Maildir"; defaultText = "Maildir";
apply = p: apply = p: if lib.hasPrefix "/" p then p else "${config.home.homeDirectory}/${p}";
if lib.hasPrefix "/" p then p else "${config.home.homeDirectory}/${p}";
description = '' description = ''
The base directory for account maildir directories. May be a The base directory for account maildir directories. May be a
relative path (e.g. the user setting this value as "MyMaildir"), relative path (e.g. the user setting this value as "MyMaildir"),
@ -543,16 +564,18 @@ in {
config = mkIf (cfg.accounts != { }) { config = mkIf (cfg.accounts != { }) {
assertions = [ assertions = [
(let (
primaries = lib.catAttrs "name" let
(lib.filter (a: a.primary) (lib.attrValues cfg.accounts)); primaries = lib.catAttrs "name" (lib.filter (a: a.primary) (lib.attrValues cfg.accounts));
in { in
assertion = lib.length primaries == 1; {
message = "Must have exactly one primary mail account but found " assertion = lib.length primaries == 1;
+ toString (lib.length primaries) message =
+ lib.optionalString (lib.length primaries > 1) "Must have exactly one primary mail account but found "
(", namely " + lib.concatStringsSep ", " primaries); + toString (lib.length primaries)
}) + lib.optionalString (lib.length primaries > 1) (", namely " + lib.concatStringsSep ", " primaries);
}
)
]; ];
}; };
} }

View file

@ -1,9 +1,27 @@
{ config, options, lib, pkgs, ... }: {
config,
options,
lib,
pkgs,
...
}:
let let
inherit (lib) inherit (lib)
mkEnableOption mkOption mkIf mkMerge mkDefault mkAliasOptionModule types mkEnableOption
literalExpression escapeShellArg hm getAttrFromPath any optional; mkOption
mkIf
mkMerge
mkDefault
mkAliasOptionModule
types
literalExpression
escapeShellArg
hm
getAttrFromPath
any
optional
;
cfg = config.home.pointerCursor; cfg = config.home.pointerCursor;
opts = options.home.pointerCursor; opts = options.home.pointerCursor;
@ -51,11 +69,13 @@ let
}; };
dotIcons = { dotIcons = {
enable = mkEnableOption '' enable =
`.icons` config generation for {option}`home.pointerCursor` mkEnableOption ''
'' // { `.icons` config generation for {option}`home.pointerCursor`
default = true; ''
}; // {
default = true;
};
}; };
hyprcursor = { hyprcursor = {
@ -70,15 +90,12 @@ let
}; };
sway = { sway = {
enable = mkEnableOption enable = mkEnableOption "sway config generation for {option}`home.pointerCursor`";
"sway config generation for {option}`home.pointerCursor`";
}; };
}; };
}; };
cursorPath = "${cfg.package}/share/icons/${escapeShellArg cfg.name}/cursors/${ cursorPath = "${cfg.package}/share/icons/${escapeShellArg cfg.name}/cursors/${escapeShellArg cfg.x11.defaultCursor}";
escapeShellArg cfg.x11.defaultCursor
}";
defaultIndexThemePackage = pkgs.writeTextFile { defaultIndexThemePackage = pkgs.writeTextFile {
name = "index.theme"; name = "index.theme";
@ -94,31 +111,44 @@ let
''; '';
}; };
in { in
{
meta.maintainers = [ lib.maintainers.league ]; meta.maintainers = [ lib.maintainers.league ];
imports = [ imports = [
(mkAliasOptionModule [ "xsession" "pointerCursor" "package" ] [ (mkAliasOptionModule
"home" [ "xsession" "pointerCursor" "package" ]
"pointerCursor" [
"package" "home"
]) "pointerCursor"
(mkAliasOptionModule [ "xsession" "pointerCursor" "name" ] [ "package"
"home" ]
"pointerCursor" )
"name" (mkAliasOptionModule
]) [ "xsession" "pointerCursor" "name" ]
(mkAliasOptionModule [ "xsession" "pointerCursor" "size" ] [ [
"home" "home"
"pointerCursor" "pointerCursor"
"size" "name"
]) ]
(mkAliasOptionModule [ "xsession" "pointerCursor" "defaultCursor" ] [ )
"home" (mkAliasOptionModule
"pointerCursor" [ "xsession" "pointerCursor" "size" ]
"x11" [
"defaultCursor" "home"
]) "pointerCursor"
"size"
]
)
(mkAliasOptionModule
[ "xsession" "pointerCursor" "defaultCursor" ]
[
"home"
"pointerCursor"
"x11"
"defaultCursor"
]
)
]; ];
options = { options = {
@ -145,107 +175,121 @@ in {
}; };
}; };
config = let config =
# Check if enable option was explicitly defined by the user let
enableDefined = any (x: x ? enable) opts.definitions; # Check if enable option was explicitly defined by the user
enableDefined = any (x: x ? enable) opts.definitions;
# Determine if cursor configuration should be enabled # Determine if cursor configuration should be enabled
enable = if enableDefined then cfg.enable else cfg != null; enable = if enableDefined then cfg.enable else cfg != null;
in mkMerge [ in
(mkIf enable (mkMerge [ mkMerge [
{ (mkIf enable (mkMerge [
assertions = [ {
(hm.assertions.assertPlatform "home.pointerCursor" pkgs assertions = [
lib.platforms.linux) (hm.assertions.assertPlatform "home.pointerCursor" pkgs lib.platforms.linux)
]; ];
home.packages = [ cfg.package defaultIndexThemePackage ]; home.packages = [
cfg.package
defaultIndexThemePackage
];
home.sessionVariables = { home.sessionVariables = {
XCURSOR_SIZE = mkDefault cfg.size; XCURSOR_SIZE = mkDefault cfg.size;
XCURSOR_THEME = mkDefault cfg.name; XCURSOR_THEME = mkDefault cfg.name;
}; };
# Set directory to look for cursors in, needed for some applications # Set directory to look for cursors in, needed for some applications
# that are unable to find cursors otherwise. See: # that are unable to find cursors otherwise. See:
# https://github.com/nix-community/home-manager/issues/2812 # https://github.com/nix-community/home-manager/issues/2812
# https://wiki.archlinux.org/title/Cursor_themes#Environment_variable # https://wiki.archlinux.org/title/Cursor_themes#Environment_variable
home.sessionSearchVariables.XCURSOR_PATH = home.sessionSearchVariables.XCURSOR_PATH = [ "${config.home.profileDirectory}/share/icons" ];
[ "${config.home.profileDirectory}/share/icons" ];
# Add cursor icon link to $XDG_DATA_HOME/icons as well for redundancy. # Add cursor icon link to $XDG_DATA_HOME/icons as well for redundancy.
xdg.dataFile."icons/default/index.theme".source = xdg.dataFile."icons/default/index.theme".source =
"${defaultIndexThemePackage}/share/icons/default/index.theme"; "${defaultIndexThemePackage}/share/icons/default/index.theme";
xdg.dataFile."icons/${cfg.name}".source = xdg.dataFile."icons/${cfg.name}".source = "${cfg.package}/share/icons/${cfg.name}";
"${cfg.package}/share/icons/${cfg.name}"; }
}
(mkIf cfg.dotIcons.enable { (mkIf cfg.dotIcons.enable {
# Add symlink of cursor icon directory to $HOME/.icons, needed for # Add symlink of cursor icon directory to $HOME/.icons, needed for
# backwards compatibility with some applications. See: # backwards compatibility with some applications. See:
# https://specifications.freedesktop.org/icon-theme-spec/latest/ar01s03.html # https://specifications.freedesktop.org/icon-theme-spec/latest/ar01s03.html
home.file.".icons/default/index.theme".source = home.file.".icons/default/index.theme".source =
"${defaultIndexThemePackage}/share/icons/default/index.theme"; "${defaultIndexThemePackage}/share/icons/default/index.theme";
home.file.".icons/${cfg.name}".source = home.file.".icons/${cfg.name}".source = "${cfg.package}/share/icons/${cfg.name}";
"${cfg.package}/share/icons/${cfg.name}"; })
})
(mkIf cfg.x11.enable { (mkIf cfg.x11.enable {
xsession.profileExtra = '' xsession.profileExtra = ''
${pkgs.xorg.xsetroot}/bin/xsetroot -xcf ${cursorPath} ${ ${pkgs.xorg.xsetroot}/bin/xsetroot -xcf ${cursorPath} ${toString cfg.size}
toString cfg.size '';
}
'';
xresources.properties = { xresources.properties = {
"Xcursor.theme" = cfg.name; "Xcursor.theme" = cfg.name;
"Xcursor.size" = cfg.size; "Xcursor.size" = cfg.size;
}; };
}) })
(mkIf cfg.gtk.enable { (mkIf cfg.gtk.enable {
gtk.cursorTheme = mkDefault { inherit (cfg) package name size; }; gtk.cursorTheme = mkDefault { inherit (cfg) package name size; };
}) })
(mkIf cfg.hyprcursor.enable { (mkIf cfg.hyprcursor.enable {
home.sessionVariables = { home.sessionVariables = {
HYPRCURSOR_THEME = cfg.name; HYPRCURSOR_THEME = cfg.name;
HYPRCURSOR_SIZE = if cfg.hyprcursor.size != null then HYPRCURSOR_SIZE = if cfg.hyprcursor.size != null then cfg.hyprcursor.size else cfg.size;
cfg.hyprcursor.size };
else })
cfg.size;
};
})
(mkIf cfg.sway.enable { (mkIf cfg.sway.enable {
wayland.windowManager.sway = { wayland.windowManager.sway = {
config = { config = {
seat = { seat = {
"*" = { "*" = {
xcursor_theme = xcursor_theme = "${cfg.name} ${toString config.gtk.cursorTheme.size}";
"${cfg.name} ${toString config.gtk.cursorTheme.size}"; };
}; };
}; };
}; };
}; })
}) ]))
]))
{ {
warnings = (optional (any (x: warnings =
getAttrFromPath (optional
([ "xsession" "pointerCursor" ] ++ [ x ] ++ [ "isDefined" ]) (any
options) [ "package" "name" "size" "defaultCursor" ]) '' (
The option `xsession.pointerCursor` has been merged into `home.pointerCursor` and will be removed x:
in the future. Please change to set `home.pointerCursor` directly and enable `home.pointerCursor.x11.enable` getAttrFromPath (
to generate x11 specific cursor configurations. You can refer to the documentation for more details. [
'') ++ (optional (opts.highestPrio != (lib.mkOptionDefault { }).priority "xsession"
&& cfg == null) '' "pointerCursor"
]
++ [ x ]
++ [ "isDefined" ]
) options
)
[
"package"
"name"
"size"
"defaultCursor"
]
)
''
The option `xsession.pointerCursor` has been merged into `home.pointerCursor` and will be removed
in the future. Please change to set `home.pointerCursor` directly and enable `home.pointerCursor.x11.enable`
to generate x11 specific cursor configurations. You can refer to the documentation for more details.
''
)
++ (optional (opts.highestPrio != (lib.mkOptionDefault { }).priority && cfg == null) ''
Setting home.pointerCursor to null is deprecated. Setting home.pointerCursor to null is deprecated.
Please update your configuration to explicitly set: Please update your configuration to explicitly set:
home.pointerCursor.enable = false; home.pointerCursor.enable = false;
''); '');
} }
]; ];
} }

View file

@ -15,7 +15,12 @@
# below for changes: # below for changes:
# https://github.com/NixOS/nixpkgs/blob/nixpkgs-unstable/pkgs/development/libraries/glibc/nix-locale-archive.patch # https://github.com/NixOS/nixpkgs/blob/nixpkgs-unstable/pkgs/development/libraries/glibc/nix-locale-archive.patch
{ lib, pkgs, config, ... }: {
lib,
pkgs,
config,
...
}:
let let
inherit (config.i18n) glibcLocales; inherit (config.i18n) glibcLocales;
@ -25,14 +30,20 @@ let
archivePath = "${glibcLocales}/lib/locale/locale-archive"; archivePath = "${glibcLocales}/lib/locale/locale-archive";
# lookup the version of glibcLocales and set the appropriate environment vars # lookup the version of glibcLocales and set the appropriate environment vars
localeVars = if lib.versionAtLeast version "2.27" then { localeVars =
LOCALE_ARCHIVE_2_27 = archivePath; if lib.versionAtLeast version "2.27" then
} else if lib.versionAtLeast version "2.11" then { {
LOCALE_ARCHIVE_2_11 = archivePath; LOCALE_ARCHIVE_2_27 = archivePath;
} else }
{ }; else if lib.versionAtLeast version "2.11" then
{
LOCALE_ARCHIVE_2_11 = archivePath;
}
else
{ };
in { in
{
meta.maintainers = with lib.maintainers; [ midchildan ]; meta.maintainers = with lib.maintainers; [ midchildan ];
options = { options = {

View file

@ -1,18 +1,24 @@
{ configuration, pkgs, lib ? pkgs.lib {
configuration,
pkgs,
lib ? pkgs.lib,
# Whether to check that each option has a matching declaration. # Whether to check that each option has a matching declaration.
, check ? true check ? true,
# Extra arguments passed to specialArgs. # Extra arguments passed to specialArgs.
, extraSpecialArgs ? { } }: extraSpecialArgs ? { },
}:
let let
collectFailed = cfg: collectFailed = cfg: map (x: x.message) (lib.filter (x: !x.assertion) cfg.assertions);
map (x: x.message) (lib.filter (x: !x.assertion) cfg.assertions);
showWarnings = res: showWarnings =
let f = w: x: builtins.trace "warning: ${w}" x; res:
in lib.fold f res res.config.warnings; let
f = w: x: builtins.trace "warning: ${w}" x;
in
lib.fold f res res.config.warnings;
extendedLib = import ./lib/stdlib-extended.nix lib; extendedLib = import ./lib/stdlib-extended.nix lib;
@ -24,24 +30,33 @@ let
rawModule = extendedLib.evalModules { rawModule = extendedLib.evalModules {
modules = [ configuration ] ++ hmModules; modules = [ configuration ] ++ hmModules;
class = "homeManager"; class = "homeManager";
specialArgs = { modulesPath = builtins.toString ./.; } // extraSpecialArgs; specialArgs = {
modulesPath = builtins.toString ./.;
} // extraSpecialArgs;
}; };
moduleChecks = raw: moduleChecks =
showWarnings (let raw:
failed = collectFailed raw.config; showWarnings (
failedStr = lib.concatStringsSep "\n" (map (x: "- ${x}") failed); let
in if failed == [ ] then failed = collectFailed raw.config;
raw failedStr = lib.concatStringsSep "\n" (map (x: "- ${x}") failed);
else in
throw '' if failed == [ ] then
raw
else
throw ''
Failed assertions: Failed assertions:
${failedStr}''); ${failedStr}''
);
withExtraAttrs = rawModule: withExtraAttrs =
let module = moduleChecks rawModule; rawModule:
in { let
module = moduleChecks rawModule;
in
{
inherit (module) options config; inherit (module) options config;
activationPackage = module.config.home.activationPackage; activationPackage = module.config.home.activationPackage;
@ -50,11 +65,13 @@ let
activation-script = module.config.home.activationPackage; activation-script = module.config.home.activationPackage;
newsDisplay = rawModule.config.news.display; newsDisplay = rawModule.config.news.display;
newsEntries = lib.sort (a: b: a.time > b.time) newsEntries = lib.sort (a: b: a.time > b.time) (
(lib.filter (a: a.condition) rawModule.config.news.entries); lib.filter (a: a.condition) rawModule.config.news.entries
);
inherit (module._module.args) pkgs; inherit (module._module.args) pkgs;
extendModules = args: withExtraAttrs (rawModule.extendModules args); extendModules = args: withExtraAttrs (rawModule.extendModules args);
}; };
in withExtraAttrs rawModule in
withExtraAttrs rawModule

View file

@ -1,4 +1,9 @@
{ pkgs, config, lib, ... }: {
pkgs,
config,
lib,
...
}:
let let
@ -6,18 +11,24 @@ let
homeDirectory = config.home.homeDirectory; homeDirectory = config.home.homeDirectory;
fileType = (import lib/file-type.nix { fileType =
inherit homeDirectory lib pkgs; (import lib/file-type.nix {
}).fileType; inherit homeDirectory lib pkgs;
}).fileType;
sourceStorePath = file: sourceStorePath =
file:
let let
sourcePath = toString file.source; sourcePath = toString file.source;
sourceName = config.lib.strings.storeFileName (baseNameOf sourcePath); sourceName = config.lib.strings.storeFileName (baseNameOf sourcePath);
in in
if builtins.hasContext sourcePath if builtins.hasContext sourcePath then
then file.source file.source
else builtins.path { path = file.source; name = sourceName; }; else
builtins.path {
path = file.source;
name = sourceName;
};
in in
@ -25,7 +36,7 @@ in
options = { options = {
home.file = lib.mkOption { home.file = lib.mkOption {
description = "Attribute set of files to link into the user home."; description = "Attribute set of files to link into the user home.";
default = {}; default = { };
type = fileType "home.file" "{env}`HOME`" homeDirectory; type = fileType "home.file" "{env}`HOME`" homeDirectory;
}; };
@ -37,26 +48,29 @@ in
}; };
config = { config = {
assertions = [( assertions = [
let (
dups = let
lib.attrNames dups = lib.attrNames (
(lib.filterAttrs (n: v: v > 1) lib.filterAttrs (n: v: v > 1) (
(lib.foldAttrs (acc: v: acc + v) 0 lib.foldAttrs (acc: v: acc + v) 0 (lib.mapAttrsToList (n: v: { ${v.target} = 1; }) cfg)
(lib.mapAttrsToList (n: v: { ${v.target} = 1; }) cfg))); )
dupsStr = lib.concatStringsSep ", " dups; );
in { dupsStr = lib.concatStringsSep ", " dups;
assertion = dups == []; in
message = '' {
Conflicting managed target files: ${dupsStr} assertion = dups == [ ];
message = ''
Conflicting managed target files: ${dupsStr}
This may happen, for example, if you have a configuration similar to This may happen, for example, if you have a configuration similar to
home.file = { home.file = {
conflict1 = { source = ./foo.nix; target = "baz"; }; conflict1 = { source = ./foo.nix; target = "baz"; };
conflict2 = { source = ./bar.nix; target = "baz"; }; conflict2 = { source = ./bar.nix; target = "baz"; };
}''; }'';
}) }
)
]; ];
# Using this function it is possible to make `home.file` create a # Using this function it is possible to make `home.file` create a
@ -67,23 +81,23 @@ in
# #
# would upon activation create a symlink `~/foo` that points to the # would upon activation create a symlink `~/foo` that points to the
# absolute path of the `bar` file relative the configuration file. # absolute path of the `bar` file relative the configuration file.
lib.file.mkOutOfStoreSymlink = path: lib.file.mkOutOfStoreSymlink =
path:
let let
pathStr = toString path; pathStr = toString path;
name = lib.hm.strings.storeFileName (baseNameOf pathStr); name = lib.hm.strings.storeFileName (baseNameOf pathStr);
in in
pkgs.runCommandLocal name {} ''ln -s ${lib.escapeShellArg pathStr} $out''; pkgs.runCommandLocal name { } ''ln -s ${lib.escapeShellArg pathStr} $out'';
# This verifies that the links we are about to create will not # This verifies that the links we are about to create will not
# overwrite an existing file. # overwrite an existing file.
home.activation.checkLinkTargets = lib.hm.dag.entryBefore ["writeBoundary"] ( home.activation.checkLinkTargets = lib.hm.dag.entryBefore [ "writeBoundary" ] (
let let
# Paths that should be forcibly overwritten by Home Manager. # Paths that should be forcibly overwritten by Home Manager.
# Caveat emptor! # Caveat emptor!
forcedPaths = forcedPaths = lib.concatMapStringsSep " " (p: ''"$HOME"/${lib.escapeShellArg p}'') (
lib.concatMapStringsSep " " (p: ''"$HOME"/${lib.escapeShellArg p}'') lib.mapAttrsToList (n: v: v.target) (lib.filterAttrs (n: v: v.force) cfg)
(lib.mapAttrsToList (n: v: v.target) );
(lib.filterAttrs (n: v: v.force) cfg));
storeDir = lib.escapeShellArg builtins.storeDir; storeDir = lib.escapeShellArg builtins.storeDir;
@ -124,7 +138,7 @@ in
# and a failure during the intermediate state FA ∩ FB will not # and a failure during the intermediate state FA ∩ FB will not
# result in lost links because this set of links are in both the # result in lost links because this set of links are in both the
# source and target generation. # source and target generation.
home.activation.linkGeneration = lib.hm.dag.entryAfter ["writeBoundary"] ( home.activation.linkGeneration = lib.hm.dag.entryAfter [ "writeBoundary" ] (
let let
link = pkgs.writeShellScript "link" '' link = pkgs.writeShellScript "link" ''
${config.lib.bash.initHomeManagerLib} ${config.lib.bash.initHomeManagerLib}
@ -189,43 +203,44 @@ in
done done
''; '';
in in
'' ''
function linkNewGen() { function linkNewGen() {
_i "Creating home file links in %s" "$HOME" _i "Creating home file links in %s" "$HOME"
local newGenFiles local newGenFiles
newGenFiles="$(readlink -e "$newGenPath/home-files")" newGenFiles="$(readlink -e "$newGenPath/home-files")"
find "$newGenFiles" \( -type f -or -type l \) \ find "$newGenFiles" \( -type f -or -type l \) \
-exec bash ${link} "$newGenFiles" {} + -exec bash ${link} "$newGenFiles" {} +
} }
function cleanOldGen() { function cleanOldGen() {
if [[ ! -v oldGenPath || ! -e "$oldGenPath/home-files" ]] ; then if [[ ! -v oldGenPath || ! -e "$oldGenPath/home-files" ]] ; then
return return
fi fi
_i "Cleaning up orphan links from %s" "$HOME" _i "Cleaning up orphan links from %s" "$HOME"
local newGenFiles oldGenFiles local newGenFiles oldGenFiles
newGenFiles="$(readlink -e "$newGenPath/home-files")" newGenFiles="$(readlink -e "$newGenPath/home-files")"
oldGenFiles="$(readlink -e "$oldGenPath/home-files")" oldGenFiles="$(readlink -e "$oldGenPath/home-files")"
# Apply the cleanup script on each leaf in the old # Apply the cleanup script on each leaf in the old
# generation. The find command below will print the # generation. The find command below will print the
# relative path of the entry. # relative path of the entry.
find "$oldGenFiles" '(' -type f -or -type l ')' -printf '%P\0' \ find "$oldGenFiles" '(' -type f -or -type l ')' -printf '%P\0' \
| xargs -0 bash ${cleanup} "$newGenFiles" | xargs -0 bash ${cleanup} "$newGenFiles"
} }
cleanOldGen cleanOldGen
linkNewGen linkNewGen
'' ''
); );
home.activation.checkFilesChanged = lib.hm.dag.entryBefore ["linkGeneration"] ( home.activation.checkFilesChanged = lib.hm.dag.entryBefore [ "linkGeneration" ] (
let let
homeDirArg = lib.escapeShellArg homeDirectory; homeDirArg = lib.escapeShellArg homeDirectory;
in '' in
''
function _cmp() { function _cmp() {
if [[ -d $1 && -d $2 ]]; then if [[ -d $1 && -d $2 ]]; then
diff -rq "$1" "$2" &> /dev/null diff -rq "$1" "$2" &> /dev/null
@ -234,21 +249,25 @@ in
fi fi
} }
declare -A changedFiles declare -A changedFiles
'' + lib.concatMapStrings (v: ''
+ lib.concatMapStrings (
v:
let let
sourceArg = lib.escapeShellArg (sourceStorePath v); sourceArg = lib.escapeShellArg (sourceStorePath v);
targetArg = lib.escapeShellArg v.target; targetArg = lib.escapeShellArg v.target;
in '' in
''
_cmp ${sourceArg} ${homeDirArg}/${targetArg} \ _cmp ${sourceArg} ${homeDirArg}/${targetArg} \
&& changedFiles[${targetArg}]=0 \ && changedFiles[${targetArg}]=0 \
|| changedFiles[${targetArg}]=1 || changedFiles[${targetArg}]=1
'') (lib.filter (v: v.onChange != "") (lib.attrValues cfg)) ''
) (lib.filter (v: v.onChange != "") (lib.attrValues cfg))
+ '' + ''
unset -f _cmp unset -f _cmp
'' ''
); );
home.activation.onFilesChange = lib.hm.dag.entryAfter ["linkGeneration"] ( home.activation.onFilesChange = lib.hm.dag.entryAfter [ "linkGeneration" ] (
lib.concatMapStrings (v: '' lib.concatMapStrings (v: ''
if (( ''${changedFiles[${lib.escapeShellArg v.target}]} == 1 )); then if (( ''${changedFiles[${lib.escapeShellArg v.target}]} == 1 )); then
if [[ -v DRY_RUN || -v VERBOSE ]]; then if [[ -v DRY_RUN || -v VERBOSE ]]; then
@ -263,85 +282,87 @@ in
# Symlink directories and files that have the right execute bit. # Symlink directories and files that have the right execute bit.
# Copy files that need their execute bit changed. # Copy files that need their execute bit changed.
home-files = pkgs.runCommandLocal home-files =
"home-manager-files" pkgs.runCommandLocal "home-manager-files"
{ {
nativeBuildInputs = [ pkgs.xorg.lndir ]; nativeBuildInputs = [ pkgs.xorg.lndir ];
}
(''
mkdir -p $out
# Needed in case /nix is a symbolic link.
realOut="$(realpath -m "$out")"
function insertFile() {
local source="$1"
local relTarget="$2"
local executable="$3"
local recursive="$4"
# If the target already exists then we have a collision. Note, this
# should not happen due to the assertion found in the 'files' module.
# We therefore simply log the conflict and otherwise ignore it, mainly
# to make the `files-target-config` test work as expected.
if [[ -e "$realOut/$relTarget" ]]; then
echo "File conflict for file '$relTarget'" >&2
return
fi
# Figure out the real absolute path to the target.
local target
target="$(realpath -m "$realOut/$relTarget")"
# Target path must be within $HOME.
if [[ ! $target == $realOut* ]] ; then
echo "Error installing file '$relTarget' outside \$HOME" >&2
exit 1
fi
mkdir -p "$(dirname "$target")"
if [[ -d $source ]]; then
if [[ $recursive ]]; then
mkdir -p "$target"
lndir -silent "$source" "$target"
else
ln -s "$source" "$target"
fi
else
[[ -x $source ]] && isExecutable=1 || isExecutable=""
# Link the file into the home file directory if possible,
# i.e., if the executable bit of the source is the same we
# expect for the target. Otherwise, we copy the file and
# set the executable bit to the expected value.
if [[ $executable == inherit || $isExecutable == $executable ]]; then
ln -s "$source" "$target"
else
cp "$source" "$target"
if [[ $executable == inherit ]]; then
# Don't change file mode if it should match the source.
:
elif [[ $executable ]]; then
chmod +x "$target"
else
chmod -x "$target"
fi
fi
fi
} }
'' + lib.concatStrings ( (
lib.mapAttrsToList (n: v: '' ''
insertFile ${ mkdir -p $out
lib.escapeShellArgs [
(sourceStorePath v) # Needed in case /nix is a symbolic link.
v.target realOut="$(realpath -m "$out")"
(if v.executable == null
then "inherit" function insertFile() {
else toString v.executable) local source="$1"
(toString v.recursive) local relTarget="$2"
]} local executable="$3"
'') cfg local recursive="$4"
));
# If the target already exists then we have a collision. Note, this
# should not happen due to the assertion found in the 'files' module.
# We therefore simply log the conflict and otherwise ignore it, mainly
# to make the `files-target-config` test work as expected.
if [[ -e "$realOut/$relTarget" ]]; then
echo "File conflict for file '$relTarget'" >&2
return
fi
# Figure out the real absolute path to the target.
local target
target="$(realpath -m "$realOut/$relTarget")"
# Target path must be within $HOME.
if [[ ! $target == $realOut* ]] ; then
echo "Error installing file '$relTarget' outside \$HOME" >&2
exit 1
fi
mkdir -p "$(dirname "$target")"
if [[ -d $source ]]; then
if [[ $recursive ]]; then
mkdir -p "$target"
lndir -silent "$source" "$target"
else
ln -s "$source" "$target"
fi
else
[[ -x $source ]] && isExecutable=1 || isExecutable=""
# Link the file into the home file directory if possible,
# i.e., if the executable bit of the source is the same we
# expect for the target. Otherwise, we copy the file and
# set the executable bit to the expected value.
if [[ $executable == inherit || $isExecutable == $executable ]]; then
ln -s "$source" "$target"
else
cp "$source" "$target"
if [[ $executable == inherit ]]; then
# Don't change file mode if it should match the source.
:
elif [[ $executable ]]; then
chmod +x "$target"
else
chmod -x "$target"
fi
fi
fi
}
''
+ lib.concatStrings (
lib.mapAttrsToList (n: v: ''
insertFile ${
lib.escapeShellArgs [
(sourceStorePath v)
v.target
(if v.executable == null then "inherit" else toString v.executable)
(toString v.recursive)
]
}
'') cfg
)
);
}; };
} }

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (lib) literalExpression mkOption types; inherit (lib) literalExpression mkOption types;
@ -112,10 +117,7 @@ let
options = { options = {
layout = mkOption { layout = mkOption {
type = with types; nullOr str; type = with types; nullOr str;
default = default = if lib.versionAtLeast config.home.stateVersion "19.09" then null else "us";
if lib.versionAtLeast config.home.stateVersion "19.09"
then null
else "us";
defaultText = literalExpression "null"; defaultText = literalExpression "null";
description = '' description = ''
Keyboard layout. If `null`, then the system Keyboard layout. If `null`, then the system
@ -137,8 +139,11 @@ let
options = mkOption { options = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
default = []; default = [ ];
example = ["grp:caps_toggle" "grp_led:scroll"]; example = [
"grp:caps_toggle"
"grp_led:scroll"
];
description = '' description = ''
X keyboard options; layout switching goes here. X keyboard options; layout switching goes here.
''; '';
@ -146,10 +151,7 @@ let
variant = mkOption { variant = mkOption {
type = with types; nullOr str; type = with types; nullOr str;
default = default = if lib.versionAtLeast config.home.stateVersion "19.09" then null else "";
if lib.versionAtLeast config.home.stateVersion "19.09"
then null
else "";
defaultText = literalExpression "null"; defaultText = literalExpression "null";
example = "colemak"; example = "colemak";
description = '' description = ''
@ -216,7 +218,7 @@ in
home.language = mkOption { home.language = mkOption {
type = languageSubModule; type = languageSubModule;
default = {}; default = { };
description = "Language configuration."; description = "Language configuration.";
}; };
@ -254,9 +256,19 @@ in
}; };
home.sessionVariables = mkOption { home.sessionVariables = mkOption {
default = {}; default = { };
type = with types; lazyAttrsOf (oneOf [ str path int float ]); type =
example = { EDITOR = "emacs"; GS_OPTIONS = "-sPAPERSIZE=a4"; }; with types;
lazyAttrsOf (oneOf [
str
path
int
float
]);
example = {
EDITOR = "emacs";
GS_OPTIONS = "-sPAPERSIZE=a4";
};
description = '' description = ''
Environment variables to always set at login. Environment variables to always set at login.
@ -352,14 +364,18 @@ in
home.packages = mkOption { home.packages = mkOption {
type = types.listOf types.package; type = types.listOf types.package;
default = []; default = [ ];
description = "The set of packages to appear in the user environment."; description = "The set of packages to appear in the user environment.";
}; };
home.extraOutputsToInstall = mkOption { home.extraOutputsToInstall = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
default = []; default = [ ];
example = [ "doc" "info" "devdoc" ]; example = [
"doc"
"info"
"devdoc"
];
description = '' description = ''
List of additional package outputs of the packages List of additional package outputs of the packages
{var}`home.packages` that should be installed into {var}`home.packages` that should be installed into
@ -391,7 +407,7 @@ in
home.activation = mkOption { home.activation = mkOption {
type = lib.hm.types.dagOf types.str; type = lib.hm.types.dagOf types.str;
default = {}; default = { };
example = literalExpression '' example = literalExpression ''
{ {
myActivationAction = lib.hm.dag.entryAfter ["writeBoundary"] ''' myActivationAction = lib.hm.dag.entryAfter ["writeBoundary"] '''
@ -518,41 +534,39 @@ in
let let
hmRelease = config.home.version.release; hmRelease = config.home.version.release;
nixpkgsRelease = lib.trivial.release; nixpkgsRelease = lib.trivial.release;
releaseMismatch = releaseMismatch = config.home.enableNixpkgsReleaseCheck && hmRelease != nixpkgsRelease;
config.home.enableNixpkgsReleaseCheck
&& hmRelease != nixpkgsRelease;
in in
lib.optional releaseMismatch '' lib.optional releaseMismatch ''
You are using You are using
Home Manager version ${hmRelease} and Home Manager version ${hmRelease} and
Nixpkgs version ${nixpkgsRelease}. Nixpkgs version ${nixpkgsRelease}.
Using mismatched versions is likely to cause errors and unexpected Using mismatched versions is likely to cause errors and unexpected
behavior. It is therefore highly recommended to use a release of Home behavior. It is therefore highly recommended to use a release of Home
Manager that corresponds with your chosen release of Nixpkgs. Manager that corresponds with your chosen release of Nixpkgs.
If you insist then you can disable this warning by adding If you insist then you can disable this warning by adding
home.enableNixpkgsReleaseCheck = false; home.enableNixpkgsReleaseCheck = false;
to your configuration. to your configuration.
''; '';
home.username = home.username = lib.mkIf (lib.versionOlder config.home.stateVersion "20.09") (
lib.mkIf (lib.versionOlder config.home.stateVersion "20.09") lib.mkDefault (builtins.getEnv "USER")
(lib.mkDefault (builtins.getEnv "USER")); );
home.homeDirectory = home.homeDirectory = lib.mkIf (lib.versionOlder config.home.stateVersion "20.09") (
lib.mkIf (lib.versionOlder config.home.stateVersion "20.09") lib.mkDefault (builtins.getEnv "HOME")
(lib.mkDefault (builtins.getEnv "HOME")); );
home.profileDirectory = home.profileDirectory =
if config.submoduleSupport.enable if config.submoduleSupport.enable && config.submoduleSupport.externalPackageInstall then
&& config.submoduleSupport.externalPackageInstall "/etc/profiles/per-user/${cfg.username}"
then "/etc/profiles/per-user/${cfg.username}" else if config.nix.enable && (config.nix.settings.use-xdg-base-directories or false) then
else if config.nix.enable && (config.nix.settings.use-xdg-base-directories or false) "${config.xdg.stateHome}/nix/profile"
then "${config.xdg.stateHome}/nix/profile" else
else cfg.homeDirectory + "/.nix-profile"; cfg.homeDirectory + "/.nix-profile";
programs.bash.shellAliases = cfg.shellAliases; programs.bash.shellAliases = cfg.shellAliases;
programs.zsh.shellAliases = cfg.shellAliases; programs.zsh.shellAliases = cfg.shellAliases;
@ -563,51 +577,41 @@ in
let let
maybeSet = n: v: lib.optionalAttrs (v != null) { ${n} = v; }; maybeSet = n: v: lib.optionalAttrs (v != null) { ${n} = v; };
in in
(maybeSet "LANG" cfg.language.base) (maybeSet "LANG" cfg.language.base)
// // (maybeSet "LC_CTYPE" cfg.language.ctype)
(maybeSet "LC_CTYPE" cfg.language.ctype) // (maybeSet "LC_NUMERIC" cfg.language.numeric)
// // (maybeSet "LC_TIME" cfg.language.time)
(maybeSet "LC_NUMERIC" cfg.language.numeric) // (maybeSet "LC_COLLATE" cfg.language.collate)
// // (maybeSet "LC_MONETARY" cfg.language.monetary)
(maybeSet "LC_TIME" cfg.language.time) // (maybeSet "LC_MESSAGES" cfg.language.messages)
// // (maybeSet "LC_PAPER" cfg.language.paper)
(maybeSet "LC_COLLATE" cfg.language.collate) // (maybeSet "LC_NAME" cfg.language.name)
// // (maybeSet "LC_ADDRESS" cfg.language.address)
(maybeSet "LC_MONETARY" cfg.language.monetary) // (maybeSet "LC_TELEPHONE" cfg.language.telephone)
// // (maybeSet "LC_MEASUREMENT" cfg.language.measurement);
(maybeSet "LC_MESSAGES" cfg.language.messages)
//
(maybeSet "LC_PAPER" cfg.language.paper)
//
(maybeSet "LC_NAME" cfg.language.name)
//
(maybeSet "LC_ADDRESS" cfg.language.address)
//
(maybeSet "LC_TELEPHONE" cfg.language.telephone)
//
(maybeSet "LC_MEASUREMENT" cfg.language.measurement);
# Provide a file holding all session variables. # Provide a file holding all session variables.
home.sessionVariablesPackage = pkgs.writeTextFile { home.sessionVariablesPackage = pkgs.writeTextFile {
name = "hm-session-vars.sh"; name = "hm-session-vars.sh";
destination = "/etc/profile.d/hm-session-vars.sh"; destination = "/etc/profile.d/hm-session-vars.sh";
text = '' text =
# Only source this once. ''
if [ -n "$__HM_SESS_VARS_SOURCED" ]; then return; fi # Only source this once.
export __HM_SESS_VARS_SOURCED=1 if [ -n "$__HM_SESS_VARS_SOURCED" ]; then return; fi
export __HM_SESS_VARS_SOURCED=1
${config.lib.shell.exportAll cfg.sessionVariables} ${config.lib.shell.exportAll cfg.sessionVariables}
'' + lib.concatStringsSep "\n" ''
(lib.mapAttrsToList + lib.concatStringsSep "\n" (
(env: values: config.lib.shell.export lib.mapAttrsToList (
env env: values: config.lib.shell.export env (config.lib.shell.prependToVar ":" env values)
(config.lib.shell.prependToVar ":" env values)) ) cfg.sessionSearchVariables
cfg.sessionSearchVariables) + "\n" )
+ "\n"
+ cfg.sessionVariablesExtra; + cfg.sessionVariablesExtra;
}; };
home.sessionSearchVariables.PATH = home.sessionSearchVariables.PATH = lib.mkIf (cfg.sessionPath != [ ]) cfg.sessionPath;
lib.mkIf (cfg.sessionPath != [ ]) cfg.sessionPath;
home.packages = [ config.home.sessionVariablesPackage ]; home.packages = [ config.home.sessionVariablesPackage ];
@ -638,9 +642,8 @@ in
# In case the user has moved from a user-install of Home Manager # In case the user has moved from a user-install of Home Manager
# to a submodule managed one we attempt to uninstall the # to a submodule managed one we attempt to uninstall the
# `home-manager-path` package if it is installed. # `home-manager-path` package if it is installed.
home.activation.installPackages = lib.hm.dag.entryAfter ["writeBoundary"] ( home.activation.installPackages = lib.hm.dag.entryAfter [ "writeBoundary" ] (
if config.submoduleSupport.externalPackageInstall if config.submoduleSupport.externalPackageInstall then
then
'' ''
nixProfileRemove home-manager-path nixProfileRemove home-manager-path
'' ''
@ -681,62 +684,66 @@ in
# in the `hm-modules` text domain. # in the `hm-modules` text domain.
lib.bash.initHomeManagerLib = lib.bash.initHomeManagerLib =
let let
domainDir = pkgs.runCommand "hm-modules-messages" { domainDir =
nativeBuildInputs = [ pkgs.buildPackages.gettext ]; pkgs.runCommand "hm-modules-messages"
} '' {
for path in ${./po}/*.po; do nativeBuildInputs = [ pkgs.buildPackages.gettext ];
lang="''${path##*/}" }
lang="''${lang%%.*}" ''
mkdir -p "$out/$lang/LC_MESSAGES" for path in ${./po}/*.po; do
msgfmt -o "$out/$lang/LC_MESSAGES/hm-modules.mo" "$path" lang="''${path##*/}"
done lang="''${lang%%.*}"
''; mkdir -p "$out/$lang/LC_MESSAGES"
msgfmt -o "$out/$lang/LC_MESSAGES/hm-modules.mo" "$path"
done
'';
in in
'' ''
export TEXTDOMAIN=hm-modules export TEXTDOMAIN=hm-modules
export TEXTDOMAINDIR=${domainDir} export TEXTDOMAINDIR=${domainDir}
source ${../lib/bash/home-manager.sh} source ${../lib/bash/home-manager.sh}
''; '';
home.activationPackage = home.activationPackage =
let let
mkCmd = res: '' mkCmd = res: ''
_iNote "Activating %s" "${res.name}" _iNote "Activating %s" "${res.name}"
${res.data} ${res.data}
''; '';
sortedCommands = lib.hm.dag.topoSort cfg.activation; sortedCommands = lib.hm.dag.topoSort cfg.activation;
activationCmds = activationCmds =
if sortedCommands ? result then if sortedCommands ? result then
lib.concatStringsSep "\n" (map mkCmd sortedCommands.result) lib.concatStringsSep "\n" (map mkCmd sortedCommands.result)
else else
abort ("Dependency cycle in activation script: " abort ("Dependency cycle in activation script: " + builtins.toJSON sortedCommands);
+ builtins.toJSON sortedCommands);
# Programs that always should be available on the activation # Programs that always should be available on the activation
# script's PATH. # script's PATH.
activationBinPaths = lib.makeBinPath ( activationBinPaths =
with pkgs; [ lib.makeBinPath (
bash with pkgs;
coreutils [
diffutils # For `cmp` and `diff`. bash
findutils coreutils
gettext diffutils # For `cmp` and `diff`.
gnugrep findutils
gnused gettext
jq gnugrep
ncurses # For `tput`. gnused
] jq
++ config.home.extraActivationPath ncurses # For `tput`.
) ]
+ ( ++ config.home.extraActivationPath
# Add path of the Nix binaries, if a Nix package is configured, then )
# use that one, otherwise grab the path of the nix-env tool. + (
if config.nix.enable && config.nix.package != null then # Add path of the Nix binaries, if a Nix package is configured, then
":${config.nix.package}/bin" # use that one, otherwise grab the path of the nix-env tool.
else if config.nix.enable && config.nix.package != null then
":$(${pkgs.coreutils}/bin/dirname $(${pkgs.coreutils}/bin/readlink -m $(type -p nix-env)))" ":${config.nix.package}/bin"
) else
+ lib.optionalString (!cfg.emptyActivationPath) "\${PATH:+:}$PATH"; ":$(${pkgs.coreutils}/bin/dirname $(${pkgs.coreutils}/bin/readlink -m $(type -p nix-env)))"
)
+ lib.optionalString (!cfg.emptyActivationPath) "\${PATH:+:}$PATH";
activationScript = pkgs.writeShellScript "activation-script" '' activationScript = pkgs.writeShellScript "activation-script" ''
set -eu set -eu
@ -770,29 +777,28 @@ in
''} ''}
''; '';
in in
pkgs.runCommand pkgs.runCommand "home-manager-generation"
"home-manager-generation" {
{ preferLocalBuild = true;
preferLocalBuild = true; }
} ''
'' mkdir -p $out
mkdir -p $out
echo "${config.home.version.full}" > $out/hm-version echo "${config.home.version.full}" > $out/hm-version
cp ${activationScript} $out/activate cp ${activationScript} $out/activate
mkdir $out/bin mkdir $out/bin
ln -s $out/activate $out/bin/home-manager-generation ln -s $out/activate $out/bin/home-manager-generation
substituteInPlace $out/activate \ substituteInPlace $out/activate \
--subst-var-by GENERATION_DIR $out --subst-var-by GENERATION_DIR $out
ln -s ${config.home-files} $out/home-files ln -s ${config.home-files} $out/home-files
ln -s ${cfg.path} $out/home-path ln -s ${cfg.path} $out/home-path
${cfg.extraBuilderCommands} ${cfg.extraBuilderCommands}
''; '';
home.path = pkgs.buildEnv { home.path = pkgs.buildEnv {
name = "home-manager-path"; name = "home-manager-path";

View file

@ -1,33 +1,65 @@
{ config, pkgs, lib, ... }: {
config,
pkgs,
lib,
...
}:
let let
cfg = config.i18n.inputMethod; cfg = config.i18n.inputMethod;
gtk2Cache = pkgs.runCommandLocal "gtk2-immodule.cache" { gtk2Cache =
buildInputs = [ pkgs.gtk2 cfg.package ]; pkgs.runCommandLocal "gtk2-immodule.cache"
} '' {
mkdir -p $out/etc/gtk-2.0/ buildInputs = [
GTK_PATH=${cfg.package}/lib/gtk-2.0/ \ pkgs.gtk2
gtk-query-immodules-2.0 > $out/etc/gtk-2.0/immodules.cache cfg.package
''; ];
}
''
mkdir -p $out/etc/gtk-2.0/
GTK_PATH=${cfg.package}/lib/gtk-2.0/ \
gtk-query-immodules-2.0 > $out/etc/gtk-2.0/immodules.cache
'';
gtk3Cache = pkgs.runCommandLocal "gtk3-immodule.cache" { gtk3Cache =
buildInputs = [ pkgs.gtk3 cfg.package ]; pkgs.runCommandLocal "gtk3-immodule.cache"
} '' {
mkdir -p $out/etc/gtk-3.0/ buildInputs = [
GTK_PATH=${cfg.package}/lib/gtk-3.0/ \ pkgs.gtk3
gtk-query-immodules-3.0 > $out/etc/gtk-3.0/immodules.cache cfg.package
''; ];
}
''
mkdir -p $out/etc/gtk-3.0/
GTK_PATH=${cfg.package}/lib/gtk-3.0/ \
gtk-query-immodules-3.0 > $out/etc/gtk-3.0/immodules.cache
'';
in { in
imports = [ ./fcitx5.nix ./hime.nix ./kime.nix ./nabi.nix ./uim.nix ]; {
imports = [
./fcitx5.nix
./hime.nix
./kime.nix
./nabi.nix
./uim.nix
];
options.i18n = { options.i18n = {
inputMethod = { inputMethod = {
enabled = lib.mkOption { enabled = lib.mkOption {
type = lib.types.nullOr type = lib.types.nullOr (
(lib.types.enum [ "fcitx" "fcitx5" "nabi" "uim" "hime" "kime" ]); lib.types.enum [
"fcitx"
"fcitx5"
"nabi"
"uim"
"hime"
"kime"
]
);
default = null; default = null;
example = "fcitx5"; example = "fcitx5";
description = '' description = ''
@ -73,15 +105,18 @@ in {
config = lib.mkIf (cfg.enabled != null) { config = lib.mkIf (cfg.enabled != null) {
assertions = [ assertions = [
(lib.hm.assertions.assertPlatform "i18n.inputMethod" pkgs (lib.hm.assertions.assertPlatform "i18n.inputMethod" pkgs lib.platforms.linux)
lib.platforms.linux)
{ {
assertion = cfg.enabled != "fcitx"; assertion = cfg.enabled != "fcitx";
message = "fcitx has been removed, please use fcitx5 instead"; message = "fcitx has been removed, please use fcitx5 instead";
} }
]; ];
home.packages = [ cfg.package gtk2Cache gtk3Cache ]; home.packages = [
cfg.package
gtk2Cache
gtk3Cache
];
}; };
meta.maintainers = [ lib.hm.maintainers.kranzes ]; meta.maintainers = [ lib.hm.maintainers.kranzes ];

View file

@ -1,9 +1,15 @@
{ config, pkgs, lib, ... }: {
config,
pkgs,
lib,
...
}:
let let
im = config.i18n.inputMethod; im = config.i18n.inputMethod;
cfg = im.fcitx5; cfg = im.fcitx5;
fcitx5Package = cfg.fcitx5-with-addons.override { inherit (cfg) addons; }; fcitx5Package = cfg.fcitx5-with-addons.override { inherit (cfg) addons; };
in { in
{
options = { options = {
i18n.inputMethod.fcitx5 = { i18n.inputMethod.fcitx5 = {
fcitx5-with-addons = lib.mkOption { fcitx5-with-addons = lib.mkOption {
@ -38,17 +44,18 @@ in {
i18n.inputMethod.package = fcitx5Package; i18n.inputMethod.package = fcitx5Package;
home = { home = {
sessionVariables = { sessionVariables =
GLFW_IM_MODULE = "ibus"; # IME support in kitty {
SDL_IM_MODULE = "fcitx"; GLFW_IM_MODULE = "ibus"; # IME support in kitty
XMODIFIERS = "@im=fcitx"; SDL_IM_MODULE = "fcitx";
} // lib.optionalAttrs (!cfg.waylandFrontend) { XMODIFIERS = "@im=fcitx";
GTK_IM_MODULE = "fcitx"; }
QT_IM_MODULE = "fcitx"; // lib.optionalAttrs (!cfg.waylandFrontend) {
}; GTK_IM_MODULE = "fcitx";
QT_IM_MODULE = "fcitx";
};
sessionSearchVariables.QT_PLUGIN_PATH = sessionSearchVariables.QT_PLUGIN_PATH = [ "${fcitx5Package}/${pkgs.qt6.qtbase.qtPluginPrefix}" ];
[ "${fcitx5Package}/${pkgs.qt6.qtbase.qtPluginPrefix}" ];
}; };
systemd.user.services.fcitx5-daemon = { systemd.user.services.fcitx5-daemon = {

View file

@ -1,4 +1,9 @@
{ config, pkgs, lib, ... }: {
config,
pkgs,
lib,
...
}:
{ {
config = lib.mkIf (config.i18n.inputMethod.enabled == "hime") { config = lib.mkIf (config.i18n.inputMethod.enabled == "hime") {

View file

@ -1,10 +1,22 @@
{ config, pkgs, lib, ... }: {
config,
pkgs,
lib,
...
}:
let let
inherit (lib) literalExpression mkIf mkOption mkRemovedOptionModule types; inherit (lib)
literalExpression
mkIf
mkOption
mkRemovedOptionModule
types
;
cfg = config.i18n.inputMethod.kime; cfg = config.i18n.inputMethod.kime;
in { in
{
imports = [ imports = [
(mkRemovedOptionModule [ "i18n" "inputMethod" "kime" "config" ] '' (mkRemovedOptionModule [ "i18n" "inputMethod" "kime" "config" ] ''
Please use 'i18n.inputMethod.kime.extraConfig' instead. Please use 'i18n.inputMethod.kime.extraConfig' instead.

View file

@ -1,4 +1,9 @@
{ config, pkgs, lib, ... }: {
config,
pkgs,
lib,
...
}:
{ {
config = lib.mkIf (config.i18n.inputMethod.enabled == "nabi") { config = lib.mkIf (config.i18n.inputMethod.enabled == "nabi") {

View file

@ -1,13 +1,25 @@
{ config, pkgs, lib, ... }: {
config,
pkgs,
lib,
...
}:
let cfg = config.i18n.inputMethod.uim; let
in { cfg = config.i18n.inputMethod.uim;
in
{
options = { options = {
i18n.inputMethod.uim = { i18n.inputMethod.uim = {
toolbar = lib.mkOption { toolbar = lib.mkOption {
type = type = lib.types.enum [
lib.types.enum [ "gtk" "gtk3" "gtk-systray" "gtk3-systray" "qt4" ]; "gtk"
"gtk3"
"gtk-systray"
"gtk3-systray"
"qt4"
];
default = "gtk"; default = "gtk";
example = "gtk-systray"; example = "gtk-systray";
description = '' description = ''
@ -32,11 +44,12 @@ in {
Description = "Uim input method editor"; Description = "Uim input method editor";
PartOf = [ "graphical-session.desktop" ]; PartOf = [ "graphical-session.desktop" ];
}; };
Service.ExecStart = toString Service.ExecStart = toString (
(pkgs.writeShellScript "start-uim-xim-and-uim-toolbar" '' pkgs.writeShellScript "start-uim-xim-and-uim-toolbar" ''
${pkgs.uim}/bin/uim-xim & ${pkgs.uim}/bin/uim-xim &
${pkgs.uim}/bin/uim-toolbar-${cfg.toolbar} ${pkgs.uim}/bin/uim-toolbar-${cfg.toolbar}
''); ''
);
Install.WantedBy = [ "graphical-session.target" ]; Install.WantedBy = [ "graphical-session.target" ];
}; };
}; };

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (pkgs.stdenv.hostPlatform) isDarwin; inherit (pkgs.stdenv.hostPlatform) isDarwin;
@ -8,45 +13,48 @@ let
labelPrefix = "org.nix-community.home."; labelPrefix = "org.nix-community.home.";
dstDir = "${config.home.homeDirectory}/Library/LaunchAgents"; dstDir = "${config.home.homeDirectory}/Library/LaunchAgents";
launchdConfig = { config, name, ... }: { launchdConfig =
options = { { config, name, ... }:
enable = lib.mkEnableOption name; {
config = lib.mkOption { options = {
type = lib.types.submodule (import ./launchd.nix); enable = lib.mkEnableOption name;
default = { }; config = lib.mkOption {
example = lib.literalExpression '' type = lib.types.submodule (import ./launchd.nix);
{ default = { };
ProgramArguments = [ "/usr/bin/say" "Good afternoon" ]; example = lib.literalExpression ''
StartCalendarInterval = [ {
{ ProgramArguments = [ "/usr/bin/say" "Good afternoon" ];
Hour = 12; StartCalendarInterval = [
Minute = 0; {
} Hour = 12;
]; Minute = 0;
} }
''; ];
description = '' }
Define a launchd job. See {manpage}`launchd.plist(5)` for details. '';
''; description = ''
Define a launchd job. See {manpage}`launchd.plist(5)` for details.
'';
};
};
config = {
config.Label = lib.mkDefault "${labelPrefix}${name}";
}; };
}; };
config = { config.Label = lib.mkDefault "${labelPrefix}${name}"; };
};
toAgent = config: pkgs.writeText "${config.Label}.plist" (toPlist { } config); toAgent = config: pkgs.writeText "${config.Label}.plist" (toPlist { } config);
agentPlists = lib.mapAttrs' agentPlists = lib.mapAttrs' (n: v: lib.nameValuePair "${v.config.Label}.plist" (toAgent v.config)) (
(n: v: lib.nameValuePair "${v.config.Label}.plist" (toAgent v.config)) lib.filterAttrs (n: v: v.enable) cfg.agents
(lib.filterAttrs (n: v: v.enable) cfg.agents); );
agentsDrv = pkgs.runCommand "home-manager-agents" { } '' agentsDrv = pkgs.runCommand "home-manager-agents" { } ''
mkdir -p "$out" mkdir -p "$out"
declare -A plists declare -A plists
plists=(${ plists=(${
lib.concatStringsSep " " lib.concatStringsSep " " (lib.mapAttrsToList (name: value: "['${name}']='${value}'") agentPlists)
(lib.mapAttrsToList (name: value: "['${name}']='${value}'") agentPlists)
}) })
for dest in "''${!plists[@]}"; do for dest in "''${!plists[@]}"; do
@ -54,8 +62,12 @@ let
ln -s "$src" "$out/$dest" ln -s "$src" "$out/$dest"
done done
''; '';
in { in
meta.maintainers = with lib.maintainers; [ khaneliman midchildan ]; {
meta.maintainers = with lib.maintainers; [
khaneliman
midchildan
];
options.launchd = { options.launchd = {
enable = lib.mkOption { enable = lib.mkOption {
@ -77,12 +89,16 @@ in {
config = lib.mkMerge [ config = lib.mkMerge [
{ {
assertions = [{ assertions = [
assertion = (cfg.enable && agentPlists != { }) -> isDarwin; {
message = assertion = (cfg.enable && agentPlists != { }) -> isDarwin;
let names = lib.concatStringsSep ", " (lib.attrNames agentPlists); message =
in "Must use Darwin for modules that require Launchd: " + names; let
}]; names = lib.concatStringsSep ", " (lib.attrNames agentPlists);
in
"Must use Darwin for modules that require Launchd: " + names;
}
];
} }
(lib.mkIf isDarwin { (lib.mkIf isDarwin {
@ -94,170 +110,170 @@ in {
# because it needs to be owned by the user running it. # because it needs to be owned by the user running it.
home.activation.setupLaunchAgents = home.activation.setupLaunchAgents =
lib.hm.dag.entryAfter [ "writeBoundary" ] # Bash lib.hm.dag.entryAfter [ "writeBoundary" ] # Bash
'' ''
# Disable errexit to ensure we process all agents even if some fail # Disable errexit to ensure we process all agents even if some fail
set +e set +e
# Stop an agent if it's running # Stop an agent if it's running
bootoutAgent() { bootoutAgent() {
local domain="$1" local domain="$1"
local agentName="$2" local agentName="$2"
verboseEcho "Stopping agent '$domain/$agentName'..." verboseEcho "Stopping agent '$domain/$agentName'..."
local bootout_output local bootout_output
bootout_output=$(run /bin/launchctl bootout "$domain/$agentName" 2>&1) || { bootout_output=$(run /bin/launchctl bootout "$domain/$agentName" 2>&1) || {
# Only show warning if it's not the common "No such process" error # Only show warning if it's not the common "No such process" error
if [[ "$bootout_output" != *"No such process"* ]]; then if [[ "$bootout_output" != *"No such process"* ]]; then
warnEcho "Failed to stop agent '$domain/$agentName': $bootout_output" warnEcho "Failed to stop agent '$domain/$agentName': $bootout_output"
else else
verboseEcho "Agent '$domain/$agentName' was not running" verboseEcho "Agent '$domain/$agentName' was not running"
fi fi
}
# Give the system a moment to fully unload the agent
sleep 1
} }
# Give the system a moment to fully unload the agent installAndBootstrapAgent() {
sleep 1 local srcPath="$1"
} local dstPath="$2"
local domain="$3"
local agentName="$4"
installAndBootstrapAgent() { verboseEcho "Installing agent file to $dstPath"
local srcPath="$1" if ! run install -Dm444 -T "$srcPath" "$dstPath"; then
local dstPath="$2" errorEcho "Failed to install agent file for '$agentName'"
local domain="$3" return 1
local agentName="$4"
verboseEcho "Installing agent file to $dstPath"
if ! run install -Dm444 -T "$srcPath" "$dstPath"; then
errorEcho "Failed to install agent file for '$agentName'"
return 1
fi
verboseEcho "Starting agent '$domain/$agentName'"
local bootstrap_output
bootstrap_output=$(run /bin/launchctl bootstrap "$domain" "$dstPath" 2>&1) || {
local error_code=$?
if [[ "$bootstrap_output" == *"Bootstrap failed: 5: Input/output error"* ]]; then
errorEcho "Failed to start agent '$domain/$agentName' with I/O error (code 5)"
errorEcho "This typically happens when the agent wasn't unloaded before attempting to bootstrap the new agent."
else
errorEcho "Failed to start agent '$domain/$agentName' with error: $bootstrap_output"
fi fi
return 1 verboseEcho "Starting agent '$domain/$agentName'"
} local bootstrap_output
bootstrap_output=$(run /bin/launchctl bootstrap "$domain" "$dstPath" 2>&1) || {
local error_code=$?
verboseEcho "Successfully started agent '$domain/$agentName'" if [[ "$bootstrap_output" == *"Bootstrap failed: 5: Input/output error"* ]]; then
return 0 errorEcho "Failed to start agent '$domain/$agentName' with I/O error (code 5)"
} errorEcho "This typically happens when the agent wasn't unloaded before attempting to bootstrap the new agent."
else
errorEcho "Failed to start agent '$domain/$agentName' with error: $bootstrap_output"
fi
processAgent() { return 1
local srcPath="$1" }
local dstDir="$2"
local domain="$3"
local agentFile="''${srcPath##*/}" verboseEcho "Successfully started agent '$domain/$agentName'"
local agentName="''${agentFile%.plist}"
local dstPath="$dstDir/$agentFile"
# Skip if unchanged
if cmp -s "$srcPath" "$dstPath"; then
verboseEcho "Agent '$agentName' is already up-to-date"
return 0 return 0
fi }
verboseEcho "Processing agent '$agentName'" processAgent() {
local srcPath="$1"
local dstDir="$2"
local domain="$3"
# Stop/Unload agent if it's already running local agentFile="''${srcPath##*/}"
if [[ -f "$dstPath" ]]; then local agentName="''${agentFile%.plist}"
local dstPath="$dstDir/$agentFile"
# Skip if unchanged
if cmp -s "$srcPath" "$dstPath"; then
verboseEcho "Agent '$agentName' is already up-to-date"
return 0
fi
verboseEcho "Processing agent '$agentName'"
# Stop/Unload agent if it's already running
if [[ -f "$dstPath" ]]; then
bootoutAgent "$domain" "$agentName"
fi
installAndBootstrapAgent "$srcPath" "$dstPath" "$domain" "$agentName"
# Note: We continue processing even if this agent fails
return 0
}
removeAgent() {
local srcPath="$1"
local dstDir="$2"
local newDir="$3"
local domain="$4"
local agentFile="''${srcPath##*/}"
local agentName="''${agentFile%.plist}"
local dstPath="$dstDir/$agentFile"
if [[ -e "$newDir/$agentFile" ]]; then
verboseEcho "Agent '$agentName' still exists in new generation, skipping cleanup"
return 0
fi
if [[ ! -e "$dstPath" ]]; then
verboseEcho "Agent file '$dstPath' already removed"
return 0
fi
if ! cmp -s "$srcPath" "$dstPath"; then
warnEcho "Skipping deletion of '$dstPath', since its contents have diverged"
return 0
fi
# Stop and remove the agent
bootoutAgent "$domain" "$agentName" bootoutAgent "$domain" "$agentName"
fi
installAndBootstrapAgent "$srcPath" "$dstPath" "$domain" "$agentName" verboseEcho "Removing agent file '$dstPath'"
# Note: We continue processing even if this agent fails if run rm -f $VERBOSE_ARG "$dstPath"; then
return 0 verboseEcho "Successfully removed agent file for '$agentName'"
} else
warnEcho "Failed to remove agent file '$dstPath'"
fi
removeAgent() {
local srcPath="$1"
local dstDir="$2"
local newDir="$3"
local domain="$4"
local agentFile="''${srcPath##*/}"
local agentName="''${agentFile%.plist}"
local dstPath="$dstDir/$agentFile"
if [[ -e "$newDir/$agentFile" ]]; then
verboseEcho "Agent '$agentName' still exists in new generation, skipping cleanup"
return 0 return 0
fi }
if [[ ! -e "$dstPath" ]]; then setupLaunchAgents() {
verboseEcho "Agent file '$dstPath' already removed" local oldDir newDir dstDir domain
return 0
fi
if ! cmp -s "$srcPath" "$dstPath"; then newDir="$(readlink -m "$newGenPath/LaunchAgents")"
warnEcho "Skipping deletion of '$dstPath', since its contents have diverged" dstDir=${lib.escapeShellArg dstDir}
return 0 domain="gui/$UID"
fi
# Stop and remove the agent if [[ -n "''${oldGenPath:-}" ]]; then
bootoutAgent "$domain" "$agentName" oldDir="$(readlink -m "$oldGenPath/LaunchAgents")"
if [[ ! -d "$oldDir" ]]; then
verboseEcho "Removing agent file '$dstPath'" verboseEcho "No previous LaunchAgents directory found"
if run rm -f $VERBOSE_ARG "$dstPath"; then oldDir=""
verboseEcho "Successfully removed agent file for '$agentName'" fi
else else
warnEcho "Failed to remove agent file '$dstPath'"
fi
return 0
}
setupLaunchAgents() {
local oldDir newDir dstDir domain
newDir="$(readlink -m "$newGenPath/LaunchAgents")"
dstDir=${lib.escapeShellArg dstDir}
domain="gui/$UID"
if [[ -n "''${oldGenPath:-}" ]]; then
oldDir="$(readlink -m "$oldGenPath/LaunchAgents")"
if [[ ! -d "$oldDir" ]]; then
verboseEcho "No previous LaunchAgents directory found"
oldDir="" oldDir=""
fi fi
else
oldDir="" verboseEcho "Setting up LaunchAgents in $dstDir"
[[ -d "$dstDir" ]] || run mkdir -p "$dstDir"
verboseEcho "Processing new/updated LaunchAgents..."
find -L "$newDir" -maxdepth 1 -name '*.plist' -type f | while read -r srcPath; do
processAgent "$srcPath" "$dstDir" "$domain"
done
# Skip cleanup if there's no previous generation
if [[ -z "$oldDir" || ! -d "$oldDir" ]]; then
verboseEcho "LaunchAgents setup complete"
return
fi
verboseEcho "Cleaning up removed LaunchAgents..."
find -L "$oldDir" -maxdepth 1 -name '*.plist' -type f | while read -r srcPath; do
removeAgent "$srcPath" "$dstDir" "$newDir" "$domain"
done
}
setupLaunchAgents
# Restore errexit
if [[ -o errexit ]]; then
set -e
fi fi
'';
verboseEcho "Setting up LaunchAgents in $dstDir"
[[ -d "$dstDir" ]] || run mkdir -p "$dstDir"
verboseEcho "Processing new/updated LaunchAgents..."
find -L "$newDir" -maxdepth 1 -name '*.plist' -type f | while read -r srcPath; do
processAgent "$srcPath" "$dstDir" "$domain"
done
# Skip cleanup if there's no previous generation
if [[ -z "$oldDir" || ! -d "$oldDir" ]]; then
verboseEcho "LaunchAgents setup complete"
return
fi
verboseEcho "Cleaning up removed LaunchAgents..."
find -L "$oldDir" -maxdepth 1 -name '*.plist' -type f | while read -r srcPath; do
removeAgent "$srcPath" "$dstDir" "$newDir" "$domain"
done
}
setupLaunchAgents
# Restore errexit
if [[ -o errexit ]]; then
set -e
fi
'';
}) })
]; ];
} }

View file

@ -29,7 +29,8 @@ let
inherit (lib) types mkOption; # added by Home Manager inherit (lib) types mkOption; # added by Home Manager
launchdTypes = import ./types.nix { inherit config lib; }; launchdTypes = import ./types.nix { inherit config lib; };
in { in
{
freeformType = with types; attrsOf anything; # added by Home Manager freeformType = with types; attrsOf anything; # added by Home Manager
options = { options = {
@ -82,23 +83,27 @@ in {
inetdCompatibility = mkOption { inetdCompatibility = mkOption {
default = null; default = null;
example = { Wait = true; }; example = {
Wait = true;
};
description = '' description = ''
The presence of this key specifies that the daemon expects to be run as if it were launched from inetd. The presence of this key specifies that the daemon expects to be run as if it were launched from inetd.
''; '';
type = types.nullOr (types.submodule { type = types.nullOr (
options = { types.submodule {
Wait = mkOption { options = {
type = types.nullOr (types.either types.bool types.str); Wait = mkOption {
default = null; type = types.nullOr (types.either types.bool types.str);
description = '' default = null;
This flag corresponds to the "wait" or "nowait" option of inetd. If true, then the listening description = ''
socket is passed via the standard in/out/error file descriptors. If false, then `accept(2)` is This flag corresponds to the "wait" or "nowait" option of inetd. If true, then the listening
called on behalf of the job, and the result is passed via the standard in/out/error descriptors. socket is passed via the standard in/out/error file descriptors. If false, then `accept(2)` is
''; called on behalf of the job, and the result is passed via the standard in/out/error descriptors.
'';
};
}; };
}; }
}); );
}; };
LimitLoadToHosts = mkOption { LimitLoadToHosts = mkOption {
@ -120,7 +125,12 @@ in {
}; };
LimitLoadToSessionType = mkOption { LimitLoadToSessionType = mkOption {
type = types.nullOr (types.oneOf [ types.str (types.listOf types.str) ]); type = types.nullOr (
types.oneOf [
types.str
(types.listOf types.str)
]
);
default = null; default = null;
description = '' description = ''
This configuration file only applies to sessions of the type specified. This key is used in concert This configuration file only applies to sessions of the type specified. This key is used in concert
@ -177,68 +187,72 @@ in {
}; };
KeepAlive = mkOption { KeepAlive = mkOption {
type = types.nullOr (types.either types.bool (types.submodule { type = types.nullOr (
options = { types.either types.bool (
types.submodule {
options = {
SuccessfulExit = mkOption { SuccessfulExit = mkOption {
type = types.nullOr types.bool; type = types.nullOr types.bool;
default = null; default = null;
description = '' description = ''
If true, the job will be restarted as long as the program exits and with an exit status of zero. If true, the job will be restarted as long as the program exits and with an exit status of zero.
If false, the job will be restarted in the inverse condition. This key implies that "RunAtLoad" If false, the job will be restarted in the inverse condition. This key implies that "RunAtLoad"
is set to true, since the job needs to run at least once before we can get an exit status. is set to true, since the job needs to run at least once before we can get an exit status.
''; '';
}; };
NetworkState = mkOption { NetworkState = mkOption {
type = types.nullOr types.bool; type = types.nullOr types.bool;
default = null; default = null;
description = '' description = ''
If true, the job will be kept alive as long as the network is up, where up is defined as at least If true, the job will be kept alive as long as the network is up, where up is defined as at least
one non-loopback interface being up and having IPv4 or IPv6 addresses assigned to them. If one non-loopback interface being up and having IPv4 or IPv6 addresses assigned to them. If
false, the job will be kept alive in the inverse condition. false, the job will be kept alive in the inverse condition.
''; '';
}; };
PathState = mkOption { PathState = mkOption {
type = types.nullOr (types.attrsOf types.bool); type = types.nullOr (types.attrsOf types.bool);
default = null; default = null;
description = '' description = ''
Each key in this dictionary is a file-system path. If the value of the key is true, then the job Each key in this dictionary is a file-system path. If the value of the key is true, then the job
will be kept alive as long as the path exists. If false, the job will be kept alive in the will be kept alive as long as the path exists. If false, the job will be kept alive in the
inverse condition. The intent of this feature is that two or more jobs may create semaphores in inverse condition. The intent of this feature is that two or more jobs may create semaphores in
the file-system namespace. the file-system namespace.
''; '';
}; };
OtherJobEnabled = mkOption { OtherJobEnabled = mkOption {
type = types.nullOr (types.attrsOf types.bool); type = types.nullOr (types.attrsOf types.bool);
default = null; default = null;
description = '' description = ''
Each key in this dictionary is the label of another job. If the value of the key is true, then Each key in this dictionary is the label of another job. If the value of the key is true, then
this job is kept alive as long as that other job is enabled. Otherwise, if the value is false, this job is kept alive as long as that other job is enabled. Otherwise, if the value is false,
then this job is kept alive as long as the other job is disabled. This feature should not be then this job is kept alive as long as the other job is disabled. This feature should not be
considered a substitute for the use of IPC. considered a substitute for the use of IPC.
''; '';
}; };
Crashed = mkOption { Crashed = mkOption {
type = types.nullOr types.bool; type = types.nullOr types.bool;
default = null; default = null;
description = '' description = ''
If true, the the job will be restarted as long as it exited due to a signal which is typically If true, the the job will be restarted as long as it exited due to a signal which is typically
associated with a crash (SIGILL, SIGSEGV, etc.). If false, the job will be restarted in the associated with a crash (SIGILL, SIGSEGV, etc.). If false, the job will be restarted in the
inverse condition. inverse condition.
''; '';
}; };
AfterInitialDemand = mkOption { AfterInitialDemand = mkOption {
type = types.nullOr types.bool; type = types.nullOr types.bool;
default = null; default = null;
}; };
}; };
})); }
)
);
default = null; default = null;
description = '' description = ''
This optional key is used to control whether your job is to be kept continuously running or to let This optional key is used to control whether your job is to be kept continuously running or to let
@ -371,10 +385,12 @@ in {
StartCalendarInterval = mkOption { StartCalendarInterval = mkOption {
default = null; default = null;
example = [{ example = [
Hour = 2; {
Minute = 30; Hour = 2;
}]; Minute = 30;
}
];
description = '' description = ''
This optional key causes the job to be started every calendar interval as specified. The semantics are This optional key causes the job to be started every calendar interval as specified. The semantics are
much like {manpage}`crontab(5)`: Missing attributes are considered to be wildcard. Unlike cron which skips much like {manpage}`crontab(5)`: Missing attributes are considered to be wildcard. Unlike cron which skips
@ -442,181 +458,187 @@ in {
Resource limits to be imposed on the job. These adjust variables set with `setrlimit(2)`. The following Resource limits to be imposed on the job. These adjust variables set with `setrlimit(2)`. The following
keys apply: keys apply:
''; '';
type = types.nullOr (types.submodule { type = types.nullOr (
options = { types.submodule {
Core = mkOption { options = {
type = types.nullOr types.int; Core = mkOption {
default = null; type = types.nullOr types.int;
description = '' default = null;
The largest size (in bytes) core file that may be created. description = ''
''; The largest size (in bytes) core file that may be created.
}; '';
};
CPU = mkOption { CPU = mkOption {
type = types.nullOr types.int; type = types.nullOr types.int;
default = null; default = null;
description = '' description = ''
The maximum amount of cpu time (in seconds) to be used by each process. The maximum amount of cpu time (in seconds) to be used by each process.
''; '';
}; };
Data = mkOption { Data = mkOption {
type = types.nullOr types.int; type = types.nullOr types.int;
default = null; default = null;
description = '' description = ''
The maximum size (in bytes) of the data segment for a process; this defines how far a program may The maximum size (in bytes) of the data segment for a process; this defines how far a program may
extend its break with the `sbrk(2)` system call. extend its break with the `sbrk(2)` system call.
''; '';
}; };
FileSize = mkOption { FileSize = mkOption {
type = types.nullOr types.int; type = types.nullOr types.int;
default = null; default = null;
description = '' description = ''
The largest size (in bytes) file that may be created. The largest size (in bytes) file that may be created.
''; '';
}; };
MemoryLock = mkOption { MemoryLock = mkOption {
type = types.nullOr types.int; type = types.nullOr types.int;
default = null; default = null;
description = '' description = ''
The maximum size (in bytes) which a process may lock into memory using the mlock(2) function. The maximum size (in bytes) which a process may lock into memory using the mlock(2) function.
''; '';
}; };
NumberOfFiles = mkOption { NumberOfFiles = mkOption {
type = types.nullOr types.int; type = types.nullOr types.int;
default = null; default = null;
description = '' description = ''
The maximum number of open files for this process. Setting this value in a system wide daemon The maximum number of open files for this process. Setting this value in a system wide daemon
will set the `sysctl(3)` kern.maxfiles (SoftResourceLimits) or kern.maxfilesperproc (HardResourceLimits) will set the `sysctl(3)` kern.maxfiles (SoftResourceLimits) or kern.maxfilesperproc (HardResourceLimits)
value in addition to the `setrlimit(2)` values. value in addition to the `setrlimit(2)` values.
''; '';
}; };
NumberOfProcesses = mkOption { NumberOfProcesses = mkOption {
type = types.nullOr types.int; type = types.nullOr types.int;
default = null; default = null;
description = '' description = ''
The maximum number of simultaneous processes for this user id. Setting this value in a system The maximum number of simultaneous processes for this user id. Setting this value in a system
wide daemon will set the `sysctl(3)` kern.maxproc (SoftResourceLimits) or kern.maxprocperuid wide daemon will set the `sysctl(3)` kern.maxproc (SoftResourceLimits) or kern.maxprocperuid
(HardResourceLimits) value in addition to the `setrlimit(2)` values. (HardResourceLimits) value in addition to the `setrlimit(2)` values.
''; '';
}; };
ResidentSetSize = mkOption { ResidentSetSize = mkOption {
type = types.nullOr types.int; type = types.nullOr types.int;
default = null; default = null;
description = '' description = ''
The maximum size (in bytes) to which a process's resident set size may grow. This imposes a The maximum size (in bytes) to which a process's resident set size may grow. This imposes a
limit on the amount of physical memory to be given to a process; if memory is tight, the system limit on the amount of physical memory to be given to a process; if memory is tight, the system
will prefer to take memory from processes that are exceeding their declared resident set size. will prefer to take memory from processes that are exceeding their declared resident set size.
''; '';
}; };
Stack = mkOption { Stack = mkOption {
type = types.nullOr types.int; type = types.nullOr types.int;
default = null; default = null;
description = '' description = ''
The maximum size (in bytes) of the stack segment for a process; this defines how far a program's The maximum size (in bytes) of the stack segment for a process; this defines how far a program's
stack segment may be extended. Stack extension is performed automatically by the system. stack segment may be extended. Stack extension is performed automatically by the system.
''; '';
};
}; };
}; }
}); );
}; };
HardResourceLimits = mkOption { HardResourceLimits = mkOption {
default = null; default = null;
example = { NumberOfFiles = 4096; }; example = {
NumberOfFiles = 4096;
};
description = '' description = ''
Resource limits to be imposed on the job. These adjust variables set with `setrlimit(2)`. The following Resource limits to be imposed on the job. These adjust variables set with `setrlimit(2)`. The following
keys apply: keys apply:
''; '';
type = types.nullOr (types.submodule { type = types.nullOr (
options = { types.submodule {
Core = mkOption { options = {
type = types.nullOr types.int; Core = mkOption {
default = null; type = types.nullOr types.int;
description = '' default = null;
The largest size (in bytes) core file that may be created. description = ''
''; The largest size (in bytes) core file that may be created.
}; '';
};
CPU = mkOption { CPU = mkOption {
type = types.nullOr types.int; type = types.nullOr types.int;
default = null; default = null;
description = '' description = ''
The maximum amount of cpu time (in seconds) to be used by each process. The maximum amount of cpu time (in seconds) to be used by each process.
''; '';
}; };
Data = mkOption { Data = mkOption {
type = types.nullOr types.int; type = types.nullOr types.int;
default = null; default = null;
description = '' description = ''
The maximum size (in bytes) of the data segment for a process; this defines how far a program may The maximum size (in bytes) of the data segment for a process; this defines how far a program may
extend its break with the `sbrk(2)` system call. extend its break with the `sbrk(2)` system call.
''; '';
}; };
FileSize = mkOption { FileSize = mkOption {
type = types.nullOr types.int; type = types.nullOr types.int;
default = null; default = null;
description = '' description = ''
The largest size (in bytes) file that may be created. The largest size (in bytes) file that may be created.
''; '';
}; };
MemoryLock = mkOption { MemoryLock = mkOption {
type = types.nullOr types.int; type = types.nullOr types.int;
default = null; default = null;
description = '' description = ''
The maximum size (in bytes) which a process may lock into memory using the `mlock(2)` function. The maximum size (in bytes) which a process may lock into memory using the `mlock(2)` function.
''; '';
}; };
NumberOfFiles = mkOption { NumberOfFiles = mkOption {
type = types.nullOr types.int; type = types.nullOr types.int;
default = null; default = null;
description = '' description = ''
The maximum number of open files for this process. Setting this value in a system wide daemon The maximum number of open files for this process. Setting this value in a system wide daemon
will set the `sysctl(3)` kern.maxfiles (SoftResourceLimits) or kern.maxfilesperproc (HardResourceLimits) will set the `sysctl(3)` kern.maxfiles (SoftResourceLimits) or kern.maxfilesperproc (HardResourceLimits)
value in addition to the `setrlimit(2)` values. value in addition to the `setrlimit(2)` values.
''; '';
}; };
NumberOfProcesses = mkOption { NumberOfProcesses = mkOption {
type = types.nullOr types.int; type = types.nullOr types.int;
default = null; default = null;
description = '' description = ''
The maximum number of simultaneous processes for this user id. Setting this value in a system The maximum number of simultaneous processes for this user id. Setting this value in a system
wide daemon will set the `sysctl(3)` kern.maxproc (SoftResourceLimits) or kern.maxprocperuid wide daemon will set the `sysctl(3)` kern.maxproc (SoftResourceLimits) or kern.maxprocperuid
(HardResourceLimits) value in addition to the `setrlimit(2)` values. (HardResourceLimits) value in addition to the `setrlimit(2)` values.
''; '';
}; };
ResidentSetSize = mkOption { ResidentSetSize = mkOption {
type = types.nullOr types.int; type = types.nullOr types.int;
default = null; default = null;
description = '' description = ''
The maximum size (in bytes) to which a process's resident set size may grow. This imposes a The maximum size (in bytes) to which a process's resident set size may grow. This imposes a
limit on the amount of physical memory to be given to a process; if memory is tight, the system limit on the amount of physical memory to be given to a process; if memory is tight, the system
will prefer to take memory from processes that are exceeding their declared resident set size. will prefer to take memory from processes that are exceeding their declared resident set size.
''; '';
}; };
Stack = mkOption { Stack = mkOption {
type = types.nullOr types.int; type = types.nullOr types.int;
default = null; default = null;
description = '' description = ''
The maximum size (in bytes) of the stack segment for a process; this defines how far a program's The maximum size (in bytes) of the stack segment for a process; this defines how far a program's
stack segment may be extended. Stack extension is performed automatically by the system. stack segment may be extended. Stack extension is performed automatically by the system.
''; '';
};
}; };
}; }
}); );
}; };
Nice = mkOption { Nice = mkOption {
@ -628,8 +650,14 @@ in {
}; };
ProcessType = mkOption { ProcessType = mkOption {
type = types.nullOr type = types.nullOr (
(types.enum [ "Background" "Standard" "Adaptive" "Interactive" ]); types.enum [
"Background"
"Standard"
"Adaptive"
"Interactive"
]
);
default = null; default = null;
example = "Background"; example = "Background";
description = '' description = ''
@ -694,7 +722,11 @@ in {
MachServices = mkOption { MachServices = mkOption {
default = null; default = null;
example = { "org.nixos.service" = { ResetAtClose = true; }; }; example = {
"org.nixos.service" = {
ResetAtClose = true;
};
};
description = '' description = ''
This optional key is used to specify Mach services to be registered with the Mach bootstrap sub-system. This optional key is used to specify Mach services to be registered with the Mach bootstrap sub-system.
Each key in this dictionary should be the name of service to be advertised. The value of the key must Each key in this dictionary should be the name of service to be advertised. The value of the key must
@ -703,32 +735,37 @@ in {
Finally, for the job itself, the values will be replaced with Mach ports at the time of check-in with Finally, for the job itself, the values will be replaced with Mach ports at the time of check-in with
launchd. launchd.
''; '';
type = types.nullOr (types.attrsOf (types.either types.bool type = types.nullOr (
(types.submodule { types.attrsOf (
options = { types.either types.bool (
ResetAtClose = mkOption { types.submodule {
type = types.nullOr types.bool; options = {
default = null; ResetAtClose = mkOption {
description = '' type = types.nullOr types.bool;
If this boolean is false, the port is recycled, thus leaving clients to remain oblivious to the default = null;
demand nature of job. If the value is set to true, clients receive port death notifications when description = ''
the job lets go of the receive right. The port will be recreated atomically with respect to bootstrap_look_up() If this boolean is false, the port is recycled, thus leaving clients to remain oblivious to the
calls, so that clients can trust that after receiving a port death notification, demand nature of job. If the value is set to true, clients receive port death notifications when
the new port will have already been recreated. Setting the value to true should be done with the job lets go of the receive right. The port will be recreated atomically with respect to bootstrap_look_up()
care. Not all clients may be able to handle this behavior. The default value is false. calls, so that clients can trust that after receiving a port death notification,
''; the new port will have already been recreated. Setting the value to true should be done with
}; care. Not all clients may be able to handle this behavior. The default value is false.
'';
};
HideUntilCheckIn = mkOption { HideUntilCheckIn = mkOption {
type = types.nullOr types.bool; type = types.nullOr types.bool;
default = null; default = null;
description = '' description = ''
Reserve the name in the namespace, but cause bootstrap_look_up() to fail until the job has Reserve the name in the namespace, but cause bootstrap_look_up() to fail until the job has
checked in with launchd. checked in with launchd.
''; '';
}; };
}; };
}))); }
)
)
);
}; };
LaunchEvents = mkOption { LaunchEvents = mkOption {
@ -790,109 +827,123 @@ in {
The parameters below are used as inputs to call `getaddrinfo(3)`. The parameters below are used as inputs to call `getaddrinfo(3)`.
''; '';
type = types.nullOr (types.attrsOf (types.submodule { type = types.nullOr (
options = { types.attrsOf (
SockType = mkOption { types.submodule {
type = types.nullOr (types.enum [ "stream" "dgram" "seqpacket" ]); options = {
default = null; SockType = mkOption {
description = '' type = types.nullOr (
This optional key tells launchctl what type of socket to create. The default is "stream" and types.enum [
other valid values for this key are "dgram" and "seqpacket" respectively. "stream"
''; "dgram"
}; "seqpacket"
]
);
default = null;
description = ''
This optional key tells launchctl what type of socket to create. The default is "stream" and
other valid values for this key are "dgram" and "seqpacket" respectively.
'';
};
SockPassive = mkOption { SockPassive = mkOption {
type = types.nullOr types.bool; type = types.nullOr types.bool;
default = null; default = null;
description = '' description = ''
This optional key specifies whether `listen(2)` or `connect(2)` should be called on the created file This optional key specifies whether `listen(2)` or `connect(2)` should be called on the created file
descriptor. The default is true ("to listen"). descriptor. The default is true ("to listen").
''; '';
}; };
SockNodeName = mkOption { SockNodeName = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = null; default = null;
description = '' description = ''
This optional key specifies the node to `connect(2)` or `bind(2)` to. This optional key specifies the node to `connect(2)` or `bind(2)` to.
''; '';
}; };
SockServiceName = mkOption { SockServiceName = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = null; default = null;
description = '' description = ''
This optional key specifies the service on the node to `connect(2)` or `bind(2)` to. This optional key specifies the service on the node to `connect(2)` or `bind(2)` to.
''; '';
}; };
SockFamily = mkOption { SockFamily = mkOption {
type = types.nullOr (types.enum [ "IPv4" "IPv6" ]); type = types.nullOr (
default = null; types.enum [
description = '' "IPv4"
This optional key can be used to specifically request that "IPv4" or "IPv6" socket(s) be created. "IPv6"
''; ]
}; );
default = null;
description = ''
This optional key can be used to specifically request that "IPv4" or "IPv6" socket(s) be created.
'';
};
SockProtocol = mkOption { SockProtocol = mkOption {
type = types.nullOr (types.enum [ "TCP" ]); type = types.nullOr (types.enum [ "TCP" ]);
default = null; default = null;
description = '' description = ''
This optional key specifies the protocol to be passed to `socket(2)`. The only value understood by This optional key specifies the protocol to be passed to `socket(2)`. The only value understood by
this key at the moment is "TCP". this key at the moment is "TCP".
''; '';
}; };
SockPathName = mkOption { SockPathName = mkOption {
type = types.nullOr types.path; type = types.nullOr types.path;
default = null; default = null;
description = '' description = ''
This optional key implies SockFamily is set to "Unix". It specifies the path to `connect(2)` or This optional key implies SockFamily is set to "Unix". It specifies the path to `connect(2)` or
`bind(2)` to. `bind(2)` to.
''; '';
}; };
SecureSocketWithKey = mkOption { SecureSocketWithKey = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = null; default = null;
description = '' description = ''
This optional key is a variant of SockPathName. Instead of binding to a known path, a securely This optional key is a variant of SockPathName. Instead of binding to a known path, a securely
generated socket is created and the path is assigned to the environment variable that is inherited generated socket is created and the path is assigned to the environment variable that is inherited
by all jobs spawned by launchd. by all jobs spawned by launchd.
''; '';
}; };
SockPathMode = mkOption { SockPathMode = mkOption {
type = types.nullOr types.int; type = types.nullOr types.int;
default = null; default = null;
description = '' description = ''
This optional key specifies the mode of the socket. Known bug: Property lists don't support This optional key specifies the mode of the socket. Known bug: Property lists don't support
octal, so please convert the value to decimal. octal, so please convert the value to decimal.
''; '';
}; };
Bonjour = mkOption { Bonjour = mkOption {
type = type = types.nullOr (types.either types.bool (types.listOf types.str));
types.nullOr (types.either types.bool (types.listOf types.str)); default = null;
default = null; description = ''
description = '' This optional key can be used to request that the service be registered with the
This optional key can be used to request that the service be registered with the `mDNSResponder(8)`. If the value is boolean, the service name is inferred from the SockServiceName.
`mDNSResponder(8)`. If the value is boolean, the service name is inferred from the SockServiceName. '';
''; };
};
MulticastGroup = mkOption { MulticastGroup = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = null; default = null;
description = '' description = ''
This optional key can be used to request that the datagram socket join a multicast group. If the This optional key can be used to request that the datagram socket join a multicast group. If the
value is a hostname, then `getaddrinfo(3)` will be used to join the correct multicast address for a value is a hostname, then `getaddrinfo(3)` will be used to join the correct multicast address for a
given socket family. If an explicit IPv4 or IPv6 address is given, it is required that the SockFamily given socket family. If an explicit IPv4 or IPv6 address is given, it is required that the SockFamily
family also be set, otherwise the results are undefined. family also be set, otherwise the results are undefined.
''; '';
}; };
}; };
})); }
)
);
}; };
}; };

View file

@ -5,117 +5,146 @@
{ lib, ... }: { lib, ... }:
let let
inherit (lib) imap1 types mkOption showOption mergeDefinitions; inherit (lib)
inherit (builtins) map filter length deepSeq throw toString concatLists; imap1
types
mkOption
showOption
mergeDefinitions
;
inherit (builtins)
map
filter
length
deepSeq
throw
toString
concatLists
;
inherit (lib.options) showDefs; inherit (lib.options) showDefs;
wildcardText = lib.literalMD "`*`"; wildcardText = lib.literalMD "`*`";
/* * /*
A type of list which does not allow duplicate elements. The base/inner *
list type to use (e.g. `types.listOf` or `types.nonEmptyListOf`) is passed A type of list which does not allow duplicate elements. The base/inner
via argument `listType`, which must be the final type and not a function. list type to use (e.g. `types.listOf` or `types.nonEmptyListOf`) is passed
via argument `listType`, which must be the final type and not a function.
NOTE: The extra check for duplicates is quadratic and strict, so use this NOTE: The extra check for duplicates is quadratic and strict, so use this
type sparingly and only: type sparingly and only:
* when needed, and * when needed, and
* when the list is expected to be recursively short (e.g. < 10 elements) * when the list is expected to be recursively short (e.g. < 10 elements)
and shallow (i.e. strict evaluation of the list won't take too long) and shallow (i.e. strict evaluation of the list won't take too long)
The implementation of this function is similar to that of The implementation of this function is similar to that of
`types.nonEmptyListOf`. `types.nonEmptyListOf`.
*/ */
types'.uniqueList = listType: types'.uniqueList =
listType // { listType:
description = "unique ${ listType
types.optionDescriptionPhrase (class: class == "noun") listType // {
}"; description = "unique ${types.optionDescriptionPhrase (class: class == "noun") listType}";
substSubModules = m: types'.uniqueList (listType.substSubModules m); substSubModules = m: types'.uniqueList (listType.substSubModules m);
# This has been taken from the implementation of `types.listOf`, but has # This has been taken from the implementation of `types.listOf`, but has
# been modified to throw on duplicates. This check cannot be done in the # been modified to throw on duplicates. This check cannot be done in the
# `check` fn as this check is deep/strict, and because `check` runs # `check` fn as this check is deep/strict, and because `check` runs
# prior to merging. # prior to merging.
merge = loc: defs: merge =
loc: defs:
let let
# Each element of `dupes` is a list. When there are duplicates, # Each element of `dupes` is a list. When there are duplicates,
# later lists will be duplicates of earlier lists, so just throw on # later lists will be duplicates of earlier lists, so just throw on
# the first set of duplicates found so that we don't have duplicate # the first set of duplicates found so that we don't have duplicate
# error msgs. # error msgs.
checked = filter (li: checked = filter (
li:
if length li > 1 then if length li > 1 then
throw '' throw ''
The option `${ The option `${showOption loc}' contains duplicate entries after merging:
showOption loc
}' contains duplicate entries after merging:
${showDefs li}'' ${showDefs li}''
else else
false) dupes; false
dupes = ) dupes;
map (def: filter (def': def'.value == def.value) merged) merged; dupes = map (def: filter (def': def'.value == def.value) merged) merged;
merged = filter (x: x ? value) (concatLists (imap1 (n: def: merged = filter (x: x ? value) (
imap1 (m: el: concatLists (
let imap1 (
inherit (def) file; n: def:
loc' = loc imap1 (
++ [ "[definition ${toString n}-entry ${toString m}]" ]; m: el:
in (mergeDefinitions loc' listType.nestedTypes.elemType [{ let
inherit file; inherit (def) file;
value = el; loc' = loc ++ [ "[definition ${toString n}-entry ${toString m}]" ];
}]).optionalValue // { in
inherit loc' file; (mergeDefinitions loc' listType.nestedTypes.elemType [
}) def.value) defs)); {
in deepSeq checked (map (x: x.value) merged); inherit file;
value = el;
}
]).optionalValue
// {
inherit loc' file;
}
) def.value
) defs
)
);
in
deepSeq checked (map (x: x.value) merged);
}; };
in { in
StartCalendarInterval = let {
CalendarIntervalEntry = types.submodule { StartCalendarInterval =
options = { let
Minute = mkOption { CalendarIntervalEntry = types.submodule {
type = types.nullOr (types.ints.between 0 59); options = {
default = null; Minute = mkOption {
defaultText = wildcardText; type = types.nullOr (types.ints.between 0 59);
description = '' default = null;
The minute on which this job will be run. defaultText = wildcardText;
''; description = ''
}; The minute on which this job will be run.
'';
};
Hour = mkOption { Hour = mkOption {
type = types.nullOr (types.ints.between 0 23); type = types.nullOr (types.ints.between 0 23);
default = null; default = null;
defaultText = wildcardText; defaultText = wildcardText;
description = '' description = ''
The hour on which this job will be run. The hour on which this job will be run.
''; '';
}; };
Day = mkOption { Day = mkOption {
type = types.nullOr (types.ints.between 1 31); type = types.nullOr (types.ints.between 1 31);
default = null; default = null;
defaultText = wildcardText; defaultText = wildcardText;
description = '' description = ''
The day on which this job will be run. The day on which this job will be run.
''; '';
}; };
Weekday = mkOption { Weekday = mkOption {
type = types.nullOr (types.ints.between 0 7); type = types.nullOr (types.ints.between 0 7);
default = null; default = null;
defaultText = wildcardText; defaultText = wildcardText;
description = '' description = ''
The weekday on which this job will be run (0 and 7 are Sunday). The weekday on which this job will be run (0 and 7 are Sunday).
''; '';
}; };
Month = mkOption { Month = mkOption {
type = types.nullOr (types.ints.between 1 12); type = types.nullOr (types.ints.between 1 12);
default = null; default = null;
defaultText = wildcardText; defaultText = wildcardText;
description = '' description = ''
The month on which this job will be run. The month on which this job will be run.
''; '';
};
}; };
}; };
}; in
in types.either CalendarIntervalEntry types.either CalendarIntervalEntry (types'.uniqueList (types.nonEmptyListOf CalendarIntervalEntry));
(types'.uniqueList (types.nonEmptyListOf CalendarIntervalEntry));
} }

View file

@ -3,12 +3,13 @@
{ {
assertPlatform = module: pkgs: platforms: { assertPlatform = module: pkgs: platforms: {
assertion = lib.elem pkgs.stdenv.hostPlatform.system platforms; assertion = lib.elem pkgs.stdenv.hostPlatform.system platforms;
message = let message =
platformsStr = lib.concatStringsSep "\n" let
(map (p: " - ${p}") (lib.sort (a: b: a < b) platforms)); platformsStr = lib.concatStringsSep "\n" (map (p: " - ${p}") (lib.sort (a: b: a < b) platforms));
in '' in
The module ${module} does not support your platform. It only supports ''
The module ${module} does not support your platform. It only supports
${platformsStr}''; ${platformsStr}'';
}; };
} }

View file

@ -1,4 +1,5 @@
{ lib }: { { lib }:
{
# Converts a boolean to a yes/no string. This is used in lots of # Converts a boolean to a yes/no string. This is used in lots of
# configuration formats. # configuration formats.
yesNo = value: if value then "yes" else "no"; yesNo = value: if value then "yes" else "no";

View file

@ -9,13 +9,23 @@
{ lib }: { lib }:
let inherit (lib) all filterAttrs head hm mapAttrs length tail toposort; let
in { inherit (lib)
all
filterAttrs
head
hm
mapAttrs
length
tail
toposort
;
in
{
empty = { }; empty = { };
isEntry = e: e ? data && e ? after && e ? before; isEntry = e: e ? data && e ? after && e ? before;
isDag = dag: isDag = dag: builtins.isAttrs dag && all hm.dag.isEntry (builtins.attrValues dag);
builtins.isAttrs dag && all hm.dag.isEntry (builtins.attrValues dag);
# Takes an attribute set containing entries built by entryAnywhere, # Takes an attribute set containing entries built by entryAnywhere,
# entryAfter, and entryBefore to a topologically sorted list of # entryAfter, and entryBefore to a topologically sorted list of
@ -73,11 +83,10 @@ in {
# ]; # ];
# } # }
# true # true
topoSort = dag: topoSort =
dag:
let let
dagBefore = dag: name: dagBefore = dag: name: builtins.attrNames (filterAttrs (n: v: builtins.elem name v.before) dag);
builtins.attrNames
(filterAttrs (n: v: builtins.elem name v.before) dag);
normalizedDag = mapAttrs (n: v: { normalizedDag = mapAttrs (n: v: {
name = n; name = n;
data = v.data; data = v.data;
@ -85,9 +94,12 @@ in {
}) dag; }) dag;
before = a: b: builtins.elem a.name b.after; before = a: b: builtins.elem a.name b.after;
sorted = toposort before (builtins.attrValues normalizedDag); sorted = toposort before (builtins.attrValues normalizedDag);
in if sorted ? result then { in
result = map (v: { inherit (v) name data; }) sorted.result; if sorted ? result then
} else {
result = map (v: { inherit (v) name data; }) sorted.result;
}
else
sorted; sorted;
# Applies a function to each element of the given DAG. # Applies a function to each element of the given DAG.
@ -107,21 +119,28 @@ in {
# #
# The entries as a whole can be given a relation to other DAG nodes. All # The entries as a whole can be given a relation to other DAG nodes. All
# generated nodes are then placed before or after those dependencies. # generated nodes are then placed before or after those dependencies.
entriesBetween = tag: entriesBetween =
tag:
let let
go = i: before: after: entries: go =
i: before: after: entries:
let let
name = "${tag}-${toString i}"; name = "${tag}-${toString i}";
i' = i + 1; i' = i + 1;
in if entries == [ ] then in
if entries == [ ] then
hm.dag.empty hm.dag.empty
else if length entries == 1 then { else if length entries == 1 then
"${name}" = hm.dag.entryBetween before after (head entries); {
} else "${name}" = hm.dag.entryBetween before after (head entries);
}
else
{ {
"${name}" = hm.dag.entryAfter after (head entries); "${name}" = hm.dag.entryAfter after (head entries);
} // go (i + 1) before [ name ] (tail entries); }
in go 0; // go (i + 1) before [ name ] (tail entries);
in
go 0;
entriesAnywhere = tag: hm.dag.entriesBetween tag [ ] [ ]; entriesAnywhere = tag: hm.dag.entriesBetween tag [ ] [ ];
entriesAfter = tag: hm.dag.entriesBetween tag [ ]; entriesAfter = tag: hm.dag.entriesBetween tag [ ];

View file

@ -1,9 +1,22 @@
{ homeDirectory, lib, pkgs }: {
homeDirectory,
lib,
pkgs,
}:
let let
inherit (lib) inherit (lib)
hasPrefix hm literalExpression mkDefault mkIf mkOption removePrefix types; hasPrefix
in { hm
literalExpression
mkDefault
mkIf
mkOption
removePrefix
types
;
in
{
# Constructs a type suitable for a `home.file` like option. The # Constructs a type suitable for a `home.file` like option. The
# target path may be either absolute or relative, in which case it # target path may be either absolute or relative, in which case it
# is relative the `basePath` argument (which itself must be an # is relative the `basePath` argument (which itself must be an
@ -13,108 +26,121 @@ in {
# - opt the name of the option, for self-references # - opt the name of the option, for self-references
# - basePathDesc docbook compatible description of the base path # - basePathDesc docbook compatible description of the base path
# - basePath the file base path # - basePath the file base path
fileType = opt: basePathDesc: basePath: fileType =
types.attrsOf (types.submodule ({ name, config, ... }: { opt: basePathDesc: basePath:
options = { types.attrsOf (
enable = mkOption { types.submodule (
type = types.bool; { name, config, ... }:
default = true; {
description = '' options = {
Whether this file should be generated. This option allows specific enable = mkOption {
files to be disabled. type = types.bool;
''; default = true;
}; description = ''
target = mkOption { Whether this file should be generated. This option allows specific
type = types.str; files to be disabled.
apply = p: '';
let absPath = if hasPrefix "/" p then p else "${basePath}/${p}"; };
in removePrefix (homeDirectory + "/") absPath; target = mkOption {
defaultText = literalExpression "name"; type = types.str;
description = '' apply =
Path to target file relative to ${basePathDesc}. p:
''; let
}; absPath = if hasPrefix "/" p then p else "${basePath}/${p}";
in
removePrefix (homeDirectory + "/") absPath;
defaultText = literalExpression "name";
description = ''
Path to target file relative to ${basePathDesc}.
'';
};
text = mkOption { text = mkOption {
default = null; default = null;
type = types.nullOr types.lines; type = types.nullOr types.lines;
description = '' description = ''
Text of the file. If this option is null then Text of the file. If this option is null then
[](#opt-${opt}._name_.source) [](#opt-${opt}._name_.source)
must be set. must be set.
''; '';
}; };
source = mkOption { source = mkOption {
type = types.path; type = types.path;
description = '' description = ''
Path of the source file or directory. If Path of the source file or directory. If
[](#opt-${opt}._name_.text) [](#opt-${opt}._name_.text)
is non-null then this option will automatically point to a file is non-null then this option will automatically point to a file
containing that text. containing that text.
''; '';
}; };
executable = mkOption { executable = mkOption {
type = types.nullOr types.bool; type = types.nullOr types.bool;
default = null; default = null;
description = '' description = ''
Set the execute bit. If `null`, defaults to the mode Set the execute bit. If `null`, defaults to the mode
of the {var}`source` file or to `false` of the {var}`source` file or to `false`
for files created through the {var}`text` option. for files created through the {var}`text` option.
''; '';
}; };
recursive = mkOption { recursive = mkOption {
type = types.bool; type = types.bool;
default = false; default = false;
description = '' description = ''
If the file source is a directory, then this option If the file source is a directory, then this option
determines whether the directory should be recursively determines whether the directory should be recursively
linked to the target location. This option has no effect linked to the target location. This option has no effect
if the source is a file. if the source is a file.
If `false` (the default) then the target If `false` (the default) then the target
will be a symbolic link to the source directory. If will be a symbolic link to the source directory. If
`true` then the target will be a `true` then the target will be a
directory structure matching the source's but whose leafs directory structure matching the source's but whose leafs
are symbolic links to the files of the source directory. are symbolic links to the files of the source directory.
''; '';
}; };
onChange = mkOption { onChange = mkOption {
type = types.lines; type = types.lines;
default = ""; default = "";
description = '' description = ''
Shell commands to run when file has changed between Shell commands to run when file has changed between
generations. The script will be run generations. The script will be run
*after* the new files have been linked *after* the new files have been linked
into place. into place.
Note, this code is always run when `recursive` is Note, this code is always run when `recursive` is
enabled. enabled.
''; '';
}; };
force = mkOption { force = mkOption {
type = types.bool; type = types.bool;
default = false; default = false;
description = '' description = ''
Whether the target path should be unconditionally replaced Whether the target path should be unconditionally replaced
by the managed file source. Warning, this will silently by the managed file source. Warning, this will silently
delete the target regardless of whether it is a file or delete the target regardless of whether it is a file or
link. link.
''; '';
}; };
}; };
config = { config = {
target = mkDefault name; target = mkDefault name;
source = mkIf (config.text != null) (mkDefault (pkgs.writeTextFile { source = mkIf (config.text != null) (
inherit (config) text; mkDefault (
executable = config.executable == true; # can be null pkgs.writeTextFile {
name = hm.strings.storeFileName name; inherit (config) text;
})); executable = config.executable == true; # can be null
}; name = hm.strings.storeFileName name;
})); }
)
);
};
}
)
);
} }

View file

@ -1,114 +1,158 @@
{ lib }: { lib }:
{ {
toHyprconf = { attrs, indentLevel ? 0, importantPrefixes ? [ "$" ], }: toHyprconf =
{
attrs,
indentLevel ? 0,
importantPrefixes ? [ "$" ],
}:
let let
inherit (lib) inherit (lib)
all concatMapStringsSep concatStrings concatStringsSep filterAttrs foldl all
generators hasPrefix isAttrs isList mapAttrsToList replicate; concatMapStringsSep
concatStrings
concatStringsSep
filterAttrs
foldl
generators
hasPrefix
isAttrs
isList
mapAttrsToList
replicate
;
initialIndent = concatStrings (replicate indentLevel " "); initialIndent = concatStrings (replicate indentLevel " ");
toHyprconf' = indent: attrs: toHyprconf' =
indent: attrs:
let let
sections = sections = filterAttrs (n: v: isAttrs v || (isList v && all isAttrs v)) attrs;
filterAttrs (n: v: isAttrs v || (isList v && all isAttrs v)) attrs;
mkSection = n: attrs: mkSection =
n: attrs:
if lib.isList attrs then if lib.isList attrs then
(concatMapStringsSep "\n" (a: mkSection n a) attrs) (concatMapStringsSep "\n" (a: mkSection n a) attrs)
else '' else
${indent}${n} { ''
${toHyprconf' " ${indent}" attrs}${indent}} ${indent}${n} {
''; ${toHyprconf' " ${indent}" attrs}${indent}}
'';
mkFields = generators.toKeyValue { mkFields = generators.toKeyValue {
listsAsDuplicateKeys = true; listsAsDuplicateKeys = true;
inherit indent; inherit indent;
}; };
allFields = allFields = filterAttrs (n: v: !(isAttrs v || (isList v && all isAttrs v))) attrs;
filterAttrs (n: v: !(isAttrs v || (isList v && all isAttrs v)))
attrs;
isImportantField = n: _: isImportantField =
foldl (acc: prev: if hasPrefix prev n then true else acc) false n: _: foldl (acc: prev: if hasPrefix prev n then true else acc) false importantPrefixes;
importantPrefixes;
importantFields = filterAttrs isImportantField allFields; importantFields = filterAttrs isImportantField allFields;
fields = builtins.removeAttrs allFields fields = builtins.removeAttrs allFields (mapAttrsToList (n: _: n) importantFields);
(mapAttrsToList (n: _: n) importantFields); in
in mkFields importantFields mkFields importantFields
+ concatStringsSep "\n" (mapAttrsToList mkSection sections) + concatStringsSep "\n" (mapAttrsToList mkSection sections)
+ mkFields fields; + mkFields fields;
in toHyprconf' initialIndent attrs; in
toHyprconf' initialIndent attrs;
toKDL = { }: toKDL =
{ }:
let let
inherit (lib) concatStringsSep splitString mapAttrsToList any; inherit (lib)
concatStringsSep
splitString
mapAttrsToList
any
;
inherit (builtins) typeOf replaceStrings elem; inherit (builtins) typeOf replaceStrings elem;
# ListOf String -> String # ListOf String -> String
indentStrings = let indentStrings =
# Although the input of this function is a list of strings, let
# the strings themselves *will* contain newlines, so you need # Although the input of this function is a list of strings,
# to normalize the list by joining and resplitting them. # the strings themselves *will* contain newlines, so you need
unlines = lib.splitString "\n"; # to normalize the list by joining and resplitting them.
lines = lib.concatStringsSep "\n"; unlines = lib.splitString "\n";
indentAll = lines: concatStringsSep "\n" (map (x: " " + x) lines); lines = lib.concatStringsSep "\n";
in stringsWithNewlines: indentAll (unlines (lines stringsWithNewlines)); indentAll = lines: concatStringsSep "\n" (map (x: " " + x) lines);
in
stringsWithNewlines: indentAll (unlines (lines stringsWithNewlines));
# String -> String # String -> String
sanitizeString = replaceStrings [ "\n" ''"'' ] [ "\\n" ''\"'' ]; sanitizeString = replaceStrings [ "\n" ''"'' ] [ "\\n" ''\"'' ];
# OneOf [Int Float String Bool Null] -> String # OneOf [Int Float String Bool Null] -> String
literalValueToString = element: literalValueToString =
element:
lib.throwIfNot lib.throwIfNot
(elem (typeOf element) [ "int" "float" "string" "bool" "null" ]) (elem (typeOf element) [
"Cannot convert value of type ${typeOf element} to KDL literal." "int"
(if typeOf element == "null" then "float"
"null" "string"
else if element == false then "bool"
"false" "null"
else if element == true then ])
"true" "Cannot convert value of type ${typeOf element} to KDL literal."
else if typeOf element == "string" then (
''"${sanitizeString element}"'' if typeOf element == "null" then
else "null"
toString element); else if element == false then
"false"
else if element == true then
"true"
else if typeOf element == "string" then
''"${sanitizeString element}"''
else
toString element
);
# Attrset Conversion # Attrset Conversion
# String -> AttrsOf Anything -> String # String -> AttrsOf Anything -> String
convertAttrsToKDL = name: attrs: convertAttrsToKDL =
name: attrs:
let let
optArgsString = lib.optionalString (attrs ? "_args") optArgsString = lib.optionalString (attrs ? "_args") (
(lib.pipe attrs._args [ lib.pipe attrs._args [
(map literalValueToString) (map literalValueToString)
(lib.concatStringsSep " ") (lib.concatStringsSep " ")
(s: s + " ") (s: s + " ")
]); ]
);
optPropsString = lib.optionalString (attrs ? "_props") optPropsString = lib.optionalString (attrs ? "_props") (
(lib.pipe attrs._props [ lib.pipe attrs._props [
(lib.mapAttrsToList (lib.mapAttrsToList (name: value: "${name}=${literalValueToString value}"))
(name: value: "${name}=${literalValueToString value}"))
(lib.concatStringsSep " ") (lib.concatStringsSep " ")
(s: s + " ") (s: s + " ")
]); ]
);
children = children = lib.filterAttrs (
lib.filterAttrs (name: _: !(elem name [ "_args" "_props" ])) attrs; name: _:
in '' !(elem name [
"_args"
"_props"
])
) attrs;
in
''
${name} ${optArgsString}${optPropsString}{ ${name} ${optArgsString}${optPropsString}{
${indentStrings (mapAttrsToList convertAttributeToKDL children)} ${indentStrings (mapAttrsToList convertAttributeToKDL children)}
}''; }'';
# List Conversion # List Conversion
# String -> ListOf (OneOf [Int Float String Bool Null]) -> String # String -> ListOf (OneOf [Int Float String Bool Null]) -> String
convertListOfFlatAttrsToKDL = name: list: convertListOfFlatAttrsToKDL =
let flatElements = map literalValueToString list; name: list:
in "${name} ${concatStringsSep " " flatElements}"; let
flatElements = map literalValueToString list;
in
"${name} ${concatStringsSep " " flatElements}";
# String -> ListOf Anything -> String # String -> ListOf Anything -> String
convertListOfNonFlatAttrsToKDL = name: list: '' convertListOfNonFlatAttrsToKDL = name: list: ''
@ -117,18 +161,39 @@
}''; }'';
# String -> ListOf Anything -> String # String -> ListOf Anything -> String
convertListToKDL = name: list: convertListToKDL =
let elementsAreFlat = !any (el: elem (typeOf el) [ "list" "set" ]) list; name: list:
in if elementsAreFlat then let
elementsAreFlat =
!any (
el:
elem (typeOf el) [
"list"
"set"
]
) list;
in
if elementsAreFlat then
convertListOfFlatAttrsToKDL name list convertListOfFlatAttrsToKDL name list
else else
convertListOfNonFlatAttrsToKDL name list; convertListOfNonFlatAttrsToKDL name list;
# Combined Conversion # Combined Conversion
# String -> Anything -> String # String -> Anything -> String
convertAttributeToKDL = name: value: convertAttributeToKDL =
let vType = typeOf value; name: value:
in if elem vType [ "int" "float" "bool" "null" "string" ] then let
vType = typeOf value;
in
if
elem vType [
"int"
"float"
"bool"
"null"
"string"
]
then
"${name} ${literalValueToString value}" "${name} ${literalValueToString value}"
else if vType == "set" then else if vType == "set" then
convertAttrsToKDL name value convertAttrsToKDL name value
@ -139,99 +204,153 @@
Cannot convert type `(${typeOf value})` to KDL: Cannot convert type `(${typeOf value})` to KDL:
${name} = ${toString value} ${name} = ${toString value}
''; '';
in attrs: '' in
attrs: ''
${concatStringsSep "\n" (mapAttrsToList convertAttributeToKDL attrs)} ${concatStringsSep "\n" (mapAttrsToList convertAttributeToKDL attrs)}
''; '';
toSCFG = { }: toSCFG =
{ }:
let let
inherit (lib) concatStringsSep mapAttrsToList any; inherit (lib) concatStringsSep mapAttrsToList any;
inherit (builtins) typeOf replaceStrings elem; inherit (builtins) typeOf replaceStrings elem;
# ListOf String -> String # ListOf String -> String
indentStrings = let indentStrings =
# Although the input of this function is a list of strings, let
# the strings themselves *will* contain newlines, so you need # Although the input of this function is a list of strings,
# to normalize the list by joining and resplitting them. # the strings themselves *will* contain newlines, so you need
unlines = lib.splitString "\n"; # to normalize the list by joining and resplitting them.
lines = lib.concatStringsSep "\n"; unlines = lib.splitString "\n";
indentAll = lines: concatStringsSep "\n" (map (x: " " + x) lines); lines = lib.concatStringsSep "\n";
in stringsWithNewlines: indentAll (unlines (lines stringsWithNewlines)); indentAll = lines: concatStringsSep "\n" (map (x: " " + x) lines);
in
stringsWithNewlines: indentAll (unlines (lines stringsWithNewlines));
# String -> Bool # String -> Bool
specialChars = s: specialChars =
any (char: elem char (reserved ++ [ " " "'" "{" "}" ])) s:
(lib.stringToCharacters s); any (
char:
elem char (
reserved
++ [
" "
"'"
"{"
"}"
]
)
) (lib.stringToCharacters s);
# String -> String # String -> String
sanitizeString = sanitizeString = replaceStrings reserved [
replaceStrings reserved [ ''\"'' "\\\\" "\\r" "\\n" "\\t" ]; ''\"''
"\\\\"
"\\r"
"\\n"
"\\t"
];
reserved = [ ''"'' "\\" "\r" "\n" " " ]; reserved = [
''"''
"\\"
"\r"
"\n"
" "
];
# OneOf [Int Float String Bool] -> String # OneOf [Int Float String Bool] -> String
literalValueToString = element: literalValueToString =
lib.throwIfNot (elem (typeOf element) [ "int" "float" "string" "bool" ]) element:
"Cannot convert value of type ${typeOf element} to SCFG literal." lib.throwIfNot
(if element == false then (elem (typeOf element) [
"false" "int"
else if element == true then "float"
"true" "string"
else if typeOf element == "string" then "bool"
if element == "" || specialChars element then ])
''"${sanitizeString element}"'' "Cannot convert value of type ${typeOf element} to SCFG literal."
else (
element if element == false then
else "false"
toString element); else if element == true then
"true"
else if typeOf element == "string" then
if element == "" || specialChars element then ''"${sanitizeString element}"'' else element
else
toString element
);
# Bool -> ListOf (OneOf [Int Float String Bool]) -> String # Bool -> ListOf (OneOf [Int Float String Bool]) -> String
toOptParamsString = cond: list: toOptParamsString =
lib.optionalString (cond) (lib.pipe list [ cond: list:
(map literalValueToString) lib.optionalString (cond) (
(concatStringsSep " ") lib.pipe list [
(s: " " + s) (map literalValueToString)
]); (concatStringsSep " ")
(s: " " + s)
]
);
# Attrset Conversion # Attrset Conversion
# String -> AttrsOf Anything -> String # String -> AttrsOf Anything -> String
convertAttrsToSCFG = name: attrs: convertAttrsToSCFG =
name: attrs:
let let
optParamsString = toOptParamsString (attrs ? "_params") attrs._params; optParamsString = toOptParamsString (attrs ? "_params") attrs._params;
in '' in
''
${name}${optParamsString} { ${name}${optParamsString} {
${indentStrings (convertToAttrsSCFG' attrs)} ${indentStrings (convertToAttrsSCFG' attrs)}
}''; }'';
# Attrset Conversion # Attrset Conversion
# AttrsOf Anything -> ListOf String # AttrsOf Anything -> ListOf String
convertToAttrsSCFG' = attrs: convertToAttrsSCFG' =
mapAttrsToList convertAttributeToSCFG attrs:
(lib.filterAttrs (name: val: !isNull val && name != "_params") attrs); mapAttrsToList convertAttributeToSCFG (
lib.filterAttrs (name: val: !isNull val && name != "_params") attrs
);
# List Conversion # List Conversion
# String -> ListOf (OneOf [Int Float String Bool]) -> String # String -> ListOf (OneOf [Int Float String Bool]) -> String
convertListOfFlatAttrsToSCFG = name: list: convertListOfFlatAttrsToSCFG =
let optParamsString = toOptParamsString (list != [ ]) list; name: list:
in "${name}${optParamsString}"; let
optParamsString = toOptParamsString (list != [ ]) list;
in
"${name}${optParamsString}";
# Combined Conversion # Combined Conversion
# String -> Anything -> String # String -> Anything -> String
convertAttributeToSCFG = name: value: convertAttributeToSCFG =
lib.throwIf (name == "") "Directive must not be empty" name: value:
(let vType = typeOf value; lib.throwIf (name == "") "Directive must not be empty" (
in if elem vType [ "int" "float" "bool" "string" ] then let
"${name} ${literalValueToString value}" vType = typeOf value;
else if vType == "set" then in
convertAttrsToSCFG name value if
else if vType == "list" then elem vType [
convertListOfFlatAttrsToSCFG name value "int"
else "float"
throw '' "bool"
Cannot convert type `(${typeOf value})` to SCFG: "string"
${name} = ${toString value} ]
''); then
in attrs: "${name} ${literalValueToString value}"
else if vType == "set" then
convertAttrsToSCFG name value
else if vType == "list" then
convertListOfFlatAttrsToSCFG name value
else
throw ''
Cannot convert type `(${typeOf value})` to SCFG:
${name} = ${toString value}
''
);
in
attrs:
lib.optionalString (attrs != { }) '' lib.optionalString (attrs != { }) ''
${concatStringsSep "\n" (convertToAttrsSCFG' attrs)} ${concatStringsSep "\n" (convertToAttrsSCFG' attrs)}
''; '';

View file

@ -7,7 +7,13 @@
let let
inherit (lib) inherit (lib)
concatMapStringsSep concatStrings escape hasPrefix head replaceStrings; concatMapStringsSep
concatStrings
escape
hasPrefix
head
replaceStrings
;
mkPrimitive = t: v: { mkPrimitive = t: v: {
_type = "gvariant"; _type = "gvariant";
@ -36,7 +42,8 @@ let
# Returns the GVariant type of a given Nix value. If no type can be # Returns the GVariant type of a given Nix value. If no type can be
# found for the value then the empty string is returned. # found for the value then the empty string is returned.
typeOf = v: typeOf =
v:
with type; with type;
if builtins.isBool v then if builtins.isBool v then
boolean boolean
@ -47,29 +54,27 @@ let
else if builtins.isString v then else if builtins.isString v then
string string
else if builtins.isList v then else if builtins.isList v then
let elemType = elemTypeOf v; let
in if elemType == "" then "" else arrayOf elemType elemType = elemTypeOf v;
in
if elemType == "" then "" else arrayOf elemType
else if builtins.isAttrs v && v ? type then else if builtins.isAttrs v && v ? type then
v.type v.type
else else
""; "";
elemTypeOf = vs: elemTypeOf = vs: if builtins.isList vs then if vs == [ ] then "" else typeOf (head vs) else "";
if builtins.isList vs then
if vs == [ ] then "" else typeOf (head vs)
else
"";
mkMaybe = elemType: elem: mkMaybe =
mkPrimitive (type.maybeOf elemType) elem // { elemType: elem:
__toString = self: mkPrimitive (type.maybeOf elemType) elem
if self.value == null then // {
"@${self.type} nothing" __toString =
else self: if self.value == null then "@${self.type} nothing" else "just ${toString self.value}";
"just ${toString self.value}";
}; };
in rec { in
rec {
inherit type typeOf; inherit type typeOf;
@ -83,7 +88,8 @@ in rec {
# Returns the GVariant value that most closely matches the given Nix # Returns the GVariant value that most closely matches the given Nix
# value. If no GVariant value can be found then `null` is returned. # value. If no GVariant value can be found then `null` is returned.
mkValue = v: mkValue =
v:
if builtins.isBool v then if builtins.isBool v then
mkBoolean v mkBoolean v
else if builtins.isInt v then else if builtins.isInt v then
@ -99,55 +105,77 @@ in rec {
else else
null; null;
mkArray = elemType: elems: mkArray =
mkPrimitive (type.arrayOf elemType) (map mkValue elems) // { elemType: elems:
__toString = self: mkPrimitive (type.arrayOf elemType) (map mkValue elems)
"@${self.type} [${concatMapStringsSep "," toString self.value}]"; // {
__toString = self: "@${self.type} [${concatMapStringsSep "," toString self.value}]";
}; };
mkEmptyArray = elemType: mkArray elemType [ ]; mkEmptyArray = elemType: mkArray elemType [ ];
mkVariant = elem: mkVariant =
let gvarElem = mkValue elem; elem:
in mkPrimitive type.variant gvarElem // { let
gvarElem = mkValue elem;
in
mkPrimitive type.variant gvarElem
// {
__toString = self: "@${self.type} <${toString self.value}>"; __toString = self: "@${self.type} <${toString self.value}>";
}; };
mkDictionaryEntry = elems: mkDictionaryEntry =
elems:
let let
gvarElems = map mkValue elems; gvarElems = map mkValue elems;
dictionaryType = type.dictionaryEntryOf (map (e: e.type) gvarElems); dictionaryType = type.dictionaryEntryOf (map (e: e.type) gvarElems);
in mkPrimitive dictionaryType gvarElems // { in
__toString = self: mkPrimitive dictionaryType gvarElems
"@${self.type} {${concatMapStringsSep "," toString self.value}}"; // {
__toString = self: "@${self.type} {${concatMapStringsSep "," toString self.value}}";
}; };
mkNothing = elemType: mkMaybe elemType null; mkNothing = elemType: mkMaybe elemType null;
mkJust = elem: let gvarElem = mkValue elem; in mkMaybe gvarElem.type gvarElem; mkJust =
elem:
let
gvarElem = mkValue elem;
in
mkMaybe gvarElem.type gvarElem;
mkTuple = elems: mkTuple =
elems:
let let
gvarElems = map mkValue elems; gvarElems = map mkValue elems;
tupleType = type.tupleOf (map (e: e.type) gvarElems); tupleType = type.tupleOf (map (e: e.type) gvarElems);
in mkPrimitive tupleType gvarElems // { in
__toString = self: mkPrimitive tupleType gvarElems
"@${self.type} (${concatMapStringsSep "," toString self.value})"; // {
__toString = self: "@${self.type} (${concatMapStringsSep "," toString self.value})";
}; };
mkBoolean = v: mkBoolean =
mkPrimitive type.boolean v // { v:
mkPrimitive type.boolean v
// {
__toString = self: if self.value then "true" else "false"; __toString = self: if self.value then "true" else "false";
}; };
mkString = v: mkString =
let sanitize = s: replaceStrings [ "\n" ] [ "\\n" ] (escape [ "'" "\\" ] s); v:
in mkPrimitive type.string v // { let
sanitize = s: replaceStrings [ "\n" ] [ "\\n" ] (escape [ "'" "\\" ] s);
in
mkPrimitive type.string v
// {
__toString = self: "'${sanitize self.value}'"; __toString = self: "'${sanitize self.value}'";
}; };
mkObjectpath = v: mkObjectpath =
mkPrimitive type.string v // { v:
mkPrimitive type.string v
// {
__toString = self: "objectpath '${escape [ "'" ] self.value}'"; __toString = self: "objectpath '${escape [ "'" ] self.value}'";
}; };
@ -157,8 +185,10 @@ in rec {
mkUint16 = mkPrimitive type.uint16; mkUint16 = mkPrimitive type.uint16;
mkInt32 = v: mkInt32 =
mkPrimitive type.int32 v // { v:
mkPrimitive type.int32 v
// {
__toString = self: toString self.value; __toString = self: toString self.value;
}; };
@ -168,8 +198,10 @@ in rec {
mkUint64 = mkPrimitive type.uint64; mkUint64 = mkPrimitive type.uint64;
mkDouble = v: mkDouble =
mkPrimitive type.double v // { v:
mkPrimitive type.double v
// {
__toString = self: toString self.value; __toString = self: toString self.value;
}; };

View file

@ -107,10 +107,12 @@
github = "d-dervishi"; github = "d-dervishi";
githubId = 61125355; githubId = 61125355;
name = "David Dervishi"; name = "David Dervishi";
keys = [{ keys = [
longKeyId = "rsa4096/0xB1C012F0E7697195"; {
fingerprint = "4C92 E3B0 21B5 5562 A1E0 CE3D B1C0 12F0 E769 7195"; longKeyId = "rsa4096/0xB1C012F0E7697195";
}]; fingerprint = "4C92 E3B0 21B5 5562 A1E0 CE3D B1C0 12F0 E769 7195";
}
];
}; };
Dines97 = { Dines97 = {
name = "Denis Kaynar"; name = "Denis Kaynar";
@ -147,8 +149,7 @@
email = "yiheng.he@proton.me"; email = "yiheng.he@proton.me";
matrix = "@hey2022:matrix.org"; matrix = "@hey2022:matrix.org";
github = "hey2022"; github = "hey2022";
keys = keys = [ { fingerprint = "128E 09C0 6F73 D678 6BB5 E551 5EA5 3C75 F7BE 3EDE"; } ];
[{ fingerprint = "128E 09C0 6F73 D678 6BB5 E551 5EA5 3C75 F7BE 3EDE"; }];
}; };
jack5079 = { jack5079 = {
name = "Jack W."; name = "Jack W.";
@ -167,10 +168,12 @@
name = "Jessica"; name = "Jessica";
email = "jess+nix@jessie.cafe"; email = "jess+nix@jessie.cafe";
githubId = 43591752; githubId = 43591752;
keys = [{ keys = [
longkeyid = "rsa3072/0xBA3350686C918606"; {
fingerprint = "8092 3BD1 ECD0 E436 671D C8E9 BA33 5068 6C91 8606"; longkeyid = "rsa3072/0xBA3350686C918606";
}]; fingerprint = "8092 3BD1 ECD0 E436 671D C8E9 BA33 5068 6C91 8606";
}
];
}; };
jkarlson = { jkarlson = {
email = "jekarlson@gmail.com"; email = "jekarlson@gmail.com";
@ -250,10 +253,12 @@
email = "kamadorueda@gmail.com"; email = "kamadorueda@gmail.com";
github = "kamadorueda"; github = "kamadorueda";
githubId = 47480384; githubId = 47480384;
keys = [{ keys = [
longkeyid = "rsa4096/0x04D0CEAF916A9A40"; {
fingerprint = "2BE3 BAFD 793E A349 ED1F F00F 04D0 CEAF 916A 9A40"; longkeyid = "rsa4096/0x04D0CEAF916A9A40";
}]; fingerprint = "2BE3 BAFD 793E A349 ED1F F00F 04D0 CEAF 916A 9A40";
}
];
}; };
katexochen = { katexochen = {
name = "Paul Meyer"; name = "Paul Meyer";
@ -339,20 +344,24 @@
email = "nick@hassan.host"; email = "nick@hassan.host";
github = "n-hass"; github = "n-hass";
githubId = 72363381; githubId = 72363381;
keys = [{ keys = [
longkeyid = "rsa4096/0xFC95AB946A781EE7"; {
fingerprint = "FDEE 6116 DBA7 8840 7323 4466 A371 5973 2728 A6A6"; longkeyid = "rsa4096/0xFC95AB946A781EE7";
}]; fingerprint = "FDEE 6116 DBA7 8840 7323 4466 A371 5973 2728 A6A6";
}
];
}; };
seylerius = { seylerius = {
email = "sable@seyleri.us"; email = "sable@seyleri.us";
name = "Sable Seyler"; name = "Sable Seyler";
github = "seylerius"; github = "seylerius";
githubId = 1145981; githubId = 1145981;
keys = [{ keys = [
logkeyid = "rsa4096/0x68BF2EAE6D91CAFF"; {
fingerprint = "F0E0 0311 126A CD72 4392 25E6 68BF 2EAE 6D91 CAFF"; logkeyid = "rsa4096/0x68BF2EAE6D91CAFF";
}]; fingerprint = "F0E0 0311 126A CD72 4392 25E6 68BF 2EAE 6D91 CAFF";
}
];
}; };
silmarp = { silmarp = {
name = "Silmar Pereira da Silva Junior"; name = "Silmar Pereira da Silva Junior";
@ -394,10 +403,12 @@
github = "msfjarvis"; github = "msfjarvis";
githubId = "13348378"; githubId = "13348378";
name = "Harsh Shandilya"; name = "Harsh Shandilya";
keys = [{ keys = [
longkeyid = "rsa4096/0xB7843F823355E9B9"; {
fingerprint = "8F87 050B 0F9C B841 1515 7399 B784 3F82 3355 E9B9"; longkeyid = "rsa4096/0xB7843F823355E9B9";
}]; fingerprint = "8F87 050B 0F9C B841 1515 7399 B784 3F82 3355 E9B9";
}
];
}; };
ambroisie = { ambroisie = {
email = "bruno.home-manager@belanyi.fr"; email = "bruno.home-manager@belanyi.fr";
@ -571,10 +582,12 @@
email = "88944439+rcerc@users.noreply.github.com"; email = "88944439+rcerc@users.noreply.github.com";
github = "rcerc"; github = "rcerc";
githubId = 88944439; githubId = 88944439;
keys = [{ keys = [
longkeyid = "ed25519/0x3F98EC7EC2B87ED1"; {
fingerprint = "D5D6 FD1F 0D9A 3284 FB9B C26D 3F98 EC7E C2B8 7ED1"; longkeyid = "ed25519/0x3F98EC7EC2B87ED1";
}]; fingerprint = "D5D6 FD1F 0D9A 3284 FB9B C26D 3F98 EC7E C2B8 7ED1";
}
];
}; };
mtoohey = { mtoohey = {
name = "Matthew Toohey"; name = "Matthew Toohey";
@ -594,8 +607,7 @@
matrix = "@soywod:matrix.org"; matrix = "@soywod:matrix.org";
github = "soywod"; github = "soywod";
githubId = 10437171; githubId = 10437171;
keys = keys = [ { fingerprint = "75F0 AB7C FE01 D077 AEE6 CAFD 353E 4A18 EE0F AB72"; } ];
[{ fingerprint = "75F0 AB7C FE01 D077 AEE6 CAFD 353E 4A18 EE0F AB72"; }];
}; };
tensor5 = { tensor5 = {
github = "tensor5"; github = "tensor5";
@ -615,8 +627,7 @@
github = "toastal"; github = "toastal";
githubId = 561087; githubId = 561087;
name = "toastal"; name = "toastal";
keys = keys = [ { fingerprint = "7944 74B7 D236 DAB9 C9EF E7F9 5CCE 6F14 66D4 7C9E"; } ];
[{ fingerprint = "7944 74B7 D236 DAB9 C9EF E7F9 5CCE 6F14 66D4 7C9E"; }];
}; };
tomodachi94 = { tomodachi94 = {
email = "tomodachi94+nixpkgs@protonmail.com"; email = "tomodachi94+nixpkgs@protonmail.com";
@ -683,8 +694,7 @@
email = "git+nix@cleslie.uk"; email = "git+nix@cleslie.uk";
github = "callumio"; github = "callumio";
githubId = 16057677; githubId = 16057677;
keys = keys = [ { fingerprint = "BC82 4BB5 1656 D144 285E A0EC D382 C4AF EECE AA90"; } ];
[{ fingerprint = "BC82 4BB5 1656 D144 285E A0EC D382 C4AF EECE AA90"; }];
}; };
ALameLlama = { ALameLlama = {
name = "Nicholas Ciechanowski"; name = "Nicholas Ciechanowski";
@ -703,10 +713,12 @@
email = "me@hpsaucii.dev"; email = "me@hpsaucii.dev";
github = "HPsaucii"; github = "HPsaucii";
githubId = 126502193; githubId = 126502193;
keys = [{ keys = [
longkeyid = "rsa4096/0xEDB2C634166AE6AD"; {
fingerprint = "AD32 73D4 5E0E 9478 E826 543F EDB2 C634 166A E6AD"; longkeyid = "rsa4096/0xEDB2C634166AE6AD";
}]; fingerprint = "AD32 73D4 5E0E 9478 E826 543F EDB2 C634 166A E6AD";
}
];
}; };
folliehiyuki = { folliehiyuki = {
name = "Hoang Nguyen"; name = "Hoang Nguyen";

View file

@ -1,66 +1,85 @@
{ lib }: rec { { lib }:
rec {
mkNushellInline = expr: lib.setType "nushell-inline" { inherit expr; }; mkNushellInline = expr: lib.setType "nushell-inline" { inherit expr; };
isNushellInline = lib.isType "nushell-inline"; isNushellInline = lib.isType "nushell-inline";
toNushell = { indent ? "", multiline ? true, asBindings ? false, }@args: toNushell =
{
indent ? "",
multiline ? true,
asBindings ? false,
}@args:
v: v:
let let
innerIndent = "${indent} "; innerIndent = "${indent} ";
introSpace = if multiline then '' introSpace =
if multiline then
''
${innerIndent}'' else ${innerIndent}''
" "; else
outroSpace = if multiline then '' " ";
outroSpace =
if multiline then
''
${indent}'' else ${indent}''
" "; else
" ";
innerArgs = args // { innerArgs = args // {
indent = if asBindings then indent else innerIndent; indent = if asBindings then indent else innerIndent;
asBindings = false; asBindings = false;
}; };
concatItems = lib.concatStringsSep introSpace; concatItems = lib.concatStringsSep introSpace;
generatedBindings = assert lib.assertMsg (badVarNames == [ ]) generatedBindings =
"Bad Nushell variable names: ${ assert lib.assertMsg (badVarNames == [ ])
lib.generators.toPretty { } badVarNames "Bad Nushell variable names: ${lib.generators.toPretty { } badVarNames}";
}"; lib.concatStrings (
lib.concatStrings (lib.mapAttrsToList (key: value: '' lib.mapAttrsToList (key: value: ''
${indent}let ${key} = ${toNushell innerArgs value} ${indent}let ${key} = ${toNushell innerArgs value}
'') v); '') v
);
isBadVarName = name: isBadVarName =
name:
# Extracted from https://github.com/nushell/nushell/blob/ebc7b80c23f777f70c5053cca428226b3fe00d30/crates/nu-parser/src/parser.rs#L33 # Extracted from https://github.com/nushell/nushell/blob/ebc7b80c23f777f70c5053cca428226b3fe00d30/crates/nu-parser/src/parser.rs#L33
# Variables with numeric or even empty names are allowed. The only requisite is not containing any of the following characters # Variables with numeric or even empty names are allowed. The only requisite is not containing any of the following characters
let invalidVariableCharacters = ".[({+-*^/=!<>&|"; let
in lib.match "^[$]?[^${lib.escapeRegex invalidVariableCharacters}]+$" invalidVariableCharacters = ".[({+-*^/=!<>&|";
name == null; in
lib.match "^[$]?[^${lib.escapeRegex invalidVariableCharacters}]+$" name == null;
badVarNames = lib.filter isBadVarName (builtins.attrNames v); badVarNames = lib.filter isBadVarName (builtins.attrNames v);
in if asBindings then in
if asBindings then
generatedBindings generatedBindings
else if v == null then else if v == null then
"null" "null"
else if lib.isInt v || lib.isFloat v || lib.isString v || lib.isBool v then else if lib.isInt v || lib.isFloat v || lib.isString v || lib.isBool v then
lib.strings.toJSON v lib.strings.toJSON v
else if lib.isList v then else if lib.isList v then
(if v == [ ] then (
"[]" if v == [ ] then
else "[]"
"[${introSpace}${ else
concatItems (map (value: "${toNushell innerArgs value}") v) "[${introSpace}${concatItems (map (value: "${toNushell innerArgs value}") v)}${outroSpace}]"
}${outroSpace}]") )
else if lib.isAttrs v then else if lib.isAttrs v then
(if isNushellInline v then (
"(${v.expr})" if isNushellInline v then
else if v == { } then "(${v.expr})"
"{}" else if v == { } then
else if lib.isDerivation v then "{}"
toString v else if lib.isDerivation v then
else toString v
"{${introSpace}${ else
concatItems (lib.mapAttrsToList (key: value: "{${introSpace}${
"${lib.strings.toJSON key}: ${toNushell innerArgs value}") v) concatItems (
}${outroSpace}}") lib.mapAttrsToList (key: value: "${lib.strings.toJSON key}: ${toNushell innerArgs value}") v
)
}${outroSpace}}"
)
else else
abort "nushell.toNushell: type ${lib.typeOf v} is unsupported"; abort "nushell.toNushell: type ${lib.typeOf v} is unsupported";
} }

View file

@ -2,27 +2,35 @@
let let
mkShellIntegrationOption = name: mkShellIntegrationOption =
{ config, baseName ? name, extraDescription ? "" }: name:
let attrName = "enable${baseName}Integration"; {
in lib.mkOption { config,
baseName ? name,
extraDescription ? "",
}:
let
attrName = "enable${baseName}Integration";
in
lib.mkOption {
default = config.home.shell.${attrName}; default = config.home.shell.${attrName};
defaultText = lib.literalMD "[](#opt-home.shell.${attrName})"; defaultText = lib.literalMD "[](#opt-home.shell.${attrName})";
example = false; example = false;
description = "Whether to enable ${name} integration.${ description = "Whether to enable ${name} integration.${
lib.optionalString (extraDescription != "") lib.optionalString (extraDescription != "") ("\n\n" + extraDescription)
("\n\n" + extraDescription) }";
}";
type = lib.types.bool; type = lib.types.bool;
}; };
in rec { in
rec {
# Produces a Bourne shell like statement that prepend new values to # Produces a Bourne shell like statement that prepend new values to
# an possibly existing variable, using sep(arator). # an possibly existing variable, using sep(arator).
# Example: # Example:
# prependToVar ":" "PATH" [ "$HOME/bin" "$HOME/.local/bin" ] # prependToVar ":" "PATH" [ "$HOME/bin" "$HOME/.local/bin" ]
# => "$HOME/bin:$HOME/.local/bin:${PATH:+:}\$PATH" # => "$HOME/bin:$HOME/.local/bin:${PATH:+:}\$PATH"
prependToVar = sep: n: v: prependToVar =
sep: n: v:
"${lib.concatStringsSep sep v}\${${n}:+${sep}}\$${n}"; "${lib.concatStringsSep sep v}\${${n}:+${sep}}\$${n}";
# Produces a Bourne shell like variable export statement. # Produces a Bourne shell like variable export statement.

View file

@ -3,11 +3,15 @@
nixpkgsLib: nixpkgsLib:
let mkHmLib = import ./.; let
in nixpkgsLib.extend (self: super: { mkHmLib = import ./.;
hm = mkHmLib { lib = self; }; in
nixpkgsLib.extend (
self: super: {
hm = mkHmLib { lib = self; };
# For forward compatibility. # For forward compatibility.
literalExpression = super.literalExpression or super.literalExample; literalExpression = super.literalExpression or super.literalExample;
literalDocBook = super.literalDocBook or super.literalExample; literalDocBook = super.literalDocBook or super.literalExample;
}) }
)

View file

@ -2,22 +2,39 @@
let let
inherit (lib) inherit (lib)
genList length lowerChars replaceStrings stringToCharacters upperChars; genList
in { length
lowerChars
replaceStrings
stringToCharacters
upperChars
;
in
{
# Figures out a valid Nix store name for the given path. # Figures out a valid Nix store name for the given path.
storeFileName = path: storeFileName =
path:
let let
# All characters that are considered safe. Note "-" is not # All characters that are considered safe. Note "-" is not
# included to avoid "-" followed by digit being interpreted as a # included to avoid "-" followed by digit being interpreted as a
# version. # version.
safeChars = [ "+" "." "_" "?" "=" ] ++ lowerChars ++ upperChars safeChars =
[
"+"
"."
"_"
"?"
"="
]
++ lowerChars
++ upperChars
++ stringToCharacters "0123456789"; ++ stringToCharacters "0123456789";
empties = l: genList (x: "") (length l); empties = l: genList (x: "") (length l);
unsafeInName = unsafeInName = stringToCharacters (replaceStrings safeChars (empties safeChars) path);
stringToCharacters (replaceStrings safeChars (empties safeChars) path);
safeName = replaceStrings unsafeInName (empties unsafeInName) path; safeName = replaceStrings unsafeInName (empties unsafeInName) path;
in "hm_" + safeName; in
"hm_" + safeName;
} }

View file

@ -2,42 +2,65 @@
let let
inherit (lib) inherit (lib)
concatStringsSep defaultFunctor fixedWidthNumber hm imap1 isAttrs isList concatStringsSep
length listToAttrs mapAttrs mkIf mkOrder mkOption mkOptionType nameValuePair defaultFunctor
stringLength types warn; fixedWidthNumber
hm
imap1
isAttrs
isList
length
listToAttrs
mapAttrs
mkIf
mkOrder
mkOption
mkOptionType
nameValuePair
stringLength
types
warn
;
dagEntryOf = elemType: dagEntryOf =
elemType:
let let
submoduleType = types.submodule ({ name, ... }: { submoduleType = types.submodule (
options = { { name, ... }:
data = mkOption { type = elemType; }; {
after = mkOption { type = with types; listOf str; }; options = {
before = mkOption { type = with types; listOf str; }; data = mkOption { type = elemType; };
}; after = mkOption { type = with types; listOf str; };
config = mkIf (elemType.name == "submodule") { before = mkOption { type = with types; listOf str; };
data._module.args.dagName = name; };
}; config = mkIf (elemType.name == "submodule") {
}); data._module.args.dagName = name;
maybeConvert = def: };
}
);
maybeConvert =
def:
if hm.dag.isEntry def.value then if hm.dag.isEntry def.value then
def.value def.value
else else
hm.dag.entryAnywhere (if def ? priority then hm.dag.entryAnywhere (if def ? priority then mkOrder def.priority def.value else def.value);
mkOrder def.priority def.value in
else mkOptionType {
def.value);
in mkOptionType {
name = "dagEntryOf"; name = "dagEntryOf";
description = "DAG entry of ${elemType.description}"; description = "DAG entry of ${elemType.description}";
# leave the checking to the submodule type # leave the checking to the submodule type
merge = loc: defs: merge =
submoduleType.merge loc (map (def: { loc: defs:
inherit (def) file; submoduleType.merge loc (
value = maybeConvert def; map (def: {
}) defs); inherit (def) file;
value = maybeConvert def;
}) defs
);
}; };
in rec { in
rec {
# A directed acyclic graph of some inner type. # A directed acyclic graph of some inner type.
# #
# Note, if the element type is a submodule then the `name` argument # Note, if the element type is a submodule then the `name` argument
@ -45,16 +68,21 @@ in rec {
# internal structure of the DAG values. To give access to the # internal structure of the DAG values. To give access to the
# "actual" attribute name a new submodule argument is provided with # "actual" attribute name a new submodule argument is provided with
# the name `dagName`. # the name `dagName`.
dagOf = elemType: dagOf =
let attrEquivalent = types.attrsOf (dagEntryOf elemType); elemType:
in mkOptionType rec { let
attrEquivalent = types.attrsOf (dagEntryOf elemType);
in
mkOptionType rec {
name = "dagOf"; name = "dagOf";
description = "DAG of ${elemType.description}"; description = "DAG of ${elemType.description}";
inherit (attrEquivalent) check merge emptyValue; inherit (attrEquivalent) check merge emptyValue;
getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "<name>" ]); getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "<name>" ]);
getSubModules = elemType.getSubModules; getSubModules = elemType.getSubModules;
substSubModules = m: dagOf (elemType.substSubModules m); substSubModules = m: dagOf (elemType.substSubModules m);
functor = (defaultFunctor name) // { wrapped = elemType; }; functor = (defaultFunctor name) // {
wrapped = elemType;
};
nestedTypes.elemType = elemType; nestedTypes.elemType = elemType;
}; };
} }

View file

@ -1,10 +1,28 @@
{ lib, gvariant ? import ./gvariant.nix { inherit lib; } }: {
lib,
gvariant ? import ./gvariant.nix { inherit lib; },
}:
let let
inherit (lib) inherit (lib)
all concatMap foldl' getFiles getValues head isFunction literalExpression all
mergeAttrs mergeDefaultOption mergeOneOption mergeOptions mkOption concatMap
mkOptionType showFiles showOption types; foldl'
getFiles
getValues
head
isFunction
literalExpression
mergeAttrs
mergeDefaultOption
mergeOneOption
mergeOptions
mkOption
mkOptionType
showFiles
showOption
types
;
typesDag = import ./types-dag.nix { inherit lib; }; typesDag = import ./types-dag.nix { inherit lib; };
@ -12,24 +30,30 @@ let
# must refer back to the type. # must refer back to the type.
gvar = gvariant; gvar = gvariant;
in rec { in
rec {
inherit (typesDag) dagOf; inherit (typesDag) dagOf;
selectorFunction = mkOptionType { selectorFunction = mkOptionType {
name = "selectorFunction"; name = "selectorFunction";
description = "Function that takes an attribute set and returns a list" description =
"Function that takes an attribute set and returns a list"
+ " containing a selection of the values of the input set"; + " containing a selection of the values of the input set";
check = isFunction; check = isFunction;
merge = _loc: defs: as: concatMap (select: select as) (getValues defs); merge =
_loc: defs: as:
concatMap (select: select as) (getValues defs);
}; };
overlayFunction = mkOptionType { overlayFunction = mkOptionType {
name = "overlayFunction"; name = "overlayFunction";
description = "An overlay function, takes self and super and returns" description =
"An overlay function, takes self and super and returns"
+ " an attribute set overriding the desired attributes."; + " an attribute set overriding the desired attributes.";
check = isFunction; check = isFunction;
merge = _loc: defs: self: super: merge =
_loc: defs: self: super:
foldl' (res: def: mergeAttrs res (def.value self super)) { } defs; foldl' (res: def: mergeAttrs res (def.value self super)) { } defs;
}; };
@ -69,28 +93,34 @@ in rec {
name = "gvariant"; name = "gvariant";
description = "GVariant value"; description = "GVariant value";
check = v: gvar.mkValue v != null; check = v: gvar.mkValue v != null;
merge = loc: defs: merge =
loc: defs:
let let
vdefs = map (d: vdefs = map (
d // { d:
value = d
if gvar.isGVariant d.value then d.value else gvar.mkValue d.value; // {
}) defs; value = if gvar.isGVariant d.value then d.value else gvar.mkValue d.value;
}
) defs;
vals = map (d: d.value) vdefs; vals = map (d: d.value) vdefs;
defTypes = map (x: x.type) vals; defTypes = map (x: x.type) vals;
sameOrNull = x: y: if x == y then y else null; sameOrNull = x: y: if x == y then y else null;
# A bit naive to just check the first entry… # A bit naive to just check the first entry…
sharedDefType = foldl' sameOrNull (head defTypes) defTypes; sharedDefType = foldl' sameOrNull (head defTypes) defTypes;
allChecked = all (x: check x) vals; allChecked = all (x: check x) vals;
in if sharedDefType == null then in
throw ("Cannot merge definitions of `${showOption loc}' with" if sharedDefType == null then
throw (
"Cannot merge definitions of `${showOption loc}' with"
+ " mismatched GVariant types given in" + " mismatched GVariant types given in"
+ " ${showFiles (getFiles defs)}.") + " ${showFiles (getFiles defs)}."
)
else if gvar.isArray sharedDefType && allChecked then else if gvar.isArray sharedDefType && allChecked then
gvar.mkValue ((types.listOf gvariant).merge loc gvar.mkValue ((types.listOf gvariant).merge loc (map (d: d // { value = d.value.value; }) vdefs))
(map (d: d // { value = d.value.value; }) vdefs)) // { // {
type = sharedDefType; type = sharedDefType;
} }
else if gvar.isTuple sharedDefType && allChecked then else if gvar.isTuple sharedDefType && allChecked then
mergeOneOption loc defs mergeOneOption loc defs
else if gvar.isMaybe sharedDefType && allChecked then else if gvar.isMaybe sharedDefType && allChecked then
@ -107,27 +137,37 @@ in rec {
mergeDefaultOption loc defs; mergeDefaultOption loc defs;
}; };
nushellValue = let nushellValue =
valueType = types.nullOr (types.oneOf [ let
(lib.mkOptionType { valueType = types.nullOr (
name = "nushell"; types.oneOf [
description = "Nushell inline value"; (lib.mkOptionType {
descriptionClass = "name"; name = "nushell";
check = lib.isType "nushell-inline"; description = "Nushell inline value";
}) descriptionClass = "name";
types.bool check = lib.isType "nushell-inline";
types.int })
types.float types.bool
types.str types.int
types.path types.float
(types.attrsOf valueType // { types.str
description = "attribute set of Nushell values"; types.path
descriptionClass = "name"; (
}) types.attrsOf valueType
(types.listOf valueType // { // {
description = "list of Nushell values"; description = "attribute set of Nushell values";
descriptionClass = "name"; descriptionClass = "name";
}) }
]); )
in valueType; (
types.listOf valueType
// {
description = "list of Nushell values";
descriptionClass = "name";
}
)
]
);
in
valueType;
} }

View file

@ -2,7 +2,8 @@
rec { rec {
# Produces a Zsh shell like value # Produces a Zsh shell like value
toZshValue = v: toZshValue =
v:
if builtins.isBool v then if builtins.isBool v then
if v then "true" else "false" if v then "true" else "false"
else if builtins.isString v then else if builtins.isString v then

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
@ -9,7 +14,8 @@ let
inherit (config.home.version) release isReleaseBranch; inherit (config.home.version) release isReleaseBranch;
}; };
in { in
{
options = { options = {
manual.html.enable = lib.mkOption { manual.html.enable = lib.mkOption {
type = lib.types.bool; type = lib.types.bool;
@ -51,7 +57,10 @@ in {
config = { config = {
home.packages = lib.mkMerge [ home.packages = lib.mkMerge [
(lib.mkIf cfg.html.enable [ docs.manual.html docs.manual.htmlOpenTool ]) (lib.mkIf cfg.html.enable [
docs.manual.html
docs.manual.htmlOpenTool
])
(lib.mkIf cfg.manpages.enable [ docs.manPages ]) (lib.mkIf cfg.manpages.enable [ docs.manPages ])
(lib.mkIf cfg.json.enable [ docs.options.json ]) (lib.mkIf cfg.json.enable [ docs.options.json ])
]; ];

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (lib) types; inherit (lib) types;
@ -7,18 +12,23 @@ let
toDconfIni = lib.generators.toINI { mkKeyValue = mkIniKeyValue; }; toDconfIni = lib.generators.toINI { mkKeyValue = mkIniKeyValue; };
mkIniKeyValue = key: value: mkIniKeyValue = key: value: "${key}=${toString (lib.hm.gvariant.mkValue value)}";
"${key}=${toString (lib.hm.gvariant.mkValue value)}";
# The dconf keys managed by this configuration. We store this as part of the # The dconf keys managed by this configuration. We store this as part of the
# generation state to be able to reset keys that become unmanaged during # generation state to be able to reset keys that become unmanaged during
# switch. # switch.
stateDconfKeys = pkgs.writeText "dconf-keys.json" (builtins.toJSON stateDconfKeys = pkgs.writeText "dconf-keys.json" (
(lib.concatLists (lib.mapAttrsToList builtins.toJSON (
(dir: entries: lib.mapAttrsToList (key: _: "/${dir}/${key}") entries) lib.concatLists (
cfg.settings))); lib.mapAttrsToList (
dir: entries: lib.mapAttrsToList (key: _: "/${dir}/${key}") entries
) cfg.settings
)
)
);
in { in
{
meta.maintainers = [ lib.maintainers.rycee ]; meta.maintainers = [ lib.maintainers.rycee ];
options = { options = {
@ -84,8 +94,8 @@ in {
ln -s ${stateDconfKeys} $out/state/${stateDconfKeys.name} ln -s ${stateDconfKeys} $out/state/${stateDconfKeys.name}
''; '';
home.activation.dconfSettings = lib.hm.dag.entryAfter [ "installPackages" ] home.activation.dconfSettings = lib.hm.dag.entryAfter [ "installPackages" ] (
(let let
iniFile = pkgs.writeText "hm-dconf.ini" (toDconfIni cfg.settings); iniFile = pkgs.writeText "hm-dconf.ini" (toDconfIni cfg.settings);
statePath = "state/${stateDconfKeys.name}"; statePath = "state/${stateDconfKeys.name}";
@ -95,7 +105,12 @@ in {
${config.lib.bash.initHomeManagerLib} ${config.lib.bash.initHomeManagerLib}
PATH=${lib.makeBinPath [ pkgs.dconf pkgs.jq ]}''${PATH:+:}$PATH PATH=${
lib.makeBinPath [
pkgs.dconf
pkgs.jq
]
}''${PATH:+:}$PATH
oldState="$1" oldState="$1"
newState="$2" newState="$2"
@ -116,7 +131,8 @@ in {
run $DCONF_DBUS_RUN_SESSION dconf reset "$key" run $DCONF_DBUS_RUN_SESSION dconf reset "$key"
done done
''; '';
in '' in
''
if [[ -v DBUS_SESSION_BUS_ADDRESS ]]; then if [[ -v DBUS_SESSION_BUS_ADDRESS ]]; then
export DCONF_DBUS_RUN_SESSION="" export DCONF_DBUS_RUN_SESSION=""
else else
@ -132,6 +148,7 @@ in {
run $DCONF_DBUS_RUN_SESSION ${pkgs.dconf}/bin/dconf load / < ${iniFile} run $DCONF_DBUS_RUN_SESSION ${pkgs.dconf}/bin/dconf load / < ${iniFile}
unset DCONF_DBUS_RUN_SESSION unset DCONF_DBUS_RUN_SESSION
''); ''
);
}; };
} }

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
@ -6,7 +11,8 @@ let
iniFormat = pkgs.formats.ini { }; iniFormat = pkgs.formats.ini { };
in { in
{
meta.maintainers = with lib.maintainers; [ loicreynier ]; meta.maintainers = with lib.maintainers; [ loicreynier ];
options.editorconfig = { options.editorconfig = {
@ -38,14 +44,18 @@ in {
}; };
config = lib.mkIf (cfg.enable && cfg.settings != { }) { config = lib.mkIf (cfg.enable && cfg.settings != { }) {
home.file.".editorconfig".text = let home.file.".editorconfig".text =
renderedSettings = lib.generators.toINIWithGlobalSection { } { let
globalSection = { root = true; }; renderedSettings = lib.generators.toINIWithGlobalSection { } {
sections = cfg.settings; globalSection = {
}; root = true;
in '' };
# Generated by Home Manager sections = cfg.settings;
${renderedSettings} };
''; in
''
# Generated by Home Manager
${renderedSettings}
'';
}; };
} }

View file

@ -2,7 +2,12 @@
# #
# https://github.com/NixOS/nixpkgs/blob/23.11/nixos/modules/config/fonts/fontconfig.nix # https://github.com/NixOS/nixpkgs/blob/23.11/nixos/modules/config/fonts/fontconfig.nix
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
@ -10,15 +15,19 @@ let
profileDirectory = config.home.profileDirectory; profileDirectory = config.home.profileDirectory;
in { in
{
meta.maintainers = [ lib.maintainers.rycee ]; meta.maintainers = [ lib.maintainers.rycee ];
imports = [ imports = [
(lib.mkRenamedOptionModule [ "fonts" "fontconfig" "enableProfileFonts" ] [ (lib.mkRenamedOptionModule
"fonts" [ "fonts" "fontconfig" "enableProfileFonts" ]
"fontconfig" [
"enable" "fonts"
]) "fontconfig"
"enable"
]
)
]; ];
options = { options = {
@ -117,53 +126,58 @@ in {
fi fi
''; '';
xdg.configFile = let xdg.configFile =
mkFontconfigConf = conf: '' let
<?xml version='1.0'?> mkFontconfigConf = conf: ''
<?xml version='1.0'?>
<!-- Generated by Home Manager. --> <!-- Generated by Home Manager. -->
<!DOCTYPE fontconfig SYSTEM 'urn:fontconfig:fonts.dtd'> <!DOCTYPE fontconfig SYSTEM 'urn:fontconfig:fonts.dtd'>
<fontconfig> <fontconfig>
${conf} ${conf}
</fontconfig> </fontconfig>
''; '';
in { in
"fontconfig/conf.d/10-hm-fonts.conf".text = mkFontconfigConf '' {
<description>Add fonts in the Nix user profile</description> "fontconfig/conf.d/10-hm-fonts.conf".text = mkFontconfigConf ''
<description>Add fonts in the Nix user profile</description>
<include ignore_missing="yes">${config.home.path}/etc/fonts/conf.d</include> <include ignore_missing="yes">${config.home.path}/etc/fonts/conf.d</include>
<include ignore_missing="yes">${config.home.path}/etc/fonts/fonts.conf</include> <include ignore_missing="yes">${config.home.path}/etc/fonts/fonts.conf</include>
<dir>${config.home.path}/lib/X11/fonts</dir> <dir>${config.home.path}/lib/X11/fonts</dir>
<dir>${config.home.path}/share/fonts</dir> <dir>${config.home.path}/share/fonts</dir>
<dir>${profileDirectory}/lib/X11/fonts</dir> <dir>${profileDirectory}/lib/X11/fonts</dir>
<dir>${profileDirectory}/share/fonts</dir> <dir>${profileDirectory}/share/fonts</dir>
<cachedir>${config.home.path}/lib/fontconfig/cache</cachedir> <cachedir>${config.home.path}/lib/fontconfig/cache</cachedir>
''; '';
"fontconfig/conf.d/52-hm-default-fonts.conf".text = let "fontconfig/conf.d/52-hm-default-fonts.conf".text =
genDefault = fonts: name: let
lib.optionalString (fonts != [ ]) '' genDefault =
<alias binding="same"> fonts: name:
<family>${name}</family> lib.optionalString (fonts != [ ]) ''
<prefer> <alias binding="same">
${ <family>${name}</family>
lib.concatStringsSep "" (map (font: '' <prefer>
<family>${font}</family> ${lib.concatStringsSep "" (
'') fonts) map (font: ''
} <family>${font}</family>
</prefer> '') fonts
</alias> )}
</prefer>
</alias>
'';
in
mkFontconfigConf ''
<!-- Default fonts -->
${genDefault cfg.defaultFonts.sansSerif "sans-serif"}
${genDefault cfg.defaultFonts.serif "serif"}
${genDefault cfg.defaultFonts.monospace "monospace"}
${genDefault cfg.defaultFonts.emoji "emoji"}
''; '';
in mkFontconfigConf '' };
<!-- Default fonts -->
${genDefault cfg.defaultFonts.sansSerif "sans-serif"}
${genDefault cfg.defaultFonts.serif "serif"}
${genDefault cfg.defaultFonts.monospace "monospace"}
${genDefault cfg.defaultFonts.emoji "emoji"}
'';
};
}; };
} }

View file

@ -1,7 +1,12 @@
{ config, lib, ... }: { config, lib, ... }:
let let
inherit (lib) literalExpression mkOption optionalAttrs types; inherit (lib)
literalExpression
mkOption
optionalAttrs
types
;
cfg = config.gtk; cfg = config.gtk;
cfg2 = config.gtk.gtk2; cfg2 = config.gtk.gtk2;
@ -9,22 +14,26 @@ let
cfg4 = config.gtk.gtk4; cfg4 = config.gtk.gtk4;
toGtk3Ini = lib.generators.toINI { toGtk3Ini = lib.generators.toINI {
mkKeyValue = key: value: mkKeyValue =
key: value:
let let
value' = value' = if lib.isBool value then lib.boolToString value else toString value;
if lib.isBool value then lib.boolToString value else toString value; in
in "${lib.escape [ "=" ] key}=${value'}"; "${lib.escape [ "=" ] key}=${value'}";
}; };
formatGtk2Option = n: v: formatGtk2Option =
n: v:
let let
v' = if lib.isBool v then v' =
lib.boolToString lib.value if lib.isBool v then
else if lib.isString v then lib.boolToString lib.value
''"${v}"'' else if lib.isString v then
else ''"${v}"''
toString v; else
in "${lib.escape [ "=" ] n} = ${v'}"; toString v;
in
"${lib.escape [ "=" ] n} = ${v'}";
themeType = types.submodule { themeType = types.submodule {
options = { options = {
@ -100,7 +109,8 @@ let
}; };
}; };
in { in
{
meta.maintainers = [ lib.maintainers.rycee ]; meta.maintainers = [ lib.maintainers.rycee ];
imports = [ imports = [
@ -153,10 +163,8 @@ in {
configLocation = mkOption { configLocation = mkOption {
type = types.path; type = types.path;
default = "${config.home.homeDirectory}/.gtkrc-2.0"; default = "${config.home.homeDirectory}/.gtkrc-2.0";
defaultText = defaultText = literalExpression ''"''${config.home.homeDirectory}/.gtkrc-2.0"'';
literalExpression ''"''${config.home.homeDirectory}/.gtkrc-2.0"''; example = literalExpression ''"''${config.xdg.configHome}/gtk-2.0/gtkrc"'';
example =
literalExpression ''"''${config.xdg.configHome}/gtk-2.0/gtkrc"'';
description = '' description = ''
The location to put the GTK configuration file. The location to put the GTK configuration file.
''; '';
@ -172,7 +180,13 @@ in {
}; };
extraConfig = mkOption { extraConfig = mkOption {
type = with types; attrsOf (oneOf [ bool int str ]); type =
with types;
attrsOf (oneOf [
bool
int
str
]);
default = { }; default = { };
example = { example = {
gtk-cursor-blink = false; gtk-cursor-blink = false;
@ -220,75 +234,86 @@ in {
}; };
}; };
config = lib.mkIf cfg.enable (let config = lib.mkIf cfg.enable (
gtkIni = optionalAttrs (cfg.font != null) { let
gtk-font-name = gtkIni =
let fontSize = if cfg.font.size != null then cfg.font.size else 10; optionalAttrs (cfg.font != null) {
in "${cfg.font.name} ${toString fontSize}"; gtk-font-name =
} // optionalAttrs (cfg.theme != null) { gtk-theme-name = cfg.theme.name; } let
// optionalAttrs (cfg.iconTheme != null) { fontSize = if cfg.font.size != null then cfg.font.size else 10;
gtk-icon-theme-name = cfg.iconTheme.name; in
} // optionalAttrs (cfg.cursorTheme != null) { "${cfg.font.name} ${toString fontSize}";
gtk-cursor-theme-name = cfg.cursorTheme.name; }
} // optionalAttrs // optionalAttrs (cfg.theme != null) { gtk-theme-name = cfg.theme.name; }
(cfg.cursorTheme != null && cfg.cursorTheme.size != null) { // optionalAttrs (cfg.iconTheme != null) {
gtk-cursor-theme-size = cfg.cursorTheme.size; gtk-icon-theme-name = cfg.iconTheme.name;
}
// optionalAttrs (cfg.cursorTheme != null) {
gtk-cursor-theme-name = cfg.cursorTheme.name;
}
// optionalAttrs (cfg.cursorTheme != null && cfg.cursorTheme.size != null) {
gtk-cursor-theme-size = cfg.cursorTheme.size;
};
gtk4Css =
lib.optionalString (cfg.theme != null && cfg.theme.package != null) ''
/**
* GTK 4 reads the theme configured by gtk-theme-name, but ignores it.
* It does however respect user CSS, so import the theme from here.
**/
@import url("file://${cfg.theme.package}/share/themes/${cfg.theme.name}/gtk-4.0/gtk.css");
''
+ cfg4.extraCss;
dconfIni =
optionalAttrs (cfg.font != null) {
font-name =
let
fontSize = if cfg.font.size != null then cfg.font.size else 10;
in
"${cfg.font.name} ${toString fontSize}";
}
// optionalAttrs (cfg.theme != null) { gtk-theme = cfg.theme.name; }
// optionalAttrs (cfg.iconTheme != null) {
icon-theme = cfg.iconTheme.name;
}
// optionalAttrs (cfg.cursorTheme != null) {
cursor-theme = cfg.cursorTheme.name;
}
// optionalAttrs (cfg.cursorTheme != null && cfg.cursorTheme.size != null) {
cursor-size = cfg.cursorTheme.size;
};
optionalPackage = opt: lib.optional (opt != null && opt.package != null) opt.package;
in
{
home.packages = lib.concatMap optionalPackage [
cfg.font
cfg.theme
cfg.iconTheme
cfg.cursorTheme
];
home.file.${cfg2.configLocation}.text =
lib.concatMapStrings (l: l + "\n") (lib.mapAttrsToList formatGtk2Option gtkIni)
+ cfg2.extraConfig
+ "\n";
home.sessionVariables.GTK2_RC_FILES = cfg2.configLocation;
xdg.configFile."gtk-3.0/settings.ini".text = toGtk3Ini { Settings = gtkIni // cfg3.extraConfig; };
xdg.configFile."gtk-3.0/gtk.css" = lib.mkIf (cfg3.extraCss != "") { text = cfg3.extraCss; };
xdg.configFile."gtk-3.0/bookmarks" = lib.mkIf (cfg3.bookmarks != [ ]) {
text = lib.concatMapStrings (l: l + "\n") cfg3.bookmarks;
}; };
gtk4Css = xdg.configFile."gtk-4.0/settings.ini".text = toGtk3Ini { Settings = gtkIni // cfg4.extraConfig; };
lib.optionalString (cfg.theme != null && cfg.theme.package != null) ''
/**
* GTK 4 reads the theme configured by gtk-theme-name, but ignores it.
* It does however respect user CSS, so import the theme from here.
**/
@import url("file://${cfg.theme.package}/share/themes/${cfg.theme.name}/gtk-4.0/gtk.css");
'' + cfg4.extraCss;
dconfIni = optionalAttrs (cfg.font != null) { xdg.configFile."gtk-4.0/gtk.css" = lib.mkIf (gtk4Css != "") { text = gtk4Css; };
font-name =
let fontSize = if cfg.font.size != null then cfg.font.size else 10;
in "${cfg.font.name} ${toString fontSize}";
} // optionalAttrs (cfg.theme != null) { gtk-theme = cfg.theme.name; }
// optionalAttrs (cfg.iconTheme != null) {
icon-theme = cfg.iconTheme.name;
} // optionalAttrs (cfg.cursorTheme != null) {
cursor-theme = cfg.cursorTheme.name;
} // optionalAttrs
(cfg.cursorTheme != null && cfg.cursorTheme.size != null) {
cursor-size = cfg.cursorTheme.size;
};
optionalPackage = opt: dconf.settings."org/gnome/desktop/interface" = dconfIni;
lib.optional (opt != null && opt.package != null) opt.package; }
in { );
home.packages = lib.concatMap optionalPackage [
cfg.font
cfg.theme
cfg.iconTheme
cfg.cursorTheme
];
home.file.${cfg2.configLocation}.text = lib.concatMapStrings (l: l + "\n")
(lib.mapAttrsToList formatGtk2Option gtkIni) + cfg2.extraConfig + "\n";
home.sessionVariables.GTK2_RC_FILES = cfg2.configLocation;
xdg.configFile."gtk-3.0/settings.ini".text =
toGtk3Ini { Settings = gtkIni // cfg3.extraConfig; };
xdg.configFile."gtk-3.0/gtk.css" =
lib.mkIf (cfg3.extraCss != "") { text = cfg3.extraCss; };
xdg.configFile."gtk-3.0/bookmarks" = lib.mkIf (cfg3.bookmarks != [ ]) {
text = lib.concatMapStrings (l: l + "\n") cfg3.bookmarks;
};
xdg.configFile."gtk-4.0/settings.ini".text =
toGtk3Ini { Settings = gtkIni // cfg4.extraConfig; };
xdg.configFile."gtk-4.0/gtk.css" =
lib.mkIf (gtk4Css != "") { text = gtk4Css; };
dconf.settings."org/gnome/desktop/interface" = dconfIni;
});
} }

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (pkgs.stdenv) isDarwin; inherit (pkgs.stdenv) isDarwin;
@ -10,16 +15,16 @@ let
(pkgs.writeTextDir "lib/mozilla/native-messaging-hosts/.keep" "") (pkgs.writeTextDir "lib/mozilla/native-messaging-hosts/.keep" "")
]; ];
thunderbirdNativeMessagingHostsPath = if isDarwin then thunderbirdNativeMessagingHostsPath =
"Library/Mozilla/NativeMessagingHosts" if isDarwin then "Library/Mozilla/NativeMessagingHosts" else ".mozilla/native-messaging-hosts";
else
".mozilla/native-messaging-hosts";
firefoxNativeMessagingHostsPath = if isDarwin then firefoxNativeMessagingHostsPath =
"Library/Application Support/Mozilla/NativeMessagingHosts" if isDarwin then
else "Library/Application Support/Mozilla/NativeMessagingHosts"
".mozilla/native-messaging-hosts"; else
in { ".mozilla/native-messaging-hosts";
in
{
meta.maintainers = with lib.maintainers; [ meta.maintainers = with lib.maintainers; [
booxter booxter
rycee rycee
@ -46,47 +51,45 @@ in {
}; };
}; };
config = lib.mkIf (cfg.firefoxNativeMessagingHosts != [ ] config =
|| cfg.thunderbirdNativeMessagingHosts != [ ]) { lib.mkIf (cfg.firefoxNativeMessagingHosts != [ ] || cfg.thunderbirdNativeMessagingHosts != [ ])
home.file = if isDarwin then {
let home.file =
firefoxNativeMessagingHostsJoined = pkgs.symlinkJoin { if isDarwin then
name = "ff-native-messaging-hosts"; let
paths = defaultPaths ++ cfg.firefoxNativeMessagingHosts; firefoxNativeMessagingHostsJoined = pkgs.symlinkJoin {
}; name = "ff-native-messaging-hosts";
thunderbirdNativeMessagingHostsJoined = pkgs.symlinkJoin { paths = defaultPaths ++ cfg.firefoxNativeMessagingHosts;
name = "th-native-messaging-hosts"; };
paths = defaultPaths ++ cfg.thunderbirdNativeMessagingHosts; thunderbirdNativeMessagingHostsJoined = pkgs.symlinkJoin {
}; name = "th-native-messaging-hosts";
in { paths = defaultPaths ++ cfg.thunderbirdNativeMessagingHosts;
"${thunderbirdNativeMessagingHostsPath}" = };
lib.mkIf (cfg.thunderbirdNativeMessagingHosts != [ ]) { in
source = {
"${thunderbirdNativeMessagingHostsJoined}/lib/mozilla/native-messaging-hosts"; "${thunderbirdNativeMessagingHostsPath}" = lib.mkIf (cfg.thunderbirdNativeMessagingHosts != [ ]) {
recursive = true; source = "${thunderbirdNativeMessagingHostsJoined}/lib/mozilla/native-messaging-hosts";
}; recursive = true;
};
"${firefoxNativeMessagingHostsPath}" = "${firefoxNativeMessagingHostsPath}" = lib.mkIf (cfg.firefoxNativeMessagingHosts != [ ]) {
lib.mkIf (cfg.firefoxNativeMessagingHosts != [ ]) { source = "${firefoxNativeMessagingHostsJoined}/lib/mozilla/native-messaging-hosts";
source = recursive = true;
"${firefoxNativeMessagingHostsJoined}/lib/mozilla/native-messaging-hosts"; };
recursive = true; }
else
let
nativeMessagingHostsJoined = pkgs.symlinkJoin {
name = "mozilla-native-messaging-hosts";
# on Linux, the directory is shared between Firefox and Thunderbird; merge both into one
paths = defaultPaths ++ cfg.firefoxNativeMessagingHosts ++ cfg.thunderbirdNativeMessagingHosts;
};
in
{
"${firefoxNativeMessagingHostsPath}" = {
source = "${nativeMessagingHostsJoined}/lib/mozilla/native-messaging-hosts";
recursive = true;
};
}; };
} };
else
let
nativeMessagingHostsJoined = pkgs.symlinkJoin {
name = "mozilla-native-messaging-hosts";
# on Linux, the directory is shared between Firefox and Thunderbird; merge both into one
paths = defaultPaths ++ cfg.firefoxNativeMessagingHosts
++ cfg.thunderbirdNativeMessagingHosts;
};
in {
"${firefoxNativeMessagingHostsPath}" = {
source =
"${nativeMessagingHostsJoined}/lib/mozilla/native-messaging-hosts";
recursive = true;
};
};
};
} }

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (lib) mkOption types; inherit (lib) mkOption types;
@ -7,58 +12,64 @@ let
hostPlatform = pkgs.stdenv.hostPlatform; hostPlatform = pkgs.stdenv.hostPlatform;
entryModule = types.submodule ({ config, ... }: { entryModule = types.submodule (
options = { { config, ... }:
id = mkOption { {
internal = true; options = {
type = types.str; id = mkOption {
description = '' internal = true;
A unique entry identifier. By default it is a base16 type = types.str;
formatted hash of the entry message. description = ''
''; A unique entry identifier. By default it is a base16
formatted hash of the entry message.
'';
};
time = mkOption {
internal = true;
type = types.str;
example = "2017-07-10T21:55:04+00:00";
description = ''
News entry time stamp in ISO-8601 format. Must be in UTC
(ending in '+00:00').
'';
};
condition = mkOption {
internal = true;
default = true;
description = "Whether the news entry should be active.";
};
message = mkOption {
internal = true;
type = types.str;
description = "The news entry content.";
};
}; };
time = mkOption { config = {
internal = true; id = lib.mkDefault (builtins.hashString "sha256" config.message);
type = types.str;
example = "2017-07-10T21:55:04+00:00";
description = ''
News entry time stamp in ISO-8601 format. Must be in UTC
(ending in '+00:00').
'';
}; };
}
condition = mkOption { );
internal = true;
default = true;
description = "Whether the news entry should be active.";
};
message = mkOption {
internal = true;
type = types.str;
description = "The news entry content.";
};
};
config = {
id = lib.mkDefault (builtins.hashString "sha256" config.message);
};
});
isNixFile = n: v: v == "regular" && lib.hasSuffix ".nix" n; isNixFile = n: v: v == "regular" && lib.hasSuffix ".nix" n;
# builtins.attrNames return the values in alphabetical order # builtins.attrNames return the values in alphabetical order
newsFiles = newsFiles = builtins.attrNames (lib.filterAttrs isNixFile (builtins.readDir ./news));
builtins.attrNames (lib.filterAttrs isNixFile (builtins.readDir ./news)); newsEntries = builtins.map (newsFile: import (./news + "/${newsFile}")) newsFiles;
newsEntries = in
builtins.map (newsFile: import (./news + "/${newsFile}")) newsFiles; {
in {
meta.maintainers = [ lib.maintainers.rycee ]; meta.maintainers = [ lib.maintainers.rycee ];
options = { options = {
news = { news = {
display = mkOption { display = mkOption {
type = types.enum [ "silent" "notify" "show" ]; type = types.enum [
"silent"
"notify"
"show"
];
default = "notify"; default = "notify";
description = '' description = ''
How unread and relevant news should be presented when How unread and relevant news should be presented when
@ -100,8 +111,9 @@ in {
}; };
config = { config = {
news.json.output = pkgs.writeText "hm-news.json" news.json.output = pkgs.writeText "hm-news.json" (
(builtins.toJSON { inherit (cfg) display entries; }); builtins.toJSON { inherit (cfg) display entries; }
);
# DO NOT define new entries here, instead use the `./create-news-entry.sh` # DO NOT define new entries here, instead use the `./create-news-entry.sh`
# script and create an individual news file inside `news` sub-directory. # script and create an individual news file inside `news` sub-directory.
@ -250,8 +262,7 @@ in {
{ {
time = "2021-09-23T17:04:48+00:00"; time = "2021-09-23T17:04:48+00:00";
condition = hostPlatform.isLinux condition = hostPlatform.isLinux && config.services.screen-locker.enable;
&& config.services.screen-locker.enable;
message = '' message = ''
'xautolock' is now optional in 'services.screen-locker', and the 'xautolock' is now optional in 'services.screen-locker', and the
'services.screen-locker' options have been reorganized for clarity. 'services.screen-locker' options have been reorganized for clarity.
@ -1699,9 +1710,12 @@ in {
{ {
time = "2024-06-26T07:07:17+00:00"; time = "2024-06-26T07:07:17+00:00";
condition = with config.programs.yazi; condition =
enable && (enableBashIntegration || enableZshIntegration with config.programs.yazi;
|| enableFishIntegration || enableNushellIntegration); enable
&& (
enableBashIntegration || enableZshIntegration || enableFishIntegration || enableNushellIntegration
);
message = '' message = ''
Yazi's shell integration wrappers have been renamed from 'ya' to 'yy'. Yazi's shell integration wrappers have been renamed from 'ya' to 'yy'.
@ -1881,10 +1895,12 @@ in {
{ {
time = "2024-12-04T20:00:00+00:00"; time = "2024-12-04T20:00:00+00:00";
condition = let condition =
sCfg = config.programs.starship; let
fCfg = config.programs.fish; sCfg = config.programs.starship;
in sCfg.enable && sCfg.enableFishIntegration && fCfg.enable; fCfg = config.programs.fish;
in
sCfg.enable && sCfg.enableFishIntegration && fCfg.enable;
message = '' message = ''
A new option 'programs.starship.enableInteractive' is available for A new option 'programs.starship.enableInteractive' is available for
the Fish shell that only enables starship if the shell is interactive. the Fish shell that only enables starship if the shell is interactive.
@ -1894,10 +1910,11 @@ in {
} }
{ {
time = "2024-12-08T17:22:13+00:00"; time = "2024-12-08T17:22:13+00:00";
condition = let condition =
usingMbsync = lib.any (a: a.mbsync.enable) let
(lib.attrValues config.accounts.email.accounts); usingMbsync = lib.any (a: a.mbsync.enable) (lib.attrValues config.accounts.email.accounts);
in usingMbsync; in
usingMbsync;
message = '' message = ''
isync/mbsync 1.5.0 has changed several things. isync/mbsync 1.5.0 has changed several things.

View file

@ -1,12 +1,38 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (lib) inherit (lib)
boolToString concatStringsSep escape floatToString getVersion isBool boolToString
isConvertibleWithToString isDerivation isFloat isInt isList isString concatStringsSep
literalExpression maintainers mapAttrsToList mkDefault mkEnableOption mkIf escape
mkMerge mkOption optionalString toPretty types versionAtLeast; floatToString
getVersion
isBool
isConvertibleWithToString
isDerivation
isFloat
isInt
isList
isString
literalExpression
maintainers
mapAttrsToList
mkDefault
mkEnableOption
mkIf
mkMerge
mkOption
optionalString
toPretty
types
versionAtLeast
;
cfg = config.nix; cfg = config.nix;
@ -16,29 +42,33 @@ let
nixPath = concatStringsSep ":" cfg.nixPath; nixPath = concatStringsSep ":" cfg.nixPath;
useXdg = config.nix.enable useXdg = config.nix.enable && (config.nix.settings.use-xdg-base-directories or false);
&& (config.nix.settings.use-xdg-base-directories or false); defexprDir =
defexprDir = if useXdg then if useXdg then
"${config.xdg.stateHome}/nix/defexpr" "${config.xdg.stateHome}/nix/defexpr"
else else
"${config.home.homeDirectory}/.nix-defexpr"; "${config.home.homeDirectory}/.nix-defexpr";
# The deploy path for declarative channels. The directory name is prefixed # The deploy path for declarative channels. The directory name is prefixed
# with a number to make it easier for files in defexprDir to control the order # with a number to make it easier for files in defexprDir to control the order
# they'll be read relative to each other. # they'll be read relative to each other.
channelPath = "${defexprDir}/50-home-manager"; channelPath = "${defexprDir}/50-home-manager";
channelsDrv = let channelsDrv =
mkEntry = name: drv: { let
inherit name; mkEntry = name: drv: {
path = toString drv; inherit name;
}; path = toString drv;
in pkgs.linkFarm "channels" (lib.mapAttrsToList mkEntry cfg.channels); };
in
pkgs.linkFarm "channels" (lib.mapAttrsToList mkEntry cfg.channels);
nixConf = assert isNixAtLeast "2.2"; nixConf =
assert isNixAtLeast "2.2";
let let
mkValueString = v: mkValueString =
v:
if v == null then if v == null then
"" ""
else if isInt v then else if isInt v then
@ -62,10 +92,10 @@ let
mkKeyValue = k: v: "${escape [ "=" ] k} = ${mkValueString v}"; mkKeyValue = k: v: "${escape [ "=" ] k} = ${mkValueString v}";
mkKeyValuePairs = attrs: mkKeyValuePairs = attrs: concatStringsSep "\n" (mapAttrsToList mkKeyValue attrs);
concatStringsSep "\n" (mapAttrsToList mkKeyValue attrs);
in pkgs.writeTextFile { in
pkgs.writeTextFile {
name = "nix.conf"; name = "nix.conf";
text = '' text = ''
# WARNING: this file is generated from the nix.settings option in # WARNING: this file is generated from the nix.settings option in
@ -75,48 +105,58 @@ let
${cfg.extraOptions} ${cfg.extraOptions}
''; '';
checkPhase = checkPhase =
if pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform then '' if pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform then
echo "Ignoring validation for cross-compilation" ''
'' else echo "Ignoring validation for cross-compilation"
''
else
let let
showCommand = showCommand = if isNixAtLeast "2.20pre" then "config show" else "show-config";
if isNixAtLeast "2.20pre" then "config show" else "show-config"; in
in '' ''
echo "Validating generated nix.conf" echo "Validating generated nix.conf"
ln -s $out ./nix.conf ln -s $out ./nix.conf
set -e set -e
set +o pipefail set +o pipefail
NIX_CONF_DIR=$PWD \ NIX_CONF_DIR=$PWD \
${cfg.package}/bin/nix ${showCommand} ${ ${cfg.package}/bin/nix ${showCommand} ${optionalString (isNixAtLeast "2.3pre") "--no-net --option experimental-features nix-command"} \
optionalString (isNixAtLeast "2.3pre")
"--no-net --option experimental-features nix-command"
} \
|& sed -e 's/^warning:/error:/' \ |& sed -e 's/^warning:/error:/' \
| (! grep '${ | (! grep '${if cfg.checkConfig then "^error:" else "^error: unknown setting"}')
if cfg.checkConfig then "^error:" else "^error: unknown setting"
}')
set -o pipefail set -o pipefail
''; '';
}; };
semanticConfType = with types; semanticConfType =
with types;
let let
confAtom = nullOr (oneOf [ bool int float str path package ]) // { confAtom =
description = nullOr (oneOf [
"Nix config atom (null, bool, int, float, str, path or package)"; bool
}; int
in attrsOf (either confAtom (listOf confAtom)); float
str
path
package
])
// {
description = "Nix config atom (null, bool, int, float, str, path or package)";
};
in
attrsOf (either confAtom (listOf confAtom));
jsonFormat = pkgs.formats.json { }; jsonFormat = pkgs.formats.json { };
in { in
{
options.nix = { options.nix = {
enable = mkEnableOption '' enable =
the Nix configuration module mkEnableOption ''
'' // { the Nix configuration module
default = true; ''
visible = false; // {
}; default = true;
visible = false;
};
package = mkOption { package = mkOption {
type = types.nullOr types.package; type = types.nullOr types.package;
@ -169,60 +209,74 @@ in {
}; };
registry = mkOption { registry = mkOption {
type = types.attrsOf (types.submodule (let type = types.attrsOf (
inputAttrs = types.attrsOf types.submodule (
(types.oneOf [ types.str types.int types.bool types.package ]); let
in { config, name, ... }: { inputAttrs = types.attrsOf (
options = { types.oneOf [
from = mkOption { types.str
type = inputAttrs; types.int
example = { types.bool
type = "indirect"; types.package
id = "nixpkgs"; ]
);
in
{ config, name, ... }:
{
options = {
from = mkOption {
type = inputAttrs;
example = {
type = "indirect";
id = "nixpkgs";
};
description = "The flake reference to be rewritten.";
};
to = mkOption {
type = inputAttrs;
example = {
type = "github";
owner = "my-org";
repo = "my-nixpkgs";
};
description = "The flake reference to which {option}`from>` is to be rewritten.";
};
flake = mkOption {
type = types.nullOr types.attrs;
default = null;
example = literalExpression "nixpkgs";
description = ''
The flake input to which {option}`from>` is to be rewritten.
'';
};
exact = mkOption {
type = types.bool;
default = true;
description = ''
Whether the {option}`from` reference needs to match exactly. If set,
a {option}`from` reference like `nixpkgs` does not
match with a reference like `nixpkgs/nixos-20.03`.
'';
};
}; };
description = "The flake reference to be rewritten."; config = {
}; from = mkDefault {
to = mkOption { type = "indirect";
type = inputAttrs; id = name;
example = { };
type = "github"; to = mkIf (config.flake != null) (
owner = "my-org"; {
repo = "my-nixpkgs"; type = "path";
path = config.flake.outPath;
}
// lib.filterAttrs (
n: v: n == "lastModified" || n == "rev" || n == "revCount" || n == "narHash"
) config.flake
);
}; };
description = }
"The flake reference to which {option}`from>` is to be rewritten."; )
}; );
flake = mkOption {
type = types.nullOr types.attrs;
default = null;
example = literalExpression "nixpkgs";
description = ''
The flake input to which {option}`from>` is to be rewritten.
'';
};
exact = mkOption {
type = types.bool;
default = true;
description = ''
Whether the {option}`from` reference needs to match exactly. If set,
a {option}`from` reference like `nixpkgs` does not
match with a reference like `nixpkgs/nixos-20.03`.
'';
};
};
config = {
from = mkDefault {
type = "indirect";
id = name;
};
to = mkIf (config.flake != null) ({
type = "path";
path = config.flake.outPath;
} // lib.filterAttrs (n: v:
n == "lastModified" || n == "rev" || n == "revCount" || n
== "narHash") config.flake);
};
}));
default = { }; default = { };
description = '' description = ''
User level flake registry. User level flake registry.
@ -290,22 +344,22 @@ in {
}) })
(mkIf (cfg.registry != { }) { (mkIf (cfg.registry != { }) {
xdg.configFile."nix/registry.json".source = xdg.configFile."nix/registry.json".source = jsonFormat.generate "registry.json" {
jsonFormat.generate "registry.json" { version = cfg.registryVersion;
version = cfg.registryVersion; flakes = mapAttrsToList (n: v: { inherit (v) from to exact; }) cfg.registry;
flakes = };
mapAttrsToList (n: v: { inherit (v) from to exact; }) cfg.registry;
};
}) })
(mkIf (cfg.settings != { } || cfg.extraOptions != "") { (mkIf (cfg.settings != { } || cfg.extraOptions != "") {
assertions = [{ assertions = [
assertion = cfg.package != null; {
message = '' assertion = cfg.package != null;
A corresponding Nix package must be specified via `nix.package` for generating message = ''
nix.conf. A corresponding Nix package must be specified via `nix.package` for generating
''; nix.conf.
}]; '';
}
];
xdg.configFile."nix/nix.conf".source = nixConf; xdg.configFile."nix/nix.conf".source = nixConf;
}) })

View file

@ -1,13 +1,23 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.nixGL; cfg = config.nixGL;
wrapperListMarkdown = with builtins; wrapperListMarkdown =
foldl' (list: name: with builtins;
list + '' foldl' (
list: name:
list
+ ''
- ${name} - ${name}
'') "" (attrNames config.lib.nixGL.wrappers); ''
in { ) "" (attrNames config.lib.nixGL.wrappers);
in
{
meta.maintainers = [ lib.maintainers.smona ]; meta.maintainers = [ lib.maintainers.smona ];
options.nixGL = { options.nixGL = {
@ -93,7 +103,12 @@ in {
}; };
prime.installScript = lib.mkOption { prime.installScript = lib.mkOption {
type = with lib.types; nullOr (enum [ "mesa" "nvidia" ]); type =
with lib.types;
nullOr (enum [
"mesa"
"nvidia"
]);
default = null; default = null;
example = "mesa"; example = "mesa";
description = '' description = ''
@ -109,10 +124,12 @@ in {
}; };
installScripts = lib.mkOption { installScripts = lib.mkOption {
type = with lib.types; type = with lib.types; nullOr (listOf (enum (builtins.attrNames config.lib.nixGL.wrappers)));
nullOr (listOf (enum (builtins.attrNames config.lib.nixGL.wrappers)));
default = null; default = null;
example = [ "mesa" "mesaPrime" ]; example = [
"mesa"
"mesaPrime"
];
description = '' description = ''
For each wrapper `wrp` named in the provided list, a wrapper script For each wrapper `wrp` named in the provided list, a wrapper script
named `nixGLWrp` is installed into the environment. These scripts are named `nixGLWrp` is installed into the environment. These scripts are
@ -137,168 +154,199 @@ in {
}; };
}; };
config = let config =
findWrapperPackage = packageAttr: let
# NixGL has wrapper packages in different places depending on how you findWrapperPackage =
# access it. We want HM configuration to be the same, regardless of how packageAttr:
# NixGL is imported. # NixGL has wrapper packages in different places depending on how you
# # access it. We want HM configuration to be the same, regardless of how
# First, let's see if we have a flake. # NixGL is imported.
if builtins.hasAttr pkgs.system cfg.packages then #
cfg.packages.${pkgs.system}.${packageAttr} # First, let's see if we have a flake.
else if builtins.hasAttr pkgs.system cfg.packages then
# Next, let's see if we have a channel. cfg.packages.${pkgs.system}.${packageAttr}
if builtins.hasAttr packageAttr cfg.packages then
cfg.packages.${packageAttr}
else
# Lastly, with channels, some wrappers are grouped under "auto".
if builtins.hasAttr "auto" cfg.packages then
cfg.packages.auto.${packageAttr}
else
throw "Incompatible NixGL package layout";
getWrapperExe = vendor:
let
glPackage = findWrapperPackage "nixGL${vendor}";
glExe = lib.getExe glPackage;
vulkanPackage = findWrapperPackage "nixVulkan${vendor}";
vulkanExe = if cfg.vulkan.enable then lib.getExe vulkanPackage else "";
in "${glExe} ${vulkanExe}";
mesaOffloadEnv = { "DRI_PRIME" = "${cfg.prime.card}"; };
nvOffloadEnv = {
"DRI_PRIME" = "${cfg.prime.card}";
"__NV_PRIME_RENDER_OFFLOAD" = "1";
"__GLX_VENDOR_LIBRARY_NAME" = "nvidia";
"__VK_LAYER_NV_optimus" = "NVIDIA_only";
} // (let provider = cfg.prime.nvidiaProvider;
in if !isNull provider then {
"__NV_PRIME_RENDER_OFFLOAD_PROVIDER" = "${provider}";
} else
{ });
makePackageWrapper = vendor: environment: pkg:
if builtins.isNull cfg.packages then
pkg
else
# Wrap the package's binaries with nixGL, while preserving the rest of
# the outputs and derivation attributes.
(pkg.overrideAttrs (old: {
name = "nixGL-${pkg.name}";
# Make sure this is false for the wrapper derivation, so nix doesn't expect
# a new debug output to be produced. We won't be producing any debug info
# for the original package.
separateDebugInfo = false;
nativeBuildInputs = old.nativeBuildInputs or [ ]
++ [ pkgs.makeWrapper ];
buildCommand = let
# We need an intermediate wrapper package because makeWrapper
# requires a single executable as the wrapper.
combinedWrapperPkg =
pkgs.writeShellScriptBin "nixGLCombinedWrapper-${vendor}" ''
exec ${getWrapperExe vendor} "$@"
'';
in ''
set -eo pipefail
${ # Heavily inspired by https://stackoverflow.com/a/68523368/6259505
lib.concatStringsSep "\n" (map (outputName: ''
echo "Copying output ${outputName}"
set -x
cp -rs --no-preserve=mode "${
pkg.${outputName}
}" "''$${outputName}"
set +x
'') (old.outputs or [ "out" ]))}
rm -rf $out/bin/*
shopt -s nullglob # Prevent loop from running if no files
for file in ${pkg.out}/bin/*; do
local prog="$(basename "$file")"
makeWrapper \
"${lib.getExe combinedWrapperPkg}" \
"$out/bin/$prog" \
--argv0 "$prog" \
--add-flags "$file" \
${
lib.concatStringsSep " " (lib.attrsets.mapAttrsToList
(var: val: "--set '${var}' '${val}'") environment)
}
done
# If .desktop files refer to the old package, replace the references
for dsk in "$out/share/applications"/*.desktop ; do
if ! grep -q "${pkg.out}" "$dsk"; then
continue
fi
src="$(readlink "$dsk")"
rm "$dsk"
sed "s|${pkg.out}|$out|g" "$src" > "$dsk"
done
shopt -u nullglob # Revert nullglob back to its normal default state
'';
})) // {
# When the nixGL-wrapped package is given to a HM module, the module
# might want to override the package arguments, but our wrapper
# wouldn't know what to do with them. So, we rewrite the override
# function to instead forward the arguments to the package's own
# override function.
override = args:
makePackageWrapper vendor environment (pkg.override args);
};
wrappers = {
mesa = makePackageWrapper "Intel" { };
mesaPrime = makePackageWrapper "Intel" mesaOffloadEnv;
nvidia = makePackageWrapper "Nvidia" { };
nvidiaPrime = makePackageWrapper "Nvidia" nvOffloadEnv;
};
in {
lib.nixGL.wrap = wrappers.${cfg.defaultWrapper};
lib.nixGL.wrapOffload = wrappers.${cfg.offloadWrapper};
lib.nixGL.wrappers = wrappers;
home.packages = let
wantsPrimeWrapper = (!isNull cfg.prime.installScript);
wantsWrapper = wrapper:
(!isNull cfg.packages) && (!isNull cfg.installScripts)
&& (builtins.elem wrapper cfg.installScripts);
envVarsAsScript = environment:
lib.concatStringsSep "\n"
(lib.attrsets.mapAttrsToList (var: val: "export ${var}=${val}")
environment);
in [
(lib.mkIf wantsPrimeWrapper (pkgs.writeShellScriptBin "prime-offload" ''
${if cfg.prime.installScript == "mesa" then
(envVarsAsScript mesaOffloadEnv)
else else
(envVarsAsScript nvOffloadEnv)} # Next, let's see if we have a channel.
exec "$@" if builtins.hasAttr packageAttr cfg.packages then
'')) cfg.packages.${packageAttr}
else
# Lastly, with channels, some wrappers are grouped under "auto".
if builtins.hasAttr "auto" cfg.packages then
cfg.packages.auto.${packageAttr}
else
throw "Incompatible NixGL package layout";
(lib.mkIf (wantsWrapper "mesa") (pkgs.writeShellScriptBin "nixGLMesa" '' getWrapperExe =
exec ${getWrapperExe "Intel"} "$@" vendor:
'')) let
glPackage = findWrapperPackage "nixGL${vendor}";
glExe = lib.getExe glPackage;
vulkanPackage = findWrapperPackage "nixVulkan${vendor}";
vulkanExe = if cfg.vulkan.enable then lib.getExe vulkanPackage else "";
in
"${glExe} ${vulkanExe}";
(lib.mkIf (wantsWrapper "mesaPrime") mesaOffloadEnv = {
(pkgs.writeShellScriptBin "nixGLMesaPrime" '' "DRI_PRIME" = "${cfg.prime.card}";
${envVarsAsScript mesaOffloadEnv} };
exec ${getWrapperExe "Intel"} "$@"
''))
(lib.mkIf (wantsWrapper "nvidia") nvOffloadEnv =
(pkgs.writeShellScriptBin "nixGLNvidia" '' {
exec ${getWrapperExe "Nvidia"} "$@" "DRI_PRIME" = "${cfg.prime.card}";
'')) "__NV_PRIME_RENDER_OFFLOAD" = "1";
"__GLX_VENDOR_LIBRARY_NAME" = "nvidia";
"__VK_LAYER_NV_optimus" = "NVIDIA_only";
}
// (
let
provider = cfg.prime.nvidiaProvider;
in
if !isNull provider then
{
"__NV_PRIME_RENDER_OFFLOAD_PROVIDER" = "${provider}";
}
else
{ }
);
(lib.mkIf (wantsWrapper "nvidia") makePackageWrapper =
(pkgs.writeShellScriptBin "nixGLNvidiaPrime" '' vendor: environment: pkg:
${envVarsAsScript nvOffloadEnv} if builtins.isNull cfg.packages then
exec ${getWrapperExe "Nvidia"} "$@" pkg
'')) else
]; # Wrap the package's binaries with nixGL, while preserving the rest of
}; # the outputs and derivation attributes.
(pkg.overrideAttrs (old: {
name = "nixGL-${pkg.name}";
# Make sure this is false for the wrapper derivation, so nix doesn't expect
# a new debug output to be produced. We won't be producing any debug info
# for the original package.
separateDebugInfo = false;
nativeBuildInputs = old.nativeBuildInputs or [ ] ++ [ pkgs.makeWrapper ];
buildCommand =
let
# We need an intermediate wrapper package because makeWrapper
# requires a single executable as the wrapper.
combinedWrapperPkg = pkgs.writeShellScriptBin "nixGLCombinedWrapper-${vendor}" ''
exec ${getWrapperExe vendor} "$@"
'';
in
''
set -eo pipefail
${
# Heavily inspired by https://stackoverflow.com/a/68523368/6259505
lib.concatStringsSep "\n" (
map (outputName: ''
echo "Copying output ${outputName}"
set -x
cp -rs --no-preserve=mode "${pkg.${outputName}}" "''$${outputName}"
set +x
'') (old.outputs or [ "out" ])
)
}
rm -rf $out/bin/*
shopt -s nullglob # Prevent loop from running if no files
for file in ${pkg.out}/bin/*; do
local prog="$(basename "$file")"
makeWrapper \
"${lib.getExe combinedWrapperPkg}" \
"$out/bin/$prog" \
--argv0 "$prog" \
--add-flags "$file" \
${lib.concatStringsSep " " (
lib.attrsets.mapAttrsToList (var: val: "--set '${var}' '${val}'") environment
)}
done
# If .desktop files refer to the old package, replace the references
for dsk in "$out/share/applications"/*.desktop ; do
if ! grep -q "${pkg.out}" "$dsk"; then
continue
fi
src="$(readlink "$dsk")"
rm "$dsk"
sed "s|${pkg.out}|$out|g" "$src" > "$dsk"
done
shopt -u nullglob # Revert nullglob back to its normal default state
'';
}))
// {
# When the nixGL-wrapped package is given to a HM module, the module
# might want to override the package arguments, but our wrapper
# wouldn't know what to do with them. So, we rewrite the override
# function to instead forward the arguments to the package's own
# override function.
override = args: makePackageWrapper vendor environment (pkg.override args);
};
wrappers = {
mesa = makePackageWrapper "Intel" { };
mesaPrime = makePackageWrapper "Intel" mesaOffloadEnv;
nvidia = makePackageWrapper "Nvidia" { };
nvidiaPrime = makePackageWrapper "Nvidia" nvOffloadEnv;
};
in
{
lib.nixGL.wrap = wrappers.${cfg.defaultWrapper};
lib.nixGL.wrapOffload = wrappers.${cfg.offloadWrapper};
lib.nixGL.wrappers = wrappers;
home.packages =
let
wantsPrimeWrapper = (!isNull cfg.prime.installScript);
wantsWrapper =
wrapper:
(!isNull cfg.packages)
&& (!isNull cfg.installScripts)
&& (builtins.elem wrapper cfg.installScripts);
envVarsAsScript =
environment:
lib.concatStringsSep "\n" (
lib.attrsets.mapAttrsToList (var: val: "export ${var}=${val}") environment
);
in
[
(lib.mkIf wantsPrimeWrapper (
pkgs.writeShellScriptBin "prime-offload" ''
${
if cfg.prime.installScript == "mesa" then
(envVarsAsScript mesaOffloadEnv)
else
(envVarsAsScript nvOffloadEnv)
}
exec "$@"
''
))
(lib.mkIf (wantsWrapper "mesa") (
pkgs.writeShellScriptBin "nixGLMesa" ''
exec ${getWrapperExe "Intel"} "$@"
''
))
(lib.mkIf (wantsWrapper "mesaPrime") (
pkgs.writeShellScriptBin "nixGLMesaPrime" ''
${envVarsAsScript mesaOffloadEnv}
exec ${getWrapperExe "Intel"} "$@"
''
))
(lib.mkIf (wantsWrapper "nvidia") (
pkgs.writeShellScriptBin "nixGLNvidia" ''
exec ${getWrapperExe "Nvidia"} "$@"
''
))
(lib.mkIf (wantsWrapper "nvidia") (
pkgs.writeShellScriptBin "nixGLNvidiaPrime" ''
${envVarsAsScript nvOffloadEnv}
exec ${getWrapperExe "Nvidia"} "$@"
''
))
];
};
} }

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
@ -11,16 +16,22 @@ let
optCall = f: x: if builtins.isFunction f then f x else f; optCall = f: x: if builtins.isFunction f then f x else f;
# Copied from nixpkgs.nix. # Copied from nixpkgs.nix.
mergeConfig = lhs_: rhs_: mergeConfig =
lhs_: rhs_:
let let
lhs = optCall lhs_ { inherit pkgs; }; lhs = optCall lhs_ { inherit pkgs; };
rhs = optCall rhs_ { inherit pkgs; }; rhs = optCall rhs_ { inherit pkgs; };
in lhs // rhs // lib.optionalAttrs (lhs ? packageOverrides) { in
packageOverrides = pkgs: lhs
optCall lhs.packageOverrides pkgs // rhs
// optCall (lib.attrByPath [ "packageOverrides" ] { } rhs) pkgs; // lib.optionalAttrs (lhs ? packageOverrides) {
} // lib.optionalAttrs (lhs ? perlPackageOverrides) { packageOverrides =
perlPackageOverrides = pkgs: pkgs:
optCall lhs.packageOverrides pkgs // optCall (lib.attrByPath [ "packageOverrides" ] { } rhs) pkgs;
}
// lib.optionalAttrs (lhs ? perlPackageOverrides) {
perlPackageOverrides =
pkgs:
optCall lhs.perlPackageOverrides pkgs optCall lhs.perlPackageOverrides pkgs
// optCall (lib.attrByPath [ "perlPackageOverrides" ] { } rhs) pkgs; // optCall (lib.attrByPath [ "perlPackageOverrides" ] { } rhs) pkgs;
}; };
@ -29,9 +40,12 @@ let
configType = lib.mkOptionType { configType = lib.mkOptionType {
name = "nixpkgs-config"; name = "nixpkgs-config";
description = "nixpkgs config"; description = "nixpkgs config";
check = x: check =
let traceXIfNot = c: if c x then true else lib.traceSeqN 1 x false; x:
in traceXIfNot isConfig; let
traceXIfNot = c: if c x then true else lib.traceSeqN 1 x false;
in
traceXIfNot isConfig;
merge = args: lib.fold (def: mergeConfig def.value) { }; merge = args: lib.fold (def: mergeConfig def.value) { };
}; };
@ -43,7 +57,8 @@ let
merge = lib.mergeOneOption; merge = lib.mergeOneOption;
}; };
in { in
{
meta.maintainers = with lib.maintainers; [ thiagokokada ]; meta.maintainers = with lib.maintainers; [ thiagokokada ];
options.nixpkgs = { options.nixpkgs = {

View file

@ -1,6 +1,12 @@
# Adapted from Nixpkgs. # Adapted from Nixpkgs.
{ config, lib, pkgs, pkgsPath, ... }: {
config,
lib,
pkgs,
pkgsPath,
...
}:
let let
@ -8,16 +14,22 @@ let
optCall = f: x: if builtins.isFunction f then f x else f; optCall = f: x: if builtins.isFunction f then f x else f;
mergeConfig = lhs_: rhs_: mergeConfig =
lhs_: rhs_:
let let
lhs = optCall lhs_ { inherit pkgs; }; lhs = optCall lhs_ { inherit pkgs; };
rhs = optCall rhs_ { inherit pkgs; }; rhs = optCall rhs_ { inherit pkgs; };
in lhs // rhs // lib.optionalAttrs (lhs ? packageOverrides) { in
packageOverrides = pkgs: lhs
optCall lhs.packageOverrides pkgs // rhs
// optCall (lib.attrByPath [ "packageOverrides" ] { } rhs) pkgs; // lib.optionalAttrs (lhs ? packageOverrides) {
} // lib.optionalAttrs (lhs ? perlPackageOverrides) { packageOverrides =
perlPackageOverrides = pkgs: pkgs:
optCall lhs.packageOverrides pkgs // optCall (lib.attrByPath [ "packageOverrides" ] { } rhs) pkgs;
}
// lib.optionalAttrs (lhs ? perlPackageOverrides) {
perlPackageOverrides =
pkgs:
optCall lhs.perlPackageOverrides pkgs optCall lhs.perlPackageOverrides pkgs
// optCall (lib.attrByPath [ "perlPackageOverrides" ] { } rhs) pkgs; // optCall (lib.attrByPath [ "perlPackageOverrides" ] { } rhs) pkgs;
}; };
@ -25,9 +37,12 @@ let
configType = lib.mkOptionType { configType = lib.mkOptionType {
name = "nixpkgs-config"; name = "nixpkgs-config";
description = "nixpkgs config"; description = "nixpkgs config";
check = x: check =
let traceXIfNot = c: if c x then true else lib.traceSeqN 1 x false; x:
in traceXIfNot isConfig; let
traceXIfNot = c: if c x then true else lib.traceSeqN 1 x false;
in
traceXIfNot isConfig;
merge = args: lib.fold (def: mergeConfig def.value) { }; merge = args: lib.fold (def: mergeConfig def.value) { };
}; };
@ -40,11 +55,14 @@ let
_pkgs = import pkgsPath (lib.filterAttrs (n: v: v != null) config.nixpkgs); _pkgs = import pkgsPath (lib.filterAttrs (n: v: v != null) config.nixpkgs);
in { in
{
options.nixpkgs = { options.nixpkgs = {
config = lib.mkOption { config = lib.mkOption {
default = null; default = null;
example = { allowBroken = true; }; example = {
allowBroken = true;
};
type = lib.types.nullOr configType; type = lib.types.nullOr configType;
description = '' description = ''
The configuration of the Nix Packages collection. (For The configuration of the Nix Packages collection. (For
@ -123,10 +141,7 @@ in {
# `_pkgs`, see https://github.com/nix-community/home-manager/pull/993 # `_pkgs`, see https://github.com/nix-community/home-manager/pull/993
pkgs = lib.mkOverride lib.modules.defaultOverridePriority _pkgs; pkgs = lib.mkOverride lib.modules.defaultOverridePriority _pkgs;
pkgs_i686 = pkgs_i686 =
if _pkgs.stdenv.isLinux && _pkgs.stdenv.hostPlatform.isx86 then if _pkgs.stdenv.isLinux && _pkgs.stdenv.hostPlatform.isx86 then _pkgs.pkgsi686Linux else { };
_pkgs.pkgsi686Linux
else
{ };
}; };
}; };
} }

View file

@ -1,18 +1,25 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.xsession.numlock; cfg = config.xsession.numlock;
in { in
{
meta.maintainers = [ lib.maintainers.evanjs ]; meta.maintainers = [ lib.maintainers.evanjs ];
options = { xsession.numlock.enable = lib.mkEnableOption "Num Lock"; }; options = {
xsession.numlock.enable = lib.mkEnableOption "Num Lock";
};
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
assertions = [ assertions = [
(lib.hm.assertions.assertPlatform "xsession.numlock" pkgs (lib.hm.assertions.assertPlatform "xsession.numlock" pkgs lib.platforms.linux)
lib.platforms.linux)
]; ];
systemd.user.services.numlockx = { systemd.user.services.numlockx = {
@ -28,7 +35,9 @@ in {
ExecStart = "${pkgs.numlockx}/bin/numlockx"; ExecStart = "${pkgs.numlockx}/bin/numlockx";
}; };
Install = { WantedBy = [ "graphical-session.target" ]; }; Install = {
WantedBy = [ "graphical-session.target" ];
};
}; };
}; };
} }

View file

@ -4,14 +4,20 @@ let
cfg = config.pam; cfg = config.pam;
in { in
meta.maintainers = with lib.maintainers; [ rycee veehaitch ]; {
meta.maintainers = with lib.maintainers; [
rycee
veehaitch
];
options = { options = {
pam.sessionVariables = lib.mkOption { pam.sessionVariables = lib.mkOption {
default = { }; default = { };
type = lib.types.attrs; type = lib.types.attrs;
example = { EDITOR = "vim"; }; example = {
EDITOR = "vim";
};
description = '' description = ''
Environment variables that will be set for the PAM session. Environment variables that will be set for the PAM session.
The variable values must be as described in The variable values must be as described in
@ -24,13 +30,15 @@ in {
pam.yubico.authorizedYubiKeys = { pam.yubico.authorizedYubiKeys = {
ids = lib.mkOption { ids = lib.mkOption {
type = with lib.types; type =
with lib.types;
let let
yubiKeyId = addCheck str (s: lib.stringLength s == 12) // { yubiKeyId = addCheck str (s: lib.stringLength s == 12) // {
name = "yubiKeyId"; name = "yubiKeyId";
description = "string of length 12"; description = "string of length 12";
}; };
in listOf yubiKeyId; in
listOf yubiKeyId;
default = [ ]; default = [ ];
description = '' description = ''
List of authorized YubiKey token IDs. Refer to List of authorized YubiKey token IDs. Refer to
@ -52,15 +60,17 @@ in {
config = lib.mkMerge [ config = lib.mkMerge [
(lib.mkIf (cfg.sessionVariables != { }) { (lib.mkIf (cfg.sessionVariables != { }) {
home.file.".pam_environment".text = lib.concatStringsSep "\n" home.file.".pam_environment".text =
(lib.mapAttrsToList (n: v: ''${n} OVERRIDE="${toString v}"'') lib.concatStringsSep "\n" (
cfg.sessionVariables) + "\n"; lib.mapAttrsToList (n: v: ''${n} OVERRIDE="${toString v}"'') cfg.sessionVariables
)
+ "\n";
}) })
(lib.mkIf (cfg.yubico.authorizedYubiKeys.ids != [ ]) { (lib.mkIf (cfg.yubico.authorizedYubiKeys.ids != [ ]) {
home.file.${cfg.yubico.authorizedYubiKeys.path}.text = home.file.${cfg.yubico.authorizedYubiKeys.path}.text = lib.concatStringsSep ":" (
lib.concatStringsSep ":" [ config.home.username ] ++ cfg.yubico.authorizedYubiKeys.ids
([ config.home.username ] ++ cfg.yubico.authorizedYubiKeys.ids); );
}) })
]; ];
} }

View file

@ -1,13 +1,27 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.qt; cfg = config.qt;
# Map platform names to their packages. # Map platform names to their packages.
platformPackages = with pkgs; { platformPackages = with pkgs; {
gnome = [ qgnomeplatform qgnomeplatform-qt6 ]; gnome = [
adwaita = [ qadwaitadecorations qadwaitadecorations-qt6 ]; qgnomeplatform
gtk = [ libsForQt5.qtstyleplugins qt6Packages.qt6gtk2 ]; qgnomeplatform-qt6
];
adwaita = [
qadwaitadecorations
qadwaitadecorations-qt6
];
gtk = [
libsForQt5.qtstyleplugins
qt6Packages.qt6gtk2
];
kde = [ kde = [
libsForQt5.kio libsForQt5.kio
libsForQt5.plasma-integration libsForQt5.plasma-integration
@ -18,8 +32,14 @@ let
kdePackages.plasma-integration kdePackages.plasma-integration
kdePackages.systemsettings kdePackages.systemsettings
]; ];
lxqt = [ lxqt.lxqt-qtplugin lxqt.lxqt-config ]; lxqt = [
qtct = [ libsForQt5.qt5ct qt6Packages.qt6ct ]; lxqt.lxqt-qtplugin
lxqt.lxqt-config
];
qtct = [
libsForQt5.qt5ct
qt6Packages.qt6ct
];
}; };
# Maps style names to their QT_QPA_PLATFORMTHEME, if necessary. # Maps style names to their QT_QPA_PLATFORMTHEME, if necessary.
@ -34,122 +54,181 @@ let
bb10bright = libsForQt5.qtstyleplugins; bb10bright = libsForQt5.qtstyleplugins;
bb10dark = libsForQt5.qtstyleplugins; bb10dark = libsForQt5.qtstyleplugins;
cleanlooks = libsForQt5.qtstyleplugins; cleanlooks = libsForQt5.qtstyleplugins;
gtk2 = [ libsForQt5.qtstyleplugins qt6Packages.qt6gtk2 ]; gtk2 = [
libsForQt5.qtstyleplugins
qt6Packages.qt6gtk2
];
motif = libsForQt5.qtstyleplugins; motif = libsForQt5.qtstyleplugins;
cde = libsForQt5.qtstyleplugins; cde = libsForQt5.qtstyleplugins;
plastique = libsForQt5.qtstyleplugins; plastique = libsForQt5.qtstyleplugins;
adwaita = [ adwaita-qt adwaita-qt6 ]; adwaita = [
adwaita-dark = [ adwaita-qt adwaita-qt6 ]; adwaita-qt
adwaita-highcontrast = [ adwaita-qt adwaita-qt6 ]; adwaita-qt6
adwaita-highcontrastinverse = [ adwaita-qt adwaita-qt6 ]; ];
adwaita-dark = [
adwaita-qt
adwaita-qt6
];
adwaita-highcontrast = [
adwaita-qt
adwaita-qt6
];
adwaita-highcontrastinverse = [
adwaita-qt
adwaita-qt6
];
breeze = libsForQt5.breeze-qt5; breeze = libsForQt5.breeze-qt5;
kvantum = kvantum = [
[ libsForQt5.qtstyleplugin-kvantum qt6Packages.qtstyleplugin-kvantum ]; libsForQt5.qtstyleplugin-kvantum
qt6Packages.qtstyleplugin-kvantum
];
}; };
in { in
meta.maintainers = with lib.maintainers; [ rycee thiagokokada ]; {
meta.maintainers = with lib.maintainers; [
rycee
thiagokokada
];
imports = [ imports = [
(lib.mkChangedOptionModule [ "qt" "useGtkTheme" ] [ "qt" "platformTheme" ] (lib.mkChangedOptionModule [ "qt" "useGtkTheme" ] [ "qt" "platformTheme" ] (
(config: config: if lib.getAttrFromPath [ "qt" "useGtkTheme" ] config then "gtk" else null
if lib.getAttrFromPath [ "qt" "useGtkTheme" ] config then ))
"gtk"
else
null))
]; ];
options = { options = {
qt = { qt = {
enable = lib.mkEnableOption "Qt 5 and 6 configuration"; enable = lib.mkEnableOption "Qt 5 and 6 configuration";
platformTheme = let platformTheme =
newOption = { let
name = lib.mkOption { newOption = {
type = with lib.types; nullOr str; name = lib.mkOption {
default = null; type = with lib.types; nullOr str;
example = "adwaita"; default = null;
relatedPackages = [ example = "adwaita";
"qgnomeplatform" relatedPackages = [
"qgnomeplatform-qt6" "qgnomeplatform"
"qadwaitadecorations" "qgnomeplatform-qt6"
"qadwaitadecorations-qt6" "qadwaitadecorations"
[ "libsForQt5" "plasma-integration" ] "qadwaitadecorations-qt6"
[ "libsForQt5" "qt5ct" ] [
[ "libsForQt5" "qtstyleplugins" ] "libsForQt5"
[ "libsForQt5" "systemsettings" ] "plasma-integration"
[ "kdePackages" "plasma-integration" ] ]
[ "kdePackages" "systemsettings" ] [
[ "lxqt" "lxqt-config" ] "libsForQt5"
[ "lxqt" "lxqt-qtplugin" ] "qt5ct"
[ "qt6Packages" "qt6ct" ] ]
[ "qt6Packages" "qt6gtk2" ] [
]; "libsForQt5"
description = '' "qtstyleplugins"
Platform theme to use for Qt applications. ]
[
"libsForQt5"
"systemsettings"
]
[
"kdePackages"
"plasma-integration"
]
[
"kdePackages"
"systemsettings"
]
[
"lxqt"
"lxqt-config"
]
[
"lxqt"
"lxqt-qtplugin"
]
[
"qt6Packages"
"qt6ct"
]
[
"qt6Packages"
"qt6gtk2"
]
];
description = ''
Platform theme to use for Qt applications.
Some examples are Some examples are
`gtk` `gtk`
: Use GTK theme with : Use GTK theme with
[`qtstyleplugins`](https://github.com/qt/qtstyleplugins) [`qtstyleplugins`](https://github.com/qt/qtstyleplugins)
`gtk3` `gtk3`
: Use [GTK3 integration](https://github.com/qt/qtbase/tree/dev/src/plugins/platformthemes/gtk3) : Use [GTK3 integration](https://github.com/qt/qtbase/tree/dev/src/plugins/platformthemes/gtk3)
for file picker dialogs, font and theme configuration for file picker dialogs, font and theme configuration
`adwaita` `adwaita`
: Use Adwaita theme with : Use Adwaita theme with
[`qadwaitadecorations`](https://github.com/FedoraQt/QAdwaitaDecorations) [`qadwaitadecorations`](https://github.com/FedoraQt/QAdwaitaDecorations)
`gnome` (deprecated) `gnome` (deprecated)
: Use GNOME theme with : Use GNOME theme with
[`qgnomeplatform`](https://github.com/FedoraQt/QGnomePlatform). [`qgnomeplatform`](https://github.com/FedoraQt/QGnomePlatform).
Is no longer maintained so prefer `adwaita`. Is no longer maintained so prefer `adwaita`.
`lxqt` `lxqt`
: Use LXQt theme style set using the : Use LXQt theme style set using the
[`lxqt-config-appearance`](https://github.com/lxqt/lxqt-config) [`lxqt-config-appearance`](https://github.com/lxqt/lxqt-config)
application application
`qtct` `qtct`
: Use Qt style set using : Use Qt style set using
[`qt5ct`](https://github.com/desktop-app/qt5ct) [`qt5ct`](https://github.com/desktop-app/qt5ct)
and [`qt6ct`](https://github.com/trialuser02/qt6ct) and [`qt6ct`](https://github.com/trialuser02/qt6ct)
applications applications
`kde` `kde`
: Use Qt settings from Plasma 5 : Use Qt settings from Plasma 5
`kde6` `kde6`
: Use Qt settings from Plasma 6 : Use Qt settings from Plasma 6
''; '';
}; };
package = lib.mkOption { package = lib.mkOption {
type = with lib.types; nullOr (either package (listOf package)); type = with lib.types; nullOr (either package (listOf package));
default = null; default = null;
example = example = lib.literalExpression "[pkgs.adwaita-qt pkgs.adwaita-qt6]";
lib.literalExpression "[pkgs.adwaita-qt pkgs.adwaita-qt6]"; description = ''
description = '' Theme package to be used in Qt5/Qt6 applications.
Theme package to be used in Qt5/Qt6 applications. Auto-detected from {option}`qt.platformTheme.name` if possible.
Auto-detected from {option}`qt.platformTheme.name` if possible. See its documentation for available options.
See its documentation for available options. '';
''; };
}; };
in
lib.mkOption {
type =
with lib.types;
nullOr (
either (enum [
"gtk"
"gtk3"
"gnome"
"adwaita"
"lxqt"
"qtct"
"kde"
"kde6"
]) (lib.types.submodule { options = newOption; })
);
default = null;
description = ''
Deprecated. Use {option}`qt.platformTheme.name` instead.
'';
}; };
in lib.mkOption {
type = with lib.types;
nullOr (either
(enum [ "gtk" "gtk3" "gnome" "adwaita" "lxqt" "qtct" "kde" "kde6" ])
(lib.types.submodule { options = newOption; }));
default = null;
description = ''
Deprecated. Use {option}`qt.platformTheme.name` instead.
'';
};
style = { style = {
name = lib.mkOption { name = lib.mkOption {
type = with lib.types; nullOr str; type = with lib.types; nullOr str;
@ -158,11 +237,26 @@ in {
relatedPackages = [ relatedPackages = [
"adwaita-qt" "adwaita-qt"
"adwaita-qt6" "adwaita-qt6"
[ "libsForQt5" "breeze-qt5" ] [
[ "libsForQt5" "qtstyleplugin-kvantum" ] "libsForQt5"
[ "libsForQt5" "qtstyleplugins" ] "breeze-qt5"
[ "qt6Packages" "qt6gtk2" ] ]
[ "qt6Packages" "qtstyleplugin-kvantum" ] [
"libsForQt5"
"qtstyleplugin-kvantum"
]
[
"libsForQt5"
"qtstyleplugins"
]
[
"qt6Packages"
"qt6gtk2"
]
[
"qt6Packages"
"qtstyleplugin-kvantum"
]
]; ];
description = '' description = ''
Style to use for Qt5/Qt6 applications. Case-insensitive. Style to use for Qt5/Qt6 applications. Case-insensitive.
@ -201,80 +295,103 @@ in {
}; };
}; };
config = let config =
platformTheme = if (builtins.isString cfg.platformTheme) then { let
option = "qt.platformTheme"; platformTheme =
name = cfg.platformTheme; if (builtins.isString cfg.platformTheme) then
package = null; {
} else if cfg.platformTheme == null then { option = "qt.platformTheme";
option = null; name = cfg.platformTheme;
name = null; package = null;
package = null; }
} else { else if cfg.platformTheme == null then
option = "qt.platformTheme.name"; {
name = cfg.platformTheme.name; option = null;
package = cfg.platformTheme.package; name = null;
package = null;
}
else
{
option = "qt.platformTheme.name";
name = cfg.platformTheme.name;
package = cfg.platformTheme.package;
};
# Necessary because home.sessionVariables doesn't support mkIf
envVars = lib.filterAttrs (n: v: v != null) {
QT_QPA_PLATFORMTHEME =
if (platformTheme.name != null) then
styleNames.${platformTheme.name} or platformTheme.name
else
null;
QT_STYLE_OVERRIDE = cfg.style.name;
};
envVarsExtra =
let
inherit (config.home) profileDirectory;
qtVersions = with pkgs; [
qt5
qt6
];
makeQtPath = prefix: (map (qt: "${profileDirectory}/${qt.qtbase.${prefix}}") qtVersions);
in
{
QT_PLUGIN_PATH = makeQtPath "qtPluginPrefix";
QML2_IMPORT_PATH = makeQtPath "qtQmlPrefix";
};
in
lib.mkIf cfg.enable {
assertions = [
{
assertion = platformTheme.name == "gnome" -> cfg.style.name != null && cfg.style.package != null;
message = ''
`qt.platformTheme.name` "gnome" must have `qt.style` set to a theme that
supports both Qt and Gtk, for example "adwaita", "adwaita-dark", or "breeze".
'';
}
];
warnings =
(lib.lists.optional (
platformTheme.option == "qt.platformTheme"
) "The option `qt.platformTheme` has been renamed to `qt.platformTheme.name`.")
++ (lib.lists.optional (
platformTheme.name == "gnome" && platformTheme.package == null
) "The value `gnome` for option `${platformTheme.option}` is deprecated. Use `adwaita` instead.");
qt.style.package = lib.mkIf (cfg.style.name != null) (
lib.mkDefault (stylePackages.${lib.toLower cfg.style.name} or null)
);
home = {
sessionVariables = envVars;
sessionSearchVariables = envVarsExtra;
};
# Apply theming also to apps started by systemd.
systemd.user.sessionVariables = envVars // {
QT_PLUGIN_PATH = lib.concatStringsSep ":" envVarsExtra.QT_PLUGIN_PATH;
QML2_IMPORT_PATH = lib.concatStringsSep ":" envVarsExtra.QML2_IMPORT_PATH;
};
home.packages =
(lib.findFirst (x: x != [ ])
[ ]
[
(lib.optionals (platformTheme.package != null) (lib.toList platformTheme.package))
(lib.optionals (platformTheme.name != null) platformPackages.${platformTheme.name} or [ ])
]
)
++ (lib.optionals (cfg.style.package != null) (lib.toList cfg.style.package));
xsession.importedVariables =
[
"QT_PLUGIN_PATH"
"QML2_IMPORT_PATH"
]
++ lib.optionals (platformTheme.name != null) [ "QT_QPA_PLATFORMTHEME" ]
++ lib.optionals (cfg.style.name != null) [ "QT_STYLE_OVERRIDE" ];
}; };
# Necessary because home.sessionVariables doesn't support mkIf
envVars = lib.filterAttrs (n: v: v != null) {
QT_QPA_PLATFORMTHEME = if (platformTheme.name != null) then
styleNames.${platformTheme.name} or platformTheme.name
else
null;
QT_STYLE_OVERRIDE = cfg.style.name;
};
envVarsExtra = let
inherit (config.home) profileDirectory;
qtVersions = with pkgs; [ qt5 qt6 ];
makeQtPath = prefix:
(map (qt: "${profileDirectory}/${qt.qtbase.${prefix}}") qtVersions);
in {
QT_PLUGIN_PATH = makeQtPath "qtPluginPrefix";
QML2_IMPORT_PATH = makeQtPath "qtQmlPrefix";
};
in lib.mkIf cfg.enable {
assertions = [{
assertion = platformTheme.name == "gnome" -> cfg.style.name != null
&& cfg.style.package != null;
message = ''
`qt.platformTheme.name` "gnome" must have `qt.style` set to a theme that
supports both Qt and Gtk, for example "adwaita", "adwaita-dark", or "breeze".
'';
}];
warnings = (lib.lists.optional (platformTheme.option == "qt.platformTheme")
"The option `qt.platformTheme` has been renamed to `qt.platformTheme.name`.")
++ (lib.lists.optional
(platformTheme.name == "gnome" && platformTheme.package == null)
"The value `gnome` for option `${platformTheme.option}` is deprecated. Use `adwaita` instead.");
qt.style.package = lib.mkIf (cfg.style.name != null)
(lib.mkDefault (stylePackages.${lib.toLower cfg.style.name} or null));
home = {
sessionVariables = envVars;
sessionSearchVariables = envVarsExtra;
};
# Apply theming also to apps started by systemd.
systemd.user.sessionVariables = envVars // {
QT_PLUGIN_PATH = lib.concatStringsSep ":" envVarsExtra.QT_PLUGIN_PATH;
QML2_IMPORT_PATH = lib.concatStringsSep ":" envVarsExtra.QML2_IMPORT_PATH;
};
home.packages = (lib.findFirst (x: x != [ ]) [ ] [
(lib.optionals (platformTheme.package != null)
(lib.toList platformTheme.package))
(lib.optionals (platformTheme.name != null)
platformPackages.${platformTheme.name} or [ ])
]) ++ (lib.optionals (cfg.style.package != null)
(lib.toList cfg.style.package));
xsession.importedVariables = [ "QT_PLUGIN_PATH" "QML2_IMPORT_PATH" ]
++ lib.optionals (platformTheme.name != null) [ "QT_QPA_PLATFORMTHEME" ]
++ lib.optionals (cfg.style.name != null) [ "QT_STYLE_OVERRIDE" ];
};
} }

View file

@ -1,17 +1,33 @@
{ config, pkgs, lib, ... }: {
config,
pkgs,
lib,
...
}:
let let
cfg = config.qt.kde.settings; cfg = config.qt.kde.settings;
in { in
{
options.qt.kde.settings = lib.mkOption { options.qt.kde.settings = lib.mkOption {
type = with lib.types; type =
with lib.types;
let let
valueType = valueType =
nullOr (oneOf [ bool int float str path (attrsOf valueType) ]) // { nullOr (oneOf [
bool
int
float
str
path
(attrsOf valueType)
])
// {
description = "KDE option value"; description = "KDE option value";
}; };
in attrsOf valueType; in
attrsOf valueType;
default = { }; default = { };
example = { example = {
powermanagementprofilesrc.AC.HandleButtonEvents.lidAction = 32; powermanagementprofilesrc.AC.HandleButtonEvents.lidAction = 32;
@ -38,28 +54,32 @@ in {
config = lib.mkIf (cfg != { }) { config = lib.mkIf (cfg != { }) {
home.activation.kconfig = lib.hm.dag.entryAfter [ "writeBoundary" ] '' home.activation.kconfig = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
${let ${
inherit (config.xdg) configHome; let
toValue = v: inherit (config.xdg) configHome;
let t = builtins.typeOf v; toValue =
in if v == null then v:
"--delete" let
else if t == "bool" then t = builtins.typeOf v;
"--type bool ${builtins.toJSON v}" in
else if v == null then
lib.escapeShellArg (toString v); "--delete"
toLine = file: path: value: else if t == "bool" then
if builtins.isAttrs value then "--type bool ${builtins.toJSON v}"
lib.mapAttrsToList else
(group: value: toLine file (path ++ [ group ]) value) value lib.escapeShellArg (toString v);
else toLine =
"run ${pkgs.kdePackages.kconfig}/bin/kwriteconfig6 --file '${configHome}/${file}' ${ file: path: value:
lib.concatMapStringsSep " " (x: "--group ${x}") if builtins.isAttrs value then
(lib.lists.init path) lib.mapAttrsToList (group: value: toLine file (path ++ [ group ]) value) value
} --key '${lib.lists.last path}' ${toValue value}"; else
lines = lib.flatten "run ${pkgs.kdePackages.kconfig}/bin/kwriteconfig6 --file '${configHome}/${file}' ${
(lib.mapAttrsToList (file: attrs: toLine file [ ] attrs) cfg); lib.concatMapStringsSep " " (x: "--group ${x}") (lib.lists.init path)
in builtins.concatStringsSep "\n" lines} } --key '${lib.lists.last path}' ${toValue value}";
lines = lib.flatten (lib.mapAttrsToList (file: attrs: toLine file [ ] attrs) cfg);
in
builtins.concatStringsSep "\n" lines
}
# TODO: some way to only call the dbus calls needed # TODO: some way to only call the dbus calls needed
run ${pkgs.kdePackages.qttools}/bin/qdbus org.kde.KWin /KWin reconfigure || echo "KWin reconfigure failed" run ${pkgs.kdePackages.qttools}/bin/qdbus org.kde.KWin /KWin reconfigure || echo "KWin reconfigure failed"

View file

@ -1,36 +1,47 @@
{ config, name, extendModules, lib, ... }: {
config,
name,
extendModules,
lib,
...
}:
{ {
imports = imports = [ (lib.mkRenamedOptionModule [ "specialization" ] [ "specialisation" ]) ];
[ (lib.mkRenamedOptionModule [ "specialization" ] [ "specialisation" ]) ];
options.specialisation = lib.mkOption { options.specialisation = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule { type = lib.types.attrsOf (
options = { lib.types.submodule {
configuration = lib.mkOption { options = {
type = let configuration = lib.mkOption {
extended = extendModules { type =
modules = [{ let
# Prevent infinite recursion extended = extendModules {
specialisation = lib.mkOverride 0 { }; modules = [
{
# Prevent infinite recursion
specialisation = lib.mkOverride 0 { };
# If used inside the NixOS/nix-darwin module, we get conflicting definitions # If used inside the NixOS/nix-darwin module, we get conflicting definitions
# of `name` inside the specialisation: one is the user name coming from the # of `name` inside the specialisation: one is the user name coming from the
# NixOS module definition and the other is `configuration`, the name of this # NixOS module definition and the other is `configuration`, the name of this
# option. Thus we need to explicitly wire the former into the module arguments. # option. Thus we need to explicitly wire the former into the module arguments.
# See discussion at https://github.com/nix-community/home-manager/issues/3716 # See discussion at https://github.com/nix-community/home-manager/issues/3716
_module.args.name = lib.mkForce name; _module.args.name = lib.mkForce name;
}]; }
}; ];
in extended.type; };
default = { }; in
visible = "shallow"; extended.type;
description = '' default = { };
Arbitrary Home Manager configuration settings. visible = "shallow";
''; description = ''
Arbitrary Home Manager configuration settings.
'';
};
}; };
}; }
}); );
default = { }; default = { };
description = '' description = ''
A set of named specialized configurations. These can be used to extend A set of named specialized configurations. These can be used to extend
@ -71,18 +82,21 @@
config = lib.mkIf (config.specialisation != { }) { config = lib.mkIf (config.specialisation != { }) {
assertions = map (n: { assertions = map (n: {
assertion = !lib.hasInfix "/" n; assertion = !lib.hasInfix "/" n;
message = message = "<name> in specialisation.<name> cannot contain a forward slash.";
"<name> in specialisation.<name> cannot contain a forward slash.";
}) (lib.attrNames config.specialisation); }) (lib.attrNames config.specialisation);
home.extraBuilderCommands = let home.extraBuilderCommands =
link = n: v: let
let pkg = v.configuration.home.activationPackage; link =
in "ln -s ${pkg} $out/specialisation/${lib.escapeShellArg n}"; n: v:
in '' let
mkdir $out/specialisation pkg = v.configuration.home.activationPackage;
${lib.concatStringsSep "\n" in
(lib.mapAttrsToList link config.specialisation)} "ln -s ${pkg} $out/specialisation/${lib.escapeShellArg n}";
''; in
''
mkdir $out/specialisation
${lib.concatStringsSep "\n" (lib.mapAttrsToList link config.specialisation)}
'';
}; };
} }

View file

@ -1,10 +1,16 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.systemd.user.tmpfiles; cfg = config.systemd.user.tmpfiles;
in { in
{
meta.maintainers = [ lib.maintainers.dawidsowa ]; meta.maintainers = [ lib.maintainers.dawidsowa ];
options.systemd.user.tmpfiles.rules = lib.mkOption { options.systemd.user.tmpfiles.rules = lib.mkOption {
@ -21,8 +27,7 @@ in {
config = lib.mkIf (cfg.rules != [ ]) { config = lib.mkIf (cfg.rules != [ ]) {
assertions = [ assertions = [
(lib.hm.assertions.assertPlatform "systemd.user.tmpfiles" pkgs (lib.hm.assertions.assertPlatform "systemd.user.tmpfiles" pkgs lib.platforms.linux)
lib.platforms.linux)
]; ];
xdg.configFile = { xdg.configFile = {

View file

@ -4,7 +4,8 @@ let
inherit (lib) mkIf mkOption types; inherit (lib) mkIf mkOption types;
in { in
{
options.uninstall = mkOption { options.uninstall = mkOption {
type = types.bool; type = types.bool;
default = false; default = false;
@ -26,25 +27,24 @@ in {
manual.manpages.enable = lib.mkForce false; manual.manpages.enable = lib.mkForce false;
news.display = lib.mkForce "silent"; news.display = lib.mkForce "silent";
home.activation.uninstall = home.activation.uninstall = lib.hm.dag.entryAfter [ "installPackages" "linkGeneration" ] ''
lib.hm.dag.entryAfter [ "installPackages" "linkGeneration" ] '' nixProfileRemove home-manager-path
nixProfileRemove home-manager-path
if [[ -e $hmDataPath ]]; then if [[ -e $hmDataPath ]]; then
run rm $VERBOSE_ARG -r "$hmDataPath" run rm $VERBOSE_ARG -r "$hmDataPath"
fi fi
if [[ -e $hmStatePath ]]; then if [[ -e $hmStatePath ]]; then
run rm $VERBOSE_ARG -r "$hmStatePath" run rm $VERBOSE_ARG -r "$hmStatePath"
fi fi
if [[ -e $genProfilePath ]]; then if [[ -e $genProfilePath ]]; then
run rm $VERBOSE_ARG "$genProfilePath"* run rm $VERBOSE_ARG "$genProfilePath"*
fi fi
if [[ -e $legacyGenGcPath ]]; then if [[ -e $legacyGenGcPath ]]; then
run rm $VERBOSE_ARG "$legacyGenGcPath" run rm $VERBOSE_ARG "$legacyGenGcPath"
fi fi
''; '';
}; };
} }

View file

@ -5,7 +5,8 @@ let
releaseInfo = lib.importJSON ../../release.json; releaseInfo = lib.importJSON ../../release.json;
in { in
{
options = { options = {
home.stateVersion = lib.mkOption { home.stateVersion = lib.mkOption {
type = types.enum [ type = types.enum [
@ -44,11 +45,12 @@ in {
internal = true; internal = true;
readOnly = true; readOnly = true;
type = types.str; type = types.str;
default = let default =
inherit (config.home.version) release revision; let
suffix = lib.optionalString (revision != null) inherit (config.home.version) release revision;
"+${lib.substring 0 8 revision}"; suffix = lib.optionalString (revision != null) "+${lib.substring 0 8 revision}";
in "${release}${suffix}"; in
"${release}${suffix}";
example = "22.11+213a0629"; example = "22.11+213a0629";
description = "The full Home Manager version."; description = "The full Home Manager version.";
}; };
@ -76,11 +78,11 @@ in {
revision = lib.mkOption { revision = lib.mkOption {
internal = true; internal = true;
type = types.nullOr types.str; type = types.nullOr types.str;
default = let gitRepo = "${toString ./../..}/.git"; default =
in if lib.pathIsGitRepo gitRepo then let
lib.commitIdFromGitRepo gitRepo gitRepo = "${toString ./../..}/.git";
else in
null; if lib.pathIsGitRepo gitRepo then lib.commitIdFromGitRepo gitRepo else null;
description = '' description = ''
The Git revision from which this Home Manager configuration was built. The Git revision from which this Home Manager configuration was built.
''; '';

View file

@ -1,23 +1,30 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
{ {
meta.maintainers = [ lib.maintainers.rycee ]; meta.maintainers = [ lib.maintainers.rycee ];
options.programs = let options.programs =
description = '' let
Whether to enable integration with terminals using the VTE description = ''
library. This will let the terminal track the current working Whether to enable integration with terminals using the VTE
directory. library. This will let the terminal track the current working
''; directory.
in { '';
bash.enableVteIntegration = lib.mkEnableOption "" // { in
inherit description; {
}; bash.enableVteIntegration = lib.mkEnableOption "" // {
inherit description;
};
zsh.enableVteIntegration = lib.mkEnableOption "" // { zsh.enableVteIntegration = lib.mkEnableOption "" // {
inherit description; inherit description;
};
}; };
};
config = lib.mkMerge [ config = lib.mkMerge [
(lib.mkIf config.programs.bash.enableVteIntegration { (lib.mkIf config.programs.bash.enableVteIntegration {

View file

@ -1,7 +1,23 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (builtins) baseNameOf listToAttrs map unsafeDiscardStringContext; inherit (builtins)
inherit (lib) literalExpression mkEnableOption mkIf mkOption types; baseNameOf
listToAttrs
map
unsafeDiscardStringContext
;
inherit (lib)
literalExpression
mkEnableOption
mkIf
mkOption
types
;
cfg = config.xdg.autostart; cfg = config.xdg.autostart;
@ -10,7 +26,8 @@ let
${lib.concatMapStringsSep "\n" (e: "ln -s ${e} $out") cfg.entries} ${lib.concatMapStringsSep "\n" (e: "ln -s ${e} $out") cfg.entries}
''; '';
in { in
{
meta.maintainers = with lib.maintainers; [ Scrumplex ]; meta.maintainers = with lib.maintainers; [ Scrumplex ];
options.xdg.autostart = { options.xdg.autostart = {

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (lib) literalExpression mkOption types; inherit (lib) literalExpression mkOption types;
@ -6,9 +11,9 @@ let
desktopEntry = { desktopEntry = {
imports = [ imports = [
(lib.mkRemovedOptionModule [ "extraConfig" ] (lib.mkRemovedOptionModule [ "extraConfig" ]
"The `extraConfig` option of `xdg.desktopEntries` has been removed following a change in Nixpkgs.") "The `extraConfig` option of `xdg.desktopEntries` has been removed following a change in Nixpkgs."
(lib.mkRemovedOptionModule [ "fileValidation" ] )
"Validation of the desktop file is always enabled.") (lib.mkRemovedOptionModule [ "fileValidation" ] "Validation of the desktop file is always enabled.")
]; ];
options = { options = {
# Since this module uses the nixpkgs/pkgs/build-support/make-desktopitem function, # Since this module uses the nixpkgs/pkgs/build-support/make-desktopitem function,
@ -28,7 +33,11 @@ let
type = mkOption { type = mkOption {
description = "The type of the desktop entry."; description = "The type of the desktop entry.";
default = "Application"; default = "Application";
type = types.enum [ "Application" "Link" "Directory" ]; type = types.enum [
"Application"
"Link"
"Directory"
];
}; };
exec = mkOption { exec = mkOption {
@ -73,8 +82,7 @@ let
}; };
categories = mkOption { categories = mkOption {
description = description = "Categories in which the entry should be shown in a menu.";
"Categories in which the entry should be shown in a menu.";
type = types.nullOr (types.listOf types.str); type = types.nullOr (types.listOf types.str);
default = null; default = null;
}; };
@ -122,24 +130,29 @@ let
}; };
actions = mkOption { actions = mkOption {
type = types.attrsOf (types.submodule ({ name, ... }: { type = types.attrsOf (
options.name = mkOption { types.submodule (
type = types.str; { name, ... }:
default = name; {
defaultText = literalExpression "<name>"; options.name = mkOption {
description = "Name of the action."; type = types.str;
}; default = name;
options.exec = mkOption { defaultText = literalExpression "<name>";
type = types.nullOr types.str; description = "Name of the action.";
description = "Program to execute, possibly with arguments."; };
default = null; options.exec = mkOption {
}; type = types.nullOr types.str;
options.icon = mkOption { description = "Program to execute, possibly with arguments.";
type = with types; nullOr (either str path); default = null;
default = null; };
description = "Icon to display in file manager, menus, etc."; options.icon = mkOption {
}; type = with types; nullOr (either str path);
})); default = null;
description = "Icon to display in file manager, menus, etc.";
};
}
)
);
default = { }; default = { };
defaultText = literalExpression "{ }"; defaultText = literalExpression "{ }";
example = literalExpression '' example = literalExpression ''
@ -149,8 +162,7 @@ let
}; };
} }
''; '';
description = description = "The set of actions made available to application launchers.";
"The set of actions made available to application launchers.";
}; };
# Required for the assertions # Required for the assertions
@ -165,18 +177,29 @@ let
}; };
#passes config options to makeDesktopItem in expected format #passes config options to makeDesktopItem in expected format
makeFile = name: config: makeFile =
name: config:
pkgs.makeDesktopItem { pkgs.makeDesktopItem {
inherit name; inherit name;
inherit (config) inherit (config)
type exec icon comment terminal genericName startupNotify noDisplay type
prefersNonDefaultGPU actions; exec
icon
comment
terminal
genericName
startupNotify
noDisplay
prefersNonDefaultGPU
actions
;
desktopName = config.name; desktopName = config.name;
mimeTypes = lib.optionals (config.mimeType != null) config.mimeType; mimeTypes = lib.optionals (config.mimeType != null) config.mimeType;
categories = lib.optionals (config.categories != null) config.categories; categories = lib.optionals (config.categories != null) config.categories;
extraConfig = config.settings; extraConfig = config.settings;
}; };
in { in
{
meta.maintainers = [ lib.hm.maintainers.cwyc ]; meta.maintainers = [ lib.hm.maintainers.cwyc ];
options.xdg.desktopEntries = mkOption { options.xdg.desktopEntries = mkOption {
@ -205,14 +228,13 @@ in {
config = lib.mkIf (config.xdg.desktopEntries != { }) { config = lib.mkIf (config.xdg.desktopEntries != { }) {
assertions = [ assertions = [
(lib.hm.assertions.assertPlatform "xdg.desktopEntries" pkgs (lib.hm.assertions.assertPlatform "xdg.desktopEntries" pkgs lib.platforms.linux)
lib.platforms.linux) ] ++ lib.flatten (lib.catAttrs "assertions" (lib.attrValues config.xdg.desktopEntries));
] ++ lib.flatten
(lib.catAttrs "assertions" (lib.attrValues config.xdg.desktopEntries));
home.packages = home.packages = (
(map lib.hiPrio # we need hiPrio to override existing entries map lib.hiPrio # we need hiPrio to override existing entries
(lib.attrsets.mapAttrsToList makeFile config.xdg.desktopEntries)); (lib.attrsets.mapAttrsToList makeFile config.xdg.desktopEntries)
);
}; };
} }

View file

@ -1,14 +1,19 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (lib) mkOption types; inherit (lib) mkOption types;
cfg = config.xdg.mimeApps; cfg = config.xdg.mimeApps;
strListOrSingleton = with types; strListOrSingleton = with types; coercedTo (either (listOf str) str) lib.toList (listOf str);
coercedTo (either (listOf str) str) lib.toList (listOf str);
in { in
{
meta.maintainers = with lib.maintainers; [ euxane ]; meta.maintainers = with lib.maintainers; [ euxane ];
options.xdg.mimeApps = { options.xdg.mimeApps = {
@ -44,7 +49,9 @@ in {
associations.removed = mkOption { associations.removed = mkOption {
type = types.attrsOf strListOrSingleton; type = types.attrsOf strListOrSingleton;
default = { }; default = { };
example = { "mimetype1" = "foo5.desktop"; }; example = {
"mimetype1" = "foo5.desktop";
};
description = '' description = ''
Removes associations of applications with mimetypes, as if the Removes associations of applications with mimetypes, as if the
.desktop file was *not* listing this .desktop file was *not* listing this
@ -75,43 +82,47 @@ in {
# Given a package that installs .desktop files in the usual location, # Given a package that installs .desktop files in the usual location,
# return a mapping from mime types to lists of desktop file names. This is # return a mapping from mime types to lists of desktop file names. This is
# suitable for use with `xdg.mimeApps.defaultApplications`. # suitable for use with `xdg.mimeApps.defaultApplications`.
lib.xdg.mimeAssociations = let lib.xdg.mimeAssociations =
processLines = str: let
lib.zipAttrs (lib.filter (e: e != null) processLines =
(map processLine (lib.splitString "\n" str))); str: lib.zipAttrs (lib.filter (e: e != null) (map processLine (lib.splitString "\n" str)));
processLine = str: processLine =
let str:
entry = lib.splitString ";" str; let
k = lib.elemAt entry 0; entry = lib.splitString ";" str;
v = lib.elemAt entry 1; k = lib.elemAt entry 0;
in if lib.length entry == 2 then { ${k} = v; } else null; v = lib.elemAt entry 1;
in
if lib.length entry == 2 then { ${k} = v; } else null;
associations = ps: associations =
pkgs.runCommand "mime-assoc" { inherit ps; } '' ps:
for p in $ps ; do pkgs.runCommand "mime-assoc" { inherit ps; } ''
for path in "$p"/share/applications/*.desktop ; do for p in $ps ; do
name="''${path##*/}" for path in "$p"/share/applications/*.desktop ; do
sed -n -E "/^MimeType=/ { s/.*=//; s/;?$|;/;$name\n/g; p; }" "$path" name="''${path##*/}"
done sed -n -E "/^MimeType=/ { s/.*=//; s/;?$|;/;$name\n/g; p; }" "$path"
done > "$out" done
''; done > "$out"
in p: processLines (builtins.readFile (associations p)); '';
in
p: processLines (builtins.readFile (associations p));
} }
(lib.mkIf cfg.enable { (lib.mkIf cfg.enable {
assertions = [ assertions = [
(lib.hm.assertions.assertPlatform "xdg.mimeApps" pkgs (lib.hm.assertions.assertPlatform "xdg.mimeApps" pkgs lib.platforms.linux)
lib.platforms.linux)
]; ];
# Deprecated but still used by some applications. # Deprecated but still used by some applications.
xdg.dataFile."applications/mimeapps.list".source = xdg.dataFile."applications/mimeapps.list".source = config.xdg.configFile."mimeapps.list".source;
config.xdg.configFile."mimeapps.list".source;
xdg.configFile."mimeapps.list".text = xdg.configFile."mimeapps.list".text =
let joinValues = lib.mapAttrs (n: lib.concatStringsSep ";"); let
in lib.generators.toINI { } { joinValues = lib.mapAttrs (n: lib.concatStringsSep ";");
in
lib.generators.toINI { } {
"Added Associations" = joinValues cfg.associations.added; "Added Associations" = joinValues cfg.associations.added;
"Removed Associations" = joinValues cfg.associations.removed; "Removed Associations" = joinValues cfg.associations.removed;
"Default Applications" = joinValues cfg.defaultApplications; "Default Applications" = joinValues cfg.defaultApplications;

View file

@ -1,19 +1,29 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.xdg.mime; cfg = config.xdg.mime;
inherit (lib) getExe getExe' mkOption types; inherit (lib)
getExe
getExe'
mkOption
types
;
in { in
{
options = { options = {
xdg.mime = { xdg.mime = {
enable = mkOption { enable = mkOption {
type = types.bool; type = types.bool;
default = pkgs.stdenv.hostPlatform.isLinux; default = pkgs.stdenv.hostPlatform.isLinux;
defaultText = lib.literalExpression defaultText = lib.literalExpression "true if host platform is Linux, false otherwise";
"true if host platform is Linux, false otherwise";
description = '' description = ''
Whether to install programs and files to support the Whether to install programs and files to support the
XDG Shared MIME-info specification and XDG MIME Applications XDG Shared MIME-info specification and XDG MIME Applications
@ -36,8 +46,7 @@ in {
type = types.package; type = types.package;
default = pkgs.desktop-file-utils; default = pkgs.desktop-file-utils;
defaultText = lib.literalExpression "pkgs.desktop-file-utils"; defaultText = lib.literalExpression "pkgs.desktop-file-utils";
description = description = "The package to use when running update-desktop-database.";
"The package to use when running update-desktop-database.";
}; };
}; };
}; };
@ -64,16 +73,14 @@ in {
XDG_DATA_DIRS=$out/share \ XDG_DATA_DIRS=$out/share \
PKGSYSTEM_ENABLE_FSYNC=0 \ PKGSYSTEM_ENABLE_FSYNC=0 \
${ ${
getExe getExe (cfg.sharedMimeInfoPackage.__spliced.buildHost or cfg.sharedMimeInfoPackage)
(cfg.sharedMimeInfoPackage.__spliced.buildHost or cfg.sharedMimeInfoPackage)
} -V $out/share/mime > /dev/null } -V $out/share/mime > /dev/null
fi fi
if [[ -w $out/share/applications ]]; then if [[ -w $out/share/applications ]]; then
${ ${
getExe' getExe' (cfg.desktopFileUtilsPackage.__spliced.buildHost or cfg.desktopFileUtilsPackage
(cfg.desktopFileUtilsPackage.__spliced.buildHost or cfg.desktopFileUtilsPackage) ) "update-desktop-database"
"update-desktop-database"
} $out/share/applications } $out/share/applications
fi fi
''; '';

View file

@ -1,14 +1,26 @@
{ config, pkgs, lib, ... }: {
config,
pkgs,
lib,
...
}:
let let
inherit (lib) mkIf mkMerge mkOption optional types; inherit (lib)
mkIf
mkMerge
mkOption
optional
types
;
associationOptions = with types; associationOptions =
attrsOf (coercedTo (either (listOf str) str) with types;
(x: lib.concatStringsSep ";" (lib.toList x)) str); attrsOf (coercedTo (either (listOf str) str) (x: lib.concatStringsSep ";" (lib.toList x)) str);
in { in
{
meta.maintainers = [ lib.maintainers.misterio77 ]; meta.maintainers = [ lib.maintainers.misterio77 ];
options.xdg.portal = { options.xdg.portal = {
@ -63,12 +75,22 @@ in {
type = types.attrsOf associationOptions; type = types.attrsOf associationOptions;
default = { }; default = { };
example = { example = {
x-cinnamon = { default = [ "xapp" "gtk" ]; }; x-cinnamon = {
default = [
"xapp"
"gtk"
];
};
pantheon = { pantheon = {
default = [ "pantheon" "gtk" ]; default = [
"pantheon"
"gtk"
];
"org.freedesktop.impl.portal.Secret" = [ "gnome-keyring" ]; "org.freedesktop.impl.portal.Secret" = [ "gnome-keyring" ];
}; };
common = { default = [ "gtk" ]; }; common = {
default = [ "gtk" ];
};
}; };
description = '' description = ''
Sets which portal backend should be used to provide the implementation Sets which portal backend should be used to provide the implementation
@ -97,51 +119,53 @@ in {
}; };
}; };
config = let config =
cfg = config.xdg.portal; let
packages = [ pkgs.xdg-desktop-portal ] ++ cfg.extraPortals; cfg = config.xdg.portal;
portalsDir = packages = [ pkgs.xdg-desktop-portal ] ++ cfg.extraPortals;
"${config.home.profileDirectory}/share/xdg-desktop-portal/portals"; portalsDir = "${config.home.profileDirectory}/share/xdg-desktop-portal/portals";
in mkIf cfg.enable { in
warnings = optional (cfg.configPackages == [ ] && cfg.config == { }) '' mkIf cfg.enable {
xdg-desktop-portal 1.17 reworked how portal implementations are loaded, you warnings = optional (cfg.configPackages == [ ] && cfg.config == { }) ''
should either set `xdg.portal.config` or `xdg.portal.configPackages` xdg-desktop-portal 1.17 reworked how portal implementations are loaded, you
to specify which portal backend to use for the requested interface. should either set `xdg.portal.config` or `xdg.portal.configPackages`
to specify which portal backend to use for the requested interface.
https://github.com/flatpak/xdg-desktop-portal/blob/1.18.1/doc/portals.conf.rst.in https://github.com/flatpak/xdg-desktop-portal/blob/1.18.1/doc/portals.conf.rst.in
If you simply want to keep the behaviour in < 1.17, which uses the first If you simply want to keep the behaviour in < 1.17, which uses the first
portal implementation found in lexicographical order, use the following: portal implementation found in lexicographical order, use the following:
xdg.portal.config.common.default = "*"; xdg.portal.config.common.default = "*";
''; '';
assertions = [ assertions = [
(lib.hm.assertions.assertPlatform "xdg.portal" pkgs lib.platforms.linux) (lib.hm.assertions.assertPlatform "xdg.portal" pkgs lib.platforms.linux)
{ {
assertion = cfg.extraPortals != [ ]; assertion = cfg.extraPortals != [ ];
message = message = "Setting xdg.portal.enable to true requires a portal implementation in xdg.portal.extraPortals such as xdg-desktop-portal-gtk or xdg-desktop-portal-kde.";
"Setting xdg.portal.enable to true requires a portal implementation in xdg.portal.extraPortals such as xdg-desktop-portal-gtk or xdg-desktop-portal-kde."; }
}
];
home = {
packages = packages ++ cfg.configPackages;
sessionVariables = mkMerge [
(mkIf cfg.xdgOpenUsePortal { NIXOS_XDG_OPEN_USE_PORTAL = "1"; })
{ NIX_XDG_DESKTOP_PORTAL_DIR = portalsDir; }
]; ];
};
systemd.user.sessionVariables = {
NIX_XDG_DESKTOP_PORTAL_DIR = portalsDir;
};
xdg.configFile = lib.concatMapAttrs (desktop: conf: home = {
lib.optionalAttrs (conf != { }) { packages = packages ++ cfg.configPackages;
"xdg-desktop-portal/${ sessionVariables = mkMerge [
lib.optionalString (desktop != "common") "${desktop}-" (mkIf cfg.xdgOpenUsePortal { NIXOS_XDG_OPEN_USE_PORTAL = "1"; })
}portals.conf".text = lib.generators.toINI { } { preferred = conf; }; { NIX_XDG_DESKTOP_PORTAL_DIR = portalsDir; }
}) cfg.config; ];
}; };
systemd.user.sessionVariables = {
NIX_XDG_DESKTOP_PORTAL_DIR = portalsDir;
};
xdg.configFile = lib.concatMapAttrs (
desktop: conf:
lib.optionalAttrs (conf != { }) {
"xdg-desktop-portal/${lib.optionalString (desktop != "common") "${desktop}-"}portals.conf".text =
lib.generators.toINI { }
{ preferred = conf; };
}
) cfg.config;
};
} }

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (lib) types; inherit (lib) types;
@ -9,7 +14,8 @@ let
dataDirs = lib.concatStringsSep ":" cfg.data; dataDirs = lib.concatStringsSep ":" cfg.data;
in { in
{
meta.maintainers = with lib.maintainers; [ tadfisher ]; meta.maintainers = with lib.maintainers; [ tadfisher ];
options.xdg.systemDirs = { options.xdg.systemDirs = {
@ -37,25 +43,20 @@ in {
config = lib.mkMerge [ config = lib.mkMerge [
(lib.mkIf (cfg.config != [ ] || cfg.data != [ ]) { (lib.mkIf (cfg.config != [ ] || cfg.data != [ ]) {
assertions = [ assertions = [
(lib.hm.assertions.assertPlatform "xdg.systemDirs" pkgs (lib.hm.assertions.assertPlatform "xdg.systemDirs" pkgs lib.platforms.linux)
lib.platforms.linux)
]; ];
}) })
(lib.mkIf (cfg.config != [ ]) { (lib.mkIf (cfg.config != [ ]) {
home.sessionVariables.XDG_CONFIG_DIRS = home.sessionVariables.XDG_CONFIG_DIRS = "${configDirs}\${XDG_CONFIG_DIRS:+:$XDG_CONFIG_DIRS}";
"${configDirs}\${XDG_CONFIG_DIRS:+:$XDG_CONFIG_DIRS}";
systemd.user.sessionVariables.XDG_CONFIG_DIRS = systemd.user.sessionVariables.XDG_CONFIG_DIRS = "${configDirs}\${XDG_CONFIG_DIRS:+:$XDG_CONFIG_DIRS}";
"${configDirs}\${XDG_CONFIG_DIRS:+:$XDG_CONFIG_DIRS}";
}) })
(lib.mkIf (cfg.data != [ ]) { (lib.mkIf (cfg.data != [ ]) {
home.sessionVariables.XDG_DATA_DIRS = home.sessionVariables.XDG_DATA_DIRS = "${dataDirs}\${XDG_DATA_DIRS:+:$XDG_DATA_DIRS}";
"${dataDirs}\${XDG_DATA_DIRS:+:$XDG_DATA_DIRS}";
systemd.user.sessionVariables.XDG_DATA_DIRS = systemd.user.sessionVariables.XDG_DATA_DIRS = "${dataDirs}\${XDG_DATA_DIRS:+:$XDG_DATA_DIRS}";
"${dataDirs}\${XDG_DATA_DIRS:+:$XDG_DATA_DIRS}";
}) })
]; ];
} }

View file

@ -1,19 +1,28 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (lib) literalExpression mkOption types; inherit (lib) literalExpression mkOption types;
cfg = config.xdg.userDirs; cfg = config.xdg.userDirs;
in { in
{
meta.maintainers = with lib.maintainers; [ euxane ]; meta.maintainers = with lib.maintainers; [ euxane ];
imports = [ imports = [
(lib.mkRenamedOptionModule [ "xdg" "userDirs" "publishShare" ] [ (lib.mkRenamedOptionModule
"xdg" [ "xdg" "userDirs" "publishShare" ]
"userDirs" [
"publicShare" "xdg"
]) "userDirs"
"publicShare"
]
)
]; ];
options.xdg.userDirs = { options.xdg.userDirs = {
@ -33,64 +42,56 @@ in {
desktop = mkOption { desktop = mkOption {
type = with types; nullOr (coercedTo path toString str); type = with types; nullOr (coercedTo path toString str);
default = "${config.home.homeDirectory}/Desktop"; default = "${config.home.homeDirectory}/Desktop";
defaultText = defaultText = literalExpression ''"''${config.home.homeDirectory}/Desktop"'';
literalExpression ''"''${config.home.homeDirectory}/Desktop"'';
description = "The Desktop directory."; description = "The Desktop directory.";
}; };
documents = mkOption { documents = mkOption {
type = with types; nullOr (coercedTo path toString str); type = with types; nullOr (coercedTo path toString str);
default = "${config.home.homeDirectory}/Documents"; default = "${config.home.homeDirectory}/Documents";
defaultText = defaultText = literalExpression ''"''${config.home.homeDirectory}/Documents"'';
literalExpression ''"''${config.home.homeDirectory}/Documents"'';
description = "The Documents directory."; description = "The Documents directory.";
}; };
download = mkOption { download = mkOption {
type = with types; nullOr (coercedTo path toString str); type = with types; nullOr (coercedTo path toString str);
default = "${config.home.homeDirectory}/Downloads"; default = "${config.home.homeDirectory}/Downloads";
defaultText = defaultText = literalExpression ''"''${config.home.homeDirectory}/Downloads"'';
literalExpression ''"''${config.home.homeDirectory}/Downloads"'';
description = "The Downloads directory."; description = "The Downloads directory.";
}; };
music = mkOption { music = mkOption {
type = with types; nullOr (coercedTo path toString str); type = with types; nullOr (coercedTo path toString str);
default = "${config.home.homeDirectory}/Music"; default = "${config.home.homeDirectory}/Music";
defaultText = defaultText = literalExpression ''"''${config.home.homeDirectory}/Music"'';
literalExpression ''"''${config.home.homeDirectory}/Music"'';
description = "The Music directory."; description = "The Music directory.";
}; };
pictures = mkOption { pictures = mkOption {
type = with types; nullOr (coercedTo path toString str); type = with types; nullOr (coercedTo path toString str);
default = "${config.home.homeDirectory}/Pictures"; default = "${config.home.homeDirectory}/Pictures";
defaultText = defaultText = literalExpression ''"''${config.home.homeDirectory}/Pictures"'';
literalExpression ''"''${config.home.homeDirectory}/Pictures"'';
description = "The Pictures directory."; description = "The Pictures directory.";
}; };
publicShare = mkOption { publicShare = mkOption {
type = with types; nullOr (coercedTo path toString str); type = with types; nullOr (coercedTo path toString str);
default = "${config.home.homeDirectory}/Public"; default = "${config.home.homeDirectory}/Public";
defaultText = defaultText = literalExpression ''"''${config.home.homeDirectory}/Public"'';
literalExpression ''"''${config.home.homeDirectory}/Public"'';
description = "The Public share directory."; description = "The Public share directory.";
}; };
templates = mkOption { templates = mkOption {
type = with types; nullOr (coercedTo path toString str); type = with types; nullOr (coercedTo path toString str);
default = "${config.home.homeDirectory}/Templates"; default = "${config.home.homeDirectory}/Templates";
defaultText = defaultText = literalExpression ''"''${config.home.homeDirectory}/Templates"'';
literalExpression ''"''${config.home.homeDirectory}/Templates"'';
description = "The Templates directory."; description = "The Templates directory.";
}; };
videos = mkOption { videos = mkOption {
type = with types; nullOr (coercedTo path toString str); type = with types; nullOr (coercedTo path toString str);
default = "${config.home.homeDirectory}/Videos"; default = "${config.home.homeDirectory}/Videos";
defaultText = defaultText = literalExpression ''"''${config.home.homeDirectory}/Videos"'';
literalExpression ''"''${config.home.homeDirectory}/Videos"'';
description = "The Videos directory."; description = "The Videos directory.";
}; };
@ -106,41 +107,48 @@ in {
description = "Other user directories."; description = "Other user directories.";
}; };
createDirectories = createDirectories = lib.mkEnableOption "automatic creation of the XDG user directories";
lib.mkEnableOption "automatic creation of the XDG user directories";
}; };
config = let config =
directories = (lib.filterAttrs (n: v: !isNull v) { let
XDG_DESKTOP_DIR = cfg.desktop; directories =
XDG_DOCUMENTS_DIR = cfg.documents; (lib.filterAttrs (n: v: !isNull v) {
XDG_DOWNLOAD_DIR = cfg.download; XDG_DESKTOP_DIR = cfg.desktop;
XDG_MUSIC_DIR = cfg.music; XDG_DOCUMENTS_DIR = cfg.documents;
XDG_PICTURES_DIR = cfg.pictures; XDG_DOWNLOAD_DIR = cfg.download;
XDG_PUBLICSHARE_DIR = cfg.publicShare; XDG_MUSIC_DIR = cfg.music;
XDG_TEMPLATES_DIR = cfg.templates; XDG_PICTURES_DIR = cfg.pictures;
XDG_VIDEOS_DIR = cfg.videos; XDG_PUBLICSHARE_DIR = cfg.publicShare;
}) // cfg.extraConfig; XDG_TEMPLATES_DIR = cfg.templates;
in lib.mkIf cfg.enable { XDG_VIDEOS_DIR = cfg.videos;
assertions = [ })
(lib.hm.assertions.assertPlatform "xdg.userDirs" pkgs lib.platforms.linux) // cfg.extraConfig;
]; in
lib.mkIf cfg.enable {
assertions = [
(lib.hm.assertions.assertPlatform "xdg.userDirs" pkgs lib.platforms.linux)
];
xdg.configFile."user-dirs.dirs".text = let xdg.configFile."user-dirs.dirs".text =
# For some reason, these need to be wrapped with quotes to be valid. let
wrapped = lib.mapAttrs (_: value: ''"${value}"'') directories; # For some reason, these need to be wrapped with quotes to be valid.
in lib.generators.toKeyValue { } wrapped; wrapped = lib.mapAttrs (_: value: ''"${value}"'') directories;
in
lib.generators.toKeyValue { } wrapped;
xdg.configFile."user-dirs.conf".text = "enabled=False"; xdg.configFile."user-dirs.conf".text = "enabled=False";
home.sessionVariables = directories; home.sessionVariables = directories;
home.activation.createXdgUserDirectories = lib.mkIf cfg.createDirectories home.activation.createXdgUserDirectories = lib.mkIf cfg.createDirectories (
(let let
directoriesList = lib.attrValues directories; directoriesList = lib.attrValues directories;
mkdir = mkdir = (dir: ''[[ -L "${dir}" ]] || run mkdir -p $VERBOSE_ARG "${dir}"'');
(dir: ''[[ -L "${dir}" ]] || run mkdir -p $VERBOSE_ARG "${dir}"''); in
in lib.hm.dag.entryAfter [ "linkGeneration" ] lib.hm.dag.entryAfter [ "linkGeneration" ] (
(lib.strings.concatMapStringsSep "\n" mkdir directoriesList)); lib.strings.concatMapStringsSep "\n" mkdir directoriesList
}; )
);
};
} }

View file

@ -1,25 +1,40 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (lib) mkOptionDefault mkIf mkOption types; inherit (lib)
mkOptionDefault
mkIf
mkOption
types
;
cfg = config.xdg; cfg = config.xdg;
fileType = (import ../lib/file-type.nix { fileType =
inherit (config.home) homeDirectory; (import ../lib/file-type.nix {
inherit lib pkgs; inherit (config.home) homeDirectory;
}).fileType; inherit lib pkgs;
}).fileType;
defaultCacheHome = "${config.home.homeDirectory}/.cache"; defaultCacheHome = "${config.home.homeDirectory}/.cache";
defaultConfigHome = "${config.home.homeDirectory}/.config"; defaultConfigHome = "${config.home.homeDirectory}/.config";
defaultDataHome = "${config.home.homeDirectory}/.local/share"; defaultDataHome = "${config.home.homeDirectory}/.local/share";
defaultStateHome = "${config.home.homeDirectory}/.local/state"; defaultStateHome = "${config.home.homeDirectory}/.local/state";
getEnvFallback = name: fallback: getEnvFallback =
let value = builtins.getEnv name; name: fallback:
in if value != "" then value else fallback; let
value = builtins.getEnv name;
in
if value != "" then value else fallback;
in { in
{
options.xdg = { options.xdg = {
enable = lib.mkEnableOption "management of XDG base directories"; enable = lib.mkEnableOption "management of XDG base directories";
@ -64,8 +79,7 @@ in {
}; };
dataFile = mkOption { dataFile = mkOption {
type = type = fileType "xdg.dataFile" "<varname>xdg.dataHome</varname>" cfg.dataHome;
fileType "xdg.dataFile" "<varname>xdg.dataHome</varname>" cfg.dataHome;
default = { }; default = { };
description = '' description = ''
Attribute set of files to link into the user's XDG Attribute set of files to link into the user's XDG
@ -85,8 +99,7 @@ in {
}; };
stateFile = mkOption { stateFile = mkOption {
type = fileType "xdg.stateFile" "<varname>xdg.stateHome</varname>" type = fileType "xdg.stateFile" "<varname>xdg.stateHome</varname>" cfg.stateHome;
cfg.stateHome;
default = { }; default = { };
description = '' description = ''
Attribute set of files to link into the user's XDG Attribute set of files to link into the user's XDG
@ -107,34 +120,32 @@ in {
}; };
config = lib.mkMerge [ config = lib.mkMerge [
(let (
variables = { let
XDG_CACHE_HOME = cfg.cacheHome; variables = {
XDG_CONFIG_HOME = cfg.configHome; XDG_CACHE_HOME = cfg.cacheHome;
XDG_DATA_HOME = cfg.dataHome; XDG_CONFIG_HOME = cfg.configHome;
XDG_STATE_HOME = cfg.stateHome; XDG_DATA_HOME = cfg.dataHome;
}; XDG_STATE_HOME = cfg.stateHome;
in mkIf cfg.enable { };
xdg.cacheHome = mkOptionDefault defaultCacheHome; in
xdg.configHome = mkOptionDefault defaultConfigHome; mkIf cfg.enable {
xdg.dataHome = mkOptionDefault defaultDataHome; xdg.cacheHome = mkOptionDefault defaultCacheHome;
xdg.stateHome = mkOptionDefault defaultStateHome; xdg.configHome = mkOptionDefault defaultConfigHome;
xdg.dataHome = mkOptionDefault defaultDataHome;
xdg.stateHome = mkOptionDefault defaultStateHome;
home.sessionVariables = variables; home.sessionVariables = variables;
systemd.user.sessionVariables = systemd.user.sessionVariables = mkIf pkgs.stdenv.hostPlatform.isLinux variables;
mkIf pkgs.stdenv.hostPlatform.isLinux variables; }
}) )
# Legacy non-deterministic setup. # Legacy non-deterministic setup.
(mkIf (!cfg.enable && lib.versionOlder config.home.stateVersion "20.09") { (mkIf (!cfg.enable && lib.versionOlder config.home.stateVersion "20.09") {
xdg.cacheHome = xdg.cacheHome = mkOptionDefault (getEnvFallback "XDG_CACHE_HOME" defaultCacheHome);
mkOptionDefault (getEnvFallback "XDG_CACHE_HOME" defaultCacheHome); xdg.configHome = mkOptionDefault (getEnvFallback "XDG_CONFIG_HOME" defaultConfigHome);
xdg.configHome = xdg.dataHome = mkOptionDefault (getEnvFallback "XDG_DATA_HOME" defaultDataHome);
mkOptionDefault (getEnvFallback "XDG_CONFIG_HOME" defaultConfigHome); xdg.stateHome = mkOptionDefault (getEnvFallback "XDG_STATE_HOME" defaultStateHome);
xdg.dataHome =
mkOptionDefault (getEnvFallback "XDG_DATA_HOME" defaultDataHome);
xdg.stateHome =
mkOptionDefault (getEnvFallback "XDG_STATE_HOME" defaultStateHome);
}) })
# "Modern" deterministic setup. # "Modern" deterministic setup.
@ -147,18 +158,10 @@ in {
{ {
home.file = lib.mkMerge [ home.file = lib.mkMerge [
(lib.mapAttrs' (lib.mapAttrs' (name: file: lib.nameValuePair "${cfg.cacheHome}/${name}" file) cfg.cacheFile)
(name: file: lib.nameValuePair "${cfg.cacheHome}/${name}" file) (lib.mapAttrs' (name: file: lib.nameValuePair "${cfg.configHome}/${name}" file) cfg.configFile)
cfg.cacheFile) (lib.mapAttrs' (name: file: lib.nameValuePair "${cfg.dataHome}/${name}" file) cfg.dataFile)
(lib.mapAttrs' (lib.mapAttrs' (name: file: lib.nameValuePair "${cfg.stateHome}/${name}" file) cfg.stateFile)
(name: file: lib.nameValuePair "${cfg.configHome}/${name}" file)
cfg.configFile)
(lib.mapAttrs'
(name: file: lib.nameValuePair "${cfg.dataHome}/${name}" file)
cfg.dataFile)
(lib.mapAttrs'
(name: file: lib.nameValuePair "${cfg.stateHome}/${name}" file)
cfg.stateFile)
{ "${cfg.cacheHome}/.keep".text = ""; } { "${cfg.cacheHome}/.keep".text = ""; }
{ "${cfg.stateHome}/.keep".text = ""; } { "${cfg.stateHome}/.keep".text = ""; }
]; ];

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (lib) mkOption types; inherit (lib) mkOption types;
@ -8,7 +13,11 @@ let
xfIntVariant = types.submodule { xfIntVariant = types.submodule {
options = { options = {
type = mkOption { type = mkOption {
type = types.enum [ "int" "uint" "uint64" ]; type = types.enum [
"int"
"uint"
"uint64"
];
description = '' description = ''
To distinguish between int, uint and uint64 in xfconf, To distinguish between int, uint and uint64 in xfconf,
you can specify the type in xfconf with this submodule. you can specify the type in xfconf with this submodule.
@ -23,38 +32,50 @@ let
}; };
}; };
withType = v: withType =
if builtins.isAttrs v then [ v:
"-t" if builtins.isAttrs v then
v.type [
"-s" "-t"
(toString v.value) v.type
] else if builtins.isBool v then [ "-s"
"-t" (toString v.value)
"bool" ]
"-s" else if builtins.isBool v then
(if v then "true" else "false") [
] else if builtins.isInt v then [ "-t"
"-t" "bool"
"int" "-s"
"-s" (if v then "true" else "false")
(toString v) ]
] else if builtins.isFloat v then [ else if builtins.isInt v then
"-t" [
"double" "-t"
"-s" "int"
(toString v) "-s"
] else if builtins.isString v then [ (toString v)
"-t" ]
"string" else if builtins.isFloat v then
"-s" [
v "-t"
] else if builtins.isList v then "double"
"-s"
(toString v)
]
else if builtins.isString v then
[
"-t"
"string"
"-s"
v
]
else if builtins.isList v then
[ "-a" ] ++ lib.concatMap withType v [ "-a" ] ++ lib.concatMap withType v
else else
throw "unexpected xfconf type: ${builtins.typeOf v}"; throw "unexpected xfconf type: ${builtins.typeOf v}";
in { in
{
meta.maintainers = [ lib.maintainers.chuangzhu ]; meta.maintainers = [ lib.maintainers.chuangzhu ];
options.xfconf = { options.xfconf = {
@ -73,10 +94,20 @@ in {
}; };
settings = mkOption { settings = mkOption {
type = with types; type =
# xfIntVariant must come AFTER str; otherwise strings are treated as submodule imports... with types;
let value = nullOr (oneOf [ bool int float str xfIntVariant ]); # xfIntVariant must come AFTER str; otherwise strings are treated as submodule imports...
in attrsOf (attrsOf (either value (listOf value))) // { let
value = nullOr (oneOf [
bool
int
float
str
xfIntVariant
]);
in
attrsOf (attrsOf (either value (listOf value)))
// {
description = "xfconf settings"; description = "xfconf settings";
}; };
default = { }; default = { };
@ -99,30 +130,33 @@ in {
}; };
config = lib.mkIf (cfg.enable && cfg.settings != { }) { config = lib.mkIf (cfg.enable && cfg.settings != { }) {
assertions = assertions = [ (lib.hm.assertions.assertPlatform "xfconf" pkgs lib.platforms.linux) ];
[ (lib.hm.assertions.assertPlatform "xfconf" pkgs lib.platforms.linux) ];
home.activation.xfconfSettings = lib.hm.dag.entryAfter [ "installPackages" ] home.activation.xfconfSettings = lib.hm.dag.entryAfter [ "installPackages" ] (
(let let
mkCommand = channel: property: value: '' mkCommand = channel: property: value: ''
run ${pkgs.xfce.xfconf}/bin/xfconf-query \ run ${pkgs.xfce.xfconf}/bin/xfconf-query \
${ ${lib.escapeShellArgs (
lib.escapeShellArgs ([ "-c" channel "-p" "/${property}" ] [
++ (if value == null then "-c"
[ "-r" ] channel
else "-p"
[ "-n" ] ++ withType value)) "/${property}"
} ]
++ (if value == null then [ "-r" ] else [ "-n" ] ++ withType value)
)}
''; '';
commands = lib.mapAttrsToList (channel: properties: commands = lib.mapAttrsToList (
lib.mapAttrsToList (mkCommand channel) properties) cfg.settings; channel: properties: lib.mapAttrsToList (mkCommand channel) properties
) cfg.settings;
load = pkgs.writeShellScript "load-xfconf" '' load = pkgs.writeShellScript "load-xfconf" ''
${config.lib.bash.initHomeManagerLib} ${config.lib.bash.initHomeManagerLib}
${lib.concatMapStrings lib.concatStrings commands} ${lib.concatMapStrings lib.concatStrings commands}
''; '';
in '' in
''
if [[ -v DBUS_SESSION_BUS_ADDRESS ]]; then if [[ -v DBUS_SESSION_BUS_ADDRESS ]]; then
export DBUS_RUN_SESSION_CMD="" export DBUS_RUN_SESSION_CMD=""
else else
@ -132,6 +166,7 @@ in {
run $DBUS_RUN_SESSION_CMD ${load} run $DBUS_RUN_SESSION_CMD ${load}
unset DBUS_RUN_SESSION_CMD unset DBUS_RUN_SESSION_CMD
''); ''
);
}; };
} }

View file

@ -1,492 +1,499 @@
{ pkgs {
pkgs,
# Note, this should be "the standard library" + HM extensions. # Note, this should be "the standard library" + HM extensions.
, lib lib,
# Whether to enable module type checking. # Whether to enable module type checking.
, check ? true check ? true,
# If disabled, the pkgs attribute passed to this function is used instead. # If disabled, the pkgs attribute passed to this function is used instead.
, useNixpkgsModule ? true }: useNixpkgsModule ? true,
}:
let let
modules = [ modules =
./accounts/email.nix [
./accounts/calendar.nix ./accounts/email.nix
./accounts/contacts.nix ./accounts/calendar.nix
./config/home-cursor.nix ./accounts/contacts.nix
./config/i18n.nix ./config/home-cursor.nix
./files.nix ./config/i18n.nix
./home-environment.nix ./files.nix
./i18n/input-method/default.nix ./home-environment.nix
./launchd/default.nix ./i18n/input-method/default.nix
./manual.nix ./launchd/default.nix
./misc/dconf.nix ./manual.nix
./misc/debug.nix ./misc/dconf.nix
./misc/editorconfig.nix ./misc/debug.nix
./misc/fontconfig.nix ./misc/editorconfig.nix
./misc/gtk.nix ./misc/fontconfig.nix
./misc/lib.nix ./misc/gtk.nix
./misc/mozilla-messaging-hosts.nix ./misc/lib.nix
./misc/news.nix ./misc/mozilla-messaging-hosts.nix
./misc/nixgl.nix ./misc/news.nix
./misc/numlock.nix ./misc/nixgl.nix
./misc/pam.nix ./misc/numlock.nix
./misc/qt.nix ./misc/pam.nix
./misc/qt/kconfig.nix ./misc/qt.nix
./misc/shell.nix ./misc/qt/kconfig.nix
./misc/specialisation.nix ./misc/shell.nix
./misc/submodule-support.nix ./misc/specialisation.nix
./misc/tmpfiles.nix ./misc/submodule-support.nix
./misc/uninstall.nix ./misc/tmpfiles.nix
./misc/version.nix ./misc/uninstall.nix
./misc/vte.nix ./misc/version.nix
./misc/xdg-autostart.nix ./misc/vte.nix
./misc/xdg-desktop-entries.nix ./misc/xdg-autostart.nix
./misc/xdg-mime-apps.nix ./misc/xdg-desktop-entries.nix
./misc/xdg-mime.nix ./misc/xdg-mime-apps.nix
./misc/xdg-portal.nix ./misc/xdg-mime.nix
./misc/xdg-system-dirs.nix ./misc/xdg-portal.nix
./misc/xdg-user-dirs.nix ./misc/xdg-system-dirs.nix
./misc/xdg.nix ./misc/xdg-user-dirs.nix
./misc/xfconf.nix ./misc/xdg.nix
./programs/abook.nix ./misc/xfconf.nix
./programs/aerc.nix ./programs/abook.nix
./programs/aerospace.nix ./programs/aerc.nix
./programs/afew.nix ./programs/aerospace.nix
./programs/alacritty.nix ./programs/afew.nix
./programs/alot.nix ./programs/alacritty.nix
./programs/antidote.nix ./programs/alot.nix
./programs/aria2.nix ./programs/antidote.nix
./programs/astroid.nix ./programs/aria2.nix
./programs/atuin.nix ./programs/astroid.nix
./programs/autojump.nix ./programs/atuin.nix
./programs/autorandr.nix ./programs/autojump.nix
./programs/awscli.nix ./programs/autorandr.nix
./programs/bash.nix ./programs/awscli.nix
./programs/bashmount.nix ./programs/bash.nix
./programs/bat.nix ./programs/bashmount.nix
./programs/bacon.nix ./programs/bat.nix
./programs/beets.nix ./programs/bacon.nix
./programs/bemenu.nix ./programs/beets.nix
./programs/borgmatic.nix ./programs/bemenu.nix
./programs/bottom.nix ./programs/borgmatic.nix
./programs/boxxy.nix ./programs/bottom.nix
./programs/broot.nix ./programs/boxxy.nix
./programs/browserpass.nix ./programs/broot.nix
./programs/btop.nix ./programs/browserpass.nix
./programs/bun.nix ./programs/btop.nix
./programs/carapace.nix ./programs/bun.nix
./programs/cava.nix ./programs/carapace.nix
./programs/cavalier.nix ./programs/cava.nix
./programs/chromium.nix ./programs/cavalier.nix
./programs/cmus.nix ./programs/chromium.nix
./programs/command-not-found/command-not-found.nix ./programs/cmus.nix
./programs/comodoro.nix ./programs/command-not-found/command-not-found.nix
./programs/darcs.nix ./programs/comodoro.nix
./programs/dircolors.nix ./programs/darcs.nix
./programs/direnv.nix ./programs/dircolors.nix
./programs/discocss.nix ./programs/direnv.nix
./programs/distrobox.nix ./programs/discocss.nix
./programs/earthly.nix ./programs/distrobox.nix
./programs/eclipse.nix ./programs/earthly.nix
./programs/emacs.nix ./programs/eclipse.nix
./programs/eww.nix ./programs/emacs.nix
./programs/eza.nix ./programs/eww.nix
./programs/fastfetch.nix ./programs/eza.nix
./programs/fd.nix ./programs/fastfetch.nix
./programs/feh.nix ./programs/fd.nix
./programs/firefox.nix ./programs/feh.nix
./programs/fish.nix ./programs/firefox.nix
./programs/floorp.nix ./programs/fish.nix
./programs/foot.nix ./programs/floorp.nix
./programs/freetube.nix ./programs/foot.nix
./programs/fuzzel.nix ./programs/freetube.nix
./programs/fzf.nix ./programs/fuzzel.nix
./programs/gallery-dl.nix ./programs/fzf.nix
./programs/getmail.nix ./programs/gallery-dl.nix
./programs/gh.nix ./programs/getmail.nix
./programs/gh-dash.nix ./programs/gh.nix
./programs/ghostty.nix ./programs/gh-dash.nix
./programs/git-cliff.nix ./programs/ghostty.nix
./programs/git-credential-oauth.nix ./programs/git-cliff.nix
./programs/git-worktree-switcher.nix ./programs/git-credential-oauth.nix
./programs/git.nix ./programs/git-worktree-switcher.nix
./programs/gitui.nix ./programs/git.nix
./programs/gnome-shell.nix ./programs/gitui.nix
./programs/gnome-terminal.nix ./programs/gnome-shell.nix
./programs/go.nix ./programs/gnome-terminal.nix
./programs/gpg.nix ./programs/go.nix
./programs/gradle.nix ./programs/gpg.nix
./programs/granted.nix ./programs/gradle.nix
./programs/havoc.nix ./programs/granted.nix
./programs/helix.nix ./programs/havoc.nix
./programs/hexchat.nix ./programs/helix.nix
./programs/himalaya.nix ./programs/hexchat.nix
./programs/home-manager.nix ./programs/himalaya.nix
./programs/hstr.nix ./programs/home-manager.nix
./programs/htop.nix ./programs/hstr.nix
./programs/hyfetch.nix ./programs/htop.nix
./programs/hyprlock.nix ./programs/hyfetch.nix
./programs/i3blocks.nix ./programs/hyprlock.nix
./programs/i3status-rust.nix ./programs/i3blocks.nix
./programs/i3status.nix ./programs/i3status-rust.nix
./programs/iamb.nix ./programs/i3status.nix
./programs/imv.nix ./programs/iamb.nix
./programs/info.nix ./programs/imv.nix
./programs/ion.nix ./programs/info.nix
./programs/irssi.nix ./programs/ion.nix
./programs/java.nix ./programs/irssi.nix
./programs/jetbrains-remote.nix ./programs/java.nix
./programs/jq.nix ./programs/jetbrains-remote.nix
./programs/jqp.nix ./programs/jq.nix
./programs/jujutsu.nix ./programs/jqp.nix
./programs/joshuto.nix ./programs/jujutsu.nix
./programs/joplin-desktop.nix ./programs/joshuto.nix
./programs/just.nix ./programs/joplin-desktop.nix
./programs/k9s.nix ./programs/just.nix
./programs/kakoune.nix ./programs/k9s.nix
./programs/keychain.nix ./programs/kakoune.nix
./programs/khal.nix ./programs/keychain.nix
./programs/khard.nix ./programs/khal.nix
./programs/kitty.nix ./programs/khard.nix
./programs/kodi.nix ./programs/kitty.nix
./programs/kubecolor.nix ./programs/kodi.nix
./programs/lapce.nix ./programs/kubecolor.nix
./programs/lazydocker.nix ./programs/lapce.nix
./programs/lazygit.nix ./programs/lazydocker.nix
./programs/ledger.nix ./programs/lazygit.nix
./programs/less.nix ./programs/ledger.nix
./programs/lesspipe.nix ./programs/less.nix
./programs/lf.nix ./programs/lesspipe.nix
./programs/librewolf.nix ./programs/lf.nix
./programs/lieer.nix ./programs/librewolf.nix
./programs/looking-glass-client.nix ./programs/lieer.nix
./programs/lsd.nix ./programs/looking-glass-client.nix
./programs/man.nix ./programs/lsd.nix
./programs/mangohud.nix ./programs/man.nix
./programs/matplotlib.nix ./programs/mangohud.nix
./programs/mbsync.nix ./programs/matplotlib.nix
./programs/mcfly.nix ./programs/mbsync.nix
./programs/mercurial.nix ./programs/mcfly.nix
./programs/mergiraf.nix ./programs/mercurial.nix
./programs/micro.nix ./programs/mergiraf.nix
./programs/mise.nix ./programs/micro.nix
./programs/mods.nix ./programs/mise.nix
./programs/mpv.nix ./programs/mods.nix
./programs/mr.nix ./programs/mpv.nix
./programs/msmtp.nix ./programs/mr.nix
./programs/mu.nix ./programs/msmtp.nix
./programs/mujmap.nix ./programs/mu.nix
./programs/navi.nix ./programs/mujmap.nix
./programs/ncmpcpp.nix ./programs/navi.nix
./programs/ncspot.nix ./programs/ncmpcpp.nix
./programs/ne.nix ./programs/ncspot.nix
./programs/neomutt.nix ./programs/ne.nix
./programs/neovide.nix ./programs/neomutt.nix
./programs/neovim.nix ./programs/neovide.nix
./programs/newsboat.nix ./programs/neovim.nix
./programs/nh.nix ./programs/newsboat.nix
./programs/nheko.nix ./programs/nh.nix
./programs/nix-index.nix ./programs/nheko.nix
./programs/nix-your-shell.nix ./programs/nix-index.nix
./programs/nnn.nix ./programs/nix-your-shell.nix
./programs/noti.nix ./programs/nnn.nix
./programs/notmuch.nix ./programs/noti.nix
./programs/nushell.nix ./programs/notmuch.nix
./programs/obs-studio.nix ./programs/nushell.nix
./programs/octant.nix ./programs/obs-studio.nix
./programs/offlineimap.nix ./programs/octant.nix
./programs/oh-my-posh.nix ./programs/offlineimap.nix
./programs/onlyoffice.nix ./programs/oh-my-posh.nix
./programs/opam.nix ./programs/onlyoffice.nix
./programs/openstackclient.nix ./programs/opam.nix
./programs/pandoc.nix ./programs/openstackclient.nix
./programs/papis.nix ./programs/pandoc.nix
./programs/password-store.nix ./programs/papis.nix
./programs/pay-respects.nix ./programs/password-store.nix
./programs/pazi.nix ./programs/pay-respects.nix
./programs/pet.nix ./programs/pazi.nix
./programs/pidgin.nix ./programs/pet.nix
./programs/pistol.nix ./programs/pidgin.nix
./programs/piston-cli.nix ./programs/pistol.nix
./programs/pls.nix ./programs/piston-cli.nix
./programs/poetry.nix ./programs/pls.nix
./programs/powerline-go.nix ./programs/poetry.nix
./programs/pqiv.nix ./programs/powerline-go.nix
./programs/pubs.nix ./programs/pqiv.nix
./programs/pyenv.nix ./programs/pubs.nix
./programs/pylint.nix ./programs/pyenv.nix
./programs/qcal.nix ./programs/pylint.nix
./programs/qutebrowser.nix ./programs/qcal.nix
./programs/ranger.nix ./programs/qutebrowser.nix
./programs/rbw.nix ./programs/ranger.nix
./programs/rclone.nix ./programs/rbw.nix
./programs/readline.nix ./programs/rclone.nix
./programs/rio.nix ./programs/readline.nix
./programs/ripgrep.nix ./programs/rio.nix
./programs/ripgrep-all.nix ./programs/ripgrep.nix
./programs/rofi-pass.nix ./programs/ripgrep-all.nix
./programs/rofi.nix ./programs/rofi-pass.nix
./programs/rtorrent.nix ./programs/rofi.nix
./programs/ruff.nix ./programs/rtorrent.nix
./programs/sagemath.nix ./programs/ruff.nix
./programs/sapling.nix ./programs/sagemath.nix
./programs/sbt.nix ./programs/sapling.nix
./programs/scmpuff.nix ./programs/sbt.nix
./programs/script-directory.nix ./programs/scmpuff.nix
./programs/senpai.nix ./programs/script-directory.nix
./programs/sesh.nix ./programs/senpai.nix
./programs/sftpman.nix ./programs/sesh.nix
./programs/sioyek.nix ./programs/sftpman.nix
./programs/skim.nix ./programs/sioyek.nix
./programs/sm64ex.nix ./programs/skim.nix
./programs/smug.nix ./programs/sm64ex.nix
./programs/spotify-player.nix ./programs/smug.nix
./programs/sqls.nix ./programs/spotify-player.nix
./programs/ssh.nix ./programs/sqls.nix
./programs/starship.nix ./programs/ssh.nix
./programs/streamlink.nix ./programs/starship.nix
./programs/swayimg.nix ./programs/streamlink.nix
./programs/swaylock.nix ./programs/swayimg.nix
./programs/swayr.nix ./programs/swaylock.nix
./programs/taskwarrior.nix ./programs/swayr.nix
./programs/tealdeer.nix ./programs/taskwarrior.nix
./programs/terminator.nix ./programs/tealdeer.nix
./programs/termite.nix ./programs/terminator.nix
./programs/tex-fmt.nix ./programs/termite.nix
./programs/texlive.nix ./programs/tex-fmt.nix
./programs/thefuck.nix ./programs/texlive.nix
./programs/thunderbird.nix ./programs/thefuck.nix
./programs/timidity.nix ./programs/thunderbird.nix
./programs/tint2.nix ./programs/timidity.nix
./programs/tiny.nix ./programs/tint2.nix
./programs/tmate.nix ./programs/tiny.nix
./programs/tmux.nix ./programs/tmate.nix
./programs/tofi.nix ./programs/tmux.nix
./programs/todoman.nix ./programs/tofi.nix
./programs/topgrade.nix ./programs/todoman.nix
./programs/translate-shell.nix ./programs/topgrade.nix
./programs/urxvt.nix ./programs/translate-shell.nix
./programs/vdirsyncer.nix ./programs/urxvt.nix
./programs/vifm.nix ./programs/vdirsyncer.nix
./programs/vim-vint.nix ./programs/vifm.nix
./programs/vim.nix ./programs/vim-vint.nix
./programs/vinegar.nix ./programs/vim.nix
./programs/vscode.nix ./programs/vinegar.nix
./programs/vscode/haskell.nix ./programs/vscode.nix
./programs/pywal.nix ./programs/vscode/haskell.nix
./programs/rbenv.nix ./programs/pywal.nix
./programs/watson.nix ./programs/rbenv.nix
./programs/waylogout.nix ./programs/watson.nix
./programs/waybar.nix ./programs/waylogout.nix
./programs/wezterm.nix ./programs/waybar.nix
./programs/wlogout.nix ./programs/wezterm.nix
./programs/wofi.nix ./programs/wlogout.nix
./programs/xmobar.nix ./programs/wofi.nix
./programs/xplr.nix ./programs/xmobar.nix
./programs/yambar.nix ./programs/xplr.nix
./programs/yazi.nix ./programs/yambar.nix
./programs/yt-dlp.nix ./programs/yazi.nix
./programs/z-lua.nix ./programs/yt-dlp.nix
./programs/zathura.nix ./programs/z-lua.nix
./programs/zed-editor.nix ./programs/zathura.nix
./programs/zellij.nix ./programs/zed-editor.nix
./programs/zk.nix ./programs/zellij.nix
./programs/zoxide.nix ./programs/zk.nix
./programs/zplug.nix ./programs/zoxide.nix
./programs/zsh.nix ./programs/zplug.nix
./programs/zsh/prezto.nix ./programs/zsh.nix
./programs/zsh/zsh-abbr.nix ./programs/zsh/prezto.nix
./services/activitywatch.nix ./programs/zsh/zsh-abbr.nix
./services/amberol.nix ./services/activitywatch.nix
./services/arrpc.nix ./services/amberol.nix
./services/autorandr.nix ./services/arrpc.nix
./services/avizo.nix ./services/autorandr.nix
./services/barrier.nix ./services/avizo.nix
./services/batsignal.nix ./services/barrier.nix
./services/betterlockscreen.nix ./services/batsignal.nix
./services/blanket.nix ./services/betterlockscreen.nix
./services/blueman-applet.nix ./services/blanket.nix
./services/borgmatic.nix ./services/blueman-applet.nix
./services/cachix-agent.nix ./services/borgmatic.nix
./services/caffeine.nix ./services/cachix-agent.nix
./services/cbatticon.nix ./services/caffeine.nix
./services/cliphist.nix ./services/cbatticon.nix
./services/clipman.nix ./services/cliphist.nix
./services/clipmenu.nix ./services/clipman.nix
./services/clipse.nix ./services/clipmenu.nix
./services/comodoro.nix ./services/clipse.nix
./services/conky.nix ./services/comodoro.nix
./services/copyq.nix ./services/conky.nix
./services/darkman.nix ./services/copyq.nix
./services/davmail.nix ./services/darkman.nix
./services/devilspie2.nix ./services/davmail.nix
./services/dropbox.nix ./services/devilspie2.nix
./services/dunst.nix ./services/dropbox.nix
./services/dwm-status.nix ./services/dunst.nix
./services/easyeffects.nix ./services/dwm-status.nix
./services/emacs.nix ./services/easyeffects.nix
./services/etesync-dav.nix ./services/emacs.nix
./services/espanso.nix ./services/etesync-dav.nix
./services/flameshot.nix ./services/espanso.nix
./services/fluidsynth.nix ./services/flameshot.nix
./services/fnott.nix ./services/fluidsynth.nix
./services/fusuma.nix ./services/fnott.nix
./services/getmail.nix ./services/fusuma.nix
./services/git-sync.nix ./services/getmail.nix
./services/glance.nix ./services/git-sync.nix
./services/gnome-keyring.nix ./services/glance.nix
./services/gpg-agent.nix ./services/gnome-keyring.nix
./services/grobi.nix ./services/gpg-agent.nix
./services/gromit-mpx.nix ./services/grobi.nix
./services/home-manager-auto-expire.nix ./services/gromit-mpx.nix
./services/home-manager-auto-upgrade.nix ./services/home-manager-auto-expire.nix
./services/hound.nix ./services/home-manager-auto-upgrade.nix
./services/hypridle.nix ./services/hound.nix
./services/hyprpaper.nix ./services/hypridle.nix
./services/hyprpolkitagent.nix ./services/hyprpaper.nix
./services/imapnotify.nix ./services/hyprpolkitagent.nix
./services/jankyborders.nix ./services/imapnotify.nix
./services/kanshi.nix ./services/jankyborders.nix
./services/kbfs.nix ./services/kanshi.nix
./services/kdeconnect.nix ./services/kbfs.nix
./services/keybase.nix ./services/kdeconnect.nix
./services/keynav.nix ./services/keybase.nix
./services/librespot.nix ./services/keynav.nix
./services/lieer.nix ./services/librespot.nix
./services/linux-wallpaperengine.nix ./services/lieer.nix
./services/listenbrainz-mpd.nix ./services/linux-wallpaperengine.nix
./services/lorri.nix ./services/listenbrainz-mpd.nix
./services/lxqt-policykit-agent.nix ./services/lorri.nix
./services/macos-remap-keys ./services/lxqt-policykit-agent.nix
./services/mako.nix ./services/macos-remap-keys
./services/mbsync.nix ./services/mako.nix
./services/megasync.nix ./services/mbsync.nix
./services/mopidy.nix ./services/megasync.nix
./services/mpd.nix ./services/mopidy.nix
./services/mpdris2.nix ./services/mpd.nix
./services/mpdscribble.nix ./services/mpdris2.nix
./services/mpd-discord-rpc.nix ./services/mpdscribble.nix
./services/mpd-mpris.nix ./services/mpd-discord-rpc.nix
./services/mpris-proxy.nix ./services/mpd-mpris.nix
./services/muchsync.nix ./services/mpris-proxy.nix
./services/network-manager-applet.nix ./services/muchsync.nix
./services/nextcloud-client.nix ./services/network-manager-applet.nix
./services/nix-gc.nix ./services/nextcloud-client.nix
./services/notify-osd.nix ./services/nix-gc.nix
./services/ollama.nix ./services/notify-osd.nix
./services/opensnitch-ui.nix ./services/ollama.nix
./services/osmscout-server.nix ./services/opensnitch-ui.nix
./services/owncloud-client.nix ./services/osmscout-server.nix
./services/pantalaimon.nix ./services/owncloud-client.nix
./services/parcellite.nix ./services/pantalaimon.nix
./services/pass-secret-service.nix ./services/parcellite.nix
./services/pasystray.nix ./services/pass-secret-service.nix
./services/pbgopy.nix ./services/pasystray.nix
./services/picom.nix ./services/pbgopy.nix
./services/plan9port.nix ./services/picom.nix
./services/playerctld.nix ./services/plan9port.nix
./services/plex-mpv-shim.nix ./services/playerctld.nix
./services/podman-linux ./services/plex-mpv-shim.nix
./services/polkit-gnome.nix ./services/podman-linux
./services/polybar.nix ./services/polkit-gnome.nix
./services/poweralertd.nix ./services/polybar.nix
./services/psd.nix ./services/poweralertd.nix
./services/pueue.nix ./services/psd.nix
./services/pulseeffects.nix ./services/pueue.nix
./services/random-background.nix ./services/pulseeffects.nix
./services/recoll.nix ./services/random-background.nix
./services/redshift-gammastep/gammastep.nix ./services/recoll.nix
./services/redshift-gammastep/redshift.nix ./services/redshift-gammastep/gammastep.nix
./services/remmina.nix ./services/redshift-gammastep/redshift.nix
./services/rsibreak.nix ./services/remmina.nix
./services/safeeyes.nix ./services/rsibreak.nix
./services/screen-locker.nix ./services/safeeyes.nix
./services/sctd.nix ./services/screen-locker.nix
./services/signaturepdf.nix ./services/sctd.nix
./services/skhd.nix ./services/signaturepdf.nix
./services/snixembed.nix ./services/skhd.nix
./services/spotifyd.nix ./services/snixembed.nix
./services/ssh-agent.nix ./services/spotifyd.nix
./services/stalonetray.nix ./services/ssh-agent.nix
./services/status-notifier-watcher.nix ./services/stalonetray.nix
./services/swayidle.nix ./services/status-notifier-watcher.nix
./services/swaync.nix ./services/swayidle.nix
./services/swayosd.nix ./services/swaync.nix
./services/swww.nix ./services/swayosd.nix
./services/sxhkd.nix ./services/swww.nix
./services/syncthing.nix ./services/sxhkd.nix
./services/systembus-notify.nix ./services/syncthing.nix
./services/taffybar.nix ./services/systembus-notify.nix
./services/tahoe-lafs.nix ./services/taffybar.nix
./services/taskwarrior-sync.nix ./services/tahoe-lafs.nix
./services/tldr-update.nix ./services/taskwarrior-sync.nix
./services/trayer.nix ./services/tldr-update.nix
./services/trayscale.nix ./services/trayer.nix
./services/twmn.nix ./services/trayscale.nix
./services/udiskie.nix ./services/twmn.nix
./services/unclutter.nix ./services/udiskie.nix
./services/unison.nix ./services/unclutter.nix
./services/vdirsyncer.nix ./services/unison.nix
./services/volnoti.nix ./services/vdirsyncer.nix
./services/window-managers/awesome.nix ./services/volnoti.nix
./services/window-managers/bspwm/default.nix ./services/window-managers/awesome.nix
./services/window-managers/fluxbox.nix ./services/window-managers/bspwm/default.nix
./services/window-managers/herbstluftwm.nix ./services/window-managers/fluxbox.nix
./services/window-managers/hyprland.nix ./services/window-managers/herbstluftwm.nix
./services/window-managers/i3-sway/i3.nix ./services/window-managers/hyprland.nix
./services/window-managers/i3-sway/sway.nix ./services/window-managers/i3-sway/i3.nix
./services/window-managers/i3-sway/swaynag.nix ./services/window-managers/i3-sway/sway.nix
./services/window-managers/river.nix ./services/window-managers/i3-sway/swaynag.nix
./services/window-managers/spectrwm.nix ./services/window-managers/river.nix
./services/window-managers/wayfire.nix ./services/window-managers/spectrwm.nix
./services/window-managers/xmonad.nix ./services/window-managers/wayfire.nix
./services/wlsunset.nix ./services/window-managers/xmonad.nix
./services/wluma.nix ./services/wlsunset.nix
./services/wob.nix ./services/wluma.nix
./services/wpaperd.nix ./services/wob.nix
./services/xcape.nix ./services/wpaperd.nix
./services/xembed-sni-proxy.nix ./services/xcape.nix
./services/xidlehook.nix ./services/xembed-sni-proxy.nix
./services/xscreensaver.nix ./services/xidlehook.nix
./services/xsettingsd.nix ./services/xscreensaver.nix
./services/xsuspender.nix ./services/xsettingsd.nix
./services/yubikey-agent.nix ./services/xsuspender.nix
./systemd.nix ./services/yubikey-agent.nix
./targets/darwin ./systemd.nix
./targets/generic-linux.nix ./targets/darwin
./wayland.nix ./targets/generic-linux.nix
./xresources.nix ./wayland.nix
./xsession.nix ./xresources.nix
./misc/nix.nix ./xsession.nix
(pkgs.path + "/nixos/modules/misc/assertions.nix") ./misc/nix.nix
(pkgs.path + "/nixos/modules/misc/meta.nix") (pkgs.path + "/nixos/modules/misc/assertions.nix")
(pkgs.path + "/nixos/modules/misc/meta.nix")
(lib.mkRemovedOptionModule [ "services" "password-store-sync" ] '' (lib.mkRemovedOptionModule [ "services" "password-store-sync" ] ''
Use services.git-sync instead. Use services.git-sync instead.
'') '')
(lib.mkRemovedOptionModule [ "services" "keepassx" ] '' (lib.mkRemovedOptionModule [ "services" "keepassx" ] ''
KeePassX is no longer maintained. KeePassX is no longer maintained.
'') '')
] ++ lib.optional useNixpkgsModule ./misc/nixpkgs.nix ]
++ lib.optional useNixpkgsModule ./misc/nixpkgs.nix
++ lib.optional (!useNixpkgsModule) ./misc/nixpkgs-disabled.nix; ++ lib.optional (!useNixpkgsModule) ./misc/nixpkgs-disabled.nix;
pkgsModule = { config, ... }: { pkgsModule =
config = { { config, ... }:
_module.args.baseModules = modules; {
_module.args.pkgsPath = lib.mkDefault config =
(if lib.versionAtLeast config.home.stateVersion "20.09" then {
pkgs.path _module.args.baseModules = modules;
else _module.args.pkgsPath = lib.mkDefault (
<nixpkgs>); if lib.versionAtLeast config.home.stateVersion "20.09" then pkgs.path else <nixpkgs>
_module.args.pkgs = lib.mkDefault pkgs; );
_module.check = check; _module.args.pkgs = lib.mkDefault pkgs;
lib = lib.hm; _module.check = check;
} // lib.optionalAttrs useNixpkgsModule { lib = lib.hm;
nixpkgs.system = lib.mkDefault pkgs.stdenv.hostPlatform.system; }
// lib.optionalAttrs useNixpkgsModule {
nixpkgs.system = lib.mkDefault pkgs.stdenv.hostPlatform.system;
};
}; };
};
in modules ++ [ pkgsModule ] in
modules ++ [ pkgsModule ]

View file

@ -1,6 +1,13 @@
{ config, lib, pkgs, ... }: {
let cfg = config.programs.abook; config,
in { lib,
pkgs,
...
}:
let
cfg = config.programs.abook;
in
{
options.programs.abook = { options.programs.abook = {
enable = lib.mkEnableOption "Abook"; enable = lib.mkEnableOption "Abook";

View file

@ -1,17 +1,29 @@
{ config, lib, confSections, confSection, ... }: {
config,
lib,
confSections,
confSection,
...
}:
let let
inherit (lib) literalExpression mkOption types; inherit (lib) literalExpression mkOption types;
mapAttrNames = f: attr: mapAttrNames =
lib.listToAttrs (lib.attrValues (lib.mapAttrs (k: v: { f: attr:
name = f k; lib.listToAttrs (
value = v; lib.attrValues (
}) attr)); lib.mapAttrs (k: v: {
name = f k;
value = v;
}) attr
)
);
addAccountName = name: k: "${k}:account=${name}"; addAccountName = name: k: "${k}:account=${name}";
oauth2Params = mkOption { oauth2Params = mkOption {
type = with types; type =
with types;
nullOr (submodule { nullOr (submodule {
options = { options = {
token_endpoint = mkOption { token_endpoint = mkOption {
@ -37,7 +49,9 @@ let
}; };
}); });
default = null; default = null;
example = { token_endpoint = "<token_endpoint>"; }; example = {
token_endpoint = "<token_endpoint>";
};
description = '' description = ''
Sets the oauth2 params if authentication mechanism oauthbearer or Sets the oauth2 params if authentication mechanism oauthbearer or
xoauth2 is used. xoauth2 is used.
@ -45,157 +59,188 @@ let
''; '';
}; };
in { in
{
type = mkOption { type = mkOption {
type = types.attrsOf (types.submodule { type = types.attrsOf (
options.aerc = { types.submodule {
enable = lib.mkEnableOption "aerc"; options.aerc = {
extraAccounts = mkOption { enable = lib.mkEnableOption "aerc";
type = confSection; extraAccounts = mkOption {
default = { }; type = confSection;
example = default = { };
literalExpression ''{ source = "maildir://~/Maildir/example"; }''; example = literalExpression ''{ source = "maildir://~/Maildir/example"; }'';
description = '' description = ''
Extra config added to the configuration section for this account in Extra config added to the configuration section for this account in
{file}`$HOME/.config/aerc/accounts.conf`. {file}`$HOME/.config/aerc/accounts.conf`.
See {manpage}`aerc-accounts(5)`. See {manpage}`aerc-accounts(5)`.
''; '';
};
extraBinds = mkOption {
type = confSections;
default = { };
example = literalExpression ''{ messages = { d = ":move ''${folder.trash}<Enter>"; }; }'';
description = ''
Extra bindings specific to this account, added to
{file}`$HOME/.config/aerc/binds.conf`.
See {manpage}`aerc-binds(5)`.
'';
};
extraConfig = mkOption {
type = confSections;
default = { };
example = literalExpression "{ ui = { sidebar-width = 25; }; }";
description = ''
Config specific to this account, added to {file}`$HOME/.config/aerc/aerc.conf`.
Aerc only supports per-account UI configuration.
For other sections of {file}`$HOME/.config/aerc/aerc.conf`,
use `programs.aerc.extraConfig`.
See {manpage}`aerc-config(5)`.
'';
};
imapAuth = mkOption {
type =
with types;
nullOr (enum [
"oauthbearer"
"xoauth2"
]);
default = null;
example = "auth";
description = ''
Sets the authentication mechanism if imap is used as the incoming
method.
See {manpage}`aerc-imap(5)`.
'';
};
imapOauth2Params = oauth2Params;
smtpAuth = mkOption {
type =
with types;
nullOr (enum [
"none"
"plain"
"login"
"oauthbearer"
"xoauth2"
]);
default = "plain";
example = "auth";
description = ''
Sets the authentication mechanism if smtp is used as the outgoing
method.
See {manpage}`aerc-smtp(5)`.
'';
};
smtpOauth2Params = oauth2Params;
}; };
}
extraBinds = mkOption { );
type = confSections;
default = { };
example = literalExpression
''{ messages = { d = ":move ''${folder.trash}<Enter>"; }; }'';
description = ''
Extra bindings specific to this account, added to
{file}`$HOME/.config/aerc/binds.conf`.
See {manpage}`aerc-binds(5)`.
'';
};
extraConfig = mkOption {
type = confSections;
default = { };
example = literalExpression "{ ui = { sidebar-width = 25; }; }";
description = ''
Config specific to this account, added to {file}`$HOME/.config/aerc/aerc.conf`.
Aerc only supports per-account UI configuration.
For other sections of {file}`$HOME/.config/aerc/aerc.conf`,
use `programs.aerc.extraConfig`.
See {manpage}`aerc-config(5)`.
'';
};
imapAuth = mkOption {
type = with types; nullOr (enum [ "oauthbearer" "xoauth2" ]);
default = null;
example = "auth";
description = ''
Sets the authentication mechanism if imap is used as the incoming
method.
See {manpage}`aerc-imap(5)`.
'';
};
imapOauth2Params = oauth2Params;
smtpAuth = mkOption {
type = with types;
nullOr (enum [ "none" "plain" "login" "oauthbearer" "xoauth2" ]);
default = "plain";
example = "auth";
description = ''
Sets the authentication mechanism if smtp is used as the outgoing
method.
See {manpage}`aerc-smtp(5)`.
'';
};
smtpOauth2Params = oauth2Params;
};
});
}; };
mkAccount = name: account: mkAccount =
name: account:
let let
nullOrMap = f: v: if v == null then v else f v; nullOrMap = f: v: if v == null then v else f v;
optPort = port: if port != null then ":${toString port}" else ""; optPort = port: if port != null then ":${toString port}" else "";
optAttr = k: v: optAttr = k: v: if v != null && v != [ ] && v != "" then { ${k} = v; } else { };
if v != null && v != [ ] && v != "" then { ${k} = v; } else { };
optPwCmd = k: p: optPwCmd = k: p: optAttr "${k}-cred-cmd" (nullOrMap (lib.concatStringsSep " ") p);
optAttr "${k}-cred-cmd" (nullOrMap (lib.concatStringsSep " ") p);
useOauth = auth: builtins.elem auth [ "oauthbearer" "xoauth2" ]; useOauth =
auth:
builtins.elem auth [
"oauthbearer"
"xoauth2"
];
oauthParams = { auth, params }: oauthParams =
{ auth, params }:
if useOauth auth && params != null && params != { } then if useOauth auth && params != null && params != { } then
"?" + builtins.concatStringsSep "&" "?"
(lib.attrsets.mapAttrsToList (k: v: k + "=" + lib.strings.escapeURL v) + builtins.concatStringsSep "&" (
(lib.attrsets.filterAttrs (k: v: v != null) params)) lib.attrsets.mapAttrsToList (k: v: k + "=" + lib.strings.escapeURL v) (
lib.attrsets.filterAttrs (k: v: v != null) params
)
)
else else
""; "";
mkConfig = { mkConfig = {
maildir = cfg: { maildir = cfg: {
source = source = "maildir://${config.accounts.email.maildirBasePath}/${cfg.maildir.path}";
"maildir://${config.accounts.email.maildirBasePath}/${cfg.maildir.path}";
}; };
maildirpp = cfg: { maildirpp = cfg: {
source = source = "maildirpp://${config.accounts.email.maildirBasePath}/${cfg.maildir.path}/Inbox";
"maildirpp://${config.accounts.email.maildirBasePath}/${cfg.maildir.path}/Inbox";
}; };
imap = { userName, imap, passwordCommand, aerc, ... }@cfg: imap =
{
userName,
imap,
passwordCommand,
aerc,
...
}@cfg:
let let
loginMethod' = loginMethod' = if cfg.aerc.imapAuth != null then "+${cfg.aerc.imapAuth}" else "";
if cfg.aerc.imapAuth != null then "+${cfg.aerc.imapAuth}" else "";
oauthParams' = oauthParams { oauthParams' = oauthParams {
auth = cfg.aerc.imapAuth; auth = cfg.aerc.imapAuth;
params = cfg.aerc.imapOauth2Params; params = cfg.aerc.imapOauth2Params;
}; };
protocol = if imap.tls.enable then protocol =
if imap.tls.useStartTls then "imap" else "imaps${loginMethod'}" if imap.tls.enable then
else if imap.tls.useStartTls then "imap" else "imaps${loginMethod'}"
"imap+insecure"; else
"imap+insecure";
port' = optPort imap.port; port' = optPort imap.port;
in { in
source = {
"${protocol}://${userName}@${imap.host}${port'}${oauthParams'}"; source = "${protocol}://${userName}@${imap.host}${port'}${oauthParams'}";
} // optPwCmd "source" passwordCommand; }
// optPwCmd "source" passwordCommand;
smtp = { userName, smtp, passwordCommand, ... }@cfg: smtp =
{
userName,
smtp,
passwordCommand,
...
}@cfg:
let let
loginMethod' = loginMethod' = if cfg.aerc.smtpAuth != null then "+${cfg.aerc.smtpAuth}" else "";
if cfg.aerc.smtpAuth != null then "+${cfg.aerc.smtpAuth}" else "";
oauthParams' = oauthParams { oauthParams' = oauthParams {
auth = cfg.aerc.smtpAuth; auth = cfg.aerc.smtpAuth;
params = cfg.aerc.smtpOauth2Params; params = cfg.aerc.smtpOauth2Params;
}; };
protocol = if smtp.tls.enable then protocol =
if smtp.tls.useStartTls then if smtp.tls.enable then
"smtp${loginMethod'}" if smtp.tls.useStartTls then "smtp${loginMethod'}" else "smtps${loginMethod'}"
else else
"smtps${loginMethod'}" "smtp+insecure${loginMethod'}";
else
"smtp+insecure${loginMethod'}";
port' = optPort smtp.port; port' = optPort smtp.port;
in { in
outgoing = {
"${protocol}://${userName}@${smtp.host}${port'}${oauthParams'}"; outgoing = "${protocol}://${userName}@${smtp.host}${port'}${oauthParams'}";
} // optPwCmd "outgoing" passwordCommand; }
// optPwCmd "outgoing" passwordCommand;
msmtp = cfg: { msmtp = cfg: {
outgoing = "msmtpq --read-envelope-from --read-recipients"; outgoing = "msmtpq --read-envelope-from --read-recipients";
@ -203,17 +248,21 @@ in {
}; };
basicCfg = account: basicCfg =
account:
{ {
from = "${account.realName} <${account.address}>"; from = "${account.realName} <${account.address}>";
} // (optAttr "copy-to" account.folders.sent) }
// (optAttr "copy-to" account.folders.sent)
// (optAttr "default" account.folders.inbox) // (optAttr "default" account.folders.inbox)
// (optAttr "postpone" account.folders.drafts) // (optAttr "postpone" account.folders.drafts)
// (optAttr "aliases" account.aliases); // (optAttr "aliases" account.aliases);
sourceCfg = account: sourceCfg =
if account.mbsync.enable && account.mbsync.flatten == null account:
&& account.mbsync.subFolders == "Maildir++" then if
account.mbsync.enable && account.mbsync.flatten == null && account.mbsync.subFolders == "Maildir++"
then
mkConfig.maildirpp account mkConfig.maildirpp account
else if account.mbsync.enable || account.offlineimap.enable then else if account.mbsync.enable || account.offlineimap.enable then
mkConfig.maildir account mkConfig.maildir account
@ -222,7 +271,8 @@ in {
else else
{ }; { };
outgoingCfg = account: outgoingCfg =
account:
if account.msmtp.enable then if account.msmtp.enable then
mkConfig.msmtp account mkConfig.msmtp account
else if account.smtp != null then else if account.smtp != null then
@ -230,19 +280,22 @@ in {
else else
{ }; { };
gpgCfg = account: gpgCfg =
account:
lib.optionalAttrs (account.gpg != null) { lib.optionalAttrs (account.gpg != null) {
pgp-key-id = account.gpg.key; pgp-key-id = account.gpg.key;
pgp-auto-sign = account.gpg.signByDefault; pgp-auto-sign = account.gpg.signByDefault;
pgp-opportunistic-encrypt = account.gpg.encryptByDefault; pgp-opportunistic-encrypt = account.gpg.encryptByDefault;
}; };
in (basicCfg account) // (sourceCfg account) // (outgoingCfg account) in
// (gpgCfg account) // account.aerc.extraAccounts; (basicCfg account)
// (sourceCfg account)
// (outgoingCfg account)
// (gpgCfg account)
// account.aerc.extraAccounts;
mkAccountConfig = name: account: mkAccountConfig = name: account: mapAttrNames (addAccountName name) account.aerc.extraConfig;
mapAttrNames (addAccountName name) account.aerc.extraConfig;
mkAccountBinds = name: account: mkAccountBinds = name: account: mapAttrNames (addAccountName name) account.aerc.extraBinds;
mapAttrNames (addAccountName name) account.aerc.extraBinds;
} }

View file

@ -1,15 +1,34 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (lib) inherit (lib)
attrsets generators literalExpression mapAttrs mkIf mkOption types; attrsets
generators
literalExpression
mapAttrs
mkIf
mkOption
types
;
cfg = config.programs.aerc; cfg = config.programs.aerc;
primitive = with types; primitive =
((type: either type (listOf type)) (nullOr (oneOf [ str int bool float ]))) with types;
((type: either type (listOf type)) (
nullOr (oneOf [
str
int
bool
float
])
))
// { // {
description = description = "values (null, bool, int, string, or float) or a list of values, that will be joined with a comma";
"values (null, bool, int, string, or float) or a list of values, that will be joined with a comma";
}; };
confSection = types.attrsOf primitive; confSection = types.attrsOf primitive;
@ -19,18 +38,25 @@ let
sectionsOrLines = types.either types.lines confSections; sectionsOrLines = types.either types.lines confSections;
accounts = import ./aerc-accounts.nix { accounts = import ./aerc-accounts.nix {
inherit config pkgs lib confSection confSections; inherit
config
pkgs
lib
confSection
confSections
;
}; };
aerc-accounts = aerc-accounts = attrsets.filterAttrs (_: v: v.aerc.enable) config.accounts.email.accounts;
attrsets.filterAttrs (_: v: v.aerc.enable) config.accounts.email.accounts;
configDir = if (pkgs.stdenv.isDarwin && !config.xdg.enable) then configDir =
"Library/Preferences/aerc" if (pkgs.stdenv.isDarwin && !config.xdg.enable) then
else "Library/Preferences/aerc"
"${config.xdg.configHome}/aerc"; else
"${config.xdg.configHome}/aerc";
in { in
{
meta.maintainers = with lib.hm.maintainers; [ lukasngl ]; meta.maintainers = with lib.hm.maintainers; [ lukasngl ];
options.accounts.email.accounts = accounts.type; options.accounts.email.accounts = accounts.type;
@ -44,8 +70,7 @@ in {
extraAccounts = mkOption { extraAccounts = mkOption {
type = sectionsOrLines; type = sectionsOrLines;
default = { }; default = { };
example = literalExpression example = literalExpression ''{ Work = { source = "maildir://~/Maildir/work"; }; }'';
''{ Work = { source = "maildir://~/Maildir/work"; }; }'';
description = '' description = ''
Extra lines added to {file}`$HOME/.config/aerc/accounts.conf`. Extra lines added to {file}`$HOME/.config/aerc/accounts.conf`.
@ -103,127 +128,151 @@ in {
}; };
}; };
config = let config =
joinCfg = cfgs: lib.concatStringsSep "\n" (lib.filter (v: v != "") cfgs); let
joinCfg = cfgs: lib.concatStringsSep "\n" (lib.filter (v: v != "") cfgs);
toINI = conf: # quirk: global section is prepended w/o section heading toINI =
let conf: # quirk: global section is prepended w/o section heading
global = conf.global or { }; let
local = removeAttrs conf [ "global" ]; global = conf.global or { };
mkValueString = v: local = removeAttrs conf [ "global" ];
if lib.isList v then # join with comma mkValueString =
lib.concatStringsSep "," v:
(map (generators.mkValueStringDefault { }) v) if lib.isList v then # join with comma
else lib.concatStringsSep "," (map (generators.mkValueStringDefault { }) v)
generators.mkValueStringDefault { } v; else
mkKeyValue = generators.mkValueStringDefault { } v;
generators.mkKeyValueDefault { inherit mkValueString; } " = "; mkKeyValue = generators.mkKeyValueDefault { inherit mkValueString; } " = ";
in joinCfg [ in
(generators.toKeyValue { inherit mkKeyValue; } global) joinCfg [
(generators.toINI { inherit mkKeyValue; } local) (generators.toKeyValue { inherit mkKeyValue; } global)
(generators.toINI { inherit mkKeyValue; } local)
];
mkINI = conf: if lib.isString conf then conf else toINI conf;
mkStyleset = attrsets.mapAttrs' (
k: v:
let
value = if lib.isString v then v else toINI { global = v; };
in
{
name = "${configDir}/stylesets/${k}";
value.text = joinCfg [
header
value
];
}
);
mkTemplates = attrsets.mapAttrs' (
k: v: {
name = "${configDir}/templates/${k}";
value.text = v;
}
);
primaryAccount = attrsets.filterAttrs (_: v: v.primary) aerc-accounts;
otherAccounts = attrsets.filterAttrs (_: v: !v.primary) aerc-accounts;
primaryAccountAccounts = mapAttrs accounts.mkAccount primaryAccount;
accountsExtraAccounts = mapAttrs accounts.mkAccount otherAccounts;
accountsExtraConfig = mapAttrs accounts.mkAccountConfig aerc-accounts;
accountsExtraBinds = mapAttrs accounts.mkAccountBinds aerc-accounts;
joinContextual = contextual: joinCfg (map mkINI (lib.attrValues contextual));
isRecursivelyEmpty =
x:
if lib.isAttrs x then lib.all (x: x == { } || isRecursivelyEmpty x) (lib.attrValues x) else false;
genAccountsConf = (
(cfg.extraAccounts != "" && cfg.extraAccounts != { })
|| !(isRecursivelyEmpty accountsExtraAccounts)
|| !(isRecursivelyEmpty primaryAccountAccounts)
);
genAercConf = (
(cfg.extraConfig != "" && cfg.extraConfig != { }) || !(isRecursivelyEmpty accountsExtraConfig)
);
genBindsConf = (
(cfg.extraBinds != "" && cfg.extraBinds != { }) || !(isRecursivelyEmpty accountsExtraBinds)
);
header = ''
# Generated by Home Manager.
'';
in
mkIf cfg.enable {
warnings =
if genAccountsConf && (cfg.extraConfig.general.unsafe-accounts-conf or false) == false then
[
''
aerc: `programs.aerc.enable` is set, but `...extraConfig.general.unsafe-accounts-conf` is set to false or unset.
This will prevent aerc from starting; see `unsafe-accounts-conf` in the man page aerc-config(5):
> By default, the file permissions of accounts.conf must be restrictive and only allow reading by the file owner (0600).
> Set this option to true to ignore this permission check. Use this with care as it may expose your credentials.
These permissions are not possible with home-manager, since the generated file is in the nix-store (permissions 0444).
Therefore, please set `programs.aerc.extraConfig.general.unsafe-accounts-conf = true`.
This option is safe; if `passwordCommand` is properly set, no credentials will be written to the nix store.
''
]
else
[ ];
assertions = [
{
assertion =
let
extraConfigSections = (
lib.unique (lib.flatten (lib.mapAttrsToList (_: v: lib.attrNames v.aerc.extraConfig) aerc-accounts))
);
in
extraConfigSections == [ ] || extraConfigSections == [ "ui" ];
message = ''
Only the ui section of $XDG_CONFIG_HOME/aerc.conf supports contextual (per-account) configuration.
Please configure it with accounts.email.accounts._.aerc.extraConfig.ui and move any other
configuration to programs.aerc.extraConfig.
'';
}
]; ];
mkINI = conf: if lib.isString conf then conf else toINI conf; home.packages = lib.mkIf (cfg.package != null) [ cfg.package ];
mkStyleset = attrsets.mapAttrs' (k: v: home.file =
let value = if lib.isString v then v else toINI { global = v; }; {
in { "${configDir}/accounts.conf" = mkIf genAccountsConf {
name = "${configDir}/stylesets/${k}"; text = joinCfg [
value.text = joinCfg [ header value ]; header
}); (mkINI cfg.extraAccounts)
(mkINI primaryAccountAccounts)
(mkINI accountsExtraAccounts)
];
};
mkTemplates = attrsets.mapAttrs' (k: v: { "${configDir}/aerc.conf" = mkIf genAercConf {
name = "${configDir}/templates/${k}"; text = joinCfg [
value.text = v; header
}); (mkINI cfg.extraConfig)
(joinContextual accountsExtraConfig)
];
};
primaryAccount = attrsets.filterAttrs (_: v: v.primary) aerc-accounts; "${configDir}/binds.conf" = mkIf genBindsConf {
otherAccounts = attrsets.filterAttrs (_: v: !v.primary) aerc-accounts; text = joinCfg [
header
primaryAccountAccounts = mapAttrs accounts.mkAccount primaryAccount; (mkINI cfg.extraBinds)
(joinContextual accountsExtraBinds)
accountsExtraAccounts = mapAttrs accounts.mkAccount otherAccounts; ];
};
accountsExtraConfig = mapAttrs accounts.mkAccountConfig aerc-accounts; }
// (mkStyleset cfg.stylesets)
accountsExtraBinds = mapAttrs accounts.mkAccountBinds aerc-accounts; // (mkTemplates cfg.templates);
};
joinContextual = contextual:
joinCfg (map mkINI (lib.attrValues contextual));
isRecursivelyEmpty = x:
if lib.isAttrs x then
lib.all (x: x == { } || isRecursivelyEmpty x) (lib.attrValues x)
else
false;
genAccountsConf = ((cfg.extraAccounts != "" && cfg.extraAccounts != { })
|| !(isRecursivelyEmpty accountsExtraAccounts)
|| !(isRecursivelyEmpty primaryAccountAccounts));
genAercConf = ((cfg.extraConfig != "" && cfg.extraConfig != { })
|| !(isRecursivelyEmpty accountsExtraConfig));
genBindsConf = ((cfg.extraBinds != "" && cfg.extraBinds != { })
|| !(isRecursivelyEmpty accountsExtraBinds));
header = ''
# Generated by Home Manager.
'';
in mkIf cfg.enable {
warnings = if genAccountsConf
&& (cfg.extraConfig.general.unsafe-accounts-conf or false) == false then [''
aerc: `programs.aerc.enable` is set, but `...extraConfig.general.unsafe-accounts-conf` is set to false or unset.
This will prevent aerc from starting; see `unsafe-accounts-conf` in the man page aerc-config(5):
> By default, the file permissions of accounts.conf must be restrictive and only allow reading by the file owner (0600).
> Set this option to true to ignore this permission check. Use this with care as it may expose your credentials.
These permissions are not possible with home-manager, since the generated file is in the nix-store (permissions 0444).
Therefore, please set `programs.aerc.extraConfig.general.unsafe-accounts-conf = true`.
This option is safe; if `passwordCommand` is properly set, no credentials will be written to the nix store.
''] else
[ ];
assertions = [{
assertion = let
extraConfigSections = (lib.unique (lib.flatten
(lib.mapAttrsToList (_: v: lib.attrNames v.aerc.extraConfig)
aerc-accounts)));
in extraConfigSections == [ ] || extraConfigSections == [ "ui" ];
message = ''
Only the ui section of $XDG_CONFIG_HOME/aerc.conf supports contextual (per-account) configuration.
Please configure it with accounts.email.accounts._.aerc.extraConfig.ui and move any other
configuration to programs.aerc.extraConfig.
'';
}];
home.packages = lib.mkIf (cfg.package != null) [ cfg.package ];
home.file = {
"${configDir}/accounts.conf" = mkIf genAccountsConf {
text = joinCfg [
header
(mkINI cfg.extraAccounts)
(mkINI primaryAccountAccounts)
(mkINI accountsExtraAccounts)
];
};
"${configDir}/aerc.conf" = mkIf genAercConf {
text = joinCfg [
header
(mkINI cfg.extraConfig)
(joinContextual accountsExtraConfig)
];
};
"${configDir}/binds.conf" = mkIf genBindsConf {
text = joinCfg [
header
(mkINI cfg.extraBinds)
(joinContextual accountsExtraBinds)
];
};
} // (mkStyleset cfg.stylesets) // (mkTemplates cfg.templates);
};
} }

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (lib) mkOption types; inherit (lib) mkOption types;
cfg = config.programs.aerospace; cfg = config.programs.aerospace;
@ -6,22 +11,29 @@ let
tomlFormat = pkgs.formats.toml { }; tomlFormat = pkgs.formats.toml { };
# filterAttrsRecursive supporting lists, as well. # filterAttrsRecursive supporting lists, as well.
filterListAndAttrsRecursive = pred: set: filterListAndAttrsRecursive =
lib.listToAttrs (lib.concatMap (name: pred: set:
let v = set.${name}; lib.listToAttrs (
in if pred v then lib.concatMap (
[ name:
(lib.nameValuePair name (if lib.isAttrs v then let
filterListAndAttrsRecursive pred v v = set.${name};
else if lib.isList v then in
(map (i: if pred v then
if lib.isAttrs i then filterListAndAttrsRecursive pred i else i) [
(lib.filter pred v)) (lib.nameValuePair name (
else if lib.isAttrs v then
v)) filterListAndAttrsRecursive pred v
] else if lib.isList v then
else (map (i: if lib.isAttrs i then filterListAndAttrsRecursive pred i else i) (lib.filter pred v))
[ ]) (lib.attrNames set)); else
v
))
]
else
[ ]
) (lib.attrNames set)
);
filterNulls = filterListAndAttrsRecursive (v: v != null); filterNulls = filterListAndAttrsRecursive (v: v != null);
# Turns # Turns
@ -30,16 +42,22 @@ let
# {"if.foo" = "xxx"; "if.bar" = "yyy"} # {"if.foo" = "xxx"; "if.bar" = "yyy"}
# so that the correct TOML is generated for the # so that the correct TOML is generated for the
# on-window-detected table. # on-window-detected table.
flattenConditions = attrs: flattenConditions =
let conditions = attrs."if" or { }; attrs:
in builtins.removeAttrs attrs [ "if" ] let
// lib.concatMapAttrs (n: v: { "if.${n}" = v; }) conditions; conditions = attrs."if" or { };
in
builtins.removeAttrs attrs [ "if" ] // lib.concatMapAttrs (n: v: { "if.${n}" = v; }) conditions;
flattenOnWindowDetected = cfg: flattenOnWindowDetected =
let owd = cfg.on-window-detected or [ ]; cfg:
in cfg // { on-window-detected = map flattenConditions owd; }; let
owd = cfg.on-window-detected or [ ];
in
cfg // { on-window-detected = map flattenConditions owd; };
in { in
{
meta.maintainers = with lib.hm.maintainers; [ damidoug ]; meta.maintainers = with lib.hm.maintainers; [ damidoug ];
options.programs.aerospace = { options.programs.aerospace = {
@ -76,102 +94,122 @@ in {
enable-normalization-flatten-containers = mkOption { enable-normalization-flatten-containers = mkOption {
type = types.bool; type = types.bool;
default = true; default = true;
description = description = ''Containers that have only one child are "flattened".'';
''Containers that have only one child are "flattened".''; };
enable-normalization-opposite-orientation-for-nested-containers = mkOption {
type = types.bool;
default = true;
description = "Containers that nest into each other must have opposite orientations.";
}; };
enable-normalization-opposite-orientation-for-nested-containers =
mkOption {
type = types.bool;
default = true;
description =
"Containers that nest into each other must have opposite orientations.";
};
accordion-padding = mkOption { accordion-padding = mkOption {
type = types.int; type = types.int;
default = 30; default = 30;
description = "Padding between windows in an accordion container."; description = "Padding between windows in an accordion container.";
}; };
default-root-container-layout = mkOption { default-root-container-layout = mkOption {
type = types.enum [ "tiles" "accordion" ]; type = types.enum [
"tiles"
"accordion"
];
default = "tiles"; default = "tiles";
description = "Default layout for the root container."; description = "Default layout for the root container.";
}; };
default-root-container-orientation = mkOption { default-root-container-orientation = mkOption {
type = types.enum [ "horizontal" "vertical" "auto" ]; type = types.enum [
"horizontal"
"vertical"
"auto"
];
default = "auto"; default = "auto";
description = "Default orientation for the root container."; description = "Default orientation for the root container.";
}; };
on-window-detected = mkOption { on-window-detected = mkOption {
type = types.listOf (types.submodule { type = types.listOf (
options = { types.submodule {
"if" = mkOption { options = {
type = types.submodule { "if" = mkOption {
options = { type = types.submodule {
app-id = mkOption { options = {
type = with types; nullOr str; app-id = mkOption {
default = null; type = with types; nullOr str;
description = "The application ID to match (optional)."; default = null;
}; description = "The application ID to match (optional).";
workspace = mkOption { };
type = with types; nullOr str; workspace = mkOption {
default = null; type = with types; nullOr str;
description = "The workspace name to match (optional)."; default = null;
}; description = "The workspace name to match (optional).";
window-title-regex-substring = mkOption { };
type = with types; nullOr str; window-title-regex-substring = mkOption {
default = null; type = with types; nullOr str;
description = default = null;
"Substring to match in the window title (optional)."; description = "Substring to match in the window title (optional).";
}; };
app-name-regex-substring = mkOption { app-name-regex-substring = mkOption {
type = with types; nullOr str; type = with types; nullOr str;
default = null; default = null;
description = description = "Regex substring to match the app name (optional).";
"Regex substring to match the app name (optional)."; };
}; during-aerospace-startup = mkOption {
during-aerospace-startup = mkOption { type = with types; nullOr bool;
type = with types; nullOr bool; default = null;
default = null; description = "Whether to match during aerospace startup (optional).";
description = };
"Whether to match during aerospace startup (optional).";
}; };
}; };
default = { };
description = "Conditions for detecting a window.";
};
check-further-callbacks = mkOption {
type = with types; nullOr bool;
default = null;
description = "Whether to check further callbacks after this rule (optional).";
};
run = mkOption {
type =
with types;
oneOf [
str
(listOf str)
];
example = [
"move-node-to-workspace m"
"resize-node"
];
description = "Commands to execute when the conditions match (required).";
}; };
default = { };
description = "Conditions for detecting a window.";
}; };
check-further-callbacks = mkOption { }
type = with types; nullOr bool; );
default = null;
description =
"Whether to check further callbacks after this rule (optional).";
};
run = mkOption {
type = with types; oneOf [ str (listOf str) ];
example = [ "move-node-to-workspace m" "resize-node" ];
description =
"Commands to execute when the conditions match (required).";
};
};
});
default = [ ]; default = [ ];
example = [{ example = [
"if" = { {
app-id = "Another.Cool.App"; "if" = {
workspace = "cool-workspace"; app-id = "Another.Cool.App";
window-title-regex-substring = "Title"; workspace = "cool-workspace";
app-name-regex-substring = "CoolApp"; window-title-regex-substring = "Title";
during-aerospace-startup = false; app-name-regex-substring = "CoolApp";
}; during-aerospace-startup = false;
check-further-callbacks = false; };
run = [ "move-node-to-workspace m" "resize-node" ]; check-further-callbacks = false;
}]; run = [
description = "move-node-to-workspace m"
"Commands to run every time a new window is detected with optional conditions."; "resize-node"
];
}
];
description = "Commands to run every time a new window is detected with optional conditions.";
}; };
workspace-to-monitor-force-assignment = mkOption { workspace-to-monitor-force-assignment = mkOption {
type = with types; type =
nullOr (attrsOf (oneOf [ int str (listOf str) ])); with types;
nullOr (
attrsOf (oneOf [
int
str
(listOf str)
])
);
default = null; default = null;
description = '' description = ''
Map workspaces to specific monitors. Map workspaces to specific monitors.
@ -182,17 +220,18 @@ in {
"2" = "main"; # Main monitor. "2" = "main"; # Main monitor.
"3" = "secondary"; # Secondary monitor (non-main). "3" = "secondary"; # Secondary monitor (non-main).
"4" = "built-in"; # Built-in display. "4" = "built-in"; # Built-in display.
"5" = "5" = "^built-in retina display$"; # Regex for the built-in retina display.
"^built-in retina display$"; # Regex for the built-in retina display. "6" = [
"6" = [ "secondary" "dell" ]; # Match first pattern in the list. "secondary"
"dell"
]; # Match first pattern in the list.
}; };
}; };
on-focus-changed = mkOption { on-focus-changed = mkOption {
type = with types; listOf str; type = with types; listOf str;
default = [ ]; default = [ ];
example = [ "move-mouse monitor-lazy-center" ]; example = [ "move-mouse monitor-lazy-center" ];
description = description = "Commands to run every time focused window or workspace changes.";
"Commands to run every time focused window or workspace changes.";
}; };
on-focused-monitor-changed = mkOption { on-focused-monitor-changed = mkOption {
type = with types; listOf str; type = with types; listOf str;
@ -210,7 +249,10 @@ in {
description = "Commands to run every time workspace changes."; description = "Commands to run every time workspace changes.";
}; };
key-mapping.preset = mkOption { key-mapping.preset = mkOption {
type = types.enum [ "qwerty" "dvorak" ]; type = types.enum [
"qwerty"
"dvorak"
];
default = "qwerty"; default = "qwerty";
description = "Keymapping preset."; description = "Keymapping preset.";
}; };
@ -243,15 +285,14 @@ in {
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
assertions = [ assertions = [
(lib.hm.assertions.assertPlatform "programs.aerospace" pkgs (lib.hm.assertions.assertPlatform "programs.aerospace" pkgs lib.platforms.darwin)
lib.platforms.darwin)
]; ];
home = { home = {
packages = lib.mkIf (cfg.package != null) [ cfg.package ]; packages = lib.mkIf (cfg.package != null) [ cfg.package ];
file.".config/aerospace/aerospace.toml".source = file.".config/aerospace/aerospace.toml".source = tomlFormat.generate "aerospace" (
tomlFormat.generate "aerospace" filterNulls (flattenOnWindowDetected cfg.userSettings)
(filterNulls (flattenOnWindowDetected cfg.userSettings)); );
}; };
}; };
} }

View file

@ -1,6 +1,13 @@
{ config, lib, pkgs, ... }: {
let cfg = config.programs.afew; config,
in { lib,
pkgs,
...
}:
let
cfg = config.programs.afew;
in
{
options.programs.afew = { options.programs.afew = {
enable = lib.mkEnableOption "the afew initial tagging script for Notmuch"; enable = lib.mkEnableOption "the afew initial tagging script for Notmuch";

View file

@ -1,8 +1,14 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.programs.alacritty; cfg = config.programs.alacritty;
tomlFormat = pkgs.formats.toml { }; tomlFormat = pkgs.formats.toml { };
in { in
{
options = { options = {
programs.alacritty = { programs.alacritty = {
enable = lib.mkEnableOption "Alacritty"; enable = lib.mkEnableOption "Alacritty";
@ -56,40 +62,45 @@ in {
}; };
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
assertions = [{ assertions = [
# If using the theme option, ensure that theme exists in the {
# alacritty-theme package. # If using the theme option, ensure that theme exists in the
assertion = let # alacritty-theme package.
available = lib.pipe "${pkgs.alacritty-theme}/share/alacritty-theme" [ assertion =
builtins.readDir let
(lib.filterAttrs available = lib.pipe "${pkgs.alacritty-theme}/share/alacritty-theme" [
(name: type: type == "regular" && lib.hasSuffix ".toml" name)) builtins.readDir
lib.attrNames (lib.filterAttrs (name: type: type == "regular" && lib.hasSuffix ".toml" name))
(lib.map (lib.removeSuffix ".toml")) lib.attrNames
]; (lib.map (lib.removeSuffix ".toml"))
in cfg.theme == null || (builtins.elem cfg.theme available); ];
message = "The alacritty theme '${cfg.theme}' does not exist."; in
}]; cfg.theme == null || (builtins.elem cfg.theme available);
message = "The alacritty theme '${cfg.theme}' does not exist.";
}
];
home.packages = lib.mkIf (cfg.package != null) [ cfg.package ]; home.packages = lib.mkIf (cfg.package != null) [ cfg.package ];
programs.alacritty.settings = let programs.alacritty.settings =
theme = "${pkgs.alacritty-theme}/share/alacritty-theme/${cfg.theme}.toml"; let
in lib.mkIf (cfg.theme != null) { theme = "${pkgs.alacritty-theme}/share/alacritty-theme/${cfg.theme}.toml";
general.import = in
lib.mkIf (lib.versionAtLeast cfg.package.version "0.14") [ theme ]; lib.mkIf (cfg.theme != null) {
import = lib.mkIf (lib.versionOlder cfg.package.version "0.14") [ theme ]; general.import = lib.mkIf (lib.versionAtLeast cfg.package.version "0.14") [ theme ];
}; import = lib.mkIf (lib.versionOlder cfg.package.version "0.14") [ theme ];
};
xdg.configFile."alacritty/alacritty.toml" = lib.mkIf (cfg.settings != { }) { xdg.configFile."alacritty/alacritty.toml" = lib.mkIf (cfg.settings != { }) {
source = (tomlFormat.generate "alacritty.toml" cfg.settings).overrideAttrs source = (tomlFormat.generate "alacritty.toml" cfg.settings).overrideAttrs (
(finalAttrs: prevAttrs: { finalAttrs: prevAttrs: {
buildCommand = lib.concatStringsSep "\n" [ buildCommand = lib.concatStringsSep "\n" [
prevAttrs.buildCommand prevAttrs.buildCommand
# TODO: why is this needed? Is there a better way to retain escape sequences? # TODO: why is this needed? Is there a better way to retain escape sequences?
"substituteInPlace $out --replace-quiet '\\\\' '\\'" "substituteInPlace $out --replace-quiet '\\\\' '\\'"
]; ];
}); }
);
}; };
}; };
} }

View file

@ -1,7 +1,9 @@
pkgs: pkgs:
{ config, lib, ... }: { config, lib, ... }:
let inherit (lib) mkOption types; let
in { inherit (lib) mkOption types;
in
{
options.alot = { options.alot = {
sendMailCommand = mkOption { sendMailCommand = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
@ -16,11 +18,9 @@ in {
type = types.attrsOf types.str; type = types.attrsOf types.str;
default = { default = {
type = "shellcommand"; type = "shellcommand";
command = command = "'${pkgs.notmuch}/bin/notmuch address --format=json --output=recipients date:6M..'";
"'${pkgs.notmuch}/bin/notmuch address --format=json --output=recipients date:6M..'"; regexp =
regexp = "'\\[?{" + '' "'\\[?{" + ''"name": "(?P<name>.*)", "address": "(?P<email>.+)", "name-addr": ".*"'' + "}[,\\]]?'";
"name": "(?P<name>.*)", "address": "(?P<email>.+)", "name-addr": ".*"''
+ "}[,\\]]?'";
shellcommand_external_filtering = "False"; shellcommand_external_filtering = "False";
}; };
example = lib.literalExpression '' example = lib.literalExpression ''
@ -48,9 +48,8 @@ in {
}; };
config = lib.mkIf config.notmuch.enable { config = lib.mkIf config.notmuch.enable {
alot.sendMailCommand = lib.mkOptionDefault (if config.msmtp.enable then alot.sendMailCommand = lib.mkOptionDefault (
"msmtpq --read-envelope-from --read-recipients" if config.msmtp.enable then "msmtpq --read-envelope-from --read-recipients" else null
else );
null);
}; };
} }

View file

@ -1,22 +1,35 @@
# alot config loader is sensitive to leading space ! # alot config loader is sensitive to leading space !
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (lib) inherit (lib)
concatStringsSep mapAttrsToList mkOption optionalAttrs optionalString types; concatStringsSep
mapAttrsToList
mkOption
optionalAttrs
optionalString
types
;
cfg = config.programs.alot; cfg = config.programs.alot;
enabledAccounts = lib.filter (a: a.notmuch.enable) enabledAccounts = lib.filter (a: a.notmuch.enable) (lib.attrValues config.accounts.email.accounts);
(lib.attrValues config.accounts.email.accounts);
# sorted: primary first # sorted: primary first
alotAccounts = lib.sort (a: b: !(a.primary -> b.primary)) enabledAccounts; alotAccounts = lib.sort (a: b: !(a.primary -> b.primary)) enabledAccounts;
boolStr = v: if v then "True" else "False"; boolStr = v: if v then "True" else "False";
mkKeyValue = key: value: mkKeyValue =
let value' = if lib.isBool value then boolStr value else toString value; key: value:
in "${key} = ${value'}"; let
value' = if lib.isBool value then boolStr value else toString value;
in
"${key} = ${value'}";
mk2ndLevelSectionName = name: "[" + name + "]"; mk2ndLevelSectionName = name: "[" + name + "]";
@ -59,67 +72,95 @@ let
}; };
}; };
accountStr = account: accountStr =
account:
let let
inherit (account) inherit (account)
alot maildir name address realName folders aliases gpg signature; alot
in concatStringsSep "\n" ([ "[[${name}]]" ] maildir
++ mapAttrsToList (n: v: n + "=" + v) ({ name
address = address; address
realname = realName; realName
sendmail_command = folders
optionalString (alot.sendMailCommand != null) alot.sendMailCommand; aliases
} // optionalAttrs (folders.sent != null) { gpg
sent_box = "maildir" + "://" + maildir.absPath + "/" + folders.sent; signature
} // optionalAttrs (folders.drafts != null) { ;
draft_box = "maildir" + "://" + maildir.absPath + "/" + folders.drafts; in
} // optionalAttrs (aliases != [ ]) { concatStringsSep "\n" (
aliases = concatStringsSep "," aliases; [ "[[${name}]]" ]
} // optionalAttrs (gpg != null) { ++ mapAttrsToList (n: v: n + "=" + v) (
gpg_key = gpg.key; {
encrypt_by_default = if gpg.encryptByDefault then "all" else "none"; address = address;
sign_by_default = boolStr gpg.signByDefault; realname = realName;
} // optionalAttrs (signature.showSignature != "none") { sendmail_command = optionalString (alot.sendMailCommand != null) alot.sendMailCommand;
signature = pkgs.writeText "signature.txt" signature.text; }
signature_as_attachment = boolStr (signature.showSignature == "attach"); // optionalAttrs (folders.sent != null) {
}) ++ [ alot.extraConfig ] ++ [ "[[[abook]]]" ] sent_box = "maildir" + "://" + maildir.absPath + "/" + folders.sent;
++ mapAttrsToList (n: v: n + "=" + v) alot.contactCompletion); }
// optionalAttrs (folders.drafts != null) {
draft_box = "maildir" + "://" + maildir.absPath + "/" + folders.drafts;
}
// optionalAttrs (aliases != [ ]) {
aliases = concatStringsSep "," aliases;
}
// optionalAttrs (gpg != null) {
gpg_key = gpg.key;
encrypt_by_default = if gpg.encryptByDefault then "all" else "none";
sign_by_default = boolStr gpg.signByDefault;
}
// optionalAttrs (signature.showSignature != "none") {
signature = pkgs.writeText "signature.txt" signature.text;
signature_as_attachment = boolStr (signature.showSignature == "attach");
}
)
++ [ alot.extraConfig ]
++ [ "[[[abook]]]" ]
++ mapAttrsToList (n: v: n + "=" + v) alot.contactCompletion
);
configFile = let configFile =
bindingsToStr = attrSet: let
concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${v}") attrSet); bindingsToStr = attrSet: concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${v}") attrSet);
in '' in
# Generated by Home Manager. ''
# See http://alot.readthedocs.io/en/latest/configuration/config_options.html # Generated by Home Manager.
# See http://alot.readthedocs.io/en/latest/configuration/config_options.html
${lib.generators.toKeyValue { inherit mkKeyValue; } cfg.settings} ${lib.generators.toKeyValue { inherit mkKeyValue; } cfg.settings}
${cfg.extraConfig} ${cfg.extraConfig}
[tags] [tags]
'' + (let ''
submoduleToAttrs = m: + (
lib.filterAttrs (name: v: name != "_module" && v != null) m; let
in lib.generators.toINI { mkSectionName = mk2ndLevelSectionName; } submoduleToAttrs = m: lib.filterAttrs (name: v: name != "_module" && v != null) m;
(lib.mapAttrs (name: x: submoduleToAttrs x) cfg.tags)) + '' in
[bindings] lib.generators.toINI { mkSectionName = mk2ndLevelSectionName; } (
${bindingsToStr cfg.bindings.global} lib.mapAttrs (name: x: submoduleToAttrs x) cfg.tags
)
)
+ ''
[bindings]
${bindingsToStr cfg.bindings.global}
[[bufferlist]] [[bufferlist]]
${bindingsToStr cfg.bindings.bufferlist} ${bindingsToStr cfg.bindings.bufferlist}
[[search]] [[search]]
${bindingsToStr cfg.bindings.search} ${bindingsToStr cfg.bindings.search}
[[envelope]] [[envelope]]
${bindingsToStr cfg.bindings.envelope} ${bindingsToStr cfg.bindings.envelope}
[[taglist]] [[taglist]]
${bindingsToStr cfg.bindings.taglist} ${bindingsToStr cfg.bindings.taglist}
[[thread]] [[thread]]
${bindingsToStr cfg.bindings.thread} ${bindingsToStr cfg.bindings.thread}
[accounts] [accounts]
${lib.concatStringsSep "\n\n" (map accountStr alotAccounts)} ${lib.concatStringsSep "\n\n" (map accountStr alotAccounts)}
''; '';
in { in
{
options = { options = {
programs.alot = { programs.alot = {
enable = mkOption { enable = mkOption {
@ -196,9 +237,12 @@ in {
}; };
settings = mkOption { settings = mkOption {
type = with types; type =
let primitive = either (either (either str int) bool) float; with types;
in attrsOf primitive; let
primitive = either (either (either str int) bool) float;
in
attrsOf primitive;
default = { default = {
initial_command = "search tag:inbox AND NOT tag:killed"; initial_command = "search tag:inbox AND NOT tag:killed";
auto_remove_unread = true; auto_remove_unread = true;
@ -237,9 +281,11 @@ in {
xdg.configFile."alot/config".text = configFile; xdg.configFile."alot/config".text = configFile;
xdg.configFile."alot/hooks.py" = lib.mkIf (cfg.hooks != "") { xdg.configFile."alot/hooks.py" = lib.mkIf (cfg.hooks != "") {
text = '' text =
# Generated by Home Manager. ''
'' + cfg.hooks; # Generated by Home Manager.
''
+ cfg.hooks;
}; };
}; };
} }

View file

@ -1,15 +1,25 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.programs.zsh.antidote; cfg = config.programs.zsh.antidote;
zPluginStr = (pluginNames: zPluginStr = (
lib.optionalString (pluginNames != [ ]) "${lib.concatStrings (map (name: '' pluginNames:
${name} lib.optionalString (pluginNames != [ ])
'') pluginNames)}"); "${lib.concatStrings (
map (name: ''
${name}
'') pluginNames
)}"
);
parseHashId = path: parseHashId = path: lib.elemAt (builtins.match "${builtins.storeDir}/([a-zA-Z0-9]+)-.*" path) 0;
lib.elemAt (builtins.match "${builtins.storeDir}/([a-zA-Z0-9]+)-.*" path) 0; in
in { {
meta.maintainers = [ lib.maintainers.hitsmaxft ]; meta.maintainers = [ lib.maintainers.hitsmaxft ];
options.programs.zsh.antidote = { options.programs.zsh.antidote = {
@ -30,25 +40,26 @@ in {
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
home.packages = lib.mkIf (cfg.package != null) [ cfg.package ]; home.packages = lib.mkIf (cfg.package != null) [ cfg.package ];
programs.zsh.initContent = let programs.zsh.initContent =
configFiles = pkgs.runCommand "hm_antidote-files" { } '' let
echo "${zPluginStr cfg.plugins}" > $out configFiles = pkgs.runCommand "hm_antidote-files" { } ''
''; echo "${zPluginStr cfg.plugins}" > $out
hashId = parseHashId "${configFiles}"; '';
in (lib.mkOrder 550 '' hashId = parseHashId "${configFiles}";
## home-manager/antidote begin : in
source ${cfg.package}/share/antidote/antidote.zsh (lib.mkOrder 550 ''
${lib.optionalString cfg.useFriendlyNames ## home-manager/antidote begin :
"zstyle ':antidote:bundle' use-friendly-names 'yes'"} source ${cfg.package}/share/antidote/antidote.zsh
${lib.optionalString cfg.useFriendlyNames "zstyle ':antidote:bundle' use-friendly-names 'yes'"}
bundlefile=${configFiles} bundlefile=${configFiles}
zstyle ':antidote:bundle' file $bundlefile zstyle ':antidote:bundle' file $bundlefile
staticfile=/tmp/tmp_hm_zsh_plugins.zsh-${hashId} staticfile=/tmp/tmp_hm_zsh_plugins.zsh-${hashId}
zstyle ':antidote:static' file $staticfile zstyle ':antidote:static' file $staticfile
antidote load $bundlefile $staticfile antidote load $bundlefile $staticfile
## home-manager/antidote end ## home-manager/antidote end
''); '');
}; };
} }

View file

@ -1,16 +1,20 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.programs.aria2; cfg = config.programs.aria2;
formatLine = n: v: formatLine =
n: v:
let let
formatValue = v: formatValue = v: if builtins.isBool v then (if v then "true" else "false") else toString v;
if builtins.isBool v then in
(if v then "true" else "false") "${n}=${formatValue v}";
else in
toString v; {
in "${n}=${formatValue v}";
in {
meta.maintainers = [ lib.hm.maintainers.justinlovinger ]; meta.maintainers = [ lib.hm.maintainers.justinlovinger ];
options.programs.aria2 = { options.programs.aria2 = {
@ -19,7 +23,14 @@ in {
package = lib.mkPackageOption pkgs "aria2" { nullable = true; }; package = lib.mkPackageOption pkgs "aria2" { nullable = true; };
settings = lib.mkOption { settings = lib.mkOption {
type = with lib.types; attrsOf (oneOf [ bool float int str ]); type =
with lib.types;
attrsOf (oneOf [
bool
float
int
str
]);
default = { }; default = { };
description = '' description = ''
Options to add to {file}`aria2.conf` file. Options to add to {file}`aria2.conf` file.
@ -50,8 +61,10 @@ in {
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
home.packages = lib.mkIf (cfg.package != null) [ cfg.package ]; home.packages = lib.mkIf (cfg.package != null) [ cfg.package ];
xdg.configFile."aria2/aria2.conf".text = lib.concatStringsSep "\n" ([ ] xdg.configFile."aria2/aria2.conf".text = lib.concatStringsSep "\n" (
[ ]
++ lib.mapAttrsToList formatLine cfg.settings ++ lib.mapAttrsToList formatLine cfg.settings
++ lib.optional (cfg.extraConfig != "") cfg.extraConfig); ++ lib.optional (cfg.extraConfig != "") cfg.extraConfig
);
}; };
} }

View file

@ -1,4 +1,5 @@
{ config, lib, ... }: { { config, lib, ... }:
{
options.astroid = { options.astroid = {
enable = lib.mkEnableOption "Astroid"; enable = lib.mkEnableOption "Astroid";
@ -14,7 +15,9 @@
extraConfig = lib.mkOption { extraConfig = lib.mkOption {
type = lib.types.attrsOf lib.types.anything; type = lib.types.attrsOf lib.types.anything;
default = { }; default = { };
example = { select_query = ""; }; example = {
select_query = "";
};
description = '' description = ''
Extra settings to add to this astroid account configuration. Extra settings to add to this astroid account configuration.
''; '';
@ -22,7 +25,8 @@
}; };
config = lib.mkIf config.notmuch.enable { config = lib.mkIf config.notmuch.enable {
astroid.sendMailCommand = lib.mkIf config.msmtp.enable astroid.sendMailCommand = lib.mkIf config.msmtp.enable (
(lib.mkOptionDefault "msmtpq --read-envelope-from --read-recipients"); lib.mkOptionDefault "msmtpq --read-envelope-from --read-recipients"
);
}; };
} }

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (lib) mkOption types; inherit (lib) mkOption types;
@ -6,12 +11,12 @@ let
jsonFormat = pkgs.formats.json { }; jsonFormat = pkgs.formats.json { };
astroidAccounts = astroidAccounts = lib.filterAttrs (n: v: v.astroid.enable) config.accounts.email.accounts;
lib.filterAttrs (n: v: v.astroid.enable) config.accounts.email.accounts;
boolOpt = b: if b then "true" else "false"; boolOpt = b: if b then "true" else "false";
accountAttr = account: accountAttr =
account:
with account; with account;
{ {
email = address; email = address;
@ -23,33 +28,38 @@ let
save_sent = "true"; save_sent = "true";
save_sent_to = "${maildir.absPath}/${folders.sent}/cur/"; save_sent_to = "${maildir.absPath}/${folders.sent}/cur/";
select_query = ""; select_query = "";
} // lib.optionalAttrs (signature.showSignature != "none") { }
// lib.optionalAttrs (signature.showSignature != "none") {
signature_attach = boolOpt (signature.showSignature == "attach"); signature_attach = boolOpt (signature.showSignature == "attach");
signature_default_on = boolOpt (signature.showSignature != "none"); signature_default_on = boolOpt (signature.showSignature != "none");
signature_file = pkgs.writeText "signature.txt" signature.text; signature_file = pkgs.writeText "signature.txt" signature.text;
signature_file_markdown = "false"; signature_file_markdown = "false";
signature_separate = "true"; # prepends '--\n' to the signature signature_separate = "true"; # prepends '--\n' to the signature
} // lib.optionalAttrs (gpg != null) { }
// lib.optionalAttrs (gpg != null) {
always_gpg_sign = boolOpt gpg.signByDefault; always_gpg_sign = boolOpt gpg.signByDefault;
gpgkey = gpg.key; gpgkey = gpg.key;
} // astroid.extraConfig; }
// astroid.extraConfig;
# See https://github.com/astroidmail/astroid/wiki/Configuration-Reference # See https://github.com/astroidmail/astroid/wiki/Configuration-Reference
finalConfig = let finalConfig =
template = lib.importJSON ./astroid-config-template.json; let
astroidConfig = lib.foldl' lib.recursiveUpdate template [ template = lib.importJSON ./astroid-config-template.json;
{ astroidConfig = lib.foldl' lib.recursiveUpdate template [
astroid.notmuch_config = {
"${config.xdg.configHome}/notmuch/default/config"; astroid.notmuch_config = "${config.xdg.configHome}/notmuch/default/config";
accounts = lib.mapAttrs (n: accountAttr) astroidAccounts; accounts = lib.mapAttrs (n: accountAttr) astroidAccounts;
crypto.gpg.path = "${pkgs.gnupg}/bin/gpg"; crypto.gpg.path = "${pkgs.gnupg}/bin/gpg";
} }
cfg.extraConfig cfg.extraConfig
cfg.externalEditor cfg.externalEditor
]; ];
in astroidConfig; in
astroidConfig;
in { in
{
options = { options = {
programs.astroid = { programs.astroid = {
enable = lib.mkEnableOption "Astroid"; enable = lib.mkEnableOption "Astroid";
@ -69,15 +79,15 @@ in {
type = types.nullOr types.str; type = types.nullOr types.str;
default = null; default = null;
# Converts it into JSON that can be merged into the configuration. # Converts it into JSON that can be merged into the configuration.
apply = cmd: apply =
cmd:
lib.optionalAttrs (cmd != null) { lib.optionalAttrs (cmd != null) {
editor = { editor = {
"external_editor" = "true"; "external_editor" = "true";
"cmd" = cmd; "cmd" = cmd;
}; };
}; };
example = example = "nvim-qt -- -c 'set ft=mail' '+set fileencoding=utf-8' '+set ff=unix' '+set enc=utf-8' '+set fo+=w' %1";
"nvim-qt -- -c 'set ft=mail' '+set fileencoding=utf-8' '+set ff=unix' '+set enc=utf-8' '+set fo+=w' %1";
description = '' description = ''
You can use the following variables: You can use the following variables:
@ -117,8 +127,7 @@ in {
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
home.packages = lib.mkIf (cfg.package != null) [ cfg.package ]; home.packages = lib.mkIf (cfg.package != null) [ cfg.package ];
xdg.configFile."astroid/config".source = xdg.configFile."astroid/config".source = jsonFormat.generate "astroid-config" finalConfig;
jsonFormat.generate "astroid-config" finalConfig;
xdg.configFile."astroid/poll.sh" = lib.mkIf (cfg.pollScript != "") { xdg.configFile."astroid/poll.sh" = lib.mkIf (cfg.pollScript != "") {
executable = true; executable = true;

View file

@ -1,4 +1,9 @@
{ config, pkgs, lib, ... }: {
config,
pkgs,
lib,
...
}:
let let
cfg = config.programs.atuin; cfg = config.programs.atuin;
daemonCfg = cfg.daemon; daemonCfg = cfg.daemon;
@ -7,8 +12,12 @@ let
inherit (lib) mkIf mkOption types; inherit (lib) mkIf mkOption types;
inherit (pkgs.stdenv) isLinux isDarwin; inherit (pkgs.stdenv) isLinux isDarwin;
in { in
meta.maintainers = with lib.maintainers; [ hawkw water-sucks ]; {
meta.maintainers = with lib.maintainers; [
hawkw
water-sucks
];
options.programs.atuin = { options.programs.atuin = {
enable = lib.mkEnableOption "atuin"; enable = lib.mkEnableOption "atuin";
@ -17,18 +26,15 @@ in {
enableBashIntegration = lib.hm.shell.mkBashIntegrationOption { enableBashIntegration = lib.hm.shell.mkBashIntegrationOption {
inherit config; inherit config;
extraDescription = extraDescription = "If enabled, this will bind `ctrl-r` to open the Atuin history.";
"If enabled, this will bind `ctrl-r` to open the Atuin history.";
}; };
enableFishIntegration = lib.hm.shell.mkFishIntegrationOption { enableFishIntegration = lib.hm.shell.mkFishIntegrationOption {
inherit config; inherit config;
extraDescription = extraDescription = "If enabled, this will bind the up-arrow key to open the Atuin history.";
"If enabled, this will bind the up-arrow key to open the Atuin history.";
}; };
enableNushellIntegration = enableNushellIntegration = lib.hm.shell.mkNushellIntegrationOption { inherit config; };
lib.hm.shell.mkNushellIntegrationOption { inherit config; };
enableZshIntegration = lib.hm.shell.mkZshIntegrationOption { enableZshIntegration = lib.hm.shell.mkZshIntegrationOption {
inherit config; inherit config;
@ -41,21 +47,30 @@ in {
flags = mkOption { flags = mkOption {
default = [ ]; default = [ ];
type = types.listOf types.str; type = types.listOf types.str;
example = [ "--disable-up-arrow" "--disable-ctrl-r" ]; example = [
"--disable-up-arrow"
"--disable-ctrl-r"
];
description = '' description = ''
Flags to append to the shell hook. Flags to append to the shell hook.
''; '';
}; };
settings = mkOption { settings = mkOption {
type = with types; type =
with types;
let let
prim = oneOf [ bool int str ]; prim = oneOf [
bool
int
str
];
primOrPrimAttrs = either prim (attrsOf prim); primOrPrimAttrs = either prim (attrsOf prim);
entry = either prim (listOf primOrPrimAttrs); entry = either prim (listOf primOrPrimAttrs);
entryOrAttrsOf = t: either entry (attrsOf t); entryOrAttrsOf = t: either entry (attrsOf t);
entries = entryOrAttrsOf (entryOrAttrsOf entry); entries = entryOrAttrsOf (entryOrAttrsOf entry);
in attrsOf entries // { description = "Atuin configuration"; }; in
attrsOf entries // { description = "Atuin configuration"; };
default = { }; default = { };
example = lib.literalExpression '' example = lib.literalExpression ''
{ {
@ -79,8 +94,15 @@ in {
logLevel = mkOption { logLevel = mkOption {
default = null; default = null;
type = type = types.nullOr (
types.nullOr (types.enum [ "trace" "debug" "info" "warn" "error" ]); types.enum [
"trace"
"debug"
"info"
"warn"
"error"
]
);
description = '' description = ''
Verbosity of Atuin daemon logging. Verbosity of Atuin daemon logging.
''; '';
@ -88,126 +110,145 @@ in {
}; };
}; };
config = let flagsStr = lib.escapeShellArgs cfg.flags; config =
in mkIf cfg.enable (lib.mkMerge [ let
{ flagsStr = lib.escapeShellArgs cfg.flags;
# Always add the configured `atuin` package. in
home.packages = [ cfg.package ]; mkIf cfg.enable (
lib.mkMerge [
{
# Always add the configured `atuin` package.
home.packages = [ cfg.package ];
# If there are user-provided settings, generate the config file. # If there are user-provided settings, generate the config file.
xdg.configFile."atuin/config.toml" = mkIf (cfg.settings != { }) { xdg.configFile."atuin/config.toml" = mkIf (cfg.settings != { }) {
source = tomlFormat.generate "atuin-config" cfg.settings; source = tomlFormat.generate "atuin-config" cfg.settings;
}; };
programs.bash.initExtra = mkIf cfg.enableBashIntegration '' programs.bash.initExtra = mkIf cfg.enableBashIntegration ''
if [[ :$SHELLOPTS: =~ :(vi|emacs): ]]; then if [[ :$SHELLOPTS: =~ :(vi|emacs): ]]; then
source "${pkgs.bash-preexec}/share/bash/bash-preexec.sh" source "${pkgs.bash-preexec}/share/bash/bash-preexec.sh"
eval "$(${lib.getExe cfg.package} init bash ${flagsStr})" eval "$(${lib.getExe cfg.package} init bash ${flagsStr})"
fi fi
''; '';
programs.zsh.initContent = mkIf cfg.enableZshIntegration '' programs.zsh.initContent = mkIf cfg.enableZshIntegration ''
if [[ $options[zle] = on ]]; then if [[ $options[zle] = on ]]; then
eval "$(${lib.getExe cfg.package} init zsh ${flagsStr})" eval "$(${lib.getExe cfg.package} init zsh ${flagsStr})"
fi fi
''; '';
programs.fish.interactiveShellInit = mkIf cfg.enableFishIntegration '' programs.fish.interactiveShellInit = mkIf cfg.enableFishIntegration ''
${lib.getExe cfg.package} init fish ${flagsStr} | source ${lib.getExe cfg.package} init fish ${flagsStr} | source
''; '';
programs.nushell = mkIf cfg.enableNushellIntegration { programs.nushell = mkIf cfg.enableNushellIntegration {
extraConfig = '' extraConfig = ''
source ${ source ${
pkgs.runCommand "atuin-nushell-config.nu" { pkgs.runCommand "atuin-nushell-config.nu"
nativeBuildInputs = [ pkgs.writableTmpDirAsHomeHook ]; {
} '' nativeBuildInputs = [ pkgs.writableTmpDirAsHomeHook ];
${lib.getExe cfg.package} init nu ${flagsStr} >> "$out" }
'' ''
} ${lib.getExe cfg.package} init nu ${flagsStr} >> "$out"
''; ''
}; }
}
(mkIf daemonCfg.enable (lib.mkMerge [
{
assertions = [
{
assertion = lib.versionAtLeast cfg.package.version "18.2.0";
message = ''
The Atuin daemon requires at least version 18.2.0 or later.
''; '';
} };
{ }
assertion = isLinux || isDarwin;
message =
"The Atuin daemon can only be configured on either Linux or macOS.";
}
];
programs.atuin.settings = { daemon = { enabled = true; }; }; (mkIf daemonCfg.enable (
} lib.mkMerge [
(mkIf isLinux { {
programs.atuin.settings = { daemon = { systemd_socket = true; }; }; assertions = [
{
assertion = lib.versionAtLeast cfg.package.version "18.2.0";
message = ''
The Atuin daemon requires at least version 18.2.0 or later.
'';
}
{
assertion = isLinux || isDarwin;
message = "The Atuin daemon can only be configured on either Linux or macOS.";
}
];
systemd.user.services.atuin-daemon = { programs.atuin.settings = {
Unit = { daemon = {
Description = "Atuin daemon"; enabled = true;
Requires = [ "atuin-daemon.socket" ]; };
};
Install = {
Also = [ "atuin-daemon.socket" ];
WantedBy = [ "default.target" ];
};
Service = {
ExecStart = "${lib.getExe cfg.package} daemon";
Environment = lib.optionals (daemonCfg.logLevel != null)
[ "ATUIN_LOG=${daemonCfg.logLevel}" ];
Restart = "on-failure";
RestartSteps = 3;
RestartMaxDelaySec = 6;
};
};
systemd.user.sockets.atuin-daemon = let
socket_dir = if lib.versionAtLeast cfg.package.version "18.4.0" then
"%t"
else
"%D/atuin";
in {
Unit = { Description = "Atuin daemon socket"; };
Install = { WantedBy = [ "sockets.target" ]; };
Socket = {
ListenStream = "${socket_dir}/atuin.sock";
SocketMode = "0600";
RemoveOnStop = true;
};
};
})
(mkIf isDarwin {
programs.atuin.settings = {
daemon = {
socket_path =
lib.mkDefault "${config.xdg.dataHome}/atuin/daemon.sock";
};
};
launchd.agents.atuin-daemon = {
enable = true;
config = {
ProgramArguments = [ "${lib.getExe cfg.package}" "daemon" ];
EnvironmentVariables =
lib.optionalAttrs (daemonCfg.logLevel != null) {
ATUIN_LOG = daemonCfg.logLevel;
}; };
KeepAlive = { }
Crashed = true; (mkIf isLinux {
SuccessfulExit = false; programs.atuin.settings = {
}; daemon = {
ProcessType = "Background"; systemd_socket = true;
}; };
}; };
})
])) systemd.user.services.atuin-daemon = {
]); Unit = {
Description = "Atuin daemon";
Requires = [ "atuin-daemon.socket" ];
};
Install = {
Also = [ "atuin-daemon.socket" ];
WantedBy = [ "default.target" ];
};
Service = {
ExecStart = "${lib.getExe cfg.package} daemon";
Environment = lib.optionals (daemonCfg.logLevel != null) [ "ATUIN_LOG=${daemonCfg.logLevel}" ];
Restart = "on-failure";
RestartSteps = 3;
RestartMaxDelaySec = 6;
};
};
systemd.user.sockets.atuin-daemon =
let
socket_dir = if lib.versionAtLeast cfg.package.version "18.4.0" then "%t" else "%D/atuin";
in
{
Unit = {
Description = "Atuin daemon socket";
};
Install = {
WantedBy = [ "sockets.target" ];
};
Socket = {
ListenStream = "${socket_dir}/atuin.sock";
SocketMode = "0600";
RemoveOnStop = true;
};
};
})
(mkIf isDarwin {
programs.atuin.settings = {
daemon = {
socket_path = lib.mkDefault "${config.xdg.dataHome}/atuin/daemon.sock";
};
};
launchd.agents.atuin-daemon = {
enable = true;
config = {
ProgramArguments = [
"${lib.getExe cfg.package}"
"daemon"
];
EnvironmentVariables = lib.optionalAttrs (daemonCfg.logLevel != null) {
ATUIN_LOG = daemonCfg.logLevel;
};
KeepAlive = {
Crashed = true;
SuccessfulExit = false;
};
ProcessType = "Background";
};
};
})
]
))
]
);
} }

View file

@ -1,9 +1,15 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.programs.autojump; cfg = config.programs.autojump;
inherit (lib) mkIf; inherit (lib) mkIf;
in { in
{
meta.maintainers = [ lib.maintainers.evanjs ]; meta.maintainers = [ lib.maintainers.evanjs ];
options.programs.autojump = { options.programs.autojump = {
@ -11,22 +17,21 @@ in {
package = lib.mkPackageOption pkgs "autojump" { }; package = lib.mkPackageOption pkgs "autojump" { };
enableBashIntegration = enableBashIntegration = lib.hm.shell.mkBashIntegrationOption { inherit config; };
lib.hm.shell.mkBashIntegrationOption { inherit config; };
enableFishIntegration = enableFishIntegration = lib.hm.shell.mkFishIntegrationOption { inherit config; };
lib.hm.shell.mkFishIntegrationOption { inherit config; };
enableZshIntegration = enableZshIntegration = lib.hm.shell.mkZshIntegrationOption { inherit config; };
lib.hm.shell.mkZshIntegrationOption { inherit config; };
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable {
home.packages = [ cfg.package ]; home.packages = [ cfg.package ];
programs.bash.initExtra = mkIf cfg.enableBashIntegration (lib.mkBefore '' programs.bash.initExtra = mkIf cfg.enableBashIntegration (
. ${cfg.package}/share/autojump/autojump.bash lib.mkBefore ''
''); . ${cfg.package}/share/autojump/autojump.bash
''
);
programs.zsh.initContent = mkIf cfg.enableZshIntegration '' programs.zsh.initContent = mkIf cfg.enableZshIntegration ''
. ${cfg.package}/share/autojump/autojump.zsh . ${cfg.package}/share/autojump/autojump.zsh

View file

@ -1,25 +1,49 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (lib) inherit (lib)
literalExpression listToAttrs mapAttrs' mapAttrsToList mkIf mkOption literalExpression
optional types; listToAttrs
mapAttrs'
mapAttrsToList
mkIf
mkOption
optional
types
;
cfg = config.programs.autorandr; cfg = config.programs.autorandr;
matrixOf = n: m: elemType: matrixOf =
n: m: elemType:
lib.mkOptionType rec { lib.mkOptionType rec {
name = "matrixOf"; name = "matrixOf";
description = description = "${toString n}×${toString m} matrix of ${elemType.description}s";
"${toString n}×${toString m} matrix of ${elemType.description}s"; check =
check = xss: xss:
let listOfSize = l: xs: lib.isList xs && lib.length xs == l; let
in listOfSize n xss listOfSize = l: xs: lib.isList xs && lib.length xs == l;
&& lib.all (xs: listOfSize m xs && lib.all elemType.check xs) xss; in
listOfSize n xss && lib.all (xs: listOfSize m xs && lib.all elemType.check xs) xss;
merge = lib.mergeOneOption; merge = lib.mergeOneOption;
getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "*" "*" ]); getSubOptions =
prefix:
elemType.getSubOptions (
prefix
++ [
"*"
"*"
]
);
getSubModules = elemType.getSubModules; getSubModules = elemType.getSubModules;
substSubModules = mod: matrixOf n m (elemType.substSubModules mod); substSubModules = mod: matrixOf n m (elemType.substSubModules mod);
functor = (lib.defaultFunctor name) // { wrapped = elemType; }; functor = (lib.defaultFunctor name) // {
wrapped = elemType;
};
}; };
profileModule = types.submodule { profileModule = types.submodule {
@ -97,7 +121,14 @@ let
}; };
rotate = mkOption { rotate = mkOption {
type = types.nullOr (types.enum [ "normal" "left" "right" "inverted" ]); type = types.nullOr (
types.enum [
"normal"
"left"
"right"
"inverted"
]
);
description = "Output rotate configuration."; description = "Output rotate configuration.";
default = null; default = null;
example = "left"; example = "left";
@ -128,26 +159,31 @@ let
}; };
scale = mkOption { scale = mkOption {
type = types.nullOr (types.submodule { type = types.nullOr (
options = { types.submodule {
method = mkOption { options = {
type = types.enum [ "factor" "pixel" ]; method = mkOption {
description = "Output scaling method."; type = types.enum [
default = "factor"; "factor"
example = "pixel"; "pixel"
}; ];
description = "Output scaling method.";
default = "factor";
example = "pixel";
};
x = mkOption { x = mkOption {
type = types.either types.float types.ints.positive; type = types.either types.float types.ints.positive;
description = "Horizontal scaling factor/pixels."; description = "Horizontal scaling factor/pixels.";
}; };
y = mkOption { y = mkOption {
type = types.either types.float types.ints.positive; type = types.either types.float types.ints.positive;
description = "Vertical scaling factor/pixels."; description = "Vertical scaling factor/pixels.";
};
}; };
}; }
}); );
description = '' description = ''
Output scale configuration. Output scale configuration.
@ -172,7 +208,12 @@ let
}; };
filter = mkOption { filter = mkOption {
type = types.nullOr (types.enum [ "bilinear" "nearest" ]); type = types.nullOr (
types.enum [
"bilinear"
"nearest"
]
);
description = "Interpolation method to be used for scaling the output."; description = "Interpolation method to be used for scaling the output.";
default = null; default = null;
example = "nearest"; example = "nearest";
@ -242,30 +283,35 @@ let
}; };
}; };
hookToFile = folder: name: hook: hookToFile =
folder: name: hook:
lib.nameValuePair "autorandr/${folder}/${name}" { lib.nameValuePair "autorandr/${folder}/${name}" {
source = "${pkgs.writeShellScriptBin "hook" hook}/bin/hook"; source = "${pkgs.writeShellScriptBin "hook" hook}/bin/hook";
}; };
profileToFiles = name: profile: profileToFiles =
let inherit (profile) hooks; name: profile:
in lib.mkMerge [ let
inherit (profile) hooks;
in
lib.mkMerge [
{ {
"autorandr/${name}/setup".text = lib.concatStringsSep "\n" "autorandr/${name}/setup".text = lib.concatStringsSep "\n" (
(mapAttrsToList fingerprintToString profile.fingerprint); mapAttrsToList fingerprintToString profile.fingerprint
"autorandr/${name}/config".text = lib.concatStringsSep "\n" );
(mapAttrsToList configToString profile.config); "autorandr/${name}/config".text = lib.concatStringsSep "\n" (
mapAttrsToList configToString profile.config
);
} }
(mkIf (hooks.postswitch != "") (mkIf (hooks.postswitch != "") (listToAttrs [ (hookToFile name "postswitch" hooks.postswitch) ]))
(listToAttrs [ (hookToFile name "postswitch" hooks.postswitch) ])) (mkIf (hooks.preswitch != "") (listToAttrs [ (hookToFile name "preswitch" hooks.preswitch) ]))
(mkIf (hooks.preswitch != "") (mkIf (hooks.predetect != "") (listToAttrs [ (hookToFile name "predetect" hooks.predetect) ]))
(listToAttrs [ (hookToFile name "preswitch" hooks.preswitch) ]))
(mkIf (hooks.predetect != "")
(listToAttrs [ (hookToFile name "predetect" hooks.predetect) ]))
]; ];
fingerprintToString = name: edid: "${name} ${edid}"; fingerprintToString = name: edid: "${name} ${edid}";
configToString = name: config: configToString =
name: config:
if config.enable then if config.enable then
lib.concatStringsSep "\n" ([ "output ${name}" ] lib.concatStringsSep "\n" (
[ "output ${name}" ]
++ optional (config.position != "") "pos ${config.position}" ++ optional (config.position != "") "pos ${config.position}"
++ optional (config.crtc != null) "crtc ${toString config.crtc}" ++ optional (config.crtc != null) "crtc ${toString config.crtc}"
++ optional config.primary "primary" ++ optional config.primary "primary"
@ -275,18 +321,23 @@ let
++ optional (config.rate != "") "rate ${config.rate}" ++ optional (config.rate != "") "rate ${config.rate}"
++ optional (config.rotate != null) "rotate ${config.rotate}" ++ optional (config.rotate != null) "rotate ${config.rotate}"
++ optional (config.filter != null) "filter ${config.filter}" ++ optional (config.filter != null) "filter ${config.filter}"
++ optional (config.transform != null) ("transform " ++ optional (config.transform != null) (
+ lib.concatMapStringsSep "," toString (lib.flatten config.transform)) "transform " + lib.concatMapStringsSep "," toString (lib.flatten config.transform)
++ optional (config.scale != null) )
((if config.scale.method == "factor" then "scale" else "scale-from") ++ optional (config.scale != null) (
+ " ${toString config.scale.x}x${toString config.scale.y}") (if config.scale.method == "factor" then "scale" else "scale-from")
++ optional (config.extraConfig != "") config.extraConfig) + " ${toString config.scale.x}x${toString config.scale.y}"
else '' )
output ${name} ++ optional (config.extraConfig != "") config.extraConfig
off )
''; else
''
output ${name}
off
'';
in { in
{
options = { options = {
programs.autorandr = { programs.autorandr = {
enable = lib.mkEnableOption "Autorandr"; enable = lib.mkEnableOption "Autorandr";
@ -358,15 +409,19 @@ in {
}; };
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
assertions = lib.flatten (mapAttrsToList (profile: assertions = lib.flatten (
{ config, ... }: mapAttrsToList (
mapAttrsToList (output: opts: { profile:
assertion = opts.scale == null || opts.transform == null; { config, ... }:
message = '' mapAttrsToList (output: opts: {
Cannot use the profile output options 'scale' and 'transform' simultaneously. assertion = opts.scale == null || opts.transform == null;
Check configuration for: programs.autorandr.profiles.${profile}.config.${output} message = ''
''; Cannot use the profile output options 'scale' and 'transform' simultaneously.
}) config) cfg.profiles); Check configuration for: programs.autorandr.profiles.${profile}.config.${output}
'';
}) config
) cfg.profiles
);
home.packages = lib.mkIf (cfg.package != null) [ cfg.package ]; home.packages = lib.mkIf (cfg.package != null) [ cfg.package ];

View file

@ -1,10 +1,16 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.programs.awscli; cfg = config.programs.awscli;
iniFormat = pkgs.formats.ini { }; iniFormat = pkgs.formats.ini { };
in { in
{
meta.maintainers = [ lib.maintainers.anthonyroussel ]; meta.maintainers = [ lib.maintainers.anthonyroussel ];
options.programs.awscli = { options.programs.awscli = {
@ -55,16 +61,12 @@ in {
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
home.packages = lib.mkIf (cfg.package != null) [ cfg.package ]; home.packages = lib.mkIf (cfg.package != null) [ cfg.package ];
home.file."${config.home.homeDirectory}/.aws/config" = home.file."${config.home.homeDirectory}/.aws/config" = lib.mkIf (cfg.settings != { }) {
lib.mkIf (cfg.settings != { }) { source = iniFormat.generate "aws-config-${config.home.username}" cfg.settings;
source = };
iniFormat.generate "aws-config-${config.home.username}" cfg.settings;
};
home.file."${config.home.homeDirectory}/.aws/credentials" = home.file."${config.home.homeDirectory}/.aws/credentials" = lib.mkIf (cfg.credentials != { }) {
lib.mkIf (cfg.credentials != { }) { source = iniFormat.generate "aws-credentials-${config.home.username}" cfg.credentials;
source = iniFormat.generate "aws-credentials-${config.home.username}" };
cfg.credentials;
};
}; };
} }

View file

@ -1,16 +1,23 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.programs.bacon; cfg = config.programs.bacon;
settingsFormat = pkgs.formats.toml { }; settingsFormat = pkgs.formats.toml { };
configDir = if pkgs.stdenv.isDarwin then configDir =
"Library/Application Support/org.dystroy.bacon" if pkgs.stdenv.isDarwin then
else "Library/Application Support/org.dystroy.bacon"
"${config.xdg.configHome}/bacon"; else
"${config.xdg.configHome}/bacon";
in { in
{
meta.maintainers = [ lib.hm.maintainers.shimunn ]; meta.maintainers = [ lib.hm.maintainers.shimunn ];
options.programs.bacon = { options.programs.bacon = {
@ -23,7 +30,13 @@ in {
default = { }; default = { };
example = { example = {
jobs.default = { jobs.default = {
command = [ "cargo" "build" "--all-features" "--color" "always" ]; command = [
"cargo"
"build"
"--all-features"
"--color"
"always"
];
need_stdout = true; need_stdout = true;
}; };
}; };

View file

@ -1,10 +1,21 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (lib) mkIf mkOption optionalAttrs types; inherit (lib)
mkIf
mkOption
optionalAttrs
types
;
cfg = config.programs.bash; cfg = config.programs.bash;
writeBashScript = name: text: writeBashScript =
name: text:
pkgs.writeTextFile { pkgs.writeTextFile {
inherit name text; inherit name text;
checkPhase = '' checkPhase = ''
@ -12,15 +23,19 @@ let
''; '';
}; };
in { in
{
meta.maintainers = [ lib.maintainers.rycee ]; meta.maintainers = [ lib.maintainers.rycee ];
imports = [ imports = [
(lib.mkRenamedOptionModule [ "programs" "bash" "enableAutojump" ] [ (lib.mkRenamedOptionModule
"programs" [ "programs" "bash" "enableAutojump" ]
"autojump" [
"enable" "programs"
]) "autojump"
"enable"
]
)
]; ];
options = { options = {
@ -70,8 +85,14 @@ in {
}; };
historyControl = mkOption { historyControl = mkOption {
type = types.listOf type = types.listOf (
(types.enum [ "erasedups" "ignoredups" "ignorespace" "ignoreboth" ]); types.enum [
"erasedups"
"ignoredups"
"ignorespace"
"ignoreboth"
]
);
default = [ ]; default = [ ];
description = "Controlling how commands are saved on the history list."; description = "Controlling how commands are saved on the history list.";
}; };
@ -79,9 +100,12 @@ in {
historyIgnore = mkOption { historyIgnore = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
default = [ ]; default = [ ];
example = [ "ls" "cd" "exit" ]; example = [
description = "ls"
"List of commands that should not be saved to the history list."; "cd"
"exit"
];
description = "List of commands that should not be saved to the history list.";
}; };
shellOptions = mkOption { shellOptions = mkOption {
@ -101,7 +125,10 @@ in {
# Warn if closing shell with running jobs. # Warn if closing shell with running jobs.
"checkjobs" "checkjobs"
]; ];
example = [ "extglob" "-cdspell" ]; example = [
"extglob"
"-cdspell"
];
description = '' description = ''
Shell options to set. Prefix an option with Shell options to set. Prefix an option with
"`-`" to unset. "`-`" to unset.
@ -111,7 +138,9 @@ in {
sessionVariables = mkOption { sessionVariables = mkOption {
default = { }; default = { };
type = types.attrs; type = types.attrs;
example = { MAILCHECK = 30; }; example = {
MAILCHECK = 30;
};
description = '' description = ''
Environment variables that will be set for the Bash session. Environment variables that will be set for the Bash session.
''; '';
@ -170,77 +199,90 @@ in {
}; };
}; };
config = let config =
aliasesStr = lib.concatStringsSep "\n" let
(lib.mapAttrsToList (k: v: "alias ${k}=${lib.escapeShellArg v}") aliasesStr = lib.concatStringsSep "\n" (
cfg.shellAliases); lib.mapAttrsToList (k: v: "alias ${k}=${lib.escapeShellArg v}") cfg.shellAliases
);
shoptsStr = let switch = v: if lib.hasPrefix "-" v then "-u" else "-s"; shoptsStr =
in lib.concatStringsSep "\n" let
(map (v: "shopt ${switch v} ${lib.removePrefix "-" v}") cfg.shellOptions); switch = v: if lib.hasPrefix "-" v then "-u" else "-s";
in
lib.concatStringsSep "\n" (map (v: "shopt ${switch v} ${lib.removePrefix "-" v}") cfg.shellOptions);
sessionVarsStr = config.lib.shell.exportAll cfg.sessionVariables; sessionVarsStr = config.lib.shell.exportAll cfg.sessionVariables;
historyControlStr = (lib.concatStringsSep "\n" historyControlStr = (
(lib.mapAttrsToList (n: v: "${n}=${v}") lib.concatStringsSep "\n" (
(optionalAttrs (cfg.historyFileSize != null) { lib.mapAttrsToList (n: v: "${n}=${v}") (
HISTFILESIZE = toString cfg.historyFileSize; optionalAttrs (cfg.historyFileSize != null) {
} // optionalAttrs (cfg.historySize != null) { HISTFILESIZE = toString cfg.historyFileSize;
HISTSIZE = toString cfg.historySize; }
} // optionalAttrs (cfg.historyFile != null) { // optionalAttrs (cfg.historySize != null) {
HISTFILE = ''"${cfg.historyFile}"''; HISTSIZE = toString cfg.historySize;
} // optionalAttrs (cfg.historyControl != [ ]) { }
HISTCONTROL = lib.concatStringsSep ":" cfg.historyControl; // optionalAttrs (cfg.historyFile != null) {
} // optionalAttrs (cfg.historyIgnore != [ ]) { HISTFILE = ''"${cfg.historyFile}"'';
HISTIGNORE = }
lib.escapeShellArg (lib.concatStringsSep ":" cfg.historyIgnore); // optionalAttrs (cfg.historyControl != [ ]) {
}) ++ lib.optional (cfg.historyFile != null) HISTCONTROL = lib.concatStringsSep ":" cfg.historyControl;
''mkdir -p "$(dirname "$HISTFILE")"'')); }
in mkIf cfg.enable { // optionalAttrs (cfg.historyIgnore != [ ]) {
home.packages = lib.mkIf (cfg.package != null) [ cfg.package ]; HISTIGNORE = lib.escapeShellArg (lib.concatStringsSep ":" cfg.historyIgnore);
}
)
++ lib.optional (cfg.historyFile != null) ''mkdir -p "$(dirname "$HISTFILE")"''
)
);
in
mkIf cfg.enable {
home.packages = lib.mkIf (cfg.package != null) [ cfg.package ];
home.file.".bash_profile".source = writeBashScript "bash_profile" '' home.file.".bash_profile".source = writeBashScript "bash_profile" ''
# include .profile if it exists # include .profile if it exists
[[ -f ~/.profile ]] && . ~/.profile [[ -f ~/.profile ]] && . ~/.profile
# include .bashrc if it exists # include .bashrc if it exists
[[ -f ~/.bashrc ]] && . ~/.bashrc [[ -f ~/.bashrc ]] && . ~/.bashrc
''; '';
# If completion is enabled then make sure it is sourced very early. This # If completion is enabled then make sure it is sourced very early. This
# is to avoid problems if any other initialization code attempts to set up # is to avoid problems if any other initialization code attempts to set up
# completion. # completion.
programs.bash.initExtra = mkIf cfg.enableCompletion (lib.mkOrder 100 '' programs.bash.initExtra = mkIf cfg.enableCompletion (
if [[ ! -v BASH_COMPLETION_VERSINFO ]]; then lib.mkOrder 100 ''
. "${pkgs.bash-completion}/etc/profile.d/bash_completion.sh" if [[ ! -v BASH_COMPLETION_VERSINFO ]]; then
fi . "${pkgs.bash-completion}/etc/profile.d/bash_completion.sh"
''); fi
''
);
home.file.".profile".source = writeBashScript "profile" '' home.file.".profile".source = writeBashScript "profile" ''
. "${config.home.profileDirectory}/etc/profile.d/hm-session-vars.sh" . "${config.home.profileDirectory}/etc/profile.d/hm-session-vars.sh"
${sessionVarsStr} ${sessionVarsStr}
${cfg.profileExtra} ${cfg.profileExtra}
''; '';
home.file.".bashrc".source = writeBashScript "bashrc" '' home.file.".bashrc".source = writeBashScript "bashrc" ''
${cfg.bashrcExtra} ${cfg.bashrcExtra}
# Commands that should be applied only for interactive shells. # Commands that should be applied only for interactive shells.
[[ $- == *i* ]] || return [[ $- == *i* ]] || return
${historyControlStr} ${historyControlStr}
${shoptsStr} ${shoptsStr}
${aliasesStr} ${aliasesStr}
${cfg.initExtra} ${cfg.initExtra}
''; '';
home.file.".bash_logout" = mkIf (cfg.logoutExtra != "") { home.file.".bash_logout" = mkIf (cfg.logoutExtra != "") {
source = writeBashScript "bash_logout" cfg.logoutExtra; source = writeBashScript "bash_logout" cfg.logoutExtra;
};
}; };
};
} }

View file

@ -1,6 +1,13 @@
{ config, lib, pkgs, ... }: {
let cfg = config.programs.bashmount; config,
in { lib,
pkgs,
...
}:
let
cfg = config.programs.bashmount;
in
{
meta.maintainers = [ lib.maintainers.AndersonTorres ]; meta.maintainers = [ lib.maintainers.AndersonTorres ];
options.programs.bashmount = { options.programs.bashmount = {
@ -24,7 +31,6 @@ in {
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
home.packages = lib.mkIf (cfg.package != null) [ cfg.package ]; home.packages = lib.mkIf (cfg.package != null) [ cfg.package ];
xdg.configFile."bashmount/config" = xdg.configFile."bashmount/config" = lib.mkIf (cfg.extraConfig != "") { text = cfg.extraConfig; };
lib.mkIf (cfg.extraConfig != "") { text = cfg.extraConfig; };
}; };
} }

View file

@ -1,10 +1,22 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (lib) literalExpression mkEnableOption mkOption mkIf types; inherit (lib)
literalExpression
mkEnableOption
mkOption
mkIf
types
;
cfg = config.programs.bat; cfg = config.programs.bat;
toConfigFile = attrs: toConfigFile =
attrs:
let let
inherit (builtins) isBool attrNames; inherit (builtins) isBool attrNames;
nonBoolFlags = lib.filterAttrs (_: v: !(isBool v)) attrs; nonBoolFlags = lib.filterAttrs (_: v: !(isBool v)) attrs;
@ -17,20 +29,31 @@ let
switches = lib.concatMapStrings (k: '' switches = lib.concatMapStrings (k: ''
--${k} --${k}
'') (attrNames enabledBoolFlags); '') (attrNames enabledBoolFlags);
in keyValuePairs + switches; in
in { keyValuePairs + switches;
in
{
meta.maintainers = with lib.maintainers; [ khaneliman ]; meta.maintainers = with lib.maintainers; [ khaneliman ];
options.programs.bat = { options.programs.bat = {
enable = mkEnableOption "bat, a cat clone with wings"; enable = mkEnableOption "bat, a cat clone with wings";
config = mkOption { config = mkOption {
type = with types; attrsOf (oneOf [ str (listOf str) bool ]); type =
with types;
attrsOf (oneOf [
str
(listOf str)
bool
]);
default = { }; default = { };
example = { example = {
theme = "TwoDark"; theme = "TwoDark";
pager = "less -FR"; pager = "less -FR";
map-syntax = [ "*.jenkinsfile:Groovy" "*.props:Java Properties" ]; map-syntax = [
"*.jenkinsfile:Groovy"
"*.props:Java Properties"
];
}; };
description = '' description = ''
Bat configuration. Bat configuration.
@ -40,8 +63,7 @@ in {
extraPackages = mkOption { extraPackages = mkOption {
type = types.listOf types.package; type = types.listOf types.package;
default = [ ]; default = [ ];
example = literalExpression example = literalExpression "with pkgs.bat-extras; [ batdiff batman batgrep batwatch ];";
"with pkgs.bat-extras; [ batdiff batman batgrep batwatch ];";
description = '' description = ''
Additional bat packages to install. Additional bat packages to install.
''; '';
@ -50,21 +72,24 @@ in {
package = lib.mkPackageOption pkgs "bat" { }; package = lib.mkPackageOption pkgs "bat" { };
themes = mkOption { themes = mkOption {
type = types.attrsOf (types.either types.lines (types.submodule { type = types.attrsOf (
options = { types.either types.lines (
src = mkOption { types.submodule {
type = types.path; options = {
description = "Path to the theme folder."; src = mkOption {
}; type = types.path;
description = "Path to the theme folder.";
};
file = mkOption { file = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = null; default = null;
description = description = "Subpath of the theme file within the source, if needed.";
"Subpath of the theme file within the source, if needed."; };
}; };
}; }
})); )
);
default = { }; default = { };
example = literalExpression '' example = literalExpression ''
{ {
@ -85,20 +110,23 @@ in {
}; };
syntaxes = mkOption { syntaxes = mkOption {
type = types.attrsOf (types.either types.lines (types.submodule { type = types.attrsOf (
options = { types.either types.lines (
src = mkOption { types.submodule {
type = types.path; options = {
description = "Path to the syntax folder."; src = mkOption {
}; type = types.path;
file = mkOption { description = "Path to the syntax folder.";
type = types.nullOr types.str; };
default = null; file = mkOption {
description = type = types.nullOr types.str;
"Subpath of the syntax file within the source, if needed."; default = null;
}; description = "Subpath of the syntax file within the source, if needed.";
}; };
})); };
}
)
);
default = { }; default = { };
example = literalExpression '' example = literalExpression ''
{ {
@ -119,54 +147,75 @@ in {
}; };
}; };
config = mkIf cfg.enable (lib.mkMerge [ config = mkIf cfg.enable (
(mkIf (lib.any lib.isString (lib.attrValues cfg.themes)) { lib.mkMerge [
warnings = ['' (mkIf (lib.any lib.isString (lib.attrValues cfg.themes)) {
Using programs.bat.themes as a string option is deprecated and will be warnings = [
removed in the future. Please change to using it as an attribute set ''
instead. Using programs.bat.themes as a string option is deprecated and will be
'']; removed in the future. Please change to using it as an attribute set
}) instead.
(mkIf (lib.any lib.isString (lib.attrValues cfg.syntaxes)) { ''
warnings = ['' ];
Using programs.bat.syntaxes as a string option is deprecated and will be })
removed in the future. Please change to using it as an attribute set (mkIf (lib.any lib.isString (lib.attrValues cfg.syntaxes)) {
instead. warnings = [
'']; ''
}) Using programs.bat.syntaxes as a string option is deprecated and will be
{ removed in the future. Please change to using it as an attribute set
home.packages = [ cfg.package ] ++ cfg.extraPackages; instead.
''
];
})
{
home.packages = [ cfg.package ] ++ cfg.extraPackages;
xdg.configFile = lib.mkMerge ([{ xdg.configFile = lib.mkMerge (
"bat/config" = [
mkIf (cfg.config != { }) { text = toConfigFile cfg.config; }; {
}] ++ (lib.flip lib.mapAttrsToList cfg.themes (name: val: { "bat/config" = mkIf (cfg.config != { }) { text = toConfigFile cfg.config; };
"bat/themes/${name}.tmTheme" = if lib.isString val then { }
text = val; ]
} else { ++ (lib.flip lib.mapAttrsToList cfg.themes (
source = name: val: {
if isNull val.file then "${val.src}" else "${val.src}/${val.file}"; "bat/themes/${name}.tmTheme" =
}; if lib.isString val then
})) ++ (lib.flip lib.mapAttrsToList cfg.syntaxes (name: val: { {
"bat/syntaxes/${name}.sublime-syntax" = if lib.isString val then { text = val;
text = val; }
} else { else
source = {
if isNull val.file then "${val.src}" else "${val.src}/${val.file}"; source = if isNull val.file then "${val.src}" else "${val.src}/${val.file}";
}; };
}))); }
))
++ (lib.flip lib.mapAttrsToList cfg.syntaxes (
name: val: {
"bat/syntaxes/${name}.sublime-syntax" =
if lib.isString val then
{
text = val;
}
else
{
source = if isNull val.file then "${val.src}" else "${val.src}/${val.file}";
};
}
))
);
# NOTE: run `bat cache --build` in an empty directory to work # NOTE: run `bat cache --build` in an empty directory to work
# around failure when ~/cache exists # around failure when ~/cache exists
# https://github.com/sharkdp/bat/issues/1726 # https://github.com/sharkdp/bat/issues/1726
home.activation.batCache = lib.hm.dag.entryAfter [ "linkGeneration" ] '' home.activation.batCache = lib.hm.dag.entryAfter [ "linkGeneration" ] ''
( (
export XDG_CACHE_HOME=${lib.escapeShellArg config.xdg.cacheHome} export XDG_CACHE_HOME=${lib.escapeShellArg config.xdg.cacheHome}
verboseEcho "Rebuilding bat theme cache" verboseEcho "Rebuilding bat theme cache"
cd "${pkgs.emptyDirectory}" cd "${pkgs.emptyDirectory}"
run ${lib.getExe cfg.package} cache --build run ${lib.getExe cfg.package} cache --build
) )
''; '';
} }
]); ]
);
} }

View file

@ -1,22 +1,34 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (lib) literalExpression mkIf mkOption types; inherit (lib)
literalExpression
mkIf
mkOption
types
;
cfg = config.programs.beets; cfg = config.programs.beets;
yamlFormat = pkgs.formats.yaml { }; yamlFormat = pkgs.formats.yaml { };
in { in
meta.maintainers = with lib.maintainers; [ rycee Scrumplex ]; {
meta.maintainers = with lib.maintainers; [
rycee
Scrumplex
];
options = { options = {
programs.beets = { programs.beets = {
enable = mkOption { enable = mkOption {
type = types.bool; type = types.bool;
default = if lib.versionAtLeast config.home.stateVersion "19.03" then default =
false if lib.versionAtLeast config.home.stateVersion "19.03" then false else cfg.settings != { };
else
cfg.settings != { };
defaultText = "false"; defaultText = "false";
description = '' description = ''
Whether to enable the beets music library manager. This Whether to enable the beets music library manager. This
@ -27,8 +39,7 @@ in {
}; };
package = lib.mkPackageOption pkgs "beets" { package = lib.mkPackageOption pkgs "beets" {
example = example = "(pkgs.beets.override { pluginOverrides = { beatport.enable = false; }; })";
"(pkgs.beets.override { pluginOverrides = { beatport.enable = false; }; })";
extraDescription = '' extraDescription = ''
Can be used to specify extensions. Can be used to specify extensions.
''; '';
@ -70,8 +81,7 @@ in {
(mkIf cfg.enable { (mkIf cfg.enable {
home.packages = [ cfg.package ]; home.packages = [ cfg.package ];
xdg.configFile."beets/config.yaml".source = xdg.configFile."beets/config.yaml".source = yamlFormat.generate "beets-config" cfg.settings;
yamlFormat.generate "beets-config" cfg.settings;
}) })
(mkIf (cfg.mpdIntegration.enableStats || cfg.mpdIntegration.enableUpdate) { (mkIf (cfg.mpdIntegration.enableStats || cfg.mpdIntegration.enableUpdate) {

View file

@ -1,6 +1,13 @@
{ config, lib, pkgs, ... }: {
let cfg = config.programs.bemenu; config,
in { lib,
pkgs,
...
}:
let
cfg = config.programs.bemenu;
in
{
meta.maintainers = [ ]; meta.maintainers = [ ];
options.programs.bemenu = { options.programs.bemenu = {
@ -9,7 +16,13 @@ in {
package = lib.mkPackageOption pkgs "bemenu" { nullable = true; }; package = lib.mkPackageOption pkgs "bemenu" { nullable = true; };
settings = lib.mkOption { settings = lib.mkOption {
type = with lib.types; attrsOf (oneOf [ str number bool ]); type =
with lib.types;
attrsOf (oneOf [
str
number
bool
]);
default = { }; default = { };
example = lib.literalExpression '' example = lib.literalExpression ''
{ {
@ -29,15 +42,13 @@ in {
width-factor = 0.3; width-factor = 0.3;
} }
''; '';
description = description = "Configuration options for bemenu. See {manpage}`bemenu(1)`.";
"Configuration options for bemenu. See {manpage}`bemenu(1)`.";
}; };
}; };
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
assertions = [ assertions = [
(lib.hm.assertions.assertPlatform "programs.bemenu" pkgs (lib.hm.assertions.assertPlatform "programs.bemenu" pkgs lib.platforms.linux)
lib.platforms.linux)
]; ];
home.packages = lib.mkIf (cfg.package != null) [ cfg.package ]; home.packages = lib.mkIf (cfg.package != null) [ cfg.package ];

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (lib) literalExpression mkOption types; inherit (lib) literalExpression mkOption types;
@ -6,24 +11,33 @@ let
yamlFormat = pkgs.formats.yaml { }; yamlFormat = pkgs.formats.yaml { };
mkNullableOption = args: mkNullableOption =
lib.mkOption (args // { args:
type = lib.types.nullOr args.type; lib.mkOption (
default = null; args
}); // {
type = lib.types.nullOr args.type;
default = null;
}
);
cleanRepositories = repos: cleanRepositories =
map (repo: repos:
if builtins.isString repo then { map (
path = repo; repo:
} else if builtins.isString repo then
removeNullValues repo) repos; {
path = repo;
}
else
removeNullValues repo
) repos;
mkRetentionOption = frequency: mkRetentionOption =
frequency:
mkNullableOption { mkNullableOption {
type = types.int; type = types.int;
description = description = "Number of ${frequency} archives to keep. Use -1 for no limit.";
"Number of ${frequency} archives to keep. Use -1 for no limit.";
example = 3; example = 3;
}; };
@ -56,7 +70,12 @@ let
consistencyCheckModule = types.submodule { consistencyCheckModule = types.submodule {
options = { options = {
name = mkOption { name = mkOption {
type = types.enum [ "repository" "archives" "data" "extract" ]; type = types.enum [
"repository"
"archives"
"data"
"extract"
];
description = "Name of consistency check to run."; description = "Name of consistency check to run.";
example = "repository"; example = "repository";
}; };
@ -69,146 +88,151 @@ let
}; };
}; };
configModule = types.submodule ({ config, ... }: { configModule = types.submodule (
config.location.extraConfig.exclude_from = { config, ... }:
lib.mkIf config.location.excludeHomeManagerSymlinks {
(lib.mkAfter [ (toString hmExcludeFile) ]); config.location.extraConfig.exclude_from = lib.mkIf config.location.excludeHomeManagerSymlinks (
options = { lib.mkAfter [ (toString hmExcludeFile) ]
location = { );
sourceDirectories = mkNullableOption { options = {
type = types.listOf types.str; location = {
default = null; sourceDirectories = mkNullableOption {
description = '' type = types.listOf types.str;
Directories to backup. default = null;
description = ''
Directories to backup.
Mutually exclusive with [](#opt-programs.borgmatic.backups._name_.location.patterns). Mutually exclusive with [](#opt-programs.borgmatic.backups._name_.location.patterns).
''; '';
example = literalExpression "[config.home.homeDirectory]"; example = literalExpression "[config.home.homeDirectory]";
};
patterns = mkNullableOption {
type = types.listOf types.str;
default = null;
description = ''
Patterns to include/exclude.
See the output of `borg help patterns` for the syntax. Pattern paths
are relative to `/` even when a different recursion root is set.
Mutually exclusive with [](#opt-programs.borgmatic.backups._name_.location.sourceDirectories).
'';
example = literalExpression ''
[
"R /home/user"
"- home/user/.cache"
"- home/user/Downloads"
"+ home/user/Videos/Important Video"
"- home/user/Videos"
]
'';
};
repositories = mkOption {
type = types.listOf (types.either types.str repositoryOption);
apply = cleanRepositories;
example = literalExpression ''
[
{
"path" = "ssh://myuser@myrepo.myserver.com/./repo";
"label" = "server";
}
{
"path" = "/var/lib/backups/local.borg";
"label" = "local";
}
]
'';
description = ''
List of local or remote repositories with paths and optional labels.
'';
};
excludeHomeManagerSymlinks = mkOption {
type = types.bool;
description = ''
Whether to exclude Home Manager generated symbolic links from
the backups. This facilitates restoring the whole home
directory when the Nix store doesn't contain the latest
Home Manager generation.
'';
default = false;
example = true;
};
extraConfig = extraConfigOption;
}; };
patterns = mkNullableOption { storage = {
type = types.listOf types.str; encryptionPasscommand = mkNullableOption {
default = null; type = types.str;
description = '' description = "Command writing the passphrase to standard output.";
Patterns to include/exclude. example = literalExpression ''"''${pkgs.password-store}/bin/pass borg-repo"'';
};
See the output of `borg help patterns` for the syntax. Pattern paths extraConfig = extraConfigOption;
are relative to `/` even when a different recursion root is set.
Mutually exclusive with [](#opt-programs.borgmatic.backups._name_.location.sourceDirectories).
'';
example = literalExpression ''
[
"R /home/user"
"- home/user/.cache"
"- home/user/Downloads"
"+ home/user/Videos/Important Video"
"- home/user/Videos"
]
'';
}; };
repositories = mkOption { retention = {
type = types.listOf (types.either types.str repositoryOption); keepWithin = mkNullableOption {
apply = cleanRepositories; type = types.strMatching "[[:digit:]]+[Hdwmy]";
example = literalExpression '' description = "Keep all archives within this time interval.";
[ example = "2d";
{ };
"path" = "ssh://myuser@myrepo.myserver.com/./repo";
"label" = "server"; keepSecondly = mkRetentionOption "secondly";
} keepMinutely = mkRetentionOption "minutely";
{ keepHourly = mkRetentionOption "hourly";
"path" = "/var/lib/backups/local.borg"; keepDaily = mkRetentionOption "daily";
"label" = "local"; keepWeekly = mkRetentionOption "weekly";
} keepMonthly = mkRetentionOption "monthly";
] keepYearly = mkRetentionOption "yearly";
'';
description = '' extraConfig = extraConfigOption;
List of local or remote repositories with paths and optional labels.
'';
}; };
excludeHomeManagerSymlinks = mkOption { consistency = {
type = types.bool; checks = mkOption {
description = '' type = types.listOf consistencyCheckModule;
Whether to exclude Home Manager generated symbolic links from default = [ ];
the backups. This facilitates restoring the whole home description = "Consistency checks to run";
directory when the Nix store doesn't contain the latest example = literalExpression ''
Home Manager generation. [
''; {
default = false; name = "repository";
example = true; frequency = "2 weeks";
}
{
name = "archives";
frequency = "4 weeks";
}
{
name = "data";
frequency = "6 weeks";
}
{
name = "extract";
frequency = "6 weeks";
}
];
'';
};
extraConfig = extraConfigOption;
}; };
extraConfig = extraConfigOption; output = {
extraConfig = extraConfigOption;
};
hooks = {
extraConfig = extraConfigOption;
};
}; };
}
);
storage = { removeNullValues = attrSet: lib.filterAttrs (key: value: value != null) attrSet;
encryptionPasscommand = mkNullableOption {
type = types.str;
description = "Command writing the passphrase to standard output.";
example =
literalExpression ''"''${pkgs.password-store}/bin/pass borg-repo"'';
};
extraConfig = extraConfigOption;
};
retention = {
keepWithin = mkNullableOption {
type = types.strMatching "[[:digit:]]+[Hdwmy]";
description = "Keep all archives within this time interval.";
example = "2d";
};
keepSecondly = mkRetentionOption "secondly";
keepMinutely = mkRetentionOption "minutely";
keepHourly = mkRetentionOption "hourly";
keepDaily = mkRetentionOption "daily";
keepWeekly = mkRetentionOption "weekly";
keepMonthly = mkRetentionOption "monthly";
keepYearly = mkRetentionOption "yearly";
extraConfig = extraConfigOption;
};
consistency = {
checks = mkOption {
type = types.listOf consistencyCheckModule;
default = [ ];
description = "Consistency checks to run";
example = literalExpression ''
[
{
name = "repository";
frequency = "2 weeks";
}
{
name = "archives";
frequency = "4 weeks";
}
{
name = "data";
frequency = "6 weeks";
}
{
name = "extract";
frequency = "6 weeks";
}
];
'';
};
extraConfig = extraConfigOption;
};
output = { extraConfig = extraConfigOption; };
hooks = { extraConfig = extraConfigOption; };
};
});
removeNullValues = attrSet:
lib.filterAttrs (key: value: value != null) attrSet;
hmFiles = builtins.attrValues config.home.file; hmFiles = builtins.attrValues config.home.file;
hmSymlinks = (lib.filter (file: !file.recursive) hmFiles); hmSymlinks = (lib.filter (file: !file.recursive) hmFiles);
@ -218,25 +242,35 @@ let
hmExcludePatterns = lib.concatMapStrings hmExcludePattern hmSymlinks; hmExcludePatterns = lib.concatMapStrings hmExcludePattern hmSymlinks;
hmExcludeFile = pkgs.writeText "hm-symlinks.txt" hmExcludePatterns; hmExcludeFile = pkgs.writeText "hm-symlinks.txt" hmExcludePatterns;
writeConfig = config: writeConfig =
lib.generators.toYAML { } (removeNullValues ({ config:
source_directories = config.location.sourceDirectories; lib.generators.toYAML { } (
patterns = config.location.patterns; removeNullValues (
repositories = config.location.repositories; {
encryption_passcommand = config.storage.encryptionPasscommand; source_directories = config.location.sourceDirectories;
keep_within = config.retention.keepWithin; patterns = config.location.patterns;
keep_secondly = config.retention.keepSecondly; repositories = config.location.repositories;
keep_minutely = config.retention.keepMinutely; encryption_passcommand = config.storage.encryptionPasscommand;
keep_hourly = config.retention.keepHourly; keep_within = config.retention.keepWithin;
keep_daily = config.retention.keepDaily; keep_secondly = config.retention.keepSecondly;
keep_weekly = config.retention.keepWeekly; keep_minutely = config.retention.keepMinutely;
keep_monthly = config.retention.keepMonthly; keep_hourly = config.retention.keepHourly;
keep_yearly = config.retention.keepYearly; keep_daily = config.retention.keepDaily;
checks = config.consistency.checks; keep_weekly = config.retention.keepWeekly;
} // config.location.extraConfig // config.storage.extraConfig keep_monthly = config.retention.keepMonthly;
// config.retention.extraConfig // config.consistency.extraConfig keep_yearly = config.retention.keepYearly;
// config.output.extraConfig // config.hooks.extraConfig)); checks = config.consistency.checks;
in { }
// config.location.extraConfig
// config.storage.extraConfig
// config.retention.extraConfig
// config.consistency.extraConfig
// config.output.extraConfig
// config.hooks.extraConfig
)
);
in
{
meta.maintainers = [ lib.maintainers.DamienCassou ]; meta.maintainers = [ lib.maintainers.DamienCassou ];
options = { options = {
@ -272,25 +306,28 @@ in {
}; };
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
assertions = (lib.mapAttrsToList (backup: opts: { assertions =
assertion = opts.location.sourceDirectories == null (lib.mapAttrsToList (backup: opts: {
|| opts.location.patterns == null; assertion = opts.location.sourceDirectories == null || opts.location.patterns == null;
message = '' message = ''
Borgmatic backup configuration "${backup}" cannot specify both 'location.sourceDirectories' and 'location.patterns'. Borgmatic backup configuration "${backup}" cannot specify both 'location.sourceDirectories' and 'location.patterns'.
''; '';
}) cfg.backups) ++ (lib.mapAttrsToList (backup: opts: { }) cfg.backups)
assertion = !(opts.location.sourceDirectories == null ++ (lib.mapAttrsToList (backup: opts: {
&& opts.location.patterns == null); assertion = !(opts.location.sourceDirectories == null && opts.location.patterns == null);
message = '' message = ''
Borgmatic backup configuration "${backup}" must specify one of 'location.sourceDirectories' or 'location.patterns'. Borgmatic backup configuration "${backup}" must specify one of 'location.sourceDirectories' or 'location.patterns'.
''; '';
}) cfg.backups); }) cfg.backups);
xdg.configFile = with lib.attrsets; xdg.configFile =
mapAttrs' (configName: config: with lib.attrsets;
mapAttrs' (
configName: config:
nameValuePair ("borgmatic.d/" + configName + ".yaml") { nameValuePair ("borgmatic.d/" + configName + ".yaml") {
text = writeConfig config; text = writeConfig config;
}) cfg.backups; }
) cfg.backups;
home.packages = lib.mkIf (cfg.package != null) [ cfg.package ]; home.packages = lib.mkIf (cfg.package != null) [ cfg.package ];
}; };

View file

@ -1,11 +1,17 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.programs.bottom; cfg = config.programs.bottom;
tomlFormat = pkgs.formats.toml { }; tomlFormat = pkgs.formats.toml { };
in { in
{
options = { options = {
programs.bottom = { programs.bottom = {
enable = lib.mkEnableOption '' enable = lib.mkEnableOption ''

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (lib) literalExpression mkOption types; inherit (lib) literalExpression mkOption types;
@ -37,7 +42,10 @@ let
}; };
mode = mkOption { mode = mkOption {
type = types.enum [ "file" "directory" ]; type = types.enum [
"file"
"directory"
];
default = "directory"; default = "directory";
description = '' description = ''
Does the current path redirect a file or a directory? Does the current path redirect a file or a directory?
@ -81,7 +89,8 @@ let
}; };
}; };
}; };
in { in
{
options.programs.boxxy = { options.programs.boxxy = {
enable = lib.mkEnableOption "boxxy: Boxes in badly behaving applications"; enable = lib.mkEnableOption "boxxy: Boxes in badly behaving applications";
@ -96,13 +105,11 @@ in {
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
assertions = [ assertions = [
(lib.hm.assertions.assertPlatform "programs.boxxy" pkgs (lib.hm.assertions.assertPlatform "programs.boxxy" pkgs lib.platforms.linux)
lib.platforms.linux)
]; ];
home.file = lib.mkIf (cfg.rules != [ ]) { home.file = lib.mkIf (cfg.rules != [ ]) {
"${configPath}".source = "${configPath}".source = settingsFormat.generate "boxxy-config.yaml" { rules = cfg.rules; };
settingsFormat.generate "boxxy-config.yaml" { rules = cfg.rules; };
}; };
home.packages = lib.mkIf (cfg.package != null) [ cfg.package ]; home.packages = lib.mkIf (cfg.package != null) [ cfg.package ];
@ -110,4 +117,3 @@ in {
meta.maintainers = with lib.hm.maintainers; [ nikp123 ]; meta.maintainers = with lib.hm.maintainers; [ nikp123 ];
} }

View file

@ -1,6 +1,17 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (lib) literalExpression mkIf mkOption mkRenamedOptionModule types; inherit (lib)
literalExpression
mkIf
mkOption
mkRenamedOptionModule
types
;
cfg = config.programs.broot; cfg = config.programs.broot;
@ -13,7 +24,15 @@ let
modal = lib.mkEnableOption "modal (vim) mode"; modal = lib.mkEnableOption "modal (vim) mode";
verbs = mkOption { verbs = mkOption {
type = with types; listOf (attrsOf (oneOf [ bool str (listOf str) ])); type =
with types;
listOf (
attrsOf (oneOf [
bool
str
(listOf str)
])
);
default = [ ]; default = [ ];
example = literalExpression '' example = literalExpression ''
[ [
@ -116,53 +135,64 @@ let
}; };
}; };
shellInit = shell: shellInit =
shell:
# Using mkAfter to make it more likely to appear after other # Using mkAfter to make it more likely to appear after other
# manipulations of the prompt. # manipulations of the prompt.
lib.mkAfter '' lib.mkAfter ''
source ${ source ${
pkgs.runCommand "br.${shell}" { nativeBuildInputs = [ cfg.package ]; } pkgs.runCommand "br.${shell}" {
"broot --print-shell-function ${shell} > $out" nativeBuildInputs = [ cfg.package ];
} "broot --print-shell-function ${shell} > $out"
} }
''; '';
in { in
meta.maintainers = [ lib.hm.maintainers.aheaume lib.maintainers.dermetfan ]; {
meta.maintainers = [
lib.hm.maintainers.aheaume
lib.maintainers.dermetfan
];
imports = [ imports = [
(mkRenamedOptionModule [ "programs" "broot" "modal" ] [ (mkRenamedOptionModule
"programs" [ "programs" "broot" "modal" ]
"broot" [
"settings" "programs"
"modal" "broot"
]) "settings"
(mkRenamedOptionModule [ "programs" "broot" "verbs" ] [ "modal"
"programs" ]
"broot" )
"settings" (mkRenamedOptionModule
"verbs" [ "programs" "broot" "verbs" ]
]) [
(mkRenamedOptionModule [ "programs" "broot" "skin" ] [ "programs"
"programs" "broot"
"broot" "settings"
"settings" "verbs"
"skin" ]
]) )
(mkRenamedOptionModule
[ "programs" "broot" "skin" ]
[
"programs"
"broot"
"settings"
"skin"
]
)
]; ];
options.programs.broot = { options.programs.broot = {
enable = lib.mkEnableOption "Broot, a better way to navigate directories"; enable = lib.mkEnableOption "Broot, a better way to navigate directories";
enableBashIntegration = enableBashIntegration = lib.hm.shell.mkBashIntegrationOption { inherit config; };
lib.hm.shell.mkBashIntegrationOption { inherit config; };
enableFishIntegration = enableFishIntegration = lib.hm.shell.mkFishIntegrationOption { inherit config; };
lib.hm.shell.mkFishIntegrationOption { inherit config; };
enableNushellIntegration = enableNushellIntegration = lib.hm.shell.mkNushellIntegrationOption { inherit config; };
lib.hm.shell.mkNushellIntegrationOption { inherit config; };
enableZshIntegration = enableZshIntegration = lib.hm.shell.mkZshIntegrationOption { inherit config; };
lib.hm.shell.mkZshIntegrationOption { inherit config; };
package = lib.mkPackageOption pkgs "broot" { }; package = lib.mkPackageOption pkgs "broot" { };
@ -190,9 +220,7 @@ in {
postBuild = '' postBuild = ''
rm $out/conf.hjson rm $out/conf.hjson
${lib.getExe pkgs.jq} --slurp add > $out/conf.hjson \ ${lib.getExe pkgs.jq} --slurp add > $out/conf.hjson \
<(${ <(${lib.getExe pkgs.hjson-go} -c ${cfg.package.src}/resources/default-conf/conf.hjson) \
lib.getExe pkgs.hjson-go
} -c ${cfg.package.src}/resources/default-conf/conf.hjson) \
${jsonFormat.generate "broot-config.json" cfg.settings} ${jsonFormat.generate "broot-config.json" cfg.settings}
''; '';
}; };
@ -205,8 +233,7 @@ in {
fish.shellInit = mkIf cfg.enableFishIntegration (shellInit "fish"); fish.shellInit = mkIf cfg.enableFishIntegration (shellInit "fish");
nushell.extraConfig = nushell.extraConfig = mkIf cfg.enableNushellIntegration (shellInit "nushell");
mkIf cfg.enableNushellIntegration (shellInit "nushell");
}; };
}; };
} }

View file

@ -1,8 +1,21 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.programs.browserpass; cfg = config.programs.browserpass;
browsers = [ "brave" "chrome" "chromium" "firefox" "librewolf" "vivaldi" ]; browsers = [
in { "brave"
"chrome"
"chromium"
"firefox"
"librewolf"
"vivaldi"
];
in
{
options = { options = {
programs.browserpass = { programs.browserpass = {
enable = lib.mkEnableOption "the browserpass extension host application"; enable = lib.mkEnableOption "the browserpass extension host application";
@ -17,82 +30,108 @@ in {
}; };
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
home.file = lib.foldl' (a: b: a // b) { } (lib.concatMap (x: home.file = lib.foldl' (a: b: a // b) { } (
with pkgs.stdenv; lib.concatMap (
if x == "brave" then x:
let with pkgs.stdenv;
dir = if isDarwin then if x == "brave" then
"Library/Application Support/BraveSoftware/Brave-Browser/NativeMessagingHosts" let
else dir =
".config/BraveSoftware/Brave-Browser/NativeMessagingHosts"; if isDarwin then
in [{ "Library/Application Support/BraveSoftware/Brave-Browser/NativeMessagingHosts"
# Policies are read from `/etc/brave/policies` only else
# https://github.com/brave/brave-browser/issues/19052 ".config/BraveSoftware/Brave-Browser/NativeMessagingHosts";
"${dir}/com.github.browserpass.native.json".source = in
"${pkgs.browserpass}/lib/browserpass/hosts/chromium/com.github.browserpass.native.json"; [
}] {
else if x == "chrome" then # Policies are read from `/etc/brave/policies` only
let # https://github.com/brave/brave-browser/issues/19052
dir = if isDarwin then "${dir}/com.github.browserpass.native.json".source =
"Library/Application Support/Google/Chrome/NativeMessagingHosts" "${pkgs.browserpass}/lib/browserpass/hosts/chromium/com.github.browserpass.native.json";
else }
".config/google-chrome/NativeMessagingHosts"; ]
in [{ else if x == "chrome" then
"${dir}/com.github.browserpass.native.json".source = let
"${pkgs.browserpass}/lib/browserpass/hosts/chromium/com.github.browserpass.native.json"; dir =
"${dir}/../policies/managed/com.github.browserpass.native.json".source = if isDarwin then
"${pkgs.browserpass}/lib/browserpass/policies/chromium/com.github.browserpass.native.json"; "Library/Application Support/Google/Chrome/NativeMessagingHosts"
}] else
else if x == "chromium" then ".config/google-chrome/NativeMessagingHosts";
let in
dir = if isDarwin then [
"Library/Application Support/Chromium/NativeMessagingHosts" {
else "${dir}/com.github.browserpass.native.json".source =
".config/chromium/NativeMessagingHosts"; "${pkgs.browserpass}/lib/browserpass/hosts/chromium/com.github.browserpass.native.json";
in [ "${dir}/../policies/managed/com.github.browserpass.native.json".source =
{ "${pkgs.browserpass}/lib/browserpass/policies/chromium/com.github.browserpass.native.json";
"${dir}/com.github.browserpass.native.json".source = }
"${pkgs.browserpass}/lib/browserpass/hosts/chromium/com.github.browserpass.native.json"; ]
} else if x == "chromium" then
{ let
"${dir}/../policies/managed/com.github.browserpass.native.json".source = dir =
"${pkgs.browserpass}/lib/browserpass/policies/chromium/com.github.browserpass.native.json"; if isDarwin then
} "Library/Application Support/Chromium/NativeMessagingHosts"
] else
else if x == "firefox" then ".config/chromium/NativeMessagingHosts";
let in
dir = if isDarwin then [
"Library/Application Support/Mozilla/NativeMessagingHosts" {
else "${dir}/com.github.browserpass.native.json".source =
".mozilla/native-messaging-hosts"; "${pkgs.browserpass}/lib/browserpass/hosts/chromium/com.github.browserpass.native.json";
in [{ }
"${dir}/com.github.browserpass.native.json".source = {
"${pkgs.browserpass}/lib/browserpass/hosts/firefox/com.github.browserpass.native.json"; "${dir}/../policies/managed/com.github.browserpass.native.json".source =
}] "${pkgs.browserpass}/lib/browserpass/policies/chromium/com.github.browserpass.native.json";
else if x == "librewolf" then }
let ]
dir = if isDarwin then else if x == "firefox" then
"Library/Application Support/LibreWolf/NativeMessagingHosts" let
else dir =
".librewolf/native-messaging-hosts"; if isDarwin then
in [{ "Library/Application Support/Mozilla/NativeMessagingHosts"
"${dir}/com.github.browserpass.native.json".source = else
"${pkgs.browserpass}/lib/browserpass/hosts/firefox/com.github.browserpass.native.json"; ".mozilla/native-messaging-hosts";
}] in
[
{
"${dir}/com.github.browserpass.native.json".source =
"${pkgs.browserpass}/lib/browserpass/hosts/firefox/com.github.browserpass.native.json";
}
]
else if x == "librewolf" then
let
dir =
if isDarwin then
"Library/Application Support/LibreWolf/NativeMessagingHosts"
else
".librewolf/native-messaging-hosts";
in
[
{
"${dir}/com.github.browserpass.native.json".source =
"${pkgs.browserpass}/lib/browserpass/hosts/firefox/com.github.browserpass.native.json";
}
]
else if x == "vivaldi" then else if x == "vivaldi" then
let let
dir = if isDarwin then dir =
"Library/Application Support/Vivaldi/NativeMessagingHosts" if isDarwin then
else "Library/Application Support/Vivaldi/NativeMessagingHosts"
".config/vivaldi/NativeMessagingHosts"; else
in [{ ".config/vivaldi/NativeMessagingHosts";
"${dir}/com.github.browserpass.native.json".source = in
"${pkgs.browserpass}/lib/browserpass/hosts/chromium/com.github.browserpass.native.json"; [
"${dir}/../policies/managed/com.github.browserpass.native.json".source = {
"${pkgs.browserpass}/lib/browserpass/policies/chromium/com.github.browserpass.native.json"; "${dir}/com.github.browserpass.native.json".source =
}] "${pkgs.browserpass}/lib/browserpass/hosts/chromium/com.github.browserpass.native.json";
else "${dir}/../policies/managed/com.github.browserpass.native.json".source =
throw "unknown browser ${x}") cfg.browsers); "${pkgs.browserpass}/lib/browserpass/policies/chromium/com.github.browserpass.native.json";
}
]
else
throw "unknown browser ${x}"
) cfg.browsers
);
}; };
} }

Some files were not shown because too many files have changed in this diff Show more