aboutsummaryrefslogtreecommitdiff
path: root/home-manager/modules
diff options
context:
space:
mode:
authorKaiden Fey <kookie@spacekookie.de>2020-09-21 14:12:32 +0200
committerKatharina Fey <kookie@spacekookie.de>2020-09-21 14:12:32 +0200
commitf80843dd45d7acd563d0a5b014cec3a2ea686fc2 (patch)
tree87189d873d6f932d85f9c1a480462b37d96cd6a5 /home-manager/modules
parente0800985dab8f8ebb4cebdfd7e361fd1fafdb2a7 (diff)
parent9b1b55ba0264a55add4b7b4e022bdc2832b531f6 (diff)
Merge commit '9b1b55ba0264a55add4b7b4e022bdc2832b531f6'
Diffstat (limited to 'home-manager/modules')
-rw-r--r--home-manager/modules/accounts/email.nix71
-rw-r--r--home-manager/modules/files.nix55
-rw-r--r--home-manager/modules/home-environment.nix124
-rwxr-xr-xhome-manager/modules/lib-bash/activation-init.sh38
-rw-r--r--home-manager/modules/lib/default.nix4
-rw-r--r--home-manager/modules/lib/file-type.nix30
-rw-r--r--home-manager/modules/lib/gvariant.nix156
-rw-r--r--home-manager/modules/lib/maintainers.nix44
-rw-r--r--home-manager/modules/lib/types-dag.nix63
-rw-r--r--home-manager/modules/lib/types.nix78
-rw-r--r--home-manager/modules/misc/dconf.nix25
-rw-r--r--home-manager/modules/misc/debug.nix26
-rw-r--r--home-manager/modules/misc/gtk.nix146
-rw-r--r--home-manager/modules/misc/news.nix357
-rw-r--r--home-manager/modules/misc/nixpkgs.nix4
-rw-r--r--home-manager/modules/misc/numlock.nix2
-rw-r--r--home-manager/modules/misc/tmpfiles.nix49
-rw-r--r--home-manager/modules/misc/version.nix2
-rw-r--r--home-manager/modules/misc/vte.nix51
-rw-r--r--home-manager/modules/misc/xdg-mime-apps.nix2
-rw-r--r--home-manager/modules/misc/xdg-mime.nix10
-rw-r--r--home-manager/modules/misc/xdg-user-dirs.nix27
-rw-r--r--home-manager/modules/misc/xdg.nix10
-rw-r--r--home-manager/modules/modules.nix60
-rw-r--r--home-manager/modules/programs/abook.nix40
-rw-r--r--home-manager/modules/programs/alacritty.nix9
-rw-r--r--home-manager/modules/programs/alot.nix342
-rw-r--r--home-manager/modules/programs/aria2.nix61
-rw-r--r--home-manager/modules/programs/astroid.nix4
-rw-r--r--home-manager/modules/programs/autorandr.nix39
-rw-r--r--home-manager/modules/programs/bash.nix11
-rw-r--r--home-manager/modules/programs/bat.nix29
-rw-r--r--home-manager/modules/programs/broot.nix6
-rw-r--r--home-manager/modules/programs/dircolors.nix223
-rw-r--r--home-manager/modules/programs/direnv.nix16
-rw-r--r--home-manager/modules/programs/eclipse.nix12
-rw-r--r--home-manager/modules/programs/emacs.nix16
-rw-r--r--home-manager/modules/programs/firefox.nix112
-rw-r--r--home-manager/modules/programs/fish.nix497
-rw-r--r--home-manager/modules/programs/fzf.nix12
-rw-r--r--home-manager/modules/programs/getmail.nix6
-rw-r--r--home-manager/modules/programs/git.nix63
-rw-r--r--home-manager/modules/programs/gnome-terminal.nix42
-rw-r--r--home-manager/modules/programs/go.nix16
-rw-r--r--home-manager/modules/programs/htop.nix46
-rw-r--r--home-manager/modules/programs/i3status.nix208
-rw-r--r--home-manager/modules/programs/info.nix78
-rw-r--r--home-manager/modules/programs/kakoune.nix70
-rw-r--r--home-manager/modules/programs/kitty.nix91
-rw-r--r--home-manager/modules/programs/lf.nix219
-rw-r--r--home-manager/modules/programs/lieer-accounts.nix69
-rw-r--r--home-manager/modules/programs/lieer.nix93
-rw-r--r--home-manager/modules/programs/man.nix66
-rw-r--r--home-manager/modules/programs/mbsync.nix4
-rw-r--r--home-manager/modules/programs/mcfly.nix79
-rw-r--r--home-manager/modules/programs/mpv.nix2
-rw-r--r--home-manager/modules/programs/msmtp.nix4
-rw-r--r--home-manager/modules/programs/ncmpcpp.nix135
-rw-r--r--home-manager/modules/programs/ne.nix95
-rw-r--r--home-manager/modules/programs/neomutt-accounts.nix18
-rw-r--r--home-manager/modules/programs/neomutt.nix36
-rw-r--r--home-manager/modules/programs/neovim.nix16
-rw-r--r--home-manager/modules/programs/newsboat.nix6
-rw-r--r--home-manager/modules/programs/notmuch.nix4
-rw-r--r--home-manager/modules/programs/nushell.nix68
-rw-r--r--home-manager/modules/programs/offlineimap.nix5
-rw-r--r--home-manager/modules/programs/powerline-go.nix123
-rw-r--r--home-manager/modules/programs/qutebrowser.nix268
-rw-r--r--home-manager/modules/programs/rofi.nix13
-rw-r--r--home-manager/modules/programs/ssh.nix55
-rw-r--r--home-manager/modules/programs/starship.nix23
-rw-r--r--home-manager/modules/programs/termite.nix2
-rw-r--r--home-manager/modules/programs/tmux.nix98
-rw-r--r--home-manager/modules/programs/vim.nix2
-rw-r--r--home-manager/modules/programs/vscode.nix66
-rw-r--r--home-manager/modules/programs/waybar.nix363
-rw-r--r--home-manager/modules/programs/zoxide.nix79
-rw-r--r--home-manager/modules/programs/zplug.nix60
-rw-r--r--home-manager/modules/programs/zsh.nix54
-rw-r--r--home-manager/modules/services/clipmenu.nix43
-rw-r--r--home-manager/modules/services/compton.nix320
-rw-r--r--home-manager/modules/services/dropbox.nix77
-rw-r--r--home-manager/modules/services/dunst.nix4
-rw-r--r--home-manager/modules/services/emacs.nix152
-rw-r--r--home-manager/modules/services/fluidsynth.nix57
-rw-r--r--home-manager/modules/services/gnome-keyring.nix2
-rw-r--r--home-manager/modules/services/kanshi.nix194
-rw-r--r--home-manager/modules/services/keynav.nix29
-rw-r--r--home-manager/modules/services/lieer-accounts.nix25
-rw-r--r--home-manager/modules/services/lieer.nix66
-rw-r--r--home-manager/modules/services/lorri.nix17
-rw-r--r--home-manager/modules/services/mako.nix316
-rw-r--r--home-manager/modules/services/mpd.nix2
-rw-r--r--home-manager/modules/services/picom.nix311
-rw-r--r--home-manager/modules/services/pulseeffects.nix55
-rw-r--r--home-manager/modules/services/screen-locker.nix13
-rw-r--r--home-manager/modules/services/spotifyd.nix18
-rw-r--r--home-manager/modules/services/status-notifier-watcher.nix9
-rw-r--r--home-manager/modules/services/syncthing.nix2
-rw-r--r--home-manager/modules/services/udiskie.nix4
-rw-r--r--home-manager/modules/services/unison.nix2
-rw-r--r--home-manager/modules/services/window-managers/i3-sway/i3.nix257
-rw-r--r--home-manager/modules/services/window-managers/i3-sway/lib/functions.nix127
-rw-r--r--home-manager/modules/services/window-managers/i3-sway/lib/options.nix763
-rw-r--r--home-manager/modules/services/window-managers/i3-sway/sway.nix416
-rw-r--r--home-manager/modules/services/window-managers/i3.nix856
-rw-r--r--home-manager/modules/services/xscreensaver.nix4
-rw-r--r--home-manager/modules/systemd-activate.rb95
-rw-r--r--home-manager/modules/systemd.nix4
-rw-r--r--home-manager/modules/targets/darwin.nix16
-rw-r--r--home-manager/modules/targets/generic-linux.nix52
-rw-r--r--home-manager/modules/xresources.nix31
112 files changed, 7908 insertions, 2039 deletions
diff --git a/home-manager/modules/accounts/email.nix b/home-manager/modules/accounts/email.nix
index b3a9db947a9..1e7aff94611 100644
--- a/home-manager/modules/accounts/email.nix
+++ b/home-manager/modules/accounts/email.nix
@@ -106,7 +106,7 @@ let
tls = mkOption {
type = tlsModule;
- default = {};
+ default = { };
description = ''
Configuration for secure connections.
'';
@@ -136,7 +136,7 @@ let
tls = mkOption {
type = tlsModule;
- default = {};
+ default = { };
description = ''
Configuration for secure connections.
'';
@@ -209,7 +209,7 @@ let
aliases = mkOption {
type = types.listOf (types.strMatching ".*@.*");
- default = [];
+ default = [ ];
example = [ "webmaster@example.org" "admin@example.org" ];
description = "Alternative email addresses of this account.";
};
@@ -276,7 +276,7 @@ let
};
};
};
- default = {};
+ default = { };
description = ''
Standard email folders.
'';
@@ -292,7 +292,7 @@ let
signature = mkOption {
type = signatureModule;
- default = {};
+ default = { };
description = ''
Signature configuration.
'';
@@ -332,9 +332,7 @@ let
(mkIf (config.flavor == "gmail.com") {
userName = mkDefault config.address;
- imap = {
- host = "imap.gmail.com";
- };
+ imap = { host = "imap.gmail.com"; };
smtp = {
host = "smtp.gmail.com";
@@ -343,20 +341,14 @@ let
})
(mkIf (config.flavor == "runbox.com") {
- imap = {
- host = "mail.runbox.com";
- };
+ imap = { host = "mail.runbox.com"; };
- smtp = {
- host = "mail.runbox.com";
- };
+ smtp = { host = "mail.runbox.com"; };
})
];
};
-in
-
-{
+in {
options.accounts.email = {
certificatesFile = mkOption {
type = types.path;
@@ -373,9 +365,7 @@ in
default = "${config.home.homeDirectory}/Maildir";
defaultText = "$HOME/Maildir";
apply = p:
- if hasPrefix "/" p
- then p
- else "${config.home.homeDirectory}/${p}";
+ if hasPrefix "/" p then p else "${config.home.homeDirectory}/${p}";
description = ''
The base directory for account maildir directories. May be a
relative path, in which case it is relative the home
@@ -384,40 +374,23 @@ in
};
accounts = mkOption {
- type = types.attrsOf (types.submodule [
- mailAccountOpts
- (import ../programs/alot-accounts.nix pkgs)
- (import ../programs/astroid-accounts.nix)
- (import ../programs/getmail-accounts.nix)
- (import ../programs/mbsync-accounts.nix)
- (import ../programs/msmtp-accounts.nix)
- (import ../programs/neomutt-accounts.nix)
- (import ../programs/notmuch-accounts.nix)
- (import ../programs/offlineimap-accounts.nix)
- ]);
- default = {};
+ type = types.attrsOf (types.submodule mailAccountOpts);
+ default = { };
description = "List of email accounts.";
};
};
- config = mkIf (cfg.accounts != {}) {
+ config = mkIf (cfg.accounts != { }) {
assertions = [
- (
- let
- primaries =
- catAttrs "name"
- (filter (a: a.primary)
- (attrValues cfg.accounts));
- in
- {
- assertion = length primaries == 1;
- message =
- "Must have exactly one primary mail account but found "
- + toString (length primaries)
- + optionalString (length primaries > 1)
- (", namely " + concatStringsSep ", " primaries);
- }
- )
+ (let
+ primaries =
+ catAttrs "name" (filter (a: a.primary) (attrValues cfg.accounts));
+ in {
+ assertion = length primaries == 1;
+ message = "Must have exactly one primary mail account but found "
+ + toString (length primaries) + optionalString (length primaries > 1)
+ (", namely " + concatStringsSep ", " primaries);
+ })
];
};
}
diff --git a/home-manager/modules/files.nix b/home-manager/modules/files.nix
index 94b3aa565a8..09ecf715497 100644
--- a/home-manager/modules/files.nix
+++ b/home-manager/modules/files.nix
@@ -39,10 +39,24 @@ in
};
config = {
+ lib.file.mkOutOfStoreSymlink = path:
+ let
+ pathStr = toString path;
+ name = hm.strings.storeFileName (baseNameOf pathStr);
+ in
+ pkgs.runCommandLocal name {} ''ln -s ${escapeShellArg pathStr} $out'';
+
# This verifies that the links we are about to create will not
# overwrite an existing file.
home.activation.checkLinkTargets = hm.dag.entryBefore ["writeBoundary"] (
let
+ # Paths that should be forcibly overwritten by Home Manager.
+ # Caveat emptor!
+ forcedPaths =
+ concatMapStringsSep " " (p: ''"$HOME/${p}"'')
+ (mapAttrsToList (n: v: v.target)
+ (filterAttrs (n: v: v.force) cfg));
+
check = pkgs.writeText "check" ''
. ${./lib-bash/color-echo.sh}
@@ -50,12 +64,25 @@ in
# considered part of a Home Manager generation.
homeFilePattern="$(readlink -e "${builtins.storeDir}")/*-home-manager-files/*"
+ forcedPaths=(${forcedPaths})
+
newGenFiles="$1"
shift
for sourcePath in "$@" ; do
relativePath="''${sourcePath#$newGenFiles/}"
targetPath="$HOME/$relativePath"
- if [[ -e "$targetPath" \
+
+ forced=""
+ for forcedPath in "''${forcedPaths[@]}"; do
+ if [[ $targetPath == $forcedPath* ]]; then
+ forced="yeah"
+ break
+ fi
+ done
+
+ if [[ -n $forced ]]; then
+ $VERBOSE_ECHO "Skipping collision check for $targetPath"
+ elif [[ -e "$targetPath" \
&& ! "$(readlink "$targetPath")" == $homeFilePattern ]] ; then
if [[ ! -L "$targetPath" && -n "$HOME_MANAGER_BACKUP_EXT" ]] ; then
backup="$targetPath.$HOME_MANAGER_BACKUP_EXT"
@@ -63,10 +90,10 @@ in
errorEcho "Existing file '$backup' would be clobbered by backing up '$targetPath'"
collision=1
else
- warnEcho "Existing file '$targetPath' is in the way, will be moved to '$backup'"
+ warnEcho "Existing file '$targetPath' is in the way of '$sourcePath', will be moved to '$backup'"
fi
else
- errorEcho "Existing file '$targetPath' is in the way"
+ errorEcho "Existing file '$targetPath' is in the way of '$sourcePath'"
collision=1
fi
fi
@@ -142,7 +169,7 @@ in
if [[ -e "$newGenFiles/$relativePath" ]] ; then
$VERBOSE_ECHO "Checking $targetPath: exists"
elif [[ ! "$(readlink "$targetPath")" == $homeFilePattern ]] ; then
- warnEcho "Path '$targetPath' not link into Home Manager generation. Skipping delete."
+ warnEcho "Path '$targetPath' does not link into a Home Manager generation. Skipping delete."
else
$VERBOSE_ECHO "Checking $targetPath: gone (deleting)"
$DRY_RUN_CMD rm $VERBOSE_ARG "$targetPath"
@@ -197,8 +224,7 @@ in
if [[ ! -v oldGenPath || "$oldGenPath" != "$newGenPath" ]] ; then
echo "Creating profile generation $newGenNum"
- $DRY_RUN_CMD ln -Tsf $VERBOSE_ARG "$newGenPath" "$newGenProfilePath"
- $DRY_RUN_CMD ln -Tsf $VERBOSE_ARG $(basename "$newGenProfilePath") "$genProfilePath"
+ $DRY_RUN_CMD nix-env $VERBOSE_ARG --profile "$genProfilePath" --set "$newGenPath"
$DRY_RUN_CMD ln -Tsf $VERBOSE_ARG "$newGenPath" "$newGenGcPath"
else
echo "No change so reusing latest profile generation $oldGenNum"
@@ -231,7 +257,7 @@ in
home-files = pkgs.runCommand
"home-manager-files"
{
- nativeBuildInputs = [ pkgs.xlibs.lndir ];
+ nativeBuildInputs = [ pkgs.xorg.lndir ];
preferLocalBuild = true;
allowSubstitutes = false;
}
@@ -290,12 +316,15 @@ in
}
'' + concatStrings (
mapAttrsToList (n: v: ''
- insertFile "${sourceStorePath v}" \
- "${v.target}" \
- "${if v.executable == null
- then "inherit"
- else builtins.toString v.executable}" \
- "${builtins.toString v.recursive}"
+ insertFile ${
+ escapeShellArgs [
+ (sourceStorePath v)
+ v.target
+ (if v.executable == null
+ then "inherit"
+ else toString v.executable)
+ (toString v.recursive)
+ ]}
'') cfg
));
};
diff --git a/home-manager/modules/home-environment.nix b/home-manager/modules/home-environment.nix
index 0b2fb8f4ef9..2947433993e 100644
--- a/home-manager/modules/home-environment.nix
+++ b/home-manager/modules/home-environment.nix
@@ -16,11 +16,35 @@ let
'';
};
- address = mkOption {
+ ctype = mkOption {
default = null;
type = types.nullOr types.str;
description = ''
- The language to use for addresses.
+ Character classification category.
+ '';
+ };
+
+ numeric = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The language to use for numerical values.
+ '';
+ };
+
+ time = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The language to use for formatting times.
+ '';
+ };
+
+ collate = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The language to use for collation (alphabetical ordering).
'';
};
@@ -32,6 +56,14 @@ let
'';
};
+ messages = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The language to use for messages, application UI languages, etc.
+ '';
+ };
+
paper = mkOption {
default = null;
type = types.nullOr types.str;
@@ -40,13 +72,38 @@ let
'';
};
- time = mkOption {
+ name = mkOption {
default = null;
type = types.nullOr types.str;
description = ''
- The language to use for formatting times.
+ The language to use for personal names.
+ '';
+ };
+
+ address = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The language to use for addresses.
+ '';
+ };
+
+ telephone = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The language to use for telephone numbers.
'';
};
+
+ measurement = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The language to use for measurement values.
+ '';
+ };
+
};
};
@@ -125,14 +182,23 @@ in
options = {
home.username = mkOption {
type = types.str;
- defaultText = "$USER";
+ defaultText = literalExample ''
+ "$USER" for state version < 20.09,
+ undefined for state version β‰₯ 20.09
+ '';
+ example = "jane.doe";
description = "The user's username.";
};
home.homeDirectory = mkOption {
type = types.path;
- defaultText = "$HOME";
- description = "The user's home directory.";
+ defaultText = literalExample ''
+ "$HOME" for state version < 20.09,
+ undefined for state version β‰₯ 20.09
+ '';
+ apply = toString;
+ example = "/home/jane.doe";
+ description = "The user's home directory. Must be an absolute path.";
};
home.profileDirectory = mkOption {
@@ -198,6 +264,16 @@ in
'';
};
+ home.sessionVariablesExtra = mkOption {
+ type = types.lines;
+ default = "";
+ internal = true;
+ description = ''
+ Extra configuration to add to the
+ <filename>hm-session-vars.sh</filename> file.
+ '';
+ };
+
home.packages = mkOption {
type = types.listOf types.package;
default = [];
@@ -226,8 +302,8 @@ in
type = types.bool;
description = ''
Whether the activation script should start with an empty
- <envvar>PATH</envvar> variable. When <literal>false</literal>
- then the user's <envvar>PATH</envvar> will be used.
+ <envar>PATH</envar> variable. When <literal>false</literal>
+ then the user's <envar>PATH</envar> will be used.
'';
};
@@ -317,13 +393,17 @@ in
}
];
- home.username = mkDefault (builtins.getEnv "USER");
- home.homeDirectory = mkDefault (builtins.getEnv "HOME");
+ home.username =
+ mkIf (versionOlder config.home.stateVersion "20.09")
+ (mkDefault (builtins.getEnv "USER"));
+ home.homeDirectory =
+ mkIf (versionOlder config.home.stateVersion "20.09")
+ (mkDefault (builtins.getEnv "HOME"));
home.profileDirectory =
if config.submoduleSupport.enable
&& config.submoduleSupport.externalPackageInstall
- then config.home.path
+ then "/etc/profiles/per-user/${cfg.username}"
else cfg.homeDirectory + "/.nix-profile";
home.sessionVariables =
@@ -332,13 +412,27 @@ in
in
(maybeSet "LANG" cfg.language.base)
//
- (maybeSet "LC_ADDRESS" cfg.language.address)
+ (maybeSet "LC_CTYPE" cfg.language.ctype)
+ //
+ (maybeSet "LC_NUMERIC" cfg.language.numeric)
+ //
+ (maybeSet "LC_TIME" cfg.language.time)
+ //
+ (maybeSet "LC_COLLATE" cfg.language.collate)
//
(maybeSet "LC_MONETARY" cfg.language.monetary)
//
+ (maybeSet "LC_MESSAGES" cfg.language.messages)
+ //
(maybeSet "LC_PAPER" cfg.language.paper)
//
- (maybeSet "LC_TIME" cfg.language.time);
+ (maybeSet "LC_NAME" cfg.language.name)
+ //
+ (maybeSet "LC_ADDRESS" cfg.language.address)
+ //
+ (maybeSet "LC_TELEPHONE" cfg.language.telephone)
+ //
+ (maybeSet "LC_MEASUREMENT" cfg.language.measurement);
home.packages = [
# Provide a file holding all session variables.
@@ -352,7 +446,7 @@ in
export __HM_SESS_VARS_SOURCED=1
${config.lib.shell.exportAll cfg.sessionVariables}
- '';
+ '' + cfg.sessionVariablesExtra;
}
)
];
diff --git a/home-manager/modules/lib-bash/activation-init.sh b/home-manager/modules/lib-bash/activation-init.sh
index 5cdb66e5920..f95008ee75b 100755
--- a/home-manager/modules/lib-bash/activation-init.sh
+++ b/home-manager/modules/lib-bash/activation-init.sh
@@ -1,15 +1,19 @@
#!/usr/bin/env bash
function setupVars() {
- local profilesPath="/nix/var/nix/profiles/per-user/$USER"
- local gcPath="/nix/var/nix/gcroots/per-user/$USER"
- local greatestGenNum
+ local nixStateDir="${NIX_STATE_DIR:-/nix/var/nix}"
+ local profilesPath="$nixStateDir/profiles/per-user/$USER"
+ local gcPath="$nixStateDir/gcroots/per-user/$USER"
+
+ genProfilePath="$profilesPath/home-manager"
+ newGenPath="@GENERATION_DIR@";
+ newGenGcPath="$gcPath/current-home"
+ local greatestGenNum
greatestGenNum=$( \
- find "$profilesPath" -name 'home-manager-*-link' \
- | sed 's/^.*-\([0-9]*\)-link$/\1/' \
- | sort -rn \
- | head -1)
+ nix-env --list-generations --profile "$genProfilePath" \
+ | tail -1 \
+ | sed -E 's/ *([[:digit:]]+) .*/\1/')
if [[ -n $greatestGenNum ]] ; then
oldGenNum=$greatestGenNum
@@ -18,15 +22,15 @@ function setupVars() {
newGenNum=1
fi
- if [[ -e $gcPath/current-home ]] ; then
- oldGenPath="$(readlink -e "$gcPath/current-home")"
+ if [[ -e $profilesPath/home-manager ]] ; then
+ oldGenPath="$(readlink -e "$profilesPath/home-manager")"
fi
$VERBOSE_ECHO "Sanity checking oldGenNum and oldGenPath"
if [[ -v oldGenNum && ! -v oldGenPath
|| ! -v oldGenNum && -v oldGenPath ]]; then
- errorEcho "Invalid profile number and GC root values! These must be"
- errorEcho "either both empty or both set but are now set to"
+ errorEcho "Invalid profile number and current profile values! These"
+ errorEcho "must be either both empty or both set but are now set to"
errorEcho " '${oldGenNum:-}' and '${oldGenPath:-}'"
errorEcho "If you don't mind losing previous profile generations then"
errorEcho "the easiest solution is probably to run"
@@ -35,12 +39,6 @@ function setupVars() {
errorEcho "and trying home-manager switch again. Good luck!"
exit 1
fi
-
-
- genProfilePath="$profilesPath/home-manager"
- newGenPath="@GENERATION_DIR@";
- newGenProfilePath="$profilesPath/home-manager-$newGenNum-link"
- newGenGcPath="$gcPath/current-home"
}
if [[ -v VERBOSE ]]; then
@@ -53,6 +51,11 @@ fi
echo "Starting home manager activation"
+# Verify that we can connect to the Nix store and/or daemon. This will
+# also create the necessary directories in profiles and gcroots.
+$VERBOSE_ECHO "Sanity checking Nix"
+nix-build --expr '{}' --no-out-link
+
setupVars
if [[ -v DRY_RUN ]] ; then
@@ -78,6 +81,5 @@ else
fi
$VERBOSE_ECHO " newGenPath=$newGenPath"
$VERBOSE_ECHO " newGenNum=$newGenNum"
-$VERBOSE_ECHO " newGenProfilePath=$newGenProfilePath"
$VERBOSE_ECHO " newGenGcPath=$newGenGcPath"
$VERBOSE_ECHO " genProfilePath=$genProfilePath"
diff --git a/home-manager/modules/lib/default.nix b/home-manager/modules/lib/default.nix
index 6b2bbacc249..7c2c72f709c 100644
--- a/home-manager/modules/lib/default.nix
+++ b/home-manager/modules/lib/default.nix
@@ -16,8 +16,10 @@ rec {
entryBefore = d.dagEntryBefore;
};
+ gvariant = import ./gvariant.nix { inherit lib; };
+ maintainers = import ./maintainers.nix;
strings = import ./strings.nix { inherit lib; };
- types = import ./types.nix { inherit dag lib; };
+ types = import ./types.nix { inherit dag gvariant lib; };
shell = import ./shell.nix { inherit lib; };
zsh = import ./zsh.nix { inherit lib; };
diff --git a/home-manager/modules/lib/file-type.nix b/home-manager/modules/lib/file-type.nix
index 3096a6d37bb..56a3a1286a0 100644
--- a/home-manager/modules/lib/file-type.nix
+++ b/home-manager/modules/lib/file-type.nix
@@ -11,7 +11,7 @@ with lib;
# Arguments:
# - basePathDesc docbook compatible description of the base path
# - basePath the file base path
- fileType = basePathDesc: basePath: types.loaOf (types.submodule (
+ fileType = basePathDesc: basePath: types.attrsOf (types.submodule (
{ name, config, ... }: {
options = {
target = mkOption {
@@ -21,6 +21,7 @@ with lib;
absPath = if hasPrefix "/" p then p else "${basePath}/${p}";
in
removePrefix (homeDirectory + "/") absPath;
+ defaultText = literalExample "<name>";
description = ''
Path to target file relative to ${basePathDesc}.
'';
@@ -29,17 +30,20 @@ with lib;
text = mkOption {
default = null;
type = types.nullOr types.lines;
- description = "Text of the file.";
+ description = ''
+ Text of the file. If this option is null then
+ <link linkend="opt-home.file._name_.source">home.file.&lt;name?&gt;.source</link>
+ must be set.
+ '';
};
source = mkOption {
type = types.path;
description = ''
- Path of the source file. The file name must not start
- with a period since Nix will not allow such names in
- the Nix store.
- </para><para>
- This may refer to a directory.
+ Path of the source file or directory. If
+ <link linkend="opt-home.file._name_.text">home.file.&lt;name?&gt;.text</link>
+ is non-null then this option will automatically point to a file
+ containing that text.
'';
};
@@ -80,6 +84,18 @@ with lib;
into place.
'';
};
+
+ force = mkOption {
+ type = types.bool;
+ default = false;
+ visible = false;
+ description = ''
+ Whether the target path should be unconditionally replaced
+ by the managed file source. Warning, this will silently
+ delete the target regardless of whether it is a file or
+ link.
+ '';
+ };
};
config = {
diff --git a/home-manager/modules/lib/gvariant.nix b/home-manager/modules/lib/gvariant.nix
new file mode 100644
index 00000000000..92aa7d98371
--- /dev/null
+++ b/home-manager/modules/lib/gvariant.nix
@@ -0,0 +1,156 @@
+# A partial and basic implementation of GVariant formatted strings.
+#
+# Note, this API is not considered fully stable and it might therefore
+# change in backwards incompatible ways without prior notice.
+
+{ lib }:
+
+with lib;
+
+let
+
+ mkPrimitive = t: v: {
+ _type = "gvariant";
+ type = t;
+ value = v;
+ __toString = self: "@${self.type} ${toString self.value}";
+ };
+
+ type = {
+ arrayOf = t: "a${t}";
+ maybeOf = t: "m${t}";
+ tupleOf = ts: "(${concatStrings ts})";
+ string = "s";
+ boolean = "b";
+ uchar = "y";
+ int16 = "n";
+ uint16 = "q";
+ int32 = "i";
+ uint32 = "u";
+ int64 = "x";
+ uint64 = "t";
+ double = "d";
+ };
+
+ # Returns the GVariant type of a given Nix value. If no type can be
+ # found for the value then the empty string is returned.
+ typeOf = v:
+ with type;
+ if builtins.isBool v then
+ boolean
+ else if builtins.isInt v then
+ int32
+ else if builtins.isFloat v then
+ double
+ else if builtins.isString v then
+ string
+ else if builtins.isList v then
+ let elemType = elemTypeOf v;
+ in if elemType == "" then "" else arrayOf elemType
+ else if builtins.isAttrs v && v ? type then
+ v.type
+ else
+ "";
+
+ elemTypeOf = vs:
+ if builtins.isList vs then
+ if vs == [ ] then "" else typeOf (head vs)
+ else
+ "";
+
+ mkMaybe = elemType: elem:
+ mkPrimitive (type.maybeOf elemType) elem // {
+ __toString = self:
+ if self.value == null then
+ "@${self.type} nothing"
+ else
+ "just ${toString self.value}";
+ };
+
+in rec {
+
+ inherit type typeOf;
+
+ isArray = hasPrefix "a";
+ isMaybe = hasPrefix "m";
+ isTuple = hasPrefix "(";
+
+ # Returns the GVariant value that most closely matches the given Nix
+ # value. If no GVariant value can be found then `null` is returned.
+ #
+ # No support for dictionaries, maybe types, or variants.
+ mkValue = v:
+ if builtins.isBool v then
+ mkBoolean v
+ else if builtins.isInt v then
+ mkInt32 v
+ else if builtins.isFloat v then
+ mkDouble v
+ else if builtins.isString v then
+ mkString v
+ else if builtins.isList v then
+ if v == [ ] then mkArray type.string [ ] else mkArray (elemTypeOf v) v
+ else if builtins.isAttrs v && (v._type or "") == "gvariant" then
+ v
+ else
+ null;
+
+ mkArray = elemType: elems:
+ mkPrimitive (type.arrayOf elemType) (map mkValue elems) // {
+ __toString = self:
+ "@${self.type} [${concatMapStringsSep "," toString self.value}]";
+ };
+
+ mkEmptyArray = elemType: mkArray elemType [ ];
+
+ mkNothing = elemType: mkMaybe elemType null;
+
+ mkJust = elem: let gvarElem = mkValue elem; in mkMaybe gvarElem.type gvarElem;
+
+ mkTuple = elems:
+ let
+ gvarElems = map mkValue elems;
+ tupleType = type.tupleOf (map (e: e.type) gvarElems);
+ in mkPrimitive tupleType gvarElems // {
+ __toString = self:
+ "@${self.type} (${concatMapStringsSep "," toString self.value})";
+ };
+
+ mkBoolean = v:
+ mkPrimitive type.boolean v // {
+ __toString = self: if self.value then "true" else "false";
+ };
+
+ mkString = v:
+ mkPrimitive type.string v // {
+ __toString = self: "'${escape [ "'" "\\" ] self.value}'";
+ };
+
+ mkObjectpath = v:
+ mkPrimitive type.string v // {
+ __toString = self: "objectpath '${escape [ "'" ] self.value}'";
+ };
+
+ mkUchar = mkPrimitive type.uchar;
+
+ mkInt16 = mkPrimitive type.int16;
+
+ mkUint16 = mkPrimitive type.uint16;
+
+ mkInt32 = v:
+ mkPrimitive type.int32 v // {
+ __toString = self: toString self.value;
+ };
+
+ mkUint32 = mkPrimitive type.uint32;
+
+ mkInt64 = mkPrimitive type.int64;
+
+ mkUint64 = mkPrimitive type.uint64;
+
+ mkDouble = v:
+ mkPrimitive type.double v // {
+ __toString = self: toString self.value;
+ };
+
+}
diff --git a/home-manager/modules/lib/maintainers.nix b/home-manager/modules/lib/maintainers.nix
new file mode 100644
index 00000000000..8cb8781228b
--- /dev/null
+++ b/home-manager/modules/lib/maintainers.nix
@@ -0,0 +1,44 @@
+# Home Manager maintainers.
+#
+# This attribute set contains Home Manager module maintainers that do
+# not have an entry in the Nixpkgs maintainer list [1]. Entries here
+# are expected to be follow the same format as described in [1].
+#
+# [1] https://github.com/NixOS/nixpkgs/blob/fca0d6e093c82b31103dc0dacc48da2a9b06e24b/maintainers/maintainer-list.nix#LC1
+
+{
+ justinlovinger = {
+ name = "Justin Lovinger";
+ email = "git@justinlovinger.com";
+ github = "JustinLovinger";
+ githubId = 7183441;
+ };
+ owm111 = {
+ email = "7798336+owm111@users.noreply.github.com";
+ name = "Owen McGrath";
+ github = "owm111";
+ githubId = 7798336;
+ };
+ cwyc = {
+ email = "cwyc@users.noreply.github.com";
+ name = "cwyc";
+ github = "cwyc";
+ githubId = 16950437;
+ };
+ berbiche = {
+ name = "Nicolas Berbiche";
+ email = "berbiche@users.noreply.github.com";
+ github = "berbiche";
+ githubId = 20448408;
+ keys = [{
+ longkeyid = "rsa4096/0xB461292445C6E696";
+ fingerprint = "D446 E58D 87A0 31C7 EC15 88D7 B461 2924 45C6 E696";
+ }];
+ };
+ olmokramer = {
+ name = "Olmo Kramer";
+ email = "olmokramer@users.noreply.github.com";
+ github = "olmokramer";
+ githubId = 3612514;
+ };
+}
diff --git a/home-manager/modules/lib/types-dag.nix b/home-manager/modules/lib/types-dag.nix
index 4dbdb907b0e..2efb12645d4 100644
--- a/home-manager/modules/lib/types-dag.nix
+++ b/home-manager/modules/lib/types-dag.nix
@@ -7,16 +7,25 @@ let
isDagEntry = e: isAttrs e && (e ? data) && (e ? after) && (e ? before);
dagContentType = elemType:
- types.submodule {
+ types.submodule ({ name, ... }: {
options = {
data = mkOption { type = elemType; };
after = mkOption { type = with types; uniq (listOf str); };
before = mkOption { type = with types; uniq (listOf str); };
};
- };
+ config = mkIf (elemType.name == "submodule") {
+ data._module.args.dagName = name;
+ };
+ });
-in {
+in rec {
# A directed acyclic graph of some inner type.
+ #
+ # Note, if the element type is a submodule then the `name` argument
+ # will always be set to the string "data" since it picks up the
+ # internal structure of the DAG values. To give access to the
+ # "actual" attribute name a new submodule argument is provided with
+ # the name `dagName`.
dagOf = elemType:
let
convertAllToDags = let
@@ -51,34 +60,40 @@ in {
let padWidth = stringLength (toString (length list));
in fixedWidthNumber padWidth i;
- convertAllToDags = defs:
+ convertAll = loc: defs:
let
- convertAttrValue = n: v:
- if isDagEntry v then v else dag.entryAnywhere v;
-
- convertListValue = namePrefix: vs:
+ convertListValue = namePrefix: def:
let
+ vs = def.value;
pad = paddedIndexStr vs;
- makeEntry = i: v:
- nameValuePair "${namePrefix}.${pad i}" (dag.entryAnywhere v);
- in listToAttrs (imap1 makeEntry vs);
+ makeEntry = i: v: nameValuePair "${namePrefix}.${pad i}" v;
+ warning = ''
+ In file ${def.file}
+ a list is being assigned to the option '${
+ concatStringsSep "." loc
+ }'.
+ This will soon be an error due to the list form being deprecated.
+ Please use the attribute set form instead with DAG functions to
+ express the desired order of entries.
+ '';
+ in warn warning (listToAttrs (imap1 makeEntry vs));
- convertValue = i: value:
- if isList value then
- convertListValue "unnamed-${paddedIndexStr defs i}" value
+ convertValue = i: def:
+ if isList def.value then
+ convertListValue "unnamed-${paddedIndexStr defs i}" def
else
- mapAttrs convertAttrValue value;
- in imap1 (i: def: def // { value = convertValue i def.value; }) defs;
+ def.value;
+ in imap1 (i: def: def // { value = convertValue i def; }) defs;
- attrEquivalent = types.attrsOf (dagContentType elemType);
+ dagType = dagOf elemType;
in mkOptionType rec {
- name = "dagOf";
- description = "DAG of ${elemType.description}s";
- check = x: isAttrs x || isList x;
- merge = loc: defs: attrEquivalent.merge loc (convertAllToDags defs);
- getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "<name>" ]);
- getSubModules = elemType.getSubModules;
- substSubModules = m: dagOf (elemType.substSubModules m);
+ name = "listOrDagOf";
+ description = "list or DAG of ${elemType.description}s";
+ check = x: isList x || dagType.check x;
+ merge = loc: defs: dagType.merge loc (convertAll loc defs);
+ getSubOptions = dagType.getSubOptions;
+ getSubModules = dagType.getSubModules;
+ substSubModules = m: listOrDagOf (elemType.substSubModules m);
functor = (defaultFunctor name) // { wrapped = elemType; };
};
}
diff --git a/home-manager/modules/lib/types.nix b/home-manager/modules/lib/types.nix
index 78a875f519e..64a6b4a34fa 100644
--- a/home-manager/modules/lib/types.nix
+++ b/home-manager/modules/lib/types.nix
@@ -1,4 +1,5 @@
-{ lib, dag ? import ./dag.nix { inherit lib; } }:
+{ lib, dag ? import ./dag.nix { inherit lib; }
+, gvariant ? import ./gvariant.nix { inherit lib; } }:
with lib;
@@ -6,31 +7,84 @@ let
typesDag = import ./types-dag.nix { inherit dag lib; };
-in
+ # Needed since the type is called gvariant and its merge attribute
+ # must refer back to the type.
+ gvar = gvariant;
-{
+in rec {
inherit (typesDag) dagOf listOrDagOf;
selectorFunction = mkOptionType {
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";
check = isFunction;
- merge = _loc: defs:
- as: concatMap (select: select as) (getValues defs);
+ merge = _loc: defs: as: concatMap (select: select as) (getValues defs);
};
overlayFunction = mkOptionType {
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.";
check = isFunction;
- merge = _loc: defs:
- self: super:
- foldl' (res: def: mergeAttrs res (def.value self super)) {} defs;
+ merge = _loc: defs: self: super:
+ foldl' (res: def: mergeAttrs res (def.value self super)) { } defs;
+ };
+
+ fontType = types.submodule {
+ options = {
+ package = mkOption {
+ type = types.nullOr types.package;
+ default = null;
+ example = literalExample "pkgs.dejavu_fonts";
+ description = ''
+ Package providing the font. This package will be installed
+ to your profile. If <literal>null</literal> then the font
+ is assumed to already be available in your profile.
+ '';
+ };
+
+ name = mkOption {
+ type = types.str;
+ example = "DejaVu Sans 8";
+ description = ''
+ The family name and size of the font within the package.
+ '';
+ };
+ };
+ };
+
+ gvariant = mkOptionType rec {
+ name = "gvariant";
+ description = "GVariant value";
+ check = v: gvar.mkValue v != null;
+ merge = loc: defs:
+ let
+ vdefs = map (d: d // { value = gvar.mkValue d.value; }) defs;
+ vals = map (d: d.value) vdefs;
+ defTypes = map (x: x.type) vals;
+ sameOrNull = x: y: if x == y then y else null;
+ # A bit naive to just check the first entry…
+ sharedDefType = foldl' sameOrNull (head defTypes) defTypes;
+ allChecked = all (x: check x) vals;
+ in if sharedDefType == null then
+ throw ("Cannot merge definitions of `${showOption loc}' with"
+ + " mismatched GVariant types given in"
+ + " ${showFiles (getFiles defs)}.")
+ else if gvar.isArray sharedDefType && allChecked then
+ (types.listOf gvariant).merge loc
+ (map (d: d // { value = d.value.value; }) vdefs)
+ else if gvar.isTuple sharedDefType && allChecked then
+ mergeOneOption loc defs
+ else if gvar.isMaybe sharedDefType && allChecked then
+ mergeOneOption loc defs
+ else if gvar.type.string == sharedDefType && allChecked then
+ types.str.merge loc defs
+ else if gvar.type.double == sharedDefType && allChecked then
+ types.float.merge loc defs
+ else
+ mergeDefaultOption loc defs;
};
}
diff --git a/home-manager/modules/misc/dconf.nix b/home-manager/modules/misc/dconf.nix
index f5c9bf71456..5fc7748a76b 100644
--- a/home-manager/modules/misc/dconf.nix
+++ b/home-manager/modules/misc/dconf.nix
@@ -9,22 +9,7 @@ let
toDconfIni = generators.toINI { mkKeyValue = mkIniKeyValue; };
mkIniKeyValue = key: value:
- let
- tweakVal = v:
- if isString v then "'${v}'"
- else if isList v then tweakList v
- else if isBool v then (if v then "true" else "false")
- else toString v;
-
- # Assume empty list is a list of strings, see #769
- tweakList = v:
- if v == [] then "@as []"
- else "[" + concatMapStringsSep "," tweakVal v + "]";
-
- in
- "${key}=${tweakVal value}";
-
- primitive = with types; either bool (either int (either float str));
+ "${key}=${toString (hm.gvariant.mkValue value)}";
in
@@ -43,8 +28,7 @@ in
};
settings = mkOption {
- type = with types;
- attrsOf (attrsOf (either primitive (listOf primitive)));
+ type = with types; attrsOf (attrsOf hm.types.gvariant);
default = {};
example = literalExample ''
{
@@ -53,6 +37,7 @@ in
show-thousands = true;
base = 10;
word-size = 64;
+ window-position = lib.hm.gvariant.mkTuple [100 100];
};
}
'';
@@ -76,9 +61,9 @@ in
fi
if [[ -v DRY_RUN ]]; then
- echo $DCONF_DBUS_RUN_SESSION ${pkgs.gnome3.dconf}/bin/dconf load / "<" ${iniFile}
+ echo $DCONF_DBUS_RUN_SESSION ${pkgs.dconf}/bin/dconf load / "<" ${iniFile}
else
- $DCONF_DBUS_RUN_SESSION ${pkgs.gnome3.dconf}/bin/dconf load / < ${iniFile}
+ $DCONF_DBUS_RUN_SESSION ${pkgs.dconf}/bin/dconf load / < ${iniFile}
fi
unset DCONF_DBUS_RUN_SESSION
diff --git a/home-manager/modules/misc/debug.nix b/home-manager/modules/misc/debug.nix
new file mode 100644
index 00000000000..d27d496b423
--- /dev/null
+++ b/home-manager/modules/misc/debug.nix
@@ -0,0 +1,26 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+ options.home = {
+ enableDebugInfo = mkEnableOption "" // {
+ description = ''
+ Some Nix-packages provide debug symbols for
+ <command>gdb</command> in the <literal>debug</literal>-output.
+ This option ensures that those are automatically fetched from
+ the binary cache if available and <command>gdb</command> is
+ configured to find those symbols.
+ '';
+ };
+ };
+
+ config = mkIf config.home.enableDebugInfo {
+ home.extraOutputsToInstall = [ "debug" ];
+
+ home.sessionVariables = {
+ NIX_DEBUG_INFO_DIRS =
+ "$NIX_DEBUG_INFO_DIRS\${NIX_DEBUG_INFO_DIRS:+:}${config.home.profileDirectory}/lib/debug";
+ };
+ };
+}
diff --git a/home-manager/modules/misc/gtk.nix b/home-manager/modules/misc/gtk.nix
index 1222db4ecab..bf25aaaf664 100644
--- a/home-manager/modules/misc/gtk.nix
+++ b/home-manager/modules/misc/gtk.nix
@@ -11,44 +11,22 @@ let
toGtk3Ini = generators.toINI {
mkKeyValue = key: value:
let
- value' =
- if isBool value then (if value then "true" else "false")
- else toString value;
- in
- "${key}=${value'}";
+ value' = if isBool value then
+ (if value then "true" else "false")
+ else
+ toString value;
+ in "${key}=${value'}";
};
formatGtk2Option = n: v:
let
- v' =
- if isBool v then (if v then "true" else "false")
- else if isString v then "\"${v}\""
- else toString v;
- in
- "${n} = ${v'}";
-
- fontType = types.submodule {
- options = {
- package = mkOption {
- type = types.nullOr types.package;
- default = null;
- example = literalExample "pkgs.dejavu_fonts";
- description = ''
- Package providing the font. This package will be installed
- to your profile. If <literal>null</literal> then the font
- is assumed to already be available in your profile.
- '';
- };
-
- name = mkOption {
- type = types.str;
- example = "DejaVu Sans 8";
- description = ''
- The family name and size of the font within the package.
- '';
- };
- };
- };
+ v' = if isBool v then
+ (if v then "true" else "false")
+ else if isString v then
+ ''"${v}"''
+ else
+ toString v;
+ in "${n} = ${v'}";
themeType = types.submodule {
options = {
@@ -71,13 +49,11 @@ let
};
};
-in
-
-{
+in {
meta.maintainers = [ maintainers.rycee ];
imports = [
- (mkRemovedOptionModule ["gtk" "gtk3" "waylandSupport"] ''
+ (mkRemovedOptionModule [ "gtk" "gtk3" "waylandSupport" ] ''
This options is not longer needed and can be removed.
'')
];
@@ -87,7 +63,7 @@ in
enable = mkEnableOption "GTK 2/3 configuration";
font = mkOption {
- type = types.nullOr fontType;
+ type = types.nullOr hm.types.fontType;
default = null;
description = ''
The font to use in GTK+ 2/3 applications.
@@ -119,10 +95,20 @@ in
};
gtk3 = {
+ bookmarks = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "file:///home/jane/Documents" ];
+ description = "Bookmarks in the sidebar of the GTK file browser";
+ };
+
extraConfig = mkOption {
type = with types; attrsOf (either bool (either int str));
- default = {};
- example = { gtk-cursor-blink = false; gtk-recent-files-limit = 20; };
+ default = { };
+ example = {
+ gtk-cursor-blink = false;
+ gtk-recent-files-limit = 20;
+ };
description = ''
Extra configuration options to add to
<filename>~/.config/gtk-3.0/settings.ini</filename>.
@@ -141,48 +127,38 @@ in
};
};
- config = mkIf cfg.enable (
- let
- ini =
- optionalAttrs (cfg.font != null)
- { gtk-font-name = cfg.font.name; }
- //
- optionalAttrs (cfg.theme != null)
- { gtk-theme-name = cfg.theme.name; }
- //
- optionalAttrs (cfg.iconTheme != null)
- { gtk-icon-theme-name = cfg.iconTheme.name; };
-
- dconfIni =
- optionalAttrs (cfg.font != null)
- { font-name = cfg.font.name; }
- //
- optionalAttrs (cfg.theme != null)
- { gtk-theme = cfg.theme.name; }
- //
- optionalAttrs (cfg.iconTheme != null)
- { icon-theme = cfg.iconTheme.name; };
-
- optionalPackage = opt:
- optional (opt != null && opt.package != null) opt.package;
- in
- {
- home.packages =
- optionalPackage cfg.font
- ++ optionalPackage cfg.theme
- ++ optionalPackage cfg.iconTheme;
-
- home.file.".gtkrc-2.0".text =
- concatStringsSep "\n" (
- mapAttrsToList formatGtk2Option ini
- ) + "\n" + cfg2.extraConfig;
-
- xdg.configFile."gtk-3.0/settings.ini".text =
- toGtk3Ini { Settings = ini // cfg3.extraConfig; };
-
- xdg.configFile."gtk-3.0/gtk.css".text = cfg3.extraCss;
-
- dconf.settings."org/gnome/desktop/interface" = dconfIni;
- }
- );
+ config = mkIf cfg.enable (let
+ ini = optionalAttrs (cfg.font != null) { gtk-font-name = cfg.font.name; }
+ // optionalAttrs (cfg.theme != null) { gtk-theme-name = cfg.theme.name; }
+ // optionalAttrs (cfg.iconTheme != null) {
+ gtk-icon-theme-name = cfg.iconTheme.name;
+ };
+
+ dconfIni = optionalAttrs (cfg.font != null) { font-name = cfg.font.name; }
+ // optionalAttrs (cfg.theme != null) { gtk-theme = cfg.theme.name; }
+ // optionalAttrs (cfg.iconTheme != null) {
+ icon-theme = cfg.iconTheme.name;
+ };
+
+ optionalPackage = opt:
+ optional (opt != null && opt.package != null) opt.package;
+ in {
+ home.packages = optionalPackage cfg.font ++ optionalPackage cfg.theme
+ ++ optionalPackage cfg.iconTheme;
+
+ home.file.".gtkrc-2.0".text =
+ concatStringsSep "\n" (mapAttrsToList formatGtk2Option ini) + "\n"
+ + cfg2.extraConfig;
+
+ xdg.configFile."gtk-3.0/settings.ini".text =
+ toGtk3Ini { Settings = ini // cfg3.extraConfig; };
+
+ xdg.configFile."gtk-3.0/gtk.css".text = cfg3.extraCss;
+
+ xdg.configFile."gtk-3.0/bookmarks" = mkIf (cfg3.bookmarks != [ ]) {
+ text = concatStringsSep "\n" cfg3.bookmarks;
+ };
+
+ dconf.settings."org/gnome/desktop/interface" = dconfIni;
+ });
}
diff --git a/home-manager/modules/misc/news.nix b/home-manager/modules/misc/news.nix
index 6b01617fc55..b031af99685 100644
--- a/home-manager/modules/misc/news.nix
+++ b/home-manager/modules/misc/news.nix
@@ -452,7 +452,7 @@ in
{
time = "2017-12-11T17:23:12+00:00";
- condition = config.home.activation ? reloadSystemD;
+ condition = config.home.activation ? reloadSystemd;
message = ''
The Boolean option 'systemd.user.startServices' is now
available. When enabled the current naive systemd unit
@@ -1318,6 +1318,361 @@ in
A new module is available: 'programs.neomutt'.
'';
}
+
+ {
+ time = "2020-02-23T10:19:48+00:00";
+ message = ''
+ A new module is available: 'programs.kitty'.
+ '';
+ }
+
+ {
+ time = "2020-02-26T21:20:55+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'wayland.windowManager.sway'
+ '';
+ }
+
+ {
+ time = "2020-03-04T18:55:03+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'programs.abook'
+ '';
+ }
+
+ {
+ time = "2020-03-07T11:43:26+00:00";
+ condition = config.programs.fish.enable;
+ message = ''
+ The option 'programs.fish.functions' has been reworked in
+ order to support all available flags, such as
+ '--description', '--on-event', and more.
+ '';
+ }
+
+ {
+ time = "2020-03-07T13:11:43+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ The NixOS module has a new option: 'home-manager.useGlobalPkgs'.
+
+ This enables using the system configuration's 'pkgs'
+ argument in Home Manager.
+
+ To learn more, see the installation section of the manual
+
+ https://rycee.gitlab.io/home-manager/#sec-install-nixos-module
+ '';
+ }
+
+ {
+ time = "2020-03-07T14:12:50+00:00";
+ message = ''
+ A new module is available: 'programs.lieer'.
+ '';
+ }
+
+ {
+ time = "2020-03-07T14:12:50+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.lieer'.
+ '';
+ }
+
+ {
+ time = "2020-03-15T16:55:28+00:00";
+ condition = config.programs.firefox.enable;
+ message = ''
+ In anticipation of Firefox dropping support for extension
+ sideloading[1], we now install extensions directly to
+ Firefox profiles managed through Home Manager's
+
+ 'programs.firefox.profiles'
+
+ option.
+
+ Unfortunately this will most likely trigger an "Existing
+ file is in the way" error when activating your configuration
+ since Firefox keeps a copy of the add-on in the location
+ Home Manager wants to overwrite. If this is the case, remove
+ the listed '.xpi' files and try again.
+
+ This change also means that extensions installed through
+ Home Manager may disappear from unmanaged profiles in future
+ Firefox releases.
+
+ [1] https://blog.mozilla.org/addons/2019/10/31/firefox-to-discontinue-sideloaded-extensions/
+ '';
+ }
+
+ {
+ time = "2020-03-17T21:56:26+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.keynav'.
+ '';
+ }
+
+ {
+ time = "2020-03-24T22:17:20+00:00";
+ condition = config.services.compton.enable;
+ message = ''
+ The 'services.compton' module has been deprecated and
+ instead the new module 'services.picom' should be used. This
+ is because Nixpkgs no longer packages compton, and instead
+ packages the (mostly) compatible fork called picom.
+
+ The 'services.compton' and 'services.picom' modules have a
+ few differences:
+
+ - 'services.picom' has a new 'experimentalBackends'
+ option.
+
+ - 'vSync' is now a boolean value on 'services.picom', as
+ opposed to the string in 'services.compton'.
+
+ Migrating to the new picom service is simple - just change
+ all references to 'services.compton' to 'services.picom',
+ and adhere to the above changes.
+
+ The deprecated 'services.compton' will eventually be removed
+ in the future. Please update your configurations to use
+ 'services.picom' as soon as possible.
+ '';
+ }
+
+ {
+ time = "2020-04-08T09:33:05+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'targets.genericLinux'.
+
+ When enabled, this module will configure various settings
+ and environment variables to make Home Manager and programs
+ installed through Nix work better on GNU/Linux distributions
+ other than NixOS.
+
+ It should not be enabled if your Home Manager configuration
+ is deployed on a NixOS host.
+ '';
+ }
+
+ {
+ time = "2020-04-08T11:51:15+00:00";
+ message = ''
+ A new module is available: 'programs.qutebrowser'
+ '';
+ }
+
+ {
+ time = "2020-04-09T09:19:38+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.mako'
+ '';
+ }
+
+ {
+ time = "2020-04-23T19:45:26+00:00";
+ message = ''
+ A new module is available: 'programs.lf'
+ '';
+ }
+
+ {
+ time = "2020-04-26T13:46:28+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.pulseeffects'
+ '';
+ }
+
+ {
+ time = "2020-05-03T11:13:07+00:00";
+ message = ''
+ A new module is available: 'programs.i3status'
+ '';
+ }
+
+ {
+ time = "2020-05-03T11:21:42+00:00";
+ message = ''
+ A new module is available: 'programs.aria2'
+ '';
+ }
+
+ {
+ time = "2020-05-04T21:19:43+00:00";
+ condition = config.programs.git.enable;
+ message = ''
+ The Git module now supports the 'delta' syntax highlighter.
+
+ It can be enabled through the option 'programs.git.delta.enable'.
+ '';
+ }
+
+ {
+ time = "2020-05-12T20:09:54+00:00";
+ message = ''
+ A new module is available: 'programs.dircolors'
+ '';
+ }
+
+ {
+ time = "2020-05-26T17:13:58+00:00";
+ message = ''
+ A new module is available: 'programs.zoxide'
+ '';
+ }
+
+ {
+ time = "2020-06-03T17:46:11+00:00";
+ condition = config.programs.ssh.enable;
+ message = ''
+ The ssh module now supports the 'ServerAliveCountMax' option
+ both globally through
+
+ programs.ssh.serverAliveCountMax
+
+ and per match blocks
+
+ programs.ssh.matchBlocks.<name>.serverAliveCountMax
+ '';
+ }
+
+ {
+ time = "2020-06-11T18:06:37+00:00";
+ condition = hostPlatform.isLinux && config.services.emacs.enable;
+ message = ''
+ The Emacs service now supports systemd socket activation.
+
+ It can be enabled through the option 'services.emacs.socketActivation.enable'.
+ '';
+ }
+
+ {
+ time = "2020-06-12T17:48:01+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.clipmenu'
+ '';
+ }
+
+ {
+ time = "2020-06-12T07:08:09+00:00";
+ condition = config.programs.bash.enable;
+ message = ''
+ A new module is available: 'programs.powerline-go'
+ '';
+ }
+
+ {
+ time = "2020-06-14T13:30:19+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'service.fluidsynth'
+ '';
+ }
+
+ {
+ time = "2020-06-17T22:17:52+00:00";
+ condition = config.programs.git.enable;
+ message = ''
+ Since May 1, 2020 string values in Git configurations are
+ automatically escaped. If you have any manually escaped characters,
+ then you may need to restore them to their unescaped form to avoid
+ double escaping.
+
+ In other words, if you now have something along the lines of
+
+ programs.git.aliases.hello = '''"!echo $'Hello\\nWorld'"''';
+
+ you must replace it by the unescaped form
+
+ programs.git.aliases.hello = "!echo $'Hello\nWorld'";
+
+ Apologies for the belated notification!
+ '';
+ }
+
+ {
+ time = "2020-06-23T20:06:39+00:00";
+ message = ''
+ A new module is available: 'programs.ne'
+ '';
+ }
+
+ {
+ time = "2020-07-24T15:03:11+00:00";
+ message = ''
+ A new module is available: 'programs.nushell'.
+ '';
+ }
+
+ {
+ time = "2020-07-25T21:04:59+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.dropbox'.
+ '';
+ }
+
+ {
+ time = "2020-08-13T22:15:27+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'programs.waybar'
+ '';
+ }
+
+ {
+ time = "2020-08-14T22:44:20+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.kanshi'
+ '';
+ }
+
+ {
+ time = "2020-08-25T22:14:01+00:00";
+ message = ''
+ A new module is available: 'programs.mcfly'
+ '';
+ }
+
+ {
+ time = "2020-09-01T18:38:18+00:00";
+ message = ''
+ A new module is available: 'programs.ncmpcpp'
+ '';
+ }
+
+ {
+ time = "2020-09-11T10:06:47+00:00";
+ condition = hostPlatform.isLinux && config.targets.genericLinux.enable;
+ message = ''
+ A new option 'targets.genericLinux.extraXdgDataDirs' is available
+ to setup the user environment with the OS's data files.
+
+ This is useful for example to get Bash completion for
+ 'systemctl' which shouldn't be installed through Home Manager.
+
+ This is also useful to have non Home Manager applications
+ available in menus.
+ '';
+ }
+
+ {
+ time = "2020-09-09T06:54:59+00:00";
+ condition = config.programs.man.enable;
+ message = ''
+ A new option 'programs.man.generateCaches' was added to
+ support the apropos command.
+ '';
+ }
];
};
}
diff --git a/home-manager/modules/misc/nixpkgs.nix b/home-manager/modules/misc/nixpkgs.nix
index 7b0904a5f20..511dbec10b2 100644
--- a/home-manager/modules/misc/nixpkgs.nix
+++ b/home-manager/modules/misc/nixpkgs.nix
@@ -1,6 +1,6 @@
# Adapted from Nixpkgs.
-{ config, lib, pkgs, ... }:
+{ config, lib, pkgs, pkgsPath, ... }:
with lib;
@@ -49,7 +49,7 @@ let
merge = lib.mergeOneOption;
};
- _pkgs = import <nixpkgs> (
+ _pkgs = import pkgsPath (
filterAttrs (n: v: v != null) config.nixpkgs
);
diff --git a/home-manager/modules/misc/numlock.nix b/home-manager/modules/misc/numlock.nix
index 199dd317daa..c823f6dbdd2 100644
--- a/home-manager/modules/misc/numlock.nix
+++ b/home-manager/modules/misc/numlock.nix
@@ -7,6 +7,8 @@ let
cfg = config.xsession.numlock;
in {
+ meta.maintainers = [ maintainers.evanjs ];
+
options = { xsession.numlock.enable = mkEnableOption "Num Lock"; };
config = mkIf cfg.enable {
diff --git a/home-manager/modules/misc/tmpfiles.nix b/home-manager/modules/misc/tmpfiles.nix
new file mode 100644
index 00000000000..c46fe2c553a
--- /dev/null
+++ b/home-manager/modules/misc/tmpfiles.nix
@@ -0,0 +1,49 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.systemd.user.tmpfiles;
+
+in {
+ meta.maintainers = [ maintainers.dawidsowa ];
+
+ options.systemd.user.tmpfiles.rules = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "L /home/user/Documents - - - - /mnt/data/Documents" ];
+ description = ''
+ Rules for creating and cleaning up temporary files
+ automatically. See
+ <citerefentry>
+ <refentrytitle>tmpfiles.d</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry>
+ for the exact format.
+ '';
+ };
+
+ config = mkIf (cfg.rules != [ ]) {
+ xdg = {
+ dataFile."user-tmpfiles.d/home-manager.conf" = {
+ text = ''
+ # This file is created automatically and should not be modified.
+ # Please change the option β€˜systemd.user.tmpfiles.rules’ instead.
+ ${concatStringsSep "\n" cfg.rules}
+ '';
+ onChange = "${pkgs.systemd}/bin/systemd-tmpfiles --user --create";
+ };
+ configFile = {
+ "systemd/user/basic.target.wants/systemd-tmpfiles-setup.service".source =
+ "${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-setup.service";
+ "systemd/user/systemd-tmpfiles-setup.service".source =
+ "${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-setup.service";
+ "systemd/user/timers.target.wants/systemd-tmpfiles-clean.timer".source =
+ "${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-clean.timer";
+ "systemd/user/systemd-tmpfiles-clean.service".source =
+ "${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-clean.service";
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/misc/version.nix b/home-manager/modules/misc/version.nix
index 1352aadc614..fbeb3ec539a 100644
--- a/home-manager/modules/misc/version.nix
+++ b/home-manager/modules/misc/version.nix
@@ -5,7 +5,7 @@ with lib;
{
options = {
home.stateVersion = mkOption {
- type = types.enum [ "18.09" "19.03" "19.09" "20.03" ];
+ type = types.enum [ "18.09" "19.03" "19.09" "20.03" "20.09" ];
default = "18.09";
description = ''
It is occasionally necessary for Home Manager to change
diff --git a/home-manager/modules/misc/vte.nix b/home-manager/modules/misc/vte.nix
new file mode 100644
index 00000000000..fbe38c0163e
--- /dev/null
+++ b/home-manager/modules/misc/vte.nix
@@ -0,0 +1,51 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ meta.maintainers = [ maintainers.rycee ];
+
+ options.programs = let
+ description = ''
+ Whether to enable integration with terminals using the VTE
+ library. This will let the terminal track the current working
+ directory.
+ '';
+ in {
+ bash.enableVteIntegration = mkEnableOption "" // { inherit description; };
+
+ zsh.enableVteIntegration = mkEnableOption "" // { inherit description; };
+ };
+
+ config = mkMerge [
+ (mkIf config.programs.bash.enableVteIntegration {
+ # Unfortunately we have to do a little dance here to fix two
+ # problems with the upstream vte.sh file:
+ #
+ # - It does `PROMPT_COMMAND="__vte_prompt_command"` which
+ # clobbers any previously assigned prompt command.
+ #
+ # - Its `__vte_prompt_command` function runs commands that will
+ # overwrite the exit status of the command the user ran.
+ programs.bash.initExtra = ''
+ __HM_PROMPT_COMMAND="''${PROMPT_COMMAND:+''${PROMPT_COMMAND%;};}__hm_vte_prompt_command"
+ . ${pkgs.vte}/etc/profile.d/vte.sh
+ if [[ $(type -t __vte_prompt_command) = function ]]; then
+ __hm_vte_prompt_command() {
+ local old_exit_status=$?
+ __vte_prompt_command
+ return $old_exit_status
+ }
+ PROMPT_COMMAND="$__HM_PROMPT_COMMAND"
+ fi
+ unset __HM_PROMPT_COMMAND
+ '';
+ })
+
+ (mkIf config.programs.zsh.enableVteIntegration {
+ programs.zsh.initExtra = ''
+ . ${pkgs.vte}/etc/profile.d/vte.sh
+ '';
+ })
+ ];
+}
diff --git a/home-manager/modules/misc/xdg-mime-apps.nix b/home-manager/modules/misc/xdg-mime-apps.nix
index 7ba4083b3c0..81d2ba0fcbe 100644
--- a/home-manager/modules/misc/xdg-mime-apps.nix
+++ b/home-manager/modules/misc/xdg-mime-apps.nix
@@ -74,7 +74,7 @@ in {
config = mkIf cfg.enable {
# Deprecated but still used by some applications.
- home.file.".local/share/applications/mimeapps.list".source =
+ xdg.dataFile."applications/mimeapps.list".source =
config.xdg.configFile."mimeapps.list".source;
xdg.configFile."mimeapps.list".text =
diff --git a/home-manager/modules/misc/xdg-mime.nix b/home-manager/modules/misc/xdg-mime.nix
index 32006e025ff..5999e1299c9 100644
--- a/home-manager/modules/misc/xdg-mime.nix
+++ b/home-manager/modules/misc/xdg-mime.nix
@@ -27,10 +27,18 @@ in {
home.packages = [
# Explicitly install package to provide basic mime types.
pkgs.shared-mime-info
+
+ # Make sure the target directories will be real directories.
+ (pkgs.runCommandLocal "dummy-xdg-mime-dirs1" { } ''
+ mkdir -p $out/share/{applications,mime/packages}
+ '')
+ (pkgs.runCommandLocal "dummy-xdg-mime-dirs2" { } ''
+ mkdir -p $out/share/{applications,mime/packages}
+ '')
];
home.extraProfileCommands = ''
- if [[ -w $out/share/mime && -d $out/share/mime/packages ]]; then
+ if [[ -w $out/share/mime && -w $out/share/mime/packages && -d $out/share/mime/packages ]]; then
XDG_DATA_DIRS=$out/share \
PKGSYSTEM_ENABLE_FSYNC=0 \
${pkgs.buildPackages.shared-mime-info}/bin/update-mime-database \
diff --git a/home-manager/modules/misc/xdg-user-dirs.nix b/home-manager/modules/misc/xdg-user-dirs.nix
index da9d3c43ad9..a1db6b115a1 100644
--- a/home-manager/modules/misc/xdg-user-dirs.nix
+++ b/home-manager/modules/misc/xdg-user-dirs.nix
@@ -89,15 +89,22 @@ in {
};
config = mkIf cfg.enable {
- xdg.configFile."user-dirs.dirs".text = generators.toKeyValue { } ({
- XDG_DESKTOP_DIR = cfg.desktop;
- XDG_DOCUMENTS_DIR = cfg.documents;
- XDG_DOWNLOAD_DIR = cfg.download;
- XDG_MUSIC_DIR = cfg.music;
- XDG_PICTURES_DIR = cfg.pictures;
- XDG_PUBLICSHARE_DIR = cfg.publicShare;
- XDG_TEMPLATES_DIR = cfg.templates;
- XDG_VIDEOS_DIR = cfg.videos;
- } // cfg.extraConfig);
+ xdg.configFile."user-dirs.dirs".text = let
+ options = {
+ XDG_DESKTOP_DIR = cfg.desktop;
+ XDG_DOCUMENTS_DIR = cfg.documents;
+ XDG_DOWNLOAD_DIR = cfg.download;
+ XDG_MUSIC_DIR = cfg.music;
+ XDG_PICTURES_DIR = cfg.pictures;
+ XDG_PUBLICSHARE_DIR = cfg.publicShare;
+ XDG_TEMPLATES_DIR = cfg.templates;
+ XDG_VIDEOS_DIR = cfg.videos;
+ } // cfg.extraConfig;
+
+ # For some reason, these need to be wrapped with quotes to be valid.
+ wrapped = mapAttrs (_: value: ''"${value}"'') options;
+ in generators.toKeyValue { } wrapped;
+
+ xdg.configFile."user-dirs.conf".text = "enabled=False";
};
}
diff --git a/home-manager/modules/misc/xdg.nix b/home-manager/modules/misc/xdg.nix
index 84ab4ada59a..7420e8e92b3 100644
--- a/home-manager/modules/misc/xdg.nix
+++ b/home-manager/modules/misc/xdg.nix
@@ -85,12 +85,20 @@ in
};
})
- (mkIf (!cfg.enable) {
+ # Legacy non-deterministic setup.
+ (mkIf (!cfg.enable && versionOlder config.home.stateVersion "20.09") {
xdg.cacheHome = getXdgDir "XDG_CACHE_HOME" defaultCacheHome;
xdg.configHome = getXdgDir "XDG_CONFIG_HOME" defaultConfigHome;
xdg.dataHome = getXdgDir "XDG_DATA_HOME" defaultDataHome;
})
+ # "Modern" deterministic setup.
+ (mkIf (!cfg.enable && versionAtLeast config.home.stateVersion "20.09") {
+ xdg.cacheHome = mkDefault defaultCacheHome;
+ xdg.configHome = mkDefault defaultConfigHome;
+ xdg.dataHome = mkDefault defaultDataHome;
+ })
+
{
home.file = mkMerge [
cfg.configFile
diff --git a/home-manager/modules/modules.nix b/home-manager/modules/modules.nix
index 64418dbae11..08c978b177d 100644
--- a/home-manager/modules/modules.nix
+++ b/home-manager/modules/modules.nix
@@ -5,6 +5,9 @@
# Whether to enable module type checking.
, check ? true
+
+# If disabled, the pkgs attribute passed to this function is used instead.
+, useNixpkgsModule ? true
}:
with lib;
@@ -13,8 +16,6 @@ let
hostPlatform = pkgs.stdenv.hostPlatform;
- checkPlatform = any (meta.platformMatch pkgs.stdenv.hostPlatform);
-
loadModule = file: { condition ? true }: {
inherit file condition;
};
@@ -25,23 +26,28 @@ let
(loadModule ./home-environment.nix { })
(loadModule ./manual.nix { })
(loadModule ./misc/dconf.nix { })
+ (loadModule ./misc/debug.nix { })
(loadModule ./misc/fontconfig.nix { })
(loadModule ./misc/gtk.nix { })
(loadModule ./misc/lib.nix { })
(loadModule ./misc/news.nix { })
- (loadModule ./misc/nixpkgs.nix { })
+ (loadModule ./misc/nixpkgs.nix { condition = useNixpkgsModule; })
(loadModule ./misc/numlock.nix { condition = hostPlatform.isLinux; })
(loadModule ./misc/pam.nix { })
(loadModule ./misc/qt.nix { })
(loadModule ./misc/submodule-support.nix { })
+ (loadModule ./misc/tmpfiles.nix { condition = hostPlatform.isLinux; })
(loadModule ./misc/version.nix { })
+ (loadModule ./misc/vte.nix { })
(loadModule ./misc/xdg-mime.nix { condition = hostPlatform.isLinux; })
(loadModule ./misc/xdg-mime-apps.nix { condition = hostPlatform.isLinux; })
(loadModule ./misc/xdg-user-dirs.nix { condition = hostPlatform.isLinux; })
(loadModule ./misc/xdg.nix { })
+ (loadModule ./programs/abook.nix { condition = hostPlatform.isLinux; })
(loadModule ./programs/afew.nix { })
(loadModule ./programs/alacritty.nix { })
(loadModule ./programs/alot.nix { })
+ (loadModule ./programs/aria2.nix { })
(loadModule ./programs/astroid.nix { })
(loadModule ./programs/autorandr.nix { })
(loadModule ./programs/bash.nix { })
@@ -51,6 +57,7 @@ let
(loadModule ./programs/browserpass.nix { })
(loadModule ./programs/chromium.nix { condition = hostPlatform.isLinux; })
(loadModule ./programs/command-not-found/command-not-found.nix { })
+ (loadModule ./programs/dircolors.nix { })
(loadModule ./programs/direnv.nix { })
(loadModule ./programs/eclipse.nix { })
(loadModule ./programs/emacs.nix { })
@@ -65,30 +72,40 @@ let
(loadModule ./programs/gpg.nix { })
(loadModule ./programs/home-manager.nix { })
(loadModule ./programs/htop.nix { })
+ (loadModule ./programs/i3status.nix { })
(loadModule ./programs/info.nix { })
(loadModule ./programs/irssi.nix { })
+ (loadModule ./programs/lieer.nix { })
(loadModule ./programs/jq.nix { })
(loadModule ./programs/kakoune.nix { })
(loadModule ./programs/keychain.nix { })
+ (loadModule ./programs/kitty.nix { })
(loadModule ./programs/lesspipe.nix { })
+ (loadModule ./programs/lf.nix { })
(loadModule ./programs/lsd.nix { })
(loadModule ./programs/man.nix { })
(loadModule ./programs/matplotlib.nix { })
(loadModule ./programs/mbsync.nix { })
+ (loadModule ./programs/mcfly.nix { })
(loadModule ./programs/mercurial.nix { })
(loadModule ./programs/mpv.nix { })
(loadModule ./programs/msmtp.nix { })
+ (loadModule ./programs/ncmpcpp.nix { })
+ (loadModule ./programs/ne.nix { })
(loadModule ./programs/neomutt.nix { })
(loadModule ./programs/neovim.nix { })
(loadModule ./programs/newsboat.nix { })
(loadModule ./programs/noti.nix { })
(loadModule ./programs/notmuch.nix { })
+ (loadModule ./programs/nushell.nix { })
(loadModule ./programs/obs-studio.nix { })
(loadModule ./programs/offlineimap.nix { })
(loadModule ./programs/opam.nix { })
(loadModule ./programs/password-store.nix { })
(loadModule ./programs/pazi.nix { })
(loadModule ./programs/pidgin.nix { })
+ (loadModule ./programs/powerline-go.nix { })
+ (loadModule ./programs/qutebrowser.nix { })
(loadModule ./programs/readline.nix { })
(loadModule ./programs/rofi.nix { })
(loadModule ./programs/rtorrent.nix { })
@@ -103,27 +120,37 @@ let
(loadModule ./programs/vim.nix { })
(loadModule ./programs/vscode.nix { })
(loadModule ./programs/vscode/haskell.nix { })
+ (loadModule ./programs/waybar.nix { condition = hostPlatform.isLinux; })
(loadModule ./programs/z-lua.nix { })
(loadModule ./programs/zathura.nix { })
+ (loadModule ./programs/zoxide.nix { })
+ (loadModule ./programs/zplug.nix { })
(loadModule ./programs/zsh.nix { })
(loadModule ./services/blueman-applet.nix { })
(loadModule ./services/cbatticon.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/clipmenu.nix { condition = hostPlatform.isLinux; })
(loadModule ./services/compton.nix { })
+ (loadModule ./services/dropbox.nix { condition = hostPlatform.isLinux; })
(loadModule ./services/dunst.nix { })
(loadModule ./services/dwm-status.nix { condition = hostPlatform.isLinux; })
(loadModule ./services/emacs.nix { condition = hostPlatform.isLinux; })
(loadModule ./services/flameshot.nix { })
+ (loadModule ./services/fluidsynth.nix { condition = hostPlatform.isLinux; })
(loadModule ./services/getmail.nix { condition = hostPlatform.isLinux; })
(loadModule ./services/gnome-keyring.nix { })
(loadModule ./services/gpg-agent.nix { })
(loadModule ./services/grobi.nix { condition = hostPlatform.isLinux; })
(loadModule ./services/hound.nix { condition = hostPlatform.isLinux; })
(loadModule ./services/imapnotify.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/kanshi.nix { condition = hostPlatform.isLinux; })
(loadModule ./services/kbfs.nix { })
(loadModule ./services/kdeconnect.nix { })
(loadModule ./services/keepassx.nix { })
(loadModule ./services/keybase.nix { })
+ (loadModule ./services/keynav.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/lieer.nix { condition = hostPlatform.isLinux; })
(loadModule ./services/lorri.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/mako.nix { condition = hostPlatform.isLinux; })
(loadModule ./services/mbsync.nix { })
(loadModule ./services/mpd.nix { })
(loadModule ./services/mpdris2.nix { condition = hostPlatform.isLinux; })
@@ -134,7 +161,9 @@ let
(loadModule ./services/parcellite.nix { })
(loadModule ./services/password-store-sync.nix { condition = hostPlatform.isLinux; })
(loadModule ./services/pasystray.nix { })
+ (loadModule ./services/picom.nix { })
(loadModule ./services/polybar.nix { })
+ (loadModule ./services/pulseeffects.nix { condition = hostPlatform.isLinux; })
(loadModule ./services/random-background.nix { })
(loadModule ./services/redshift.nix { })
(loadModule ./services/rsibreak.nix { condition = hostPlatform.isLinux; })
@@ -152,13 +181,16 @@ let
(loadModule ./services/unison.nix { condition = hostPlatform.isLinux; })
(loadModule ./services/window-managers/awesome.nix { })
(loadModule ./services/window-managers/bspwm/default.nix { condition = hostPlatform.isLinux; })
- (loadModule ./services/window-managers/i3.nix { })
+ (loadModule ./services/window-managers/i3-sway/i3.nix { })
+ (loadModule ./services/window-managers/i3-sway/sway.nix { condition = hostPlatform.isLinux; })
(loadModule ./services/window-managers/xmonad.nix { })
(loadModule ./services/xcape.nix { condition = hostPlatform.isLinux; })
(loadModule ./services/xembed-sni-proxy.nix { condition = hostPlatform.isLinux; })
(loadModule ./services/xscreensaver.nix { })
(loadModule ./services/xsuspender.nix { condition = hostPlatform.isLinux; })
(loadModule ./systemd.nix { })
+ (loadModule ./targets/darwin.nix { condition = hostPlatform.isDarwin; })
+ (loadModule ./targets/generic-linux.nix { condition = hostPlatform.isLinux; })
(loadModule ./xcursor.nix { })
(loadModule ./xresources.nix { })
(loadModule ./xsession.nix { })
@@ -168,12 +200,20 @@ let
modules = map (getAttr "file") (filter (getAttr "condition") allModules);
- pkgsModule = {
- config._module.args.baseModules = modules;
- config._module.args.pkgs = lib.mkDefault pkgs;
- config._module.check = check;
- config.lib = lib.hm;
- config.nixpkgs.system = mkDefault pkgs.system;
+ pkgsModule = { config, ... }: {
+ config = {
+ _module.args.baseModules = modules;
+ _module.args.pkgsPath = lib.mkDefault (
+ if versionAtLeast config.home.stateVersion "20.09" then
+ pkgs.path
+ else
+ <nixpkgs>);
+ _module.args.pkgs = lib.mkDefault pkgs;
+ _module.check = check;
+ lib = lib.hm;
+ } // optionalAttrs useNixpkgsModule {
+ nixpkgs.system = mkDefault pkgs.system;
+ };
};
in
diff --git a/home-manager/modules/programs/abook.nix b/home-manager/modules/programs/abook.nix
new file mode 100644
index 00000000000..4ddc080ad51
--- /dev/null
+++ b/home-manager/modules/programs/abook.nix
@@ -0,0 +1,40 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.abook;
+
+in {
+ options.programs.abook = {
+ enable = mkEnableOption "Abook";
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ field pager = Pager
+ view CONTACT = name, email
+ set autosave=true
+ '';
+ description = ''
+ Extra lines added to <filename>$HOME/.config/abook/abookrc</filename>.
+ Available configuration options are described in the abook repository:
+ <link xlink:href="https://sourceforge.net/p/abook/git/ci/master/tree/sample.abookrc" />.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.abook ];
+ xdg.configFile."abook/abookrc" = mkIf (cfg.extraConfig != "") {
+ text = ''
+ # Generated by Home Manager.
+ # See http://abook.sourceforge.net/
+
+ ${cfg.extraConfig}
+ '';
+ };
+ };
+}
diff --git a/home-manager/modules/programs/alacritty.nix b/home-manager/modules/programs/alacritty.nix
index 69b9ea9673d..ea908f2b056 100644
--- a/home-manager/modules/programs/alacritty.nix
+++ b/home-manager/modules/programs/alacritty.nix
@@ -11,6 +11,13 @@ in {
programs.alacritty = {
enable = mkEnableOption "Alacritty";
+ package = mkOption {
+ type = types.package;
+ default = pkgs.alacritty;
+ defaultText = literalExample "pkgs.alacritty";
+ description = "The Alacritty package to install.";
+ };
+
settings = mkOption {
type = types.attrs;
default = { };
@@ -41,7 +48,7 @@ in {
config = mkMerge [
(mkIf cfg.enable {
- home.packages = [ pkgs.alacritty ];
+ home.packages = [ cfg.package ];
xdg.configFile."alacritty/alacritty.yml" = mkIf (cfg.settings != { }) {
text =
diff --git a/home-manager/modules/programs/alot.nix b/home-manager/modules/programs/alot.nix
index 2b28f34caa3..e907cd3e0ac 100644
--- a/home-manager/modules/programs/alot.nix
+++ b/home-manager/modules/programs/alot.nix
@@ -7,167 +7,231 @@ let
cfg = config.programs.alot;
- alotAccounts = filter (a: a.notmuch.enable)
- (attrValues config.accounts.email.accounts);
+ alotAccounts =
+ filter (a: a.notmuch.enable) (attrValues config.accounts.email.accounts);
boolStr = v: if v then "True" else "False";
- accountStr = account: with account;
- concatStringsSep "\n" (
- [ "[[${name}]]" ]
- ++ mapAttrsToList (n: v: n + "=" + v) (
- {
- address = address;
- realname = realName;
- sendmail_command =
- optionalString (alot.sendMailCommand != null) alot.sendMailCommand;
- sent_box = "maildir" + "://" + maildir.absPath + "/" + folders.sent;
- 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
- bindingsToStr = attrSet:
- concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${v}") attrSet);
- in
- ''
- # Generated by Home Manager.
- # See http://alot.readthedocs.io/en/latest/configuration/config_options.html
-
- ${cfg.extraConfig}
-
- [bindings]
- ${bindingsToStr cfg.bindings.global}
-
- [[bufferlist]]
- ${bindingsToStr cfg.bindings.bufferlist}
- [[search]]
- ${bindingsToStr cfg.bindings.search}
- [[envelope]]
- ${bindingsToStr cfg.bindings.envelope}
- [[taglist]]
- ${bindingsToStr cfg.bindings.taglist}
- [[thread]]
- ${bindingsToStr cfg.bindings.thread}
-
- [accounts]
-
- ${concatStringsSep "\n\n" (map accountStr alotAccounts)}
- '';
-
-in
-
-{
- options.programs.alot = {
- enable = mkOption {
- type = types.bool;
- default = false;
- example = true;
- description = ''
- Whether to enable the Alot mail user agent. Alot uses the
- Notmuch email system and will therefore be automatically
- enabled for each email account that is managed by Notmuch.
- '';
- };
+ mkKeyValue = key: value:
+ let value' = if isBool value then boolStr value else toString value;
+ in "${key} = ${value'}";
+
+ mk2ndLevelSectionName = name: "[" + name + "]";
+
+ tagSubmodule = types.submodule {
+ options = {
+ translated = mkOption {
+ type = types.nullOr types.str;
+ description = ''
+ Fixed string representation for this tag. The tag can be
+ hidden from view, if the key translated is set to
+ <literal>""</literal>, the empty string.
+ '';
+ };
- hooks = mkOption {
- type = types.lines;
- default = "";
- description = ''
- Content of the hooks file.
- '';
- };
+ translation = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ A pair of strings that define a regular substitution to
+ compute the string representation on the fly using
+ <literal>re.sub</literal>.
+ '';
+ };
- bindings = mkOption {
- type = types.submodule {
- options = {
- global = mkOption {
- type = types.attrsOf types.str;
- default = {};
- description = "Global keybindings.";
- };
+ normal = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "'','', 'white','light red', 'white','#d66'";
+ description = ''
+ How to display the tag when unfocused.
+ See <link xlink:href="https://alot.readthedocs.io/en/latest/configuration/theming.html#tagstring-formatting"/>.
+ '';
+ };
- bufferlist = mkOption {
- type = types.attrsOf types.str;
- default = {};
- description = "Bufferlist mode keybindings.";
- };
+ focus = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "How to display the tag when focused.";
+ };
+ };
+ };
- search = mkOption {
- type = types.attrsOf types.str;
- default = {};
- description = "Search mode keybindings.";
- };
+ accountStr = account:
+ with account;
+ concatStringsSep "\n" ([ "[[${name}]]" ]
+ ++ mapAttrsToList (n: v: n + "=" + v) ({
+ address = address;
+ realname = realName;
+ sendmail_command =
+ optionalString (alot.sendMailCommand != null) alot.sendMailCommand;
+ sent_box = "maildir" + "://" + maildir.absPath + "/" + folders.sent;
+ 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
+ bindingsToStr = attrSet:
+ concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${v}") attrSet);
+ in ''
+ # Generated by Home Manager.
+ # See http://alot.readthedocs.io/en/latest/configuration/config_options.html
+
+ ${generators.toKeyValue { inherit mkKeyValue; } cfg.settings}
+ ${cfg.extraConfig}
+ [tags]
+ '' + (let
+ submoduleToAttrs = m:
+ filterAttrs (name: v: name != "_module" && v != null) m;
+ in generators.toINI { mkSectionName = mk2ndLevelSectionName; }
+ (mapAttrs (name: x: submoduleToAttrs x) cfg.tags)) + ''
+ [bindings]
+ ${bindingsToStr cfg.bindings.global}
+
+ [[bufferlist]]
+ ${bindingsToStr cfg.bindings.bufferlist}
+ [[search]]
+ ${bindingsToStr cfg.bindings.search}
+ [[envelope]]
+ ${bindingsToStr cfg.bindings.envelope}
+ [[taglist]]
+ ${bindingsToStr cfg.bindings.taglist}
+ [[thread]]
+ ${bindingsToStr cfg.bindings.thread}
+
+ [accounts]
+
+ ${concatStringsSep "\n\n" (map accountStr alotAccounts)}
+ '';
+
+in {
+ options = {
+ programs.alot = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ example = true;
+ description = ''
+ Whether to enable the Alot mail user agent. Alot uses the
+ Notmuch email system and will therefore be automatically
+ enabled for each email account that is managed by Notmuch.
+ '';
+ };
- envelope = mkOption {
- type = types.attrsOf types.str;
- default = {};
- description = "Envelope mode keybindings.";
- };
+ hooks = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Content of the hooks file.
+ '';
+ };
- taglist = mkOption {
- type = types.attrsOf types.str;
- default = {};
- description = "Taglist mode keybindings.";
+ bindings = mkOption {
+ type = types.submodule {
+ options = {
+ global = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ description = "Global keybindings.";
+ };
+
+ bufferlist = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ description = "Bufferlist mode keybindings.";
+ };
+
+ search = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ description = "Search mode keybindings.";
+ };
+
+ envelope = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ description = "Envelope mode keybindings.";
+ };
+
+ taglist = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ description = "Taglist mode keybindings.";
+ };
+
+ thread = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ description = "Thread mode keybindings.";
+ };
};
+ };
+ default = { };
+ description = ''
+ Keybindings.
+ '';
+ };
- thread = mkOption {
- type = types.attrsOf types.str;
- default = {};
- description = "Thread mode keybindings.";
- };
+ tags = mkOption {
+ type = types.attrsOf tagSubmodule;
+ default = { };
+ description = "How to display the tags.";
+ };
+
+ settings = mkOption {
+ type = with types;
+ let primitive = either (either (either str int) bool) float;
+ in attrsOf primitive;
+ default = {
+ initial_command = "search tag:inbox AND NOT tag:killed";
+ auto_remove_unread = true;
+ handle_mouse = true;
+ prefer_plaintext = true;
};
+ example = literalExample ''
+ {
+ auto_remove_unread = true;
+ ask_subject = false;
+ thread_indent_replies = 2;
+ }
+ '';
+ description = ''
+ Configuration options added to alot configuration file.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra lines added to alot configuration file.
+ '';
};
- default = {};
- description = ''
- Keybindings.
- '';
};
- extraConfig = mkOption {
- type = types.lines;
- default = ''
- auto_remove_unread = True
- ask_subject = False
- handle_mouse = True
- initial_command = "search tag:inbox AND NOT tag:killed"
- input_timeout = 0.3
- prefer_plaintext = True
- thread_indent_replies = 4
- '';
- description = ''
- Extra lines added to alot configuration file.
- '';
+ accounts.email.accounts = mkOption {
+ type = with types; attrsOf (submodule (import ./alot-accounts.nix pkgs));
};
};
config = mkIf cfg.enable {
- home.packages = [ pkgs.alot ];
+ home.packages = [ pkgs.alot ];
xdg.configFile."alot/config".text = configFile;
- xdg.configFile."alot/hooks.py".text =
- ''
+ xdg.configFile."alot/hooks.py" = mkIf (cfg.hooks != "") {
+ text = ''
# Generated by Home Manager.
- ''
- + cfg.hooks;
+ '' + cfg.hooks;
+ };
};
}
diff --git a/home-manager/modules/programs/aria2.nix b/home-manager/modules/programs/aria2.nix
new file mode 100644
index 00000000000..d1317ff7616
--- /dev/null
+++ b/home-manager/modules/programs/aria2.nix
@@ -0,0 +1,61 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.aria2;
+
+ formatLine = n: v:
+ let
+ formatValue = v:
+ if builtins.isBool v then
+ (if v then "true" else "false")
+ else
+ toString v;
+ in "${n}=${formatValue v}";
+in {
+ meta.maintainers = [ hm.maintainers.justinlovinger ];
+
+ options.programs.aria2 = {
+ enable = mkEnableOption "aria2";
+
+ settings = mkOption {
+ type = with types; attrsOf (oneOf [ bool float int str ]);
+ default = { };
+ description = ''
+ Options to add to <filename>aria2.conf</filename> file.
+ See
+ <citerefentry>
+ <refentrytitle>aria2c</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ for options.
+ '';
+ example = literalExample ''
+ {
+ listen-port = 60000;
+ dht-listen-port = 60000;
+ seed-ratio = 1.0;
+ max-upload-limit = "50K";
+ ftp-pasv = true;
+ }
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra lines added to <filename>aria2.conf</filename> file.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.aria2 ];
+
+ xdg.configFile."aria2/aria2.conf".text = concatStringsSep "\n" ([ ]
+ ++ mapAttrsToList formatLine cfg.settings
+ ++ optional (cfg.extraConfig != "") cfg.extraConfig);
+ };
+}
diff --git a/home-manager/modules/programs/astroid.nix b/home-manager/modules/programs/astroid.nix
index 8b3762fac0b..af12b10edbb 100644
--- a/home-manager/modules/programs/astroid.nix
+++ b/home-manager/modules/programs/astroid.nix
@@ -98,6 +98,10 @@ in {
'';
};
};
+
+ accounts.email.accounts = mkOption {
+ type = with types; attrsOf (submodule (import ./astroid-accounts.nix));
+ };
};
config = mkIf cfg.enable {
diff --git a/home-manager/modules/programs/autorandr.nix b/home-manager/modules/programs/autorandr.nix
index 02fc77c1e58..40cad704db9 100644
--- a/home-manager/modules/programs/autorandr.nix
+++ b/home-manager/modules/programs/autorandr.nix
@@ -55,6 +55,13 @@ let
default = true;
};
+ crtc = mkOption {
+ type = types.nullOr types.ints.unsigned;
+ description = "Output video display controller.";
+ default = null;
+ example = 0;
+ };
+
primary = mkOption {
type = types.bool;
description = "Whether output should be marked as primary";
@@ -244,21 +251,22 @@ let
]);
fingerprintToString = name: edid: "${name} ${edid}";
configToString = name: config:
- if config.enable then ''
- output ${name}
- ${optionalString (config.position != "") "pos ${config.position}"}
- ${optionalString config.primary "primary"}
- ${optionalString (config.dpi != null) "dpi ${toString config.dpi}"}
- ${optionalString (config.gamma != "") "gamma ${config.gamma}"}
- ${optionalString (config.mode != "") "mode ${config.mode}"}
- ${optionalString (config.rate != "") "rate ${config.rate}"}
- ${optionalString (config.rotate != null) "rotate ${config.rotate}"}
- ${optionalString (config.scale != null)
- ((if config.scale.method == "factor" then "scale" else "scale-from")
- + " ${toString config.scale.x}x${toString config.scale.y}")}
- ${optionalString (config.transform != null) ("transform "
- + concatMapStringsSep "," toString (flatten config.transform))}
- '' else ''
+ if config.enable then
+ concatStringsSep "\n" ([ "output ${name}" ]
+ ++ optional (config.position != "") "pos ${config.position}"
+ ++ optional (config.crtc != null) "crtc ${toString config.crtc}"
+ ++ optional config.primary "primary"
+ ++ optional (config.dpi != null) "dpi ${toString config.dpi}"
+ ++ optional (config.gamma != "") "gamma ${config.gamma}"
+ ++ optional (config.mode != "") "mode ${config.mode}"
+ ++ optional (config.rate != "") "rate ${config.rate}"
+ ++ optional (config.rotate != null) "rotate ${config.rotate}"
+ ++ optional (config.transform != null) ("transform "
+ + concatMapStringsSep "," toString (flatten config.transform))
+ ++ optional (config.scale != null)
+ ((if config.scale.method == "factor" then "scale" else "scale-from")
+ + " ${toString config.scale.x}x${toString config.scale.y}"))
+ else ''
output ${name}
off
'';
@@ -315,6 +323,7 @@ in {
eDP1.enable = false;
DP1 = {
enable = true;
+ crtc = 0;
primary = true;
position = "0x0";
mode = "3840x2160";
diff --git a/home-manager/modules/programs/bash.nix b/home-manager/modules/programs/bash.nix
index 82a9fbe8f8b..45fe368bddc 100644
--- a/home-manager/modules/programs/bash.nix
+++ b/home-manager/modules/programs/bash.nix
@@ -82,7 +82,12 @@ in
shellAliases = mkOption {
default = {};
type = types.attrsOf types.str;
- example = { ll = "ls -l"; ".." = "cd .."; };
+ example = literalExample ''
+ {
+ ll = "ls -l";
+ ".." = "cd ..";
+ }
+ '';
description = ''
An attribute set that maps aliases (the top level attribute names in
this option) to command strings or directly to build outputs.
@@ -171,10 +176,10 @@ in
${aliasesStr}
- ${cfg.initExtra}
-
${optionalString cfg.enableAutojump
". ${pkgs.autojump}/share/autojump/autojump.bash"}
+
+ ${cfg.initExtra}
fi
'';
diff --git a/home-manager/modules/programs/bat.nix b/home-manager/modules/programs/bat.nix
index aa0df9abd45..e2b30ea9333 100644
--- a/home-manager/modules/programs/bat.nix
+++ b/home-manager/modules/programs/bat.nix
@@ -24,14 +24,35 @@ in {
'';
};
+ themes = mkOption {
+ type = types.attrsOf types.lines;
+ default = { };
+ example = literalExample ''
+ {
+ dracula = builtins.readFile (pkgs.fetchFromGitHub {
+ owner = "dracula";
+ repo = "sublime"; # Bat uses sublime syntax for its themes
+ rev = "26c57ec282abcaa76e57e055f38432bd827ac34e";
+ sha256 = "019hfl4zbn4vm4154hh3bwk6hm7bdxbr1hdww83nabxwjn99ndhv";
+ } + "/Dracula.tmTheme");
+ }
+ '';
+ description = ''
+ Additional themes to provide.
+ '';
+ };
+
};
config = mkIf cfg.enable {
home.packages = [ pkgs.bat ];
- xdg.configFile."bat/config" = mkIf (cfg.config != { }) {
- text = concatStringsSep "\n"
- (mapAttrsToList (n: v: ''--${n}="${v}"'') cfg.config);
- };
+ xdg.configFile = mkMerge ([{
+ "bat/config" = mkIf (cfg.config != { }) {
+ text = concatStringsSep "\n"
+ (mapAttrsToList (n: v: ''--${n}="${v}"'') cfg.config);
+ };
+ }] ++ flip mapAttrsToList cfg.themes
+ (name: body: { "bat/themes/${name}.tmTheme" = { text = body; }; }));
};
}
diff --git a/home-manager/modules/programs/broot.nix b/home-manager/modules/programs/broot.nix
index eac31b56801..6951e035d32 100644
--- a/home-manager/modules/programs/broot.nix
+++ b/home-manager/modules/programs/broot.nix
@@ -172,11 +172,11 @@ in {
xdg.configFile."broot/conf.toml".source = configFile brootConf;
# Dummy file to prevent broot from trying to reinstall itself
- xdg.configFile."broot/launcher/installed".text = "";
+ xdg.configFile."broot/launcher/installed-v1".text = "";
programs.bash.initExtra = mkIf cfg.enableBashIntegration (
- # Using mkAfter to make it more likely to appear after other
- # manipulations of the prompt.
+ # Using mkAfter to make it more likely to appear after other
+ # manipulations of the prompt.
mkAfter ''
# This script was automatically generated by the broot function
# More information can be found in https://github.com/Canop/broot
diff --git a/home-manager/modules/programs/dircolors.nix b/home-manager/modules/programs/dircolors.nix
new file mode 100644
index 00000000000..026de72d711
--- /dev/null
+++ b/home-manager/modules/programs/dircolors.nix
@@ -0,0 +1,223 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.dircolors;
+
+ formatLine = n: v: "${n} ${toString v}";
+in {
+ meta.maintainers = [ hm.maintainers.justinlovinger ];
+
+ options.programs.dircolors = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to manage <filename>.dir_colors</filename>
+ and set <code>LS_COLORS</code>.
+ '';
+ };
+
+ enableBashIntegration = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable Bash integration.
+ '';
+ };
+
+ enableFishIntegration = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable Fish integration.
+ '';
+ };
+
+ enableZshIntegration = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable Zsh integration.
+ '';
+ };
+
+ settings = mkOption {
+ type = with types; attrsOf str;
+ default = { };
+ description = ''
+ Options to add to <filename>.dir_colors</filename> file.
+ See <command>dircolors --print-database</command>
+ for options.
+ '';
+ example = literalExample ''
+ {
+ OTHER_WRITABLE = "30;46";
+ ".sh" = "01;32";
+ ".csh" = "01;32";
+ }
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra lines added to <filename>.dir_colors</filename> file.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ # Add default settings from `dircolors --print-database`.
+ programs.dircolors.settings = {
+ RESET = mkDefault "0";
+ DIR = mkDefault "01;34";
+ LINK = mkDefault "01;36";
+ MULTIHARDLINK = mkDefault "00";
+ FIFO = mkDefault "40;33";
+ SOCK = mkDefault "01;35";
+ DOOR = mkDefault "01;35";
+ BLK = mkDefault "40;33;01";
+ CHR = mkDefault "40;33;01";
+ ORPHAN = mkDefault "40;31;01";
+ MISSING = mkDefault "00";
+ SETUID = mkDefault "37;41";
+ SETGID = mkDefault "30;43";
+ CAPABILITY = mkDefault "30;41";
+ STICKY_OTHER_WRITABLE = mkDefault "30;42";
+ OTHER_WRITABLE = mkDefault "34;42";
+ STICKY = mkDefault "37;44";
+ EXEC = mkDefault "01;32";
+ ".tar" = mkDefault "01;31";
+ ".tgz" = mkDefault "01;31";
+ ".arc" = mkDefault "01;31";
+ ".arj" = mkDefault "01;31";
+ ".taz" = mkDefault "01;31";
+ ".lha" = mkDefault "01;31";
+ ".lz4" = mkDefault "01;31";
+ ".lzh" = mkDefault "01;31";
+ ".lzma" = mkDefault "01;31";
+ ".tlz" = mkDefault "01;31";
+ ".txz" = mkDefault "01;31";
+ ".tzo" = mkDefault "01;31";
+ ".t7z" = mkDefault "01;31";
+ ".zip" = mkDefault "01;31";
+ ".z" = mkDefault "01;31";
+ ".dz" = mkDefault "01;31";
+ ".gz" = mkDefault "01;31";
+ ".lrz" = mkDefault "01;31";
+ ".lz" = mkDefault "01;31";
+ ".lzo" = mkDefault "01;31";
+ ".xz" = mkDefault "01;31";
+ ".zst" = mkDefault "01;31";
+ ".tzst" = mkDefault "01;31";
+ ".bz2" = mkDefault "01;31";
+ ".bz" = mkDefault "01;31";
+ ".tbz" = mkDefault "01;31";
+ ".tbz2" = mkDefault "01;31";
+ ".tz" = mkDefault "01;31";
+ ".deb" = mkDefault "01;31";
+ ".rpm" = mkDefault "01;31";
+ ".jar" = mkDefault "01;31";
+ ".war" = mkDefault "01;31";
+ ".ear" = mkDefault "01;31";
+ ".sar" = mkDefault "01;31";
+ ".rar" = mkDefault "01;31";
+ ".alz" = mkDefault "01;31";
+ ".ace" = mkDefault "01;31";
+ ".zoo" = mkDefault "01;31";
+ ".cpio" = mkDefault "01;31";
+ ".7z" = mkDefault "01;31";
+ ".rz" = mkDefault "01;31";
+ ".cab" = mkDefault "01;31";
+ ".wim" = mkDefault "01;31";
+ ".swm" = mkDefault "01;31";
+ ".dwm" = mkDefault "01;31";
+ ".esd" = mkDefault "01;31";
+ ".jpg" = mkDefault "01;35";
+ ".jpeg" = mkDefault "01;35";
+ ".mjpg" = mkDefault "01;35";
+ ".mjpeg" = mkDefault "01;35";
+ ".gif" = mkDefault "01;35";
+ ".bmp" = mkDefault "01;35";
+ ".pbm" = mkDefault "01;35";
+ ".pgm" = mkDefault "01;35";
+ ".ppm" = mkDefault "01;35";
+ ".tga" = mkDefault "01;35";
+ ".xbm" = mkDefault "01;35";
+ ".xpm" = mkDefault "01;35";
+ ".tif" = mkDefault "01;35";
+ ".tiff" = mkDefault "01;35";
+ ".png" = mkDefault "01;35";
+ ".svg" = mkDefault "01;35";
+ ".svgz" = mkDefault "01;35";
+ ".mng" = mkDefault "01;35";
+ ".pcx" = mkDefault "01;35";
+ ".mov" = mkDefault "01;35";
+ ".mpg" = mkDefault "01;35";
+ ".mpeg" = mkDefault "01;35";
+ ".m2v" = mkDefault "01;35";
+ ".mkv" = mkDefault "01;35";
+ ".webm" = mkDefault "01;35";
+ ".ogm" = mkDefault "01;35";
+ ".mp4" = mkDefault "01;35";
+ ".m4v" = mkDefault "01;35";
+ ".mp4v" = mkDefault "01;35";
+ ".vob" = mkDefault "01;35";
+ ".qt" = mkDefault "01;35";
+ ".nuv" = mkDefault "01;35";
+ ".wmv" = mkDefault "01;35";
+ ".asf" = mkDefault "01;35";
+ ".rm" = mkDefault "01;35";
+ ".rmvb" = mkDefault "01;35";
+ ".flc" = mkDefault "01;35";
+ ".avi" = mkDefault "01;35";
+ ".fli" = mkDefault "01;35";
+ ".flv" = mkDefault "01;35";
+ ".gl" = mkDefault "01;35";
+ ".dl" = mkDefault "01;35";
+ ".xcf" = mkDefault "01;35";
+ ".xwd" = mkDefault "01;35";
+ ".yuv" = mkDefault "01;35";
+ ".cgm" = mkDefault "01;35";
+ ".emf" = mkDefault "01;35";
+ ".ogv" = mkDefault "01;35";
+ ".ogx" = mkDefault "01;35";
+ ".aac" = mkDefault "00;36";
+ ".au" = mkDefault "00;36";
+ ".flac" = mkDefault "00;36";
+ ".m4a" = mkDefault "00;36";
+ ".mid" = mkDefault "00;36";
+ ".midi" = mkDefault "00;36";
+ ".mka" = mkDefault "00;36";
+ ".mp3" = mkDefault "00;36";
+ ".mpc" = mkDefault "00;36";
+ ".ogg" = mkDefault "00;36";
+ ".ra" = mkDefault "00;36";
+ ".wav" = mkDefault "00;36";
+ ".oga" = mkDefault "00;36";
+ ".opus" = mkDefault "00;36";
+ ".spx" = mkDefault "00;36";
+ ".xspf" = mkDefault "00;36";
+ };
+
+ home.file.".dir_colors".text = concatStringsSep "\n" ([ ]
+ ++ optional (cfg.extraConfig != "") cfg.extraConfig
+ ++ mapAttrsToList formatLine cfg.settings) + "\n";
+
+ programs.bash.initExtra = mkIf cfg.enableBashIntegration ''
+ eval $(${pkgs.coreutils}/bin/dircolors -b ~/.dir_colors)
+ '';
+
+ programs.fish.shellInit = mkIf cfg.enableFishIntegration ''
+ eval (${pkgs.coreutils}/bin/dircolors -c ~/.dir_colors)
+ '';
+
+ # Set `LS_COLORS` before Oh My Zsh and `initExtra`.
+ programs.zsh.initExtraBeforeCompInit = mkIf cfg.enableZshIntegration ''
+ eval $(${pkgs.coreutils}/bin/dircolors -b ~/.dir_colors)
+ '';
+ };
+}
diff --git a/home-manager/modules/programs/direnv.nix b/home-manager/modules/programs/direnv.nix
index beb40a96261..1d1374b8e26 100644
--- a/home-manager/modules/programs/direnv.nix
+++ b/home-manager/modules/programs/direnv.nix
@@ -70,6 +70,11 @@ in {
Whether to enable Fish integration.
'';
};
+
+ enableNixDirenvIntegration = mkEnableOption ''
+ <link
+ xlink:href="https://github.com/nix-community/nix-direnv">nix-direnv</link>,
+ a fast, persistent use_nix implementation for direnv'';
};
config = mkIf cfg.enable {
@@ -78,12 +83,15 @@ in {
xdg.configFile."direnv/config.toml" =
mkIf (cfg.config != { }) { source = configFile cfg.config; };
- xdg.configFile."direnv/direnvrc" =
- mkIf (cfg.stdlib != "") { text = cfg.stdlib; };
+ xdg.configFile."direnv/direnvrc" = let
+ text = concatStringsSep "\n" (optional (cfg.stdlib != "") cfg.stdlib
+ ++ optional cfg.enableNixDirenvIntegration
+ "source ${pkgs.nix-direnv}/share/nix-direnv/direnvrc");
+ in mkIf (text != "") { inherit text; };
programs.bash.initExtra = mkIf cfg.enableBashIntegration (
- # Using mkAfter to make it more likely to appear after other
- # manipulations of the prompt.
+ # Using mkAfter to make it more likely to appear after other
+ # manipulations of the prompt.
mkAfter ''
eval "$(${pkgs.direnv}/bin/direnv hook bash)"
'');
diff --git a/home-manager/modules/programs/eclipse.nix b/home-manager/modules/programs/eclipse.nix
index 8ce605b106a..21973ab937e 100644
--- a/home-manager/modules/programs/eclipse.nix
+++ b/home-manager/modules/programs/eclipse.nix
@@ -13,6 +13,16 @@ in {
programs.eclipse = {
enable = mkEnableOption "Eclipse";
+ package = mkOption {
+ type = types.package;
+ default = pkgs.eclipses.eclipse-platform;
+ defaultText = literalExample "pkgs.eclipses.eclipse-platform";
+ example = literalExample "pkgs.eclipses.eclipse-java";
+ description = ''
+ The Eclipse package to install.
+ '';
+ };
+
enableLombok = mkOption {
type = types.bool;
default = false;
@@ -40,7 +50,7 @@ in {
config = mkIf cfg.enable {
home.packages = [
(pkgs.eclipses.eclipseWithPlugins {
- eclipse = pkgs.eclipses.eclipse-platform;
+ eclipse = cfg.package;
jvmArgs = cfg.jvmArgs ++ optional cfg.enableLombok
"-javaagent:${pkgs.lombok}/share/java/lombok.jar";
plugins = cfg.plugins;
diff --git a/home-manager/modules/programs/emacs.nix b/home-manager/modules/programs/emacs.nix
index 987a9f2431e..b785f71358c 100644
--- a/home-manager/modules/programs/emacs.nix
+++ b/home-manager/modules/programs/emacs.nix
@@ -8,16 +8,12 @@ let
# Copied from all-packages.nix, with modifications to support
# overrides.
- emacsPackages =
- let
- epkgs = pkgs.emacsPackagesGen cfg.package;
- in
- epkgs.overrideScope' cfg.overrides;
- emacsWithPackages = emacsPackages.emacsWithPackages;
+ emacsPackages = let epkgs = pkgs.emacsPackagesFor cfg.package;
+ in epkgs.overrideScope' cfg.overrides;
-in
+ emacsWithPackages = emacsPackages.emacsWithPackages;
-{
+in {
meta.maintainers = [ maintainers.rycee ];
options = {
@@ -33,7 +29,7 @@ in
};
extraPackages = mkOption {
- default = self: [];
+ default = self: [ ];
type = hm.types.selectorFunction;
defaultText = "epkgs: []";
example = literalExample "epkgs: [ epkgs.emms epkgs.magit ]";
@@ -45,7 +41,7 @@ in
};
overrides = mkOption {
- default = self: super: {};
+ default = self: super: { };
type = hm.types.overlayFunction;
defaultText = "self: super: {}";
example = literalExample ''
diff --git a/home-manager/modules/programs/firefox.nix b/home-manager/modules/programs/firefox.nix
index 17c64752d66..d5003f59edc 100644
--- a/home-manager/modules/programs/firefox.nix
+++ b/home-manager/modules/programs/firefox.nix
@@ -23,8 +23,15 @@ let
then "${firefoxConfigPath}/Profiles"
else firefoxConfigPath;
+ # The extensions path shared by all profiles; will not be supported
+ # by future Firefox versions.
extensionPath = "extensions/{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
+ extensionsEnvPkg = pkgs.buildEnv {
+ name = "hm-firefox-extensions";
+ paths = cfg.extensions;
+ };
+
profiles =
flip mapAttrs' cfg.profiles (_: profile:
nameValuePair "Profile${toString profile.id}" {
@@ -59,6 +66,13 @@ in
{
meta.maintainers = [ maintainers.rycee ];
+ imports = [
+ (mkRemovedOptionModule ["programs" "firefox" "enableGoogleTalk"]
+ "Support for this option has been removed.")
+ (mkRemovedOptionModule ["programs" "firefox" "enableIcedTea"]
+ "Support for this option has been removed.")
+ ];
+
options = {
programs.firefox = {
enable = mkEnableOption "Firefox";
@@ -87,9 +101,29 @@ in
]
'';
description = ''
- List of Firefox add-on packages to install. Note, it is
- necessary to manually enable these extensions inside Firefox
- after the first installation.
+ List of Firefox add-on packages to install. Some
+ pre-packaged add-ons are accessible from NUR,
+ <link xlink:href="https://github.com/nix-community/NUR"/>.
+ Once you have NUR installed run
+
+ <screen language="console">
+ <prompt>$</prompt> <userinput>nix-env -f '&lt;nixpkgs&gt;' -qaP -A nur.repos.rycee.firefox-addons</userinput>
+ </screen>
+
+ to list the available Firefox add-ons.
+
+ </para><para>
+
+ Note that it is necessary to manually enable these
+ extensions inside Firefox after the first installation.
+
+ </para><para>
+
+ Extensions listed here will only be available in Firefox
+ profiles managed through the
+ <link linkend="opt-programs.firefox.profiles">programs.firefox.profiles</link>
+ option. This is due to recent changes in the way Firefox
+ handles extension side-loading.
'';
};
@@ -137,7 +171,7 @@ in
userChrome = mkOption {
type = types.lines;
default = "";
- description = "Custom Firefox CSS.";
+ description = "Custom Firefox user chrome CSS.";
example = ''
/* Hide tab bar in FF Quantum */
@-moz-document url("chrome://browser/content/browser.xul") {
@@ -153,6 +187,16 @@ in
'';
};
+ userContent = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Custom Firefox user content CSS.";
+ example = ''
+ /* Hide scrollbar in FF Quantum */
+ *{scrollbar-width:none !important}
+ '';
+ };
+
path = mkOption {
type = types.str;
default = name;
@@ -176,38 +220,6 @@ in
default = false;
description = "Whether to enable the unfree Adobe Flash plugin.";
};
-
- enableGoogleTalk = mkOption {
- type = types.bool;
- default = false;
- description = ''
- Whether to enable the unfree Google Talk plugin. This option
- is <emphasis>deprecated</emphasis> and will only work if
-
- <programlisting language="nix">
- programs.firefox.package = pkgs.firefox-esr-52-unwrapped;
- </programlisting>
-
- and the <option>plugin.load_flash_only</option> Firefox
- option has been disabled.
- '';
- };
-
- enableIcedTea = mkOption {
- type = types.bool;
- default = false;
- description = ''
- Whether to enable the Java applet plugin. This option is
- <emphasis>deprecated</emphasis> and will only work if
-
- <programlisting language="nix">
- programs.firefox.package = pkgs.firefox-esr-52-unwrapped;
- </programlisting>
-
- and the <option>plugin.load_flash_only</option> Firefox
- option has been disabled.
- '';
- };
};
};
@@ -250,8 +262,6 @@ in
# The configuration expected by the Firefox wrapper.
fcfg = {
enableAdobeFlash = cfg.enableAdobeFlash;
- enableGoogleTalkPlugin = cfg.enableGoogleTalk;
- icedtea = cfg.enableIcedTea;
};
# A bit of hackery to force a config into the wrapper.
@@ -273,17 +283,10 @@ in
home.file = mkMerge (
[{
- "${mozillaConfigPath}/${extensionPath}" = mkIf (cfg.extensions != []) (
- let
- extensionsEnv = pkgs.buildEnv {
- name = "hm-firefox-extensions";
- paths = cfg.extensions;
- };
- in {
- source = "${extensionsEnv}/share/mozilla/${extensionPath}";
- recursive = true;
- }
- );
+ "${mozillaConfigPath}/${extensionPath}" = mkIf (cfg.extensions != []) {
+ source = "${extensionsEnvPkg}/share/mozilla/${extensionPath}";
+ recursive = true;
+ };
"${firefoxConfigPath}/profiles.ini" = mkIf (cfg.profiles != {}) {
text = profilesIni;
@@ -295,10 +298,21 @@ in
text = profile.userChrome;
};
+ "${profilesPath}/${profile.path}/chrome/userContent.css" =
+ mkIf (profile.userContent != "") {
+ text = profile.userContent;
+ };
+
"${profilesPath}/${profile.path}/user.js" =
mkIf (profile.settings != {} || profile.extraConfig != "") {
text = mkUserJs profile.settings profile.extraConfig;
};
+
+ "${profilesPath}/${profile.path}/extensions" = mkIf (cfg.extensions != []) {
+ source = "${extensionsEnvPkg}/share/mozilla/${extensionPath}";
+ recursive = true;
+ force = true;
+ };
})
);
};
diff --git a/home-manager/modules/programs/fish.nix b/home-manager/modules/programs/fish.nix
index 87a17b85507..730afa79262 100644
--- a/home-manager/modules/programs/fish.nix
+++ b/home-manager/modules/programs/fish.nix
@@ -6,119 +6,310 @@ let
cfg = config.programs.fish;
- abbrsStr = concatStringsSep "\n" (
- mapAttrsToList (k: v: "abbr --add --global ${k} '${v}'") cfg.shellAbbrs
- );
+ pluginModule = types.submodule ({ config, ... }: {
+ options = {
+ src = mkOption {
+ type = types.path;
+ description = ''
+ Path to the plugin folder.
+ </para><para>
+ Relevant pieces will be added to the fish function path and
+ the completion path. The <filename>init.fish</filename> and
+ <filename>key_binding.fish</filename> files are sourced if
+ they exist.
+ '';
+ };
- aliasesStr = concatStringsSep "\n" (
- mapAttrsToList (k: v: "alias ${k}='${v}'") cfg.shellAliases
- );
+ name = mkOption {
+ type = types.str;
+ description = ''
+ The name of the plugin.
+ '';
+ };
+ };
+ });
-in
+ functionModule = types.submodule {
+ options = {
+ body = mkOption {
+ type = types.lines;
+ description = ''
+ The function body.
+ '';
+ };
+
+ argumentNames = mkOption {
+ type = with types; nullOr (either str (listOf str));
+ default = null;
+ description = ''
+ Assigns the value of successive command line arguments to the names
+ given.
+ '';
+ };
-{
+ description = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ A description of what the function does, suitable as a completion
+ description.
+ '';
+ };
+
+ wraps = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Causes the function to inherit completions from the given wrapped
+ command.
+ '';
+ };
+
+ onEvent = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Tells fish to run this function when the specified named event is
+ emitted. Fish internally generates named events e.g. when showing the
+ prompt.
+ '';
+ };
+
+ onVariable = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Tells fish to run this function when the specified variable changes
+ value.
+ '';
+ };
+
+ onJobExit = mkOption {
+ type = with types; nullOr (either str int);
+ default = null;
+ description = ''
+ Tells fish to run this function when the job with the specified group
+ ID exits. Instead of a PID, the stringer <literal>caller</literal> can
+ be specified. This is only legal when in a command substitution, and
+ will result in the handler being triggered by the exit of the job
+ which created this command substitution.
+ '';
+ };
+
+ onProcessExit = mkOption {
+ type = with types; nullOr (either str int);
+ default = null;
+ example = "$fish_pid";
+ description = ''
+ Tells fish to run this function when the fish child process with the
+ specified process ID exits. Instead of a PID, for backwards
+ compatibility, <literal>%self</literal> can be specified as an alias
+ for <literal>$fish_pid</literal>, and the function will be run when
+ the current fish instance exits.
+ '';
+ };
+
+ onSignal = mkOption {
+ type = with types; nullOr (either str int);
+ default = null;
+ example = [ "SIGHUP" "HUP" 1 ];
+ description = ''
+ Tells fish to run this function when the specified signal is
+ delievered. The signal can be a signal number or signal name.
+ '';
+ };
+
+ noScopeShadowing = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Allows the function to access the variables of calling functions.
+ '';
+ };
+
+ inheritVariable = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Snapshots the value of the specified variable and defines a local
+ variable with that same name and value when the function is defined.
+ '';
+ };
+ };
+ };
+
+ abbrsStr = concatStringsSep "\n"
+ (mapAttrsToList (k: v: "abbr --add --global -- ${k} ${escapeShellArg v}")
+ cfg.shellAbbrs);
+
+ aliasesStr = concatStringsSep "\n"
+ (mapAttrsToList (k: v: "alias ${k} ${escapeShellArg v}") cfg.shellAliases);
+
+in {
options = {
programs.fish = {
- enable = mkEnableOption "fish friendly interactive shell";
+ enable = mkEnableOption "fish, the friendly interactive shell";
package = mkOption {
+ type = types.package;
default = pkgs.fish;
defaultText = literalExample "pkgs.fish";
description = ''
The fish package to install. May be used to change the version.
'';
- type = types.package;
};
shellAliases = mkOption {
- default = {};
+ type = with types; attrsOf str;
+ default = { };
+ example = literalExample ''
+ {
+ ll = "ls -l";
+ ".." = "cd ..";
+ }
+ '';
description = ''
- Set of aliases for fish shell. See
- <option>environment.shellAliases</option> for an option
- format description.
+ An attribute set that maps aliases (the top level attribute names
+ in this option) to command strings or directly to build outputs.
'';
- type = types.attrs;
};
shellAbbrs = mkOption {
- default = {};
+ type = with types; attrsOf str;
+ default = { };
+ example = {
+ l = "less";
+ gco = "git checkout";
+ };
description = ''
- Set of abbreviations for fish shell.
+ An attribute set that maps aliases (the top level attribute names
+ in this option) to abbreviations. Abbreviations are expanded with
+ the longer phrase after they are entered.
'';
- type = types.attrs;
};
shellInit = mkOption {
+ type = types.lines;
default = "";
description = ''
- Shell script code called during fish shell initialisation.
+ Shell script code called during fish shell
+ initialisation.
'';
- type = types.lines;
};
loginShellInit = mkOption {
+ type = types.lines;
default = "";
description = ''
- Shell script code called during fish login shell initialisation.
+ Shell script code called during fish login shell
+ initialisation.
'';
- type = types.lines;
};
interactiveShellInit = mkOption {
+ type = types.lines;
default = "";
description = ''
- Shell script code called during interactive fish shell initialisation.
+ Shell script code called during interactive fish shell
+ initialisation.
'';
- type = types.lines;
};
promptInit = mkOption {
+ type = types.lines;
default = "";
description = ''
Shell script code used to initialise fish prompt.
'';
- type = types.lines;
};
};
+
+ programs.fish.plugins = mkOption {
+ type = types.listOf pluginModule;
+ default = [ ];
+ example = literalExample ''
+ [
+ {
+ name = "z";
+ src = pkgs.fetchFromGitHub {
+ owner = "jethrokuan";
+ repo = "z";
+ rev = "ddeb28a7b6a1f0ec6dae40c636e5ca4908ad160a";
+ sha256 = "0c5i7sdrsp0q3vbziqzdyqn4fmp235ax4mn4zslrswvn8g3fvdyh";
+ };
+ }
+
+ # oh-my-fish plugins are stored in their own repositories, which
+ # makes them simple to import into home-manager.
+ {
+ name = "fasd";
+ src = pkgs.fetchFromGitHub {
+ owner = "oh-my-fish";
+ repo = "plugin-fasd";
+ rev = "38a5b6b6011106092009549e52249c6d6f501fba";
+ sha256 = "06v37hqy5yrv5a6ssd1p3cjd9y3hnp19d3ab7dag56fs1qmgyhbs";
+ };
+ }
+ ]
+ '';
+ description = ''
+ The plugins to source in
+ <filename>conf.d/99plugins.fish</filename>.
+ '';
+ };
+
+ programs.fish.functions = mkOption {
+ type = with types; attrsOf (either lines functionModule);
+ default = { };
+ example = literalExample ''
+ {
+ __fish_command_not_found_handler = {
+ body = "__fish_default_command_not_found_handler $argv[1]";
+ onEvent = "fish_command_not_found";
+ };
+
+ gitignore = "curl -sL https://www.gitignore.io/api/$argv";
+ }
+ '';
+ description = ''
+ Basic functions to add to fish. For more information see
+ <link xlink:href="https://fishshell.com/docs/current/cmds/function.html"/>.
+ '';
+ };
+
};
- config = mkIf cfg.enable {
- home.packages = [ cfg.package ];
+ config = mkIf cfg.enable (mkMerge [
+ {
+ home.packages = [ cfg.package ];
- xdg.dataFile."fish/home-manager_generated_completions".source =
- let
+ xdg.dataFile."fish/home-manager_generated_completions".source = let
# paths later in the list will overwrite those already linked
- destructiveSymlinkJoin =
- args_@{ name
- , paths
- , preferLocalBuild ? true
- , allowSubstitutes ? false
- , postBuild ? ""
- , ...
- }:
+ destructiveSymlinkJoin = args_@{ name, paths, preferLocalBuild ? true
+ , allowSubstitutes ? false, postBuild ? "", ... }:
let
- args = removeAttrs args_ [ "name" "postBuild" ]
- // { inherit preferLocalBuild allowSubstitutes; }; # pass the defaults
- in pkgs.runCommand name args
- ''
- mkdir -p $out
- for i in $paths; do
- if [ -z "$(find $i -prune -empty)" ]; then
- cp -srf $i/* $out
- fi
- done
- ${postBuild}
- '';
- generateCompletions = package: pkgs.runCommand
- "${package.name}-fish-completions"
- {
+ args = removeAttrs args_ [ "name" "postBuild" ] // {
+ # pass the defaults
+ inherit preferLocalBuild allowSubstitutes;
+ };
+ in pkgs.runCommand name args ''
+ mkdir -p $out
+ for i in $paths; do
+ if [ -z "$(find $i -prune -empty)" ]; then
+ cp -srf $i/* $out
+ fi
+ done
+ ${postBuild}
+ '';
+
+ generateCompletions = package:
+ pkgs.runCommand "${package.name}-fish-completions" {
src = package;
nativeBuildInputs = [ pkgs.python2 ];
buildInputs = [ cfg.package ];
preferLocalBuild = true;
allowSubstitutes = false;
- }
- ''
+ } ''
mkdir -p $out
if [ -d $src/share/man ]; then
find $src/share/man -type f \
@@ -126,66 +317,144 @@ in
> /dev/null
fi
'';
- in
- destructiveSymlinkJoin {
- name = "${config.home.username}-fish-completions";
- paths =
- let
- cmp = (a: b: (a.meta.priority or 0) > (b.meta.priority or 0));
- in
- map generateCompletions (sort cmp config.home.packages);
+ in destructiveSymlinkJoin {
+ name = "${config.home.username}-fish-completions";
+ paths =
+ let cmp = (a: b: (a.meta.priority or 0) > (b.meta.priority or 0));
+ in map generateCompletions (sort cmp config.home.packages);
+ };
+
+ programs.fish.interactiveShellInit = ''
+ # add completions generated by Home Manager to $fish_complete_path
+ begin
+ set -l joined (string join " " $fish_complete_path)
+ set -l prev_joined (string replace --regex "[^\s]*generated_completions.*" "" $joined)
+ set -l post_joined (string replace $prev_joined "" $joined)
+ set -l prev (string split " " (string trim $prev_joined))
+ set -l post (string split " " (string trim $post_joined))
+ set fish_complete_path $prev "${config.xdg.dataHome}/fish/home-manager_generated_completions" $post
+ end
+ '';
+
+ xdg.configFile."fish/config.fish".text = ''
+ # ~/.config/fish/config.fish: DO NOT EDIT -- this file has been generated
+ # automatically by home-manager.
+
+ # if we haven't sourced the general config, do it
+ if not set -q __fish_general_config_sourced
+
+ set -p fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions
+ fenv source ${config.home.profileDirectory}/etc/profile.d/hm-session-vars.sh > /dev/null
+ set -e fish_function_path[1]
+
+ ${cfg.shellInit}
+ # and leave a note so we don't source this config section again from
+ # this very shell (children will source the general config anew)
+ set -g __fish_general_config_sourced 1
+
+ end
+
+ # if we haven't sourced the login config, do it
+ status --is-login; and not set -q __fish_login_config_sourced
+ and begin
+
+ # Login shell initialisation
+ ${cfg.loginShellInit}
+
+ # and leave a note so we don't source this config section again from
+ # this very shell (children will source the general config anew)
+ set -g __fish_login_config_sourced 1
+
+ end
+
+ # if we haven't sourced the interactive config, do it
+ status --is-interactive; and not set -q __fish_interactive_config_sourced
+ and begin
+
+ # Abbreviations
+ ${abbrsStr}
+
+ # Aliases
+ ${aliasesStr}
+
+ # Prompt initialisation
+ ${cfg.promptInit}
+
+ # Interactive shell intialisation
+ ${cfg.interactiveShellInit}
+
+ # and leave a note so we don't source this config section again from
+ # this very shell (children will source the general config anew,
+ # allowing configuration changes in, e.g, aliases, to propagate)
+ set -g __fish_interactive_config_sourced 1
+
+ end
+ '';
+ }
+ {
+ xdg.configFile = mapAttrs' (name: def: {
+ name = "fish/functions/${name}.fish";
+ value = {
+ text = let
+ modifierStr = n: v: optional (v != null) ''--${n}="${toString v}"'';
+ modifierStrs = n: v: optional (v != null) "--${n}=${toString v}";
+ modifierBool = n: v: optional (v != null && v) "--${n}";
+
+ mods = with def;
+ modifierStr "description" description ++ modifierStr "wraps" wraps
+ ++ modifierStr "on-event" onEvent
+ ++ modifierStr "on-variable" onVariable
+ ++ modifierStr "on-job-exit" onJobExit
+ ++ modifierStr "on-process-exit" onProcessExit
+ ++ modifierStr "on-signal" onSignal
+ ++ modifierBool "no-scope-shadowing" noScopeShadowing
+ ++ modifierStr "inherit-variable" inheritVariable
+ ++ modifierStrs "argument-names" argumentNames;
+
+ modifiers = if isAttrs def then " ${toString mods}" else "";
+ body = if isAttrs def then def.body else def;
+ in ''
+ function ${name}${modifiers}
+ ${body}
+ end
+ '';
};
+ }) cfg.functions;
+ }
- programs.fish.interactiveShellInit = ''
- # add completions generated by Home Manager to $fish_complete_path
- begin
- set -l joined (string join " " $fish_complete_path)
- set -l prev_joined (string replace --regex "[^\s]*generated_completions.*" "" $joined)
- set -l post_joined (string replace $prev_joined "" $joined)
- set -l prev (string split " " (string trim $prev_joined))
- set -l post (string split " " (string trim $post_joined))
- set fish_complete_path $prev "${config.xdg.dataHome}/fish/home-manager_generated_completions" $post
- end
- '';
-
- xdg.configFile."fish/config.fish".text = ''
- # ~/.config/fish/config.fish: DO NOT EDIT -- this file has been generated automatically.
- # if we haven't sourced the general config, do it
- if not set -q __fish_general_config_sourced
- set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $fish_function_path
- fenv source ${config.home.profileDirectory}/etc/profile.d/hm-session-vars.sh > /dev/null
- set -e fish_function_path[1]
-
- ${cfg.shellInit}
- # and leave a note so we don't source this config section again from
- # this very shell (children will source the general config anew)
- set -g __fish_general_config_sourced 1
- end
- # if we haven't sourced the login config, do it
- status --is-login; and not set -q __fish_login_config_sourced
- and begin
-
- ${cfg.loginShellInit}
- # and leave a note so we don't source this config section again from
- # this very shell (children will source the general config anew)
- set -g __fish_login_config_sourced 1
- end
- # if we haven't sourced the interactive config, do it
- status --is-interactive; and not set -q __fish_interactive_config_sourced
- and begin
- # Abbrs
- ${abbrsStr}
-
- # Aliases
- ${aliasesStr}
-
- ${cfg.promptInit}
- ${cfg.interactiveShellInit}
- # and leave a note so we don't source this config section again from
- # this very shell (children will source the general config anew,
- # allowing configuration changes in, e.g, aliases, to propagate)
- set -g __fish_interactive_config_sourced 1
- end
- '';
- };
+ # Each plugin gets a corresponding conf.d/plugin-NAME.fish file to load
+ # in the paths and any initialization scripts.
+ (mkIf (length cfg.plugins > 0) {
+ xdg.configFile = mkMerge ((map (plugin: {
+ "fish/conf.d/plugin-${plugin.name}.fish".text = ''
+ # Plugin ${plugin.name}
+ set -l plugin_dir ${plugin.src}
+
+ # Set paths to import plugin components
+ if test -d $plugin_dir/functions
+ set fish_function_path $fish_function_path[1] $plugin_dir/functions $fish_function_path[2..-1]
+ end
+
+ if test -d $plugin_dir/completions
+ set fish_complete_path $fish_complete_path[1] $plugin_dir/completions $fish_complete_path[2..-1]
+ end
+
+ # Source initialization code if it exists.
+ if test -d $plugin_dir/conf.d
+ for f in $plugin_dir/conf.d/*.fish
+ source $f
+ end
+ end
+
+ if test -f $plugin_dir/key_bindings.fish
+ source $plugin_dir/key_bindings.fish
+ end
+
+ if test -f $plugin_dir/init.fish
+ source $plugin_dir/init.fish
+ end
+ '';
+ }) cfg.plugins));
+ })
+ ]);
}
diff --git a/home-manager/modules/programs/fzf.nix b/home-manager/modules/programs/fzf.nix
index 36eb3a1cdba..3aee57768ea 100644
--- a/home-manager/modules/programs/fzf.nix
+++ b/home-manager/modules/programs/fzf.nix
@@ -100,6 +100,14 @@ in {
Whether to enable Zsh integration.
'';
};
+
+ enableFishIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Fish integration.
+ '';
+ };
};
config = mkIf cfg.enable {
@@ -130,5 +138,9 @@ in {
. ${pkgs.fzf}/share/fzf/key-bindings.zsh
fi
'';
+
+ programs.fish.shellInit = mkIf cfg.enableFishIntegration ''
+ source ${pkgs.fzf}/share/fzf/key-bindings.fish && fzf_key_bindings
+ '';
};
}
diff --git a/home-manager/modules/programs/getmail.nix b/home-manager/modules/programs/getmail.nix
index 2c3919dcf2f..f83c469ff24 100644
--- a/home-manager/modules/programs/getmail.nix
+++ b/home-manager/modules/programs/getmail.nix
@@ -49,6 +49,12 @@ let
".getmail/getmail${if a.primary then "rc" else a.name}";
in {
+ options = {
+ accounts.email.accounts = mkOption {
+ type = with types; attrsOf (submodule (import ./getmail-accounts.nix));
+ };
+ };
+
config = mkIf getmailEnabled {
home.file = foldl' (a: b: a // b) { }
(map (a: { "${renderConfigFilepath a}".text = renderAccountConfig a; })
diff --git a/home-manager/modules/programs/git.nix b/home-manager/modules/programs/git.nix
index a56aa10d50e..312269de316 100644
--- a/home-manager/modules/programs/git.nix
+++ b/home-manager/modules/programs/git.nix
@@ -19,10 +19,20 @@ let
else
''${section} "${subsection}"'';
+ mkValueString = v:
+ let
+ escapedV = ''
+ "${
+ replaceStrings [ "\n" " " ''"'' "\\" ] [ "\\n" "\\t" ''\"'' "\\\\" ] v
+ }"'';
+ in generators.mkValueStringDefault { } (if isString v then escapedV else v);
+
# generation for multiple ini values
mkKeyValue = k: v:
- let mkKeyValue = generators.mkKeyValueDefault { } "=" k;
- in concatStringsSep "\n" (map mkKeyValue (toList v));
+ let
+ mkKeyValue =
+ generators.mkKeyValueDefault { inherit mkValueString; } " = " k;
+ in concatStringsSep "\n" (map (kv: " " + mkKeyValue kv) (toList v));
# converts { a.b.c = 5; } to { "a.b".c = 5; } for toINI
gitFlattenAttrs = let
@@ -205,6 +215,36 @@ in {
'';
};
};
+
+ delta = {
+ enable = mkEnableOption "" // {
+ description = ''
+ Whether to enable the <command>delta</command> syntax highlighter.
+ See <link xlink:href="https://github.com/dandavison/delta" />.
+ '';
+ };
+
+ options = mkOption {
+ type = with types;
+ let
+ primitiveType = either str (either bool int);
+ sectionType = attrsOf primitiveType;
+ in attrsOf (either primitiveType sectionType);
+ default = { };
+ example = {
+ features = "decorations";
+ whitespace-error-style = "22 reverse";
+ decorations = {
+ commit-decoration-style = "bold yellow box ul";
+ file-style = "bold yellow ul";
+ file-decoration-style = "none";
+ };
+ };
+ description = ''
+ Options to configure delta.
+ '';
+ };
+ };
};
};
@@ -237,7 +277,14 @@ in {
genIdentity = name: account:
with account;
nameValuePair "sendemail.${name}" ({
- smtpEncryption = if smtp.tls.enable then "tls" else "";
+ smtpEncryption = if smtp.tls.enable then
+ (if smtp.tls.useStartTls
+ || versionOlder config.home.stateVersion "20.09" then
+ "tls"
+ else
+ "ssl")
+ else
+ "";
smtpServer = smtp.host;
smtpUser = userName;
from = address;
@@ -299,5 +346,15 @@ in {
([ "git-lfs" "smudge" ] ++ skipArg ++ [ "--" "%f" ]);
};
})
+
+ (mkIf cfg.delta.enable {
+ programs.git.iniContent =
+ let deltaCommand = "${pkgs.gitAndTools.delta}/bin/delta";
+ in {
+ core.pager = deltaCommand;
+ interactive.diffFilter = "${deltaCommand} --color-only";
+ delta = cfg.delta.options;
+ };
+ })
]);
}
diff --git a/home-manager/modules/programs/gnome-terminal.nix b/home-manager/modules/programs/gnome-terminal.nix
index 570a1fc7df0..f1b15862130 100644
--- a/home-manager/modules/programs/gnome-terminal.nix
+++ b/home-manager/modules/programs/gnome-terminal.nix
@@ -6,11 +6,6 @@ let
cfg = config.programs.gnome-terminal;
- vteInitStr = ''
- # gnome-terminal: Show current directory in the terminal window title.
- . ${pkgs.gnome3.vte}/etc/profile.d/vte.sh
- '';
-
backForeSubModule = types.submodule ({ ... }: {
options = {
foreground = mkOption {
@@ -81,6 +76,12 @@ let
description = "The terminal colors, null to use system default.";
};
+ cursorBlinkMode = mkOption {
+ default = "system";
+ type = types.enum [ "system" "on" "off" ];
+ description = "The cursor blink mode.";
+ };
+
cursorShape = mkOption {
default = "block";
type = types.enum [ "block" "ibeam" "underline" ];
@@ -121,6 +122,20 @@ let
The number of scrollback lines to keep, null for infinite.
'';
};
+
+ customCommand = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The command to use to start the shell, or null for default shell.
+ '';
+ };
+
+ loginShell = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Run command as a login shell.";
+ };
};
});
@@ -130,7 +145,14 @@ let
scrollbar-policy = if pcfg.showScrollbar then "always" else "never";
scrollback-lines = pcfg.scrollbackLines;
cursor-shape = pcfg.cursorShape;
- } // (if (pcfg.font == null) then {
+ cursor-blink-mode = pcfg.cursorBlinkMode;
+ login-shell = pcfg.loginShell;
+ } // (if (pcfg.customCommand != null) then {
+ use-custom-command = true;
+ custom-command = pcfg.customCommand;
+ } else {
+ use-custom-command = false;
+ }) // (if (pcfg.font == null) then {
use-system-font = true;
} else {
use-system-font = false;
@@ -179,7 +201,7 @@ in {
themeVariant = mkOption {
default = "default";
- type = types.enum [ "default" "light" "dark" ];
+ type = types.enum [ "default" "light" "dark" "system" ];
description = "The theme variation to request";
};
@@ -192,7 +214,7 @@ in {
};
config = mkIf cfg.enable {
- home.packages = [ pkgs.gnome3.gnome_terminal ];
+ home.packages = [ pkgs.gnome3.gnome-terminal ];
dconf.settings = let dconfPath = "org/gnome/terminal/legacy";
in {
@@ -210,7 +232,7 @@ in {
(n: v: nameValuePair ("${dconfPath}/profiles:/:${n}") (buildProfileSet v))
cfg.profile;
- programs.bash.initExtra = mkBefore vteInitStr;
- programs.zsh.initExtra = vteInitStr;
+ programs.bash.enableVteIntegration = true;
+ programs.zsh.enableVteIntegration = true;
};
}
diff --git a/home-manager/modules/programs/go.nix b/home-manager/modules/programs/go.nix
index 983769d26af..4b85ec854ad 100644
--- a/home-manager/modules/programs/go.nix
+++ b/home-manager/modules/programs/go.nix
@@ -62,6 +62,18 @@ in {
example = ".local/bin.go";
description = "GOBIN relative to HOME";
};
+
+ goPrivate = mkOption {
+ type = with types; listOf str;
+ default = [ ];
+ example = [ "*.corp.example.com" "rsc.io/private" ];
+ description = ''
+ The <envar>GOPRIVATE</envar> environment variable controls
+ which modules the go command considers to be private (not
+ available publicly) and should therefore not use the proxy
+ or checksum database.
+ '';
+ };
};
};
@@ -85,5 +97,9 @@ in {
home.sessionVariables.GOBIN =
builtins.toPath "${config.home.homeDirectory}/${cfg.goBin}";
})
+
+ (mkIf (cfg.goPrivate != [ ]) {
+ home.sessionVariables.GOPRIVATE = concatStringsSep "," cfg.goPrivate;
+ })
]);
}
diff --git a/home-manager/modules/programs/htop.nix b/home-manager/modules/programs/htop.nix
index 84966040534..1fb397cdc38 100644
--- a/home-manager/modules/programs/htop.nix
+++ b/home-manager/modules/programs/htop.nix
@@ -61,6 +61,9 @@ let
CGROUP = 112;
OOM = 113;
IO_PRIORITY = 114;
+ M_PSS = 118;
+ M_SWAP = 119;
+ M_PSSWP = 120;
};
# Mapping from names to defaults
@@ -76,16 +79,32 @@ let
Hostname = 2;
AllCPUs = 1;
AllCPUs2 = 1;
+ AllCPUs4 = 1;
LeftCPUs = 1;
RightCPUs = 1;
+ Right = 1;
+ CPUs = 1;
LeftCPUs2 = 1;
RightCPUs2 = 1;
+ LeftCPUs4 = 1;
+ RightCPUs4 = 1;
Blank = 2;
+ PressureStallCPUSome = 2;
+ PressureStallIOSome = 2;
+ PressureStallIOFull = 2;
+ PressureStallMemorySome = 2;
+ PressureStallMemoryFull = 2;
+ ZFSARC = 2;
+ ZFSCARC = 2;
CPU = 1;
"CPU(1)" = 1;
"CPU(2)" = 1;
"CPU(3)" = 1;
"CPU(4)" = 1;
+ "CPU(5)" = 1;
+ "CPU(6)" = 1;
+ "CPU(7)" = 1;
+ "CPU(8)" = 1;
};
singleMeterType = let
@@ -268,6 +287,18 @@ in {
description = "Count CPUs from 0 instead of 1.";
};
+ showCpuUsage = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Show CPU usage frequency.";
+ };
+
+ showCpuFrequency = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Show CPU frequency.";
+ };
+
updateProcessNames = mkOption {
type = types.bool;
default = false;
@@ -287,6 +318,12 @@ in {
description = "Which color scheme to use.";
};
+ enableMouse = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Enable mouse support.";
+ };
+
delay = mkOption {
type = types.int;
default = 15;
@@ -328,6 +365,11 @@ in {
type = meterType;
};
+ vimMode = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Vim key bindings.";
+ };
};
config = mkIf cfg.enable {
@@ -357,14 +399,18 @@ in {
header_margin=${bool cfg.headerMargin}
detailed_cpu_time=${bool cfg.detailedCpuTime}
cpu_count_from_zero=${bool cfg.cpuCountFromZero}
+ show_cpu_usage=${bool cfg.showCpuUsage}
+ show_cpu_frequency=${bool cfg.showCpuFrequency}
update_process_names=${bool cfg.updateProcessNames}
account_guest_in_cpu_meter=${bool cfg.accountGuestInCpuMeter}
color_scheme=${toString cfg.colorScheme}
+ enable_mouse=${bool cfg.enableMouse}
delay=${toString cfg.delay}
left_meters=${list leftMeters}
left_meter_modes=${list leftModes}
right_meters=${list rightMeters}
right_meter_modes=${list rightModes}
+ vim_mode=${bool cfg.vimMode}
'';
};
}
diff --git a/home-manager/modules/programs/i3status.nix b/home-manager/modules/programs/i3status.nix
new file mode 100644
index 00000000000..c1e12fe71d7
--- /dev/null
+++ b/home-manager/modules/programs/i3status.nix
@@ -0,0 +1,208 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.i3status;
+
+ enabledModules = filterAttrs (n: v: v.enable) cfg.modules;
+
+ formatOrder = n: ''order += "${n}"'';
+
+ formatModule = n: v:
+ let
+ formatLine = n: v:
+ let
+ formatValue = v:
+ if isBool v then
+ (if v then "true" else "false")
+ else if isString v then
+ ''"${v}"''
+ else
+ toString v;
+ in "${n} = ${formatValue v}";
+ in ''
+ ${n} {
+ ${concatStringsSep "\n " (mapAttrsToList formatLine v)}
+ }
+ '';
+
+ settingsType = with types; attrsOf (oneOf [ bool int str ]);
+
+ sortAttrNamesByPosition = comparator: set:
+ let pos = n: set."${n}".position;
+ in sort (a: b: comparator (pos a) (pos b)) (attrNames set);
+in {
+ meta.maintainers = [ hm.maintainers.justinlovinger ];
+
+ options.programs.i3status = {
+ enable = mkEnableOption "i3status";
+
+ enableDefault = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether or not to enable
+ the default configuration.
+ '';
+ };
+
+ general = mkOption {
+ type = settingsType;
+ default = { };
+ description = ''
+ Configuration to add to i3status <filename>config</filename>
+ <code>general</code> section.
+ See
+ <citerefentry>
+ <refentrytitle>i3status</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ for options.
+ '';
+ example = literalExample ''
+ {
+ colors = true;
+ color_good = "#e0e0e0";
+ color_degraded = "#d7ae00";
+ color_bad = "#f69d6a";
+ interval = 1;
+ }
+ '';
+ };
+
+ modules = mkOption {
+ type = types.attrsOf (types.submodule {
+ options = {
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether or not to enable this module.
+ '';
+ };
+ position = mkOption {
+ type = with types; either int float;
+ description = ''
+ Position of this module in i3status <code>order</code>.
+ '';
+ };
+ settings = mkOption {
+ type = settingsType;
+ default = { };
+ description = ''
+ Configuration to add to this i3status module.
+ See
+ <citerefentry>
+ <refentrytitle>i3status</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ for options.
+ '';
+ example = literalExample ''
+ {
+ format = "β™ͺ %volume";
+ format_muted = "β™ͺ muted (%volume)";
+ device = "pulse:1";
+ }
+ '';
+ };
+ };
+ });
+ default = { };
+ description = ''
+ Modules to add to i3status <filename>config</filename> file.
+ See
+ <citerefentry>
+ <refentrytitle>i3status</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ for options.
+ '';
+ example = literalExample ''
+ {
+ "volume master" = {
+ position = 1;
+ settings = {
+ format = "β™ͺ %volume";
+ format_muted = "β™ͺ muted (%volume)";
+ device = "pulse:1";
+ };
+ };
+ "disk /" = {
+ position = 2;
+ settings = {
+ format = "/ %avail";
+ };
+ };
+ }
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ programs.i3status = mkIf cfg.enableDefault {
+ general = {
+ colors = mkDefault true;
+ interval = mkDefault 5;
+ };
+
+ modules = {
+ ipv6 = { position = mkDefault 1; };
+
+ "wireless _first_" = {
+ position = mkDefault 2;
+ settings = {
+ format_up = mkDefault "W: (%quality at %essid) %ip";
+ format_down = mkDefault "W: down";
+ };
+ };
+
+ "ethernet _first_" = {
+ position = mkDefault 3;
+ settings = {
+ format_up = mkDefault "E: %ip (%speed)";
+ format_down = mkDefault "E: down";
+ };
+ };
+
+ "battery all" = {
+ position = mkDefault 4;
+ settings = { format = mkDefault "%status %percentage %remaining"; };
+ };
+
+ "disk /" = {
+ position = mkDefault 5;
+ settings = { format = mkDefault "%avail"; };
+ };
+
+ load = {
+ position = mkDefault 6;
+ settings = { format = mkDefault "%1min"; };
+ };
+
+ memory = {
+ position = mkDefault 7;
+ settings = {
+ format = mkDefault "%used | %available";
+ threshold_degraded = mkDefault "1G";
+ format_degraded = mkDefault "MEMORY < %available";
+ };
+ };
+
+ "tztime local" = {
+ position = mkDefault 8;
+ settings = { format = mkDefault "%Y-%m-%d %H:%M:%S"; };
+ };
+ };
+ };
+
+ home.packages = [ pkgs.i3status ];
+
+ xdg.configFile."i3status/config".text = concatStringsSep "\n" ([ ]
+ ++ optional (cfg.general != { }) (formatModule "general" cfg.general)
+ ++ map formatOrder (sortAttrNamesByPosition lessThan enabledModules)
+ ++ mapAttrsToList formatModule
+ (mapAttrs (n: v: v.settings) enabledModules));
+ };
+}
diff --git a/home-manager/modules/programs/info.nix b/home-manager/modules/programs/info.nix
index 9e4a5d4aaff..a7d2692b515 100644
--- a/home-manager/modules/programs/info.nix
+++ b/home-manager/modules/programs/info.nix
@@ -1,22 +1,21 @@
-# info.nix -- install texinfo, set INFOPATH, create `dir` file
+# info.nix -- install texinfo and create `dir` file
# This is a helper for the GNU info documentation system. By default,
# the `info` command (and the Info subsystem within Emacs) gives easy
# access to the info files stored system-wide, but not info files in
# your ~/.nix-profile.
-# We set $INFOPATH to include `/run/current-system/sw/share/info` and
-# `~/.nix-profile/share/info` but it's not enough. Although info can
-# then find files when you explicitly ask for them, it doesn't show
-# them to you in the table of contents on startup. To do that requires
-# a `dir` file. NixOS keeps the system-wide `dir` file up to date, but
-# ignores home-installed packages.
+# Specifically, although info can then find files when you explicitly
+# ask for them, it doesn't show them to you in the table of contents
+# on startup. To do that requires a `dir` file. NixOS keeps the
+# system-wide `dir` file up to date, but ignores files installed in
+# user profiles.
-# So this module contains an activation script that generates the
-# `dir` for your home profile. Then when you start info (and both
-# `dir` files are in your $INFOPATH), it will *merge* the contents of
-# the two files, showing you a unified table of contents for all
-# packages. This is really nice.
+# This module contains extra profile commands that generate the `dir`
+# for your home profile. Then when you start info (and both `dir`
+# files are in your $INFOPATH), it will *merge* the contents of the
+# two files, showing you a unified table of contents for all packages.
+# This is really nice.
{ config, lib, pkgs, ... }:
@@ -26,50 +25,39 @@ let
cfg = config.programs.info;
- # Indexes info files found in this location
- homeInfoPath = "${config.home.profileDirectory}/share/info";
-
# Installs this package -- the interactive just means that it
# includes the curses `info` program. We also use `install-info`
# from this package in the activation script.
infoPkg = pkgs.texinfoInteractive;
in {
- options = {
- programs.info = {
- enable = mkEnableOption "GNU Info";
+ imports = [
+ (mkRemovedOptionModule [ "programs" "info" "homeInfoDirLocation" ] ''
+ The `dir` file is now generated as part of the Home Manager profile and
+ will no longer be placed in your home directory.
+ '')
+ ];
- homeInfoDirLocation = mkOption {
- default = "\${XDG_CACHE_HOME:-$HOME/.cache}/info";
- description = ''
- Directory in which to store the info <filename>dir</filename>
- file within your home.
- '';
- };
- };
- };
+ options.programs.info.enable = mkEnableOption "GNU Info";
config = mkIf cfg.enable {
- home.sessionVariables.INFOPATH =
- "${cfg.homeInfoDirLocation}\${INFOPATH:+:}\${INFOPATH}";
+ home.packages = [
+ infoPkg
- home.activation.createHomeInfoDir =
- hm.dag.entryAfter [ "installPackages" ] ''
- oPATH=$PATH
- export PATH="${lib.makeBinPath [ pkgs.gzip ]}''${PATH:+:}$PATH"
- $DRY_RUN_CMD mkdir -p "${cfg.homeInfoDirLocation}"
- $DRY_RUN_CMD rm -f "${cfg.homeInfoDirLocation}/dir"
- if [[ -d "${homeInfoPath}" ]]; then
- find -L "${homeInfoPath}" \( -name '*.info' -o -name '*.info.gz' \) \
- -exec $DRY_RUN_CMD ${infoPkg}/bin/install-info '{}' \
- "${cfg.homeInfoDirLocation}/dir" \;
- fi
- export PATH="$oPATH"
- unset oPATH
- '';
-
- home.packages = [ infoPkg ];
+ # Make sure the target directory is a real directory.
+ (pkgs.runCommandLocal "dummy-info-dir1" { } "mkdir -p $out/share/info")
+ (pkgs.runCommandLocal "dummy-info-dir2" { } "mkdir -p $out/share/info")
+ ];
home.extraOutputsToInstall = [ "info" ];
+
+ home.extraProfileCommands = let infoPath = "$out/share/info";
+ in ''
+ if [[ -w "${infoPath}" && ! -e "${infoPath}/dir" ]]; then
+ PATH="${lib.makeBinPath [ pkgs.gzip infoPkg ]}''${PATH:+:}$PATH" \
+ find -L "${infoPath}" \( -name '*.info' -o -name '*.info.gz' \) \
+ -exec install-info '{}' "${infoPath}/dir" ';'
+ fi
+ '';
};
}
diff --git a/home-manager/modules/programs/kakoune.nix b/home-manager/modules/programs/kakoune.nix
index faf2542dc70..6db311a1376 100644
--- a/home-manager/modules/programs/kakoune.nix
+++ b/home-manager/modules/programs/kakoune.nix
@@ -49,6 +49,7 @@ let
"InsertCompletionShow"
"InsertCompletionHide"
"InsertCompletionSelect"
+ "ModuleLoaded"
];
example = "SetOption";
description = ''
@@ -96,16 +97,7 @@ let
keyMapping = types.submodule {
options = {
mode = mkOption {
- type = types.enum [
- "insert"
- "normal"
- "prompt"
- "menu"
- "user"
- "goto"
- "view"
- "object"
- ];
+ type = types.str;
example = "user";
description = ''
The mode in which the mapping takes effect.
@@ -497,6 +489,10 @@ let
};
};
+ kakouneWithPlugins = pkgs.wrapKakoune pkgs.kakoune-unwrapped {
+ configure = { plugins = cfg.plugins; };
+ };
+
configFile = let
wrapOptions = with cfg.config.wrapLines;
concatStrings [
@@ -513,6 +509,25 @@ let
"${optionalString (separator != null) " -separator ${separator}"}"
];
+ showWhitespaceOptions = with cfg.config.showWhitespace;
+ let
+ quoteSep = sep:
+ if sep == "'" then
+ ''"'"''
+ else if lib.strings.stringLength sep == 1 then
+ "'${sep}'"
+ else
+ sep; # backwards compat, in case sep == "' '", etc.
+
+ in concatStrings [
+ (optionalString (tab != null) " -tab ${quoteSep tab}")
+ (optionalString (tabStop != null) " -tabpad ${quoteSep tabStop}")
+ (optionalString (space != null) " -spc ${quoteSep space}")
+ (optionalString (nonBreakingSpace != null)
+ " -nbsp ${quoteSep nonBreakingSpace}")
+ (optionalString (lineFeed != null) " -lf ${quoteSep lineFeed}")
+ ];
+
uiOptions = with cfg.config.ui;
concatStringsSep " " [
"ncurses_set_title=${if setTitle then "true" else "false"}"
@@ -533,6 +548,21 @@ let
}"
];
+ userModeString = mode:
+ optionalString (!builtins.elem mode [
+ "insert"
+ "normal"
+ "prompt"
+ "menu"
+ "user"
+ "goto"
+ "view"
+ "object"
+ ]) "try %{declare-user-mode ${mode}}";
+
+ userModeStrings = map userModeString
+ (lists.unique (map (km: km.mode) cfg.config.keyMappings));
+
keyMappingString = km:
concatStringsSep " " [
"map global"
@@ -566,12 +596,14 @@ let
++ optional (autoComplete != null)
"set-option global autocomplete ${concatStringsSep "|" autoComplete}"
++ optional (autoReload != null)
- "set-option global/ autoreload ${autoReload}"
+ "set-option global autoreload ${autoReload}"
++ optional (wrapLines != null && wrapLines.enable)
"add-highlighter global/ wrap${wrapOptions}"
++ optional (numberLines != null && numberLines.enable)
"add-highlighter global/ number-lines${numberLinesOptions}"
++ optional showMatching "add-highlighter global/ show-matching"
+ ++ optional (showWhitespace != null && showWhitespace.enable)
+ "add-highlighter global/ show-whitespaces${showWhitespaceOptions}"
++ optional (scrollOff != null)
"set-option global scrolloff ${toString scrollOff.lines},${
toString scrollOff.columns
@@ -580,7 +612,8 @@ let
++ [ "# UI options" ]
++ optional (ui != null) "set-option global ui_options ${uiOptions}"
- ++ [ "# Key mappings" ] ++ map keyMappingString keyMappings
+ ++ [ "# User modes" ] ++ userModeStrings ++ [ "# Key mappings" ]
+ ++ map keyMappingString keyMappings
++ [ "# Hooks" ] ++ map hookString hooks);
in pkgs.writeText "kakrc"
@@ -605,11 +638,22 @@ in {
<filename>~/.config/kak/kakrc</filename>.
'';
};
+
+ plugins = mkOption {
+ type = with types; listOf package;
+ default = [ ];
+ example = literalExample "[ pkgs.kakounePlugins.kak-fzf ]";
+ description = ''
+ List of kakoune plugins to install. To get a list of
+ supported plugins run:
+ <command>nix-env -f '&lt;nixpkgs&gt;' -qaP -A kakounePlugins</command>.
+ '';
+ };
};
};
config = mkIf cfg.enable {
- home.packages = [ pkgs.kakoune ];
+ home.packages = [ kakouneWithPlugins ];
xdg.configFile."kak/kakrc".source = configFile;
};
}
diff --git a/home-manager/modules/programs/kitty.nix b/home-manager/modules/programs/kitty.nix
new file mode 100644
index 00000000000..313a0bfadd7
--- /dev/null
+++ b/home-manager/modules/programs/kitty.nix
@@ -0,0 +1,91 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.kitty;
+
+ eitherStrBoolInt = with types; either str (either bool int);
+
+ optionalPackage = opt:
+ optional (opt != null && opt.package != null) opt.package;
+
+ toKittyConfig = generators.toKeyValue {
+ mkKeyValue = key: value:
+ let
+ value' = if isBool value then
+ (if value then "yes" else "no")
+ else
+ toString value;
+ in "${key} ${value'}";
+ };
+
+ toKittyKeybindings = generators.toKeyValue {
+ mkKeyValue = key: command: "map ${key} ${command}";
+ };
+
+in {
+ options.programs.kitty = {
+ enable = mkEnableOption "Kitty terminal emulator";
+
+ settings = mkOption {
+ type = types.attrsOf eitherStrBoolInt;
+ default = { };
+ example = literalExample ''
+ {
+ scrollback_lines = 10000;
+ enable_audio_bell = false;
+ update_check_interval = 0;
+ }
+ '';
+ description = ''
+ Configuration written to
+ <filename>~/.config/kitty/kitty.conf</filename>. See
+ <link xlink:href="https://sw.kovidgoyal.net/kitty/conf.html" />
+ for the documentation.
+ '';
+ };
+
+ font = mkOption {
+ type = types.nullOr hm.types.fontType;
+ default = null;
+ description = "The font to use.";
+ };
+
+ keybindings = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ description = "Mapping of keybindings to actions.";
+ example = literalExample ''
+ {
+ "ctrl+c" = "copy_or_interrupt";
+ "ctrl+f>2" = "set_font_size 20";
+ }
+ '';
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = "Additional configuration to add.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.kitty ] ++ optionalPackage cfg.font;
+
+ xdg.configFile."kitty/kitty.conf".text = ''
+ # Generated by Home Manager.
+ # See https://sw.kovidgoyal.net/kitty/conf.html
+
+ ${optionalString (cfg.font != null) "font_family ${cfg.font.name}"}
+
+ ${toKittyConfig cfg.settings}
+
+ ${toKittyKeybindings cfg.keybindings}
+
+ ${cfg.extraConfig}
+ '';
+ };
+}
diff --git a/home-manager/modules/programs/lf.nix b/home-manager/modules/programs/lf.nix
new file mode 100644
index 00000000000..ee4e9b5bfce
--- /dev/null
+++ b/home-manager/modules/programs/lf.nix
@@ -0,0 +1,219 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.lf;
+
+ knownSettings = {
+ anchorfind = types.bool;
+ color256 = types.bool;
+ dircounts = types.bool;
+ dirfirst = types.bool;
+ drawbox = types.bool;
+ globsearch = types.bool;
+ icons = types.bool;
+ hidden = types.bool;
+ ignorecase = types.bool;
+ ignoredia = types.bool;
+ incsearch = types.bool;
+ preview = types.bool;
+ reverse = types.bool;
+ smartcase = types.bool;
+ smartdia = types.bool;
+ wrapscan = types.bool;
+ wrapscroll = types.bool;
+ number = types.bool;
+ relativenumber = types.bool;
+ findlen = types.int;
+ period = types.int;
+ scrolloff = types.int;
+ tabstop = types.int;
+ errorfmt = types.str;
+ filesep = types.str;
+ ifs = types.str;
+ promptfmt = types.str;
+ shell = types.str;
+ sortby = types.str;
+ timefmt = types.str;
+ ratios = types.str;
+ info = types.str;
+ shellopts = types.str;
+ };
+
+ lfSettingsType = types.submodule {
+ options = let
+ opt = name: type:
+ mkOption {
+ type = types.nullOr type;
+ default = null;
+ visible = false;
+ };
+ in mapAttrs opt knownSettings;
+ };
+in {
+ meta.maintainers = [ hm.maintainers.owm111 ];
+
+ options = {
+ programs.lf = {
+ enable = mkEnableOption "lf";
+
+ settings = mkOption {
+ type = lfSettingsType;
+ default = { };
+ example = {
+ tabstop = 4;
+ number = true;
+ ratios = "1:1:2";
+ };
+ description = ''
+ An attribute set of lf settings. The attribute names and corresponding
+ values must be among the following supported options.
+
+ <informaltable frame="none"><tgroup cols="1"><tbody>
+ ${concatStringsSep "\n" (mapAttrsToList (n: v: ''
+ <row>
+ <entry><varname>${n}</varname></entry>
+ <entry>${v.description}</entry>
+ </row>
+ '') knownSettings)}
+ </tbody></tgroup></informaltable>
+
+ See the lf documentation for detailed descriptions of these options.
+ Note, use <varname>previewer</varname> to set lf's
+ <varname>previewer</varname> option, and
+ <varname>extraConfig</varname> for any other option not listed above.
+ All string options are quoted with double quotes.
+ '';
+ };
+
+ commands = mkOption {
+ type = with types; attrsOf (nullOr str);
+ default = { };
+ example = {
+ get-mime-type = ''%xdg-mime query filetype "$f"'';
+ open = "$$OPENER $f";
+ };
+ description = ''
+ Commands to declare. Commands set to null or an empty string are
+ deleted.
+ '';
+ };
+
+ keybindings = mkOption {
+ type = with types; attrsOf (nullOr str);
+ default = { };
+ example = {
+ gh = "cd ~";
+ D = "trash";
+ i = "$less $f";
+ U = "!du -sh";
+ gg = null;
+ };
+ description =
+ "Keys to bind. Keys set to null or an empty string are deleted.";
+ };
+
+ cmdKeybindings = mkOption {
+ type = with types; attrsOf (nullOr str);
+ default = { };
+ example = literalExample ''{ "<c-g>" = "cmd-escape"; }'';
+ description = ''
+ Keys to bind to command line commands which can only be one of the
+ builtin commands. Keys set to null or an empty string are deleted.
+ '';
+ };
+
+ previewer.source = mkOption {
+ type = with types; nullOr path;
+ default = null;
+ example = literalExample ''
+ pkgs.writeShellScript "pv.sh" '''
+ #!/bin/sh
+
+ case "$1" in
+ *.tar*) tar tf "$1";;
+ *.zip) unzip -l "$1";;
+ *.rar) unrar l "$1";;
+ *.7z) 7z l "$1";;
+ *.pdf) pdftotext "$1" -;;
+ *) highlight -O ansi "$1" || cat "$1";;
+ esac
+ '''
+ '';
+ description = ''
+ Script or executable to use to preview files. Sets lf's
+ <varname>previewer</varname> option.
+ '';
+ };
+
+ previewer.keybinding = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ example = "i";
+ description = ''
+ Key to bind to the script at <varname>previewer.source</varname> and
+ pipe through less. Setting to null will not bind any key.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ $mkdir -p ~/.trash
+ '';
+ description = "Custom lfrc lines.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.lf ];
+
+ xdg.configFile."lf/lfrc".text = let
+ fmtSetting = k: v:
+ optionalString (v != null) "set ${
+ if isBool v then
+ "${optionalString (!v) "no"}${k}"
+ else
+ "${k} ${if isInt v then toString v else ''"${v}"''}"
+ }";
+
+ settingsStr = concatStringsSep "\n" (filter (x: x != "")
+ (mapAttrsToList fmtSetting
+ (builtins.intersectAttrs knownSettings cfg.settings)));
+
+ fmtCmdMap = before: k: v:
+ "${before} ${k}${optionalString (v != null && v != "") " ${v}"}";
+ fmtCmd = fmtCmdMap "cmd";
+ fmtMap = fmtCmdMap "map";
+ fmtCmap = fmtCmdMap "cmap";
+
+ commandsStr = concatStringsSep "\n" (mapAttrsToList fmtCmd cfg.commands);
+ keybindingsStr =
+ concatStringsSep "\n" (mapAttrsToList fmtMap cfg.keybindings);
+ cmdKeybindingsStr =
+ concatStringsSep "\n" (mapAttrsToList fmtCmap cfg.cmdKeybindings);
+
+ previewerStr = optionalString (cfg.previewer.source != null) ''
+ set previewer ${cfg.previewer.source}
+ ${optionalString (cfg.previewer.keybinding != null) ''
+ map ${cfg.previewer.keybinding} ''$${cfg.previewer.source} "$f" | less -R
+ ''}
+ '';
+ in ''
+ ${settingsStr}
+
+ ${commandsStr}
+
+ ${keybindingsStr}
+
+ ${cmdKeybindingsStr}
+
+ ${previewerStr}
+
+ ${cfg.extraConfig}
+ '';
+ };
+}
diff --git a/home-manager/modules/programs/lieer-accounts.nix b/home-manager/modules/programs/lieer-accounts.nix
new file mode 100644
index 00000000000..238049065b3
--- /dev/null
+++ b/home-manager/modules/programs/lieer-accounts.nix
@@ -0,0 +1,69 @@
+{ lib, ... }:
+
+with lib;
+
+{
+ options.lieer = {
+ enable = mkEnableOption "lieer Gmail synchronization for notmuch";
+
+ timeout = mkOption {
+ type = types.ints.unsigned;
+ default = 0;
+ description = ''
+ HTTP timeout in seconds. 0 means forever or system timeout.
+ '';
+ };
+
+ replaceSlashWithDot = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Replace '/' with '.' in Gmail labels.
+ '';
+ };
+
+ dropNonExistingLabels = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Allow missing labels on the Gmail side to be dropped.
+ '';
+ };
+
+ ignoreTagsLocal = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = ''
+ Set custom tags to ignore when syncing from local to
+ remote (after translations).
+ '';
+ };
+
+ ignoreTagsRemote = mkOption {
+ type = types.listOf types.str;
+ default = [
+ "CATEGORY_FORUMS"
+ "CATEGORY_PROMOTIONS"
+ "CATEGORY_UPDATES"
+ "CATEGORY_SOCIAL"
+ "CATEGORY_PERSONAL"
+ ];
+ description = ''
+ Set custom tags to ignore when syncing from remote to
+ local (before translations).
+ '';
+ };
+
+ notmuchSetupWarning = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Warn if Notmuch is not also enabled for this account.
+ </para><para>
+ This can safely be disabled if <command>notmuch init</command>
+ has been used to configure this account outside of Home
+ Manager.
+ '';
+ };
+ };
+}
diff --git a/home-manager/modules/programs/lieer.nix b/home-manager/modules/programs/lieer.nix
new file mode 100644
index 00000000000..e34a247af46
--- /dev/null
+++ b/home-manager/modules/programs/lieer.nix
@@ -0,0 +1,93 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.lieer;
+
+ lieerAccounts =
+ filter (a: a.lieer.enable) (attrValues config.accounts.email.accounts);
+
+ nonGmailAccounts =
+ map (a: a.name) (filter (a: a.flavor != "gmail.com") lieerAccounts);
+
+ nonGmailConfigHelp =
+ map (name: ''accounts.email.accounts.${name}.flavor = "gmail.com";'')
+ nonGmailAccounts;
+
+ missingNotmuchAccounts = map (a: a.name)
+ (filter (a: !a.notmuch.enable && a.lieer.notmuchSetupWarning)
+ lieerAccounts);
+
+ notmuchConfigHelp =
+ map (name: "accounts.email.accounts.${name}.notmuch.enable = true;")
+ missingNotmuchAccounts;
+
+ configFile = account: {
+ name = "${account.maildir.absPath}/.gmailieer.json";
+ value = {
+ text = builtins.toJSON {
+ inherit (account.lieer) timeout;
+ account = account.address;
+ replace_slash_with_dot = account.lieer.replaceSlashWithDot;
+ drop_non_existing_label = account.lieer.dropNonExistingLabels;
+ ignore_tags = account.lieer.ignoreTagsLocal;
+ ignore_remote_labels = account.lieer.ignoreTagsRemote;
+ } + "\n";
+ };
+ };
+
+in {
+ meta.maintainers = [ maintainers.tadfisher ];
+
+ options = {
+ programs.lieer.enable =
+ mkEnableOption "lieer Gmail synchronization for notmuch";
+
+ accounts.email.accounts = mkOption {
+ type = with types; attrsOf (submodule (import ./lieer-accounts.nix));
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ (mkIf (missingNotmuchAccounts != [ ]) {
+ warnings = [''
+ lieer is enabled for the following email accounts, but notmuch is not:
+
+ ${concatStringsSep "\n " missingNotmuchAccounts}
+
+ Notmuch can be enabled with:
+
+ ${concatStringsSep "\n " notmuchConfigHelp}
+
+ If you have configured notmuch outside of Home Manager, you can suppress this
+ warning with:
+
+ programs.lieer.notmuchSetupWarning = false;
+ ''];
+ })
+
+ {
+ assertions = [{
+ assertion = nonGmailAccounts == [ ];
+ message = ''
+ lieer is enabled for non-Gmail accounts:
+
+ ${concatStringsSep "\n " nonGmailAccounts}
+
+ If these accounts are actually Gmail accounts, you can
+ fix this error with:
+
+ ${concatStringsSep "\n " nonGmailConfigHelp}
+ '';
+ }];
+
+ home.packages = [ pkgs.gmailieer ];
+
+ # Notmuch should ignore non-mail files created by lieer.
+ programs.notmuch.new.ignore = [ "/.*[.](json|lock|bak)$/" ];
+
+ home.file = listToAttrs (map configFile lieerAccounts);
+ }
+ ]);
+}
diff --git a/home-manager/modules/programs/man.nix b/home-manager/modules/programs/man.nix
index 0ed376780d4..b235b02fe2d 100644
--- a/home-manager/modules/programs/man.nix
+++ b/home-manager/modules/programs/man.nix
@@ -4,19 +4,69 @@ with lib;
{
options = {
- programs.man.enable = mkOption {
- type = types.bool;
- default = true;
- description = ''
- Whether to enable manual pages and the <command>man</command>
- command. This also includes "man" outputs of all
- <literal>home.packages</literal>.
- '';
+ programs.man = {
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable manual pages and the <command>man</command>
+ command. This also includes "man" outputs of all
+ <literal>home.packages</literal>.
+ '';
+ };
+
+ generateCaches = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to generate the manual page index caches using
+ <citerefentry>
+ <refentrytitle>mandb</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </citerefentry>. This allows searching for a page or
+ keyword using utilities like <citerefentry>
+ <refentrytitle>apropos</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>.
+ </para><para>
+ This feature is disabled by default because it slows down
+ building. If you don't mind waiting a few more seconds when
+ Home Manager builds a new generation, you may safely enable
+ this option.
+ '';
+ };
};
};
config = mkIf config.programs.man.enable {
home.packages = [ pkgs.man ];
home.extraOutputsToInstall = [ "man" ];
+
+ # This is mostly copy/pasted/adapted from NixOS' documentation.nix.
+ home.file = mkIf config.programs.man.generateCaches {
+ ".manpath".text = let
+ # Generate a directory containing installed packages' manpages.
+ manualPages = pkgs.buildEnv {
+ name = "man-paths";
+ paths = config.home.packages;
+ pathsToLink = [ "/share/man" ];
+ extraOutputsToInstall = [ "man" ];
+ ignoreCollisions = true;
+ };
+
+ # Generate a database of all manpages in ${manualPages}.
+ manualCache = pkgs.runCommandLocal "man-cache" { } ''
+ # Generate a temporary man.conf so mandb knows where to
+ # write cache files.
+ echo "MANDB_MAP ${manualPages}/share/man $out" > man.conf
+
+ # Run mandb to generate cache files:
+ ${pkgs.man-db}/bin/mandb -C man.conf --no-straycats --create \
+ ${manualPages}/share/man
+ '';
+ in ''
+ MANDB_MAP ${config.home.profileDirectory}/share/man ${manualCache}
+ '';
+ };
};
}
diff --git a/home-manager/modules/programs/mbsync.nix b/home-manager/modules/programs/mbsync.nix
index 6ade10986fe..f2814b393d0 100644
--- a/home-manager/modules/programs/mbsync.nix
+++ b/home-manager/modules/programs/mbsync.nix
@@ -122,6 +122,10 @@ in {
'';
};
};
+
+ accounts.email.accounts = mkOption {
+ type = with types; attrsOf (submodule (import ./mbsync-accounts.nix));
+ };
};
config = mkIf cfg.enable {
diff --git a/home-manager/modules/programs/mcfly.nix b/home-manager/modules/programs/mcfly.nix
new file mode 100644
index 00000000000..1206f9da566
--- /dev/null
+++ b/home-manager/modules/programs/mcfly.nix
@@ -0,0 +1,79 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+
+ cfg = config.programs.mcfly;
+
+in {
+ meta.maintainers = [ maintainers.marsam ];
+
+ options.programs.mcfly = {
+ enable = mkEnableOption "mcfly";
+
+ keyScheme = mkOption {
+ type = types.enum [ "emacs" "vim" ];
+ default = "emacs";
+ description = ''
+ Key scheme to use.
+ '';
+ };
+
+ enableLightTheme = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to enable light mode theme.
+ '';
+ };
+
+ enableBashIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Bash integration.
+ '';
+ };
+
+ enableZshIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Zsh integration.
+ '';
+ };
+
+ enableFishIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Fish integration.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+ home.packages = [ pkgs.mcfly ];
+
+ programs.bash.initExtra = mkIf cfg.enableBashIntegration ''
+ source "${pkgs.mcfly}/share/mcfly/mcfly.bash"
+ '';
+
+ programs.zsh.initExtra = mkIf cfg.enableZshIntegration ''
+ source "${pkgs.mcfly}/share/mcfly/mcfly.zsh"
+ '';
+
+ programs.fish.shellInit = mkIf cfg.enableFishIntegration ''
+ source "${pkgs.mcfly}/share/mcfly/mcfly.fish"
+ if status is-interactive
+ mcfly_key_bindings
+ end
+ '';
+
+ home.sessionVariables.MCFLY_KEY_SCHEME = cfg.keyScheme;
+ }
+
+ (mkIf cfg.enableLightTheme { home.sessionVariables.MCFLY_LIGHT = "TRUE"; })
+ ]);
+}
diff --git a/home-manager/modules/programs/mpv.nix b/home-manager/modules/programs/mpv.nix
index 3a4e5092f9a..a5b0517fe0a 100644
--- a/home-manager/modules/programs/mpv.nix
+++ b/home-manager/modules/programs/mpv.nix
@@ -125,7 +125,7 @@ in {
(if cfg.scripts == [ ] then
pkgs.mpv
else
- pkgs.mpv-with-scripts.override { scripts = cfg.scripts; })
+ pkgs.wrapMpv pkgs.mpv-unwrapped { scripts = cfg.scripts; })
];
}
(mkIf (cfg.config != { } || cfg.profiles != { }) {
diff --git a/home-manager/modules/programs/msmtp.nix b/home-manager/modules/programs/msmtp.nix
index f34fd72f8b1..7b6704860e0 100644
--- a/home-manager/modules/programs/msmtp.nix
+++ b/home-manager/modules/programs/msmtp.nix
@@ -56,6 +56,10 @@ in {
'';
};
};
+
+ accounts.email.accounts = mkOption {
+ type = with types; attrsOf (submodule (import ./msmtp-accounts.nix));
+ };
};
config = mkIf cfg.enable {
diff --git a/home-manager/modules/programs/ncmpcpp.nix b/home-manager/modules/programs/ncmpcpp.nix
new file mode 100644
index 00000000000..a39baab6ca5
--- /dev/null
+++ b/home-manager/modules/programs/ncmpcpp.nix
@@ -0,0 +1,135 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.ncmpcpp;
+
+ renderSettings = settings:
+ concatStringsSep "\n" (mapAttrsToList renderSetting settings);
+
+ renderSetting = name: value: "${name}=${renderValue value}";
+
+ renderValue = option:
+ {
+ int = toString option;
+ bool = if option then "yes" else "no";
+ string = option;
+ }.${builtins.typeOf option};
+
+ renderBindings = bindings: concatStringsSep "\n" (map renderBinding bindings);
+
+ renderBinding = { key, command }:
+ concatStringsSep "\n " ([ ''def_key "${key}"'' ] ++ maybeWrapList command);
+
+ maybeWrapList = xs: if isList xs then xs else [ xs ];
+
+ valueType = with types; oneOf [ bool int str ];
+
+ bindingType = types.submodule ({ name, config, ... }: {
+ options = {
+ key = mkOption {
+ type = types.str;
+ description = "Key to bind.";
+ example = "j";
+ };
+
+ command = mkOption {
+ type = with types; either str (listOf str);
+ description = "Command or sequence of commands to be executed.";
+ example = "scroll_down";
+ };
+ };
+ });
+
+in {
+ meta.maintainers = with maintainers; [ olmokramer ];
+
+ options.programs.ncmpcpp = {
+ enable =
+ mkEnableOption "ncmpcpp - an ncurses Music Player Daemon (MPD) client";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.ncmpcpp;
+ defaultText = literalExample "pkgs.ncmpcpp";
+ description = ''
+ Package providing the <code>ncmpcpp</code> command.
+ '';
+ example =
+ literalExample "pkgs.ncmpcpp.override { visualizerSupport = true; }";
+ };
+
+ mpdMusicDir = mkOption {
+ type = types.nullOr types.path;
+ default = let mpdCfg = config.services.mpd;
+ in if pkgs.stdenv.hostPlatform.isLinux && mpdCfg.enable then
+ mpdCfg.musicDirectory
+ else
+ null;
+ defaultText = literalExample ''
+ if pkgs.stdenv.hostPlatform.isLinux && config.services.mpd.enable then
+ config.services.mpd.musicDirectory
+ else
+ null
+ '';
+ description = ''
+ Value of the <code>mpd_music_dir</code> setting. On Linux platforms the
+ value of <varname>services.mpd.musicDirectory</varname> is used as the
+ default if <varname>services.mpd.enable</varname> is
+ <literal>true</literal>.
+ '';
+ example = "~/music";
+ };
+
+ settings = mkOption {
+ type = types.attrsOf valueType;
+ default = { };
+ description = ''
+ Attribute set from name of a setting to its value. For available options
+ see
+ <citerefentry>
+ <refentrytitle>ncmpcpp</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>.
+ '';
+ example = { ncmpcpp_directory = "~/.local/share/ncmpcpp"; };
+ };
+
+ bindings = mkOption {
+ type = types.listOf bindingType;
+ default = [ ];
+ description = "List of keybindings.";
+ example = literalExample ''
+ [
+ { key = "j"; command = "scroll_down"; }
+ { key = "k"; command = "scroll_up"; }
+ { key = "J"; command = [ "select_item" "scroll_down" ]; }
+ { key = "K"; command = [ "select_item" "scroll_up" ]; }
+ ]
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ warnings = mkIf (cfg.settings ? mpd_music_dir && cfg.mpdMusicDir != null) [
+ ("programs.ncmpcpp.settings.mpd_music_dir will be overridden by"
+ + " programs.ncmpcpp.mpdMusicDir.")
+ ];
+
+ home.packages = [ cfg.package ];
+
+ xdg.configFile = {
+ "ncmpcpp/config" = let
+ settings = cfg.settings // optionalAttrs (cfg.mpdMusicDir != null) {
+ mpd_music_dir = toString cfg.mpdMusicDir;
+ };
+ in mkIf (settings != { }) { text = renderSettings settings + "\n"; };
+
+ "ncmpcpp/bindings" = mkIf (cfg.bindings != [ ]) {
+ text = renderBindings cfg.bindings + "\n";
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/programs/ne.nix b/home-manager/modules/programs/ne.nix
new file mode 100644
index 00000000000..a88d23d9133
--- /dev/null
+++ b/home-manager/modules/programs/ne.nix
@@ -0,0 +1,95 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.ne;
+
+ autoPrefFiles = let
+ autoprefs = cfg.automaticPreferences
+ // optionalAttrs (cfg.defaultPreferences != "") {
+ ".default" = cfg.defaultPreferences;
+ };
+
+ gen = fileExtension: configText:
+ nameValuePair ".ne/${fileExtension}#ap" {
+ text = configText;
+ }; # Generates [path].text format expected by home.file.
+ in mapAttrs' gen autoprefs;
+
+in {
+ meta.maintainers = [ hm.maintainers.cwyc ];
+
+ options.programs.ne = {
+ enable = mkEnableOption "ne";
+
+ keybindings = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ KEY 7f BS
+ SEQ "\x1b[1;5D" 7f
+ '';
+ description = ''
+ Keybinding file for ne.
+ '';
+ };
+
+ defaultPreferences = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Default preferences for ne.
+ </para><para>
+ Equivalent to <literal>programs.ne.automaticPreferences.".default"</literal>.
+ '';
+ };
+
+ automaticPreferences = mkOption {
+ type = types.attrsOf types.lines;
+ default = { };
+ example = literalExample ''
+ {
+ nix = '''
+ TAB 0
+ TS 2
+ ''';
+ js = '''
+ TS 4
+ ''';
+ }
+ '';
+ description = ''
+ Automatic preferences files for ne.
+ '';
+ };
+
+ menus = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Menu configuration file for ne.";
+ };
+
+ virtualExtensions = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ sh 1 ^#!\s*/.*\b(bash|sh|ksh|zsh)\s*
+ csh 1 ^#!\s*/.*\b(csh|tcsh)\s*
+ '';
+ description = "Virtual extensions configuration file for ne.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.ne ];
+
+ home.file = {
+ ".ne/.keys" = mkIf (cfg.keybindings != "") { text = cfg.keybindings; };
+ ".ne/.extensions" =
+ mkIf (cfg.virtualExtensions != "") { text = cfg.virtualExtensions; };
+ ".ne/.menus" = mkIf (cfg.menus != "") { text = cfg.menus; };
+ } // autoPrefFiles;
+ };
+}
diff --git a/home-manager/modules/programs/neomutt-accounts.nix b/home-manager/modules/programs/neomutt-accounts.nix
index 033db38eb0a..009cf1fa7e8 100644
--- a/home-manager/modules/programs/neomutt-accounts.nix
+++ b/home-manager/modules/programs/neomutt-accounts.nix
@@ -8,7 +8,16 @@ with lib;
sendMailCommand = mkOption {
type = types.nullOr types.str;
- default = null;
+ default = if config.msmtp.enable then
+ "msmtpq --read-envelope-from --read-recipients"
+ else
+ null;
+ defaultText = literalExample ''
+ if config.msmtp.enable then
+ "msmtpq --read-envelope-from --read-recipients"
+ else
+ null
+ '';
example = "msmtpq --read-envelope-from --read-recipients";
description = ''
Command to send a mail. If not set, neomutt will be in charge of sending mails.
@@ -24,11 +33,4 @@ with lib;
'';
};
};
-
- config = mkIf config.neomutt.enable {
- neomutt.sendMailCommand = mkOptionDefault (if config.msmtp.enable then
- "msmtpq --read-envelope-from --read-recipients"
- else
- null);
- };
}
diff --git a/home-manager/modules/programs/neomutt.nix b/home-manager/modules/programs/neomutt.nix
index 85af0353b6c..f2a6bbfff08 100644
--- a/home-manager/modules/programs/neomutt.nix
+++ b/home-manager/modules/programs/neomutt.nix
@@ -38,6 +38,19 @@ let
};
};
+ sortOptions = [
+ "date"
+ "date-received"
+ "from"
+ "mailbox-order"
+ "score"
+ "size"
+ "spam"
+ "subject"
+ "threads"
+ "to"
+ ];
+
bindModule = types.submodule {
options = {
map = mkOption {
@@ -98,7 +111,9 @@ let
} else
let
smtpProto = if smtp.tls.enable then "smtps" else "smtp";
- smtpBaseUrl = "${smtpProto}://${escape userName}@${smtp.host}";
+ smtpPort = if smtp.port != null then ":${toString smtp.port}" else "";
+ smtpBaseUrl =
+ "${smtpProto}://${escape userName}@${smtp.host}${smtpPort}";
in {
smtp_url = "'${smtpBaseUrl}'";
smtp_pass = "'`${passCmd}`'";
@@ -211,18 +226,9 @@ in {
};
sort = mkOption {
- type = types.enum [
- "date"
- "date-received"
- "from"
- "mailbox-order"
- "score"
- "size"
- "spam"
- "subject"
- "threads"
- "to"
- ];
+ # allow users to choose any option from sortOptions, or any option prefixed with "reverse-"
+ type = types.enum
+ (sortOptions ++ (map (option: "reverse-" + option) sortOptions));
default = "threads";
description = "Sorting method on messages.";
};
@@ -258,6 +264,10 @@ in {
description = "Extra configuration appended to the end.";
};
};
+
+ accounts.email.accounts = mkOption {
+ type = with types; attrsOf (submodule (import ./neomutt-accounts.nix));
+ };
};
config = mkIf cfg.enable {
diff --git a/home-manager/modules/programs/neovim.nix b/home-manager/modules/programs/neovim.nix
index 4101dc0f4e7..858f5576ad1 100644
--- a/home-manager/modules/programs/neovim.nix
+++ b/home-manager/modules/programs/neovim.nix
@@ -43,7 +43,7 @@ in
type = types.bool;
default = false;
description = ''
- Symlink `vi` to `nvim` binary.
+ Symlink <command>vi</command> to <command>nvim</command> binary.
'';
};
@@ -51,7 +51,15 @@ in
type = types.bool;
default = false;
description = ''
- Symlink `vim` to `nvim` binary.
+ Symlink <command>vim</command> to <command>nvim</command> binary.
+ '';
+ };
+
+ vimdiffAlias = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Alias <command>vimdiff</command> to <command>nvim -d</command>.
'';
};
@@ -203,5 +211,9 @@ in
configure = cfg.configure // moduleConfigure;
};
+
+ programs.bash.shellAliases = mkIf cfg.vimdiffAlias { vimdiff = "nvim -d"; };
+ programs.fish.shellAliases = mkIf cfg.vimdiffAlias { vimdiff = "nvim -d"; };
+ programs.zsh.shellAliases = mkIf cfg.vimdiffAlias { vimdiff = "nvim -d"; };
};
}
diff --git a/home-manager/modules/programs/newsboat.nix b/home-manager/modules/programs/newsboat.nix
index 6b59ed713d8..793b30680bf 100644
--- a/home-manager/modules/programs/newsboat.nix
+++ b/home-manager/modules/programs/newsboat.nix
@@ -102,7 +102,11 @@ in {
mkQueryEntry = n: v: ''"query:${n}:${escape [ ''"'' ] v}"'';
queries = mapAttrsToList mkQueryEntry cfg.queries;
- in concatStringsSep "\n" (urls ++ queries) + "\n";
+ in concatStringsSep "\n"
+ (if versionAtLeast config.home.stateVersion "20.03" then
+ queries ++ urls
+ else
+ urls ++ queries) + "\n";
home.file.".newsboat/config".text = ''
max-items ${toString cfg.maxItems}
diff --git a/home-manager/modules/programs/notmuch.nix b/home-manager/modules/programs/notmuch.nix
index 7e7a140b20c..9070d755671 100644
--- a/home-manager/modules/programs/notmuch.nix
+++ b/home-manager/modules/programs/notmuch.nix
@@ -143,6 +143,10 @@ in {
};
};
};
+
+ accounts.email.accounts = mkOption {
+ type = with types; attrsOf (submodule (import ./notmuch-accounts.nix));
+ };
};
config = mkIf cfg.enable {
diff --git a/home-manager/modules/programs/nushell.nix b/home-manager/modules/programs/nushell.nix
new file mode 100644
index 00000000000..1eb42f9515c
--- /dev/null
+++ b/home-manager/modules/programs/nushell.nix
@@ -0,0 +1,68 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.nushell;
+
+ configFile = config:
+ pkgs.runCommand "config.toml" {
+ buildInputs = [ pkgs.remarshal ];
+ preferLocalBuild = true;
+ allowSubstitutes = false;
+ } ''
+ remarshal -if json -of toml \
+ < ${pkgs.writeText "config.json" (builtins.toJSON config)} \
+ > $out
+ '';
+
+in {
+ meta.maintainers = [ maintainers.Philipp-M ];
+
+ options.programs.nushell = {
+ enable = mkEnableOption "nushell";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.nushell;
+ defaultText = literalExample "pkgs.nushell";
+ description = "The package to use for nushell.";
+ };
+
+ settings = mkOption {
+ type = with types;
+ let
+ prim = oneOf [ bool int str ];
+ primOrPrimAttrs = either prim (attrsOf prim);
+ entry = either prim (listOf primOrPrimAttrs);
+ entryOrAttrsOf = t: either entry (attrsOf t);
+ entries = entryOrAttrsOf (entryOrAttrsOf entry);
+ in attrsOf entries // { description = "Nushell configuration"; };
+ default = { };
+ example = literalExample ''
+ {
+ edit_mode = "vi";
+ startup = [ "alias la [] { ls -a }" "alias e [msg] { echo $msg }" ];
+ key_timeout = 10;
+ completion_mode = "circular";
+ no_auto_pivot = true;
+ }
+ '';
+ description = ''
+ Configuration written to
+ <filename>~/.config/nushell/config.toml</filename>.
+ </para><para>
+ See <link xlink:href="https://www.nushell.sh/book/en/configuration.html" /> for the full list
+ of options.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+
+ xdg.configFile."nu/config.toml" =
+ mkIf (cfg.settings != { }) { source = configFile cfg.settings; };
+ };
+}
diff --git a/home-manager/modules/programs/offlineimap.nix b/home-manager/modules/programs/offlineimap.nix
index 4ce12ec0a61..b6ba847e9b7 100644
--- a/home-manager/modules/programs/offlineimap.nix
+++ b/home-manager/modules/programs/offlineimap.nix
@@ -147,6 +147,11 @@ in {
'';
};
};
+
+ accounts.email.accounts = mkOption {
+ type = with types;
+ attrsOf (submodule (import ./offlineimap-accounts.nix));
+ };
};
config = mkIf cfg.enable {
diff --git a/home-manager/modules/programs/powerline-go.nix b/home-manager/modules/programs/powerline-go.nix
new file mode 100644
index 00000000000..a4cd233cf70
--- /dev/null
+++ b/home-manager/modules/programs/powerline-go.nix
@@ -0,0 +1,123 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.powerline-go;
+
+ # Convert an option value to a string to be passed as argument to
+ # powerline-go:
+ valueToString = value:
+ if builtins.isList value then
+ builtins.concatStringsSep "," (builtins.map valueToString value)
+ else if builtins.isAttrs value then
+ valueToString
+ (mapAttrsToList (key: val: "${valueToString key}=${valueToString val}")
+ value)
+ else
+ builtins.toString value;
+
+ modulesArgument = optionalString (cfg.modules != null)
+ "-modules ${valueToString cfg.modules}";
+
+ newlineArgument = optionalString cfg.newline "-newline";
+
+ pathAliasesArgument = optionalString (cfg.pathAliases != null)
+ "-path-aliases ${valueToString cfg.pathAliases}";
+
+ otherSettingPairArgument = name: value:
+ if value == true then "-${name}" else "-${name} ${valueToString value}";
+
+ otherSettingsArgument = optionalString (cfg.settings != { })
+ (concatStringsSep " "
+ (mapAttrsToList otherSettingPairArgument cfg.settings));
+
+ commandLineArguments = ''
+ ${modulesArgument} ${newlineArgument} ${pathAliasesArgument} ${otherSettingsArgument}
+ '';
+
+in {
+ meta.maintainers = [ maintainers.DamienCassou ];
+
+ options = {
+ programs.powerline-go = {
+ enable = mkEnableOption
+ "Powerline-go, a beautiful and useful low-latency prompt for your shell";
+
+ modules = mkOption {
+ default = null;
+ type = types.nullOr (types.listOf types.str);
+ description = ''
+ List of module names to load. The list of all available
+ modules as well as the choice of default ones are at
+ <link xlink:href="https://github.com/justjanne/powerline-go"/>.
+ '';
+ example = [ "host" "ssh" "cwd" "gitlite" "jobs" "exit" ];
+ };
+
+ newline = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Set to true if the prompt should be on a line of its own.
+ '';
+ example = true;
+ };
+
+ pathAliases = mkOption {
+ default = null;
+ type = types.nullOr (types.attrsOf types.str);
+ description = ''
+ Pairs of full-path and corresponding desired short name. You
+ may use '~' to represent your home directory but you should
+ protect it to avoid shell substitution.
+ '';
+ example = literalExample ''
+ { "\\~/projects/home-manager" = "prj:home-manager"; }
+ '';
+ };
+
+ settings = mkOption {
+ default = { };
+ type = with types; attrsOf (oneOf [ bool int str (listOf str) ]);
+ description = ''
+ This can be any key/value pair as described in
+ <link xlink:href="https://github.com/justjanne/powerline-go"/>.
+ '';
+ example = literalExample ''
+ {
+ hostname-only-if-ssh = true;
+ numeric-exit-codes = true;
+ cwd-max-depth = 7;
+ ignore-repos = [ "/home/me/big-project" "/home/me/huge-project" ];
+ }
+ '';
+ };
+
+ extraUpdatePS1 = mkOption {
+ default = "";
+ description = "Shell code to execute after the prompt is set.";
+ example = ''
+ PS1=$PS1"NixOS> ";
+ '';
+ type = types.str;
+ };
+ };
+ };
+
+ config = mkIf (cfg.enable && config.programs.bash.enable) {
+ programs.bash.initExtra = ''
+ function _update_ps1() {
+ local old_exit_status=$?
+ PS1="$(${pkgs.powerline-go}/bin/powerline-go -error $old_exit_status ${commandLineArguments})"
+ ${cfg.extraUpdatePS1}
+ return $old_exit_status
+ }
+
+ if [ "$TERM" != "linux" ]; then
+ PROMPT_COMMAND="_update_ps1;$PROMPT_COMMAND"
+ fi
+ '';
+ };
+}
diff --git a/home-manager/modules/programs/qutebrowser.nix b/home-manager/modules/programs/qutebrowser.nix
new file mode 100644
index 00000000000..798363fb187
--- /dev/null
+++ b/home-manager/modules/programs/qutebrowser.nix
@@ -0,0 +1,268 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.qutebrowser;
+
+ formatLine = o: n: v:
+ let
+ formatValue = v:
+ if builtins.isNull v then
+ "None"
+ else if builtins.isBool v then
+ (if v then "True" else "False")
+ else if builtins.isString v then
+ ''"${v}"''
+ else if builtins.isList v then
+ "[${concatStringsSep ", " (map formatValue v)}]"
+ else
+ builtins.toString v;
+ in if builtins.isAttrs v then
+ concatStringsSep "\n" (mapAttrsToList (formatLine "${o}${n}.") v)
+ else
+ "${o}${n} = ${formatValue v}";
+
+ formatDictLine = o: n: v: ''${o}['${n}'] = "${v}"'';
+
+ formatKeyBindings = m: b:
+ let
+ formatKeyBinding = m: k: c:
+ ''config.bind("${k}", "${escape [ ''"'' ] c}", mode="${m}")'';
+ in concatStringsSep "\n" (mapAttrsToList (formatKeyBinding m) b);
+
+in {
+ options.programs.qutebrowser = {
+ enable = mkEnableOption "qutebrowser";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.qutebrowser;
+ defaultText = literalExample "pkgs.qutebrowser";
+ description = "Qutebrowser package to install.";
+ };
+
+ aliases = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ description = ''
+ Aliases for commands.
+ '';
+ };
+
+ searchEngines = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ description = ''
+ Search engines that can be used via the address bar. Maps a search
+ engine name (such as <literal>DEFAULT</literal>, or
+ <literal>ddg</literal>) to a URL with a <literal>{}</literal>
+ placeholder. The placeholder will be replaced by the search term, use
+ <literal>{{</literal> and <literal>}}</literal> for literal
+ <literal>{/}</literal> signs. The search engine named
+ <literal>DEFAULT</literal> is used when
+ <literal>url.auto_search</literal> is turned on and something else than
+ a URL was entered to be opened. Other search engines can be used by
+ prepending the search engine name to the search term, for example
+ <literal>:open google qutebrowser</literal>.
+ '';
+ example = literalExample ''
+ {
+ w = "https://en.wikipedia.org/wiki/Special:Search?search={}&go=Go&ns0=1";
+ aw = "https://wiki.archlinux.org/?search={}";
+ nw = "https://nixos.wiki/index.php?search={}";
+ g = "https://www.google.com/search?hl=en&q={}";
+ }
+ '';
+ };
+
+ settings = mkOption {
+ type = types.attrs;
+ default = { };
+ description = ''
+ Options to add to qutebrowser <filename>config.py</filename> file.
+ See <link xlink:href="https://qutebrowser.org/doc/help/settings.html"/>
+ for options.
+ '';
+ example = literalExample ''
+ {
+ colors = {
+ hints = {
+ bg = "#000000";
+ fg = "#ffffff";
+ };
+ tabs.bar.bg = "#000000";
+ };
+ tabs.tabs_are_windows = true;
+ }
+ '';
+ };
+
+ keyMappings = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ description = ''
+ This setting can be used to map keys to other keys. When the key used
+ as dictionary-key is pressed, the binding for the key used as
+ dictionary-value is invoked instead. This is useful for global
+ remappings of keys, for example to map Ctrl-[ to Escape. Note that when
+ a key is bound (via <literal>bindings.default</literal> or
+ <literal>bindings.commands</literal>), the mapping is ignored.
+ '';
+ };
+
+ enableDefaultBindings = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Disable to prevent loading default key bindings.
+ '';
+ };
+
+ keyBindings = mkOption {
+ type = types.attrsOf (types.attrsOf types.str);
+ default = { };
+ description = ''
+ Key bindings mapping keys to commands in different modes. This setting
+ is a dictionary containing mode names and dictionaries mapping keys to
+ commands: <literal>{mode: {key: command}}</literal> If you want to map
+ a key to another key, check the <literal>keyMappings</literal> setting
+ instead. For modifiers, you can use either <literal>-</literal> or
+ <literal>+</literal> as delimiters, and these names:
+
+ <itemizedlist>
+ <listitem><para>
+ Control: <literal>Control</literal>, <literal>Ctrl</literal>
+ </para></listitem>
+ <listitem><para>
+ Meta: <literal>Meta</literal>, <literal>Windows</literal>,
+ <literal>Mod4</literal>
+ </para></listitem>
+ <listitem><para>
+ Alt: <literal>Alt</literal>, <literal>Mod1</literal>
+ </para></listitem>
+ <listitem><para>
+ Shift: <literal>Shift</literal>
+ </para></listitem>
+ </itemizedlist>
+
+ For simple keys (no <literal>&lt;&gt;</literal>-signs), a capital
+ letter means the key is pressed with Shift. For special keys (with
+ <literal>&lt;&gt;</literal>-signs), you need to explicitly add
+ <literal>Shift-</literal> to match a key pressed with shift. If you
+ want a binding to do nothing, bind it to the <literal>nop</literal>
+ command. If you want a default binding to be passed through to the
+ website, bind it to null. Note that some commands which are only useful
+ for bindings (but not used interactively) are hidden from the command
+ completion. See <literal>:</literal>help for a full list of available
+ commands. The following modes are available:
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>normal</literal></term>
+ <listitem><para>
+ Default mode, where most commands are invoked.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>insert</literal></term>
+ <listitem><para>
+ Entered when an input field is focused on a website, or by
+ pressing i in normal mode. Passes through almost all keypresses
+ to the website, but has some bindings like
+ <literal>&lt;Ctrl-e&gt;</literal> to open an external editor.
+ Note that single keys can’t be bound in this mode.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>hint</literal></term>
+ <listitem><para>
+ Entered when f is pressed to select links with the keyboard. Note
+ that single keys can’t be bound in this mode.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>passthrough</literal></term>
+ <listitem><para>
+ Similar to insert mode, but passes through all keypresses except
+ <literal>&lt;Escape&gt;</literal> to leave the mode. It might be
+ useful to bind <literal>&lt;Escape&gt;</literal> to some other
+ key in this mode if you want to be able to send an Escape key to
+ the website as well. Note that single keys can’t be bound in this
+ mode.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>command</literal></term>
+ <listitem><para>
+ Entered when pressing the : key in order to enter a command. Note
+ that single keys can’t be bound in this mode.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>prompt</literal></term>
+ <listitem><para>
+ Entered when there’s a prompt to display, like for download
+ locations or when invoked from JavaScript.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>yesno</literal></term>
+ <listitem><para>
+ Entered when there’s a yes/no prompt displayed.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>caret</literal></term>
+ <listitem><para>
+ Entered when pressing the v mode, used to select text using the
+ keyboard.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>register</literal></term>
+ <listitem><para>
+ Entered when qutebrowser is waiting for a register name/key for
+ commands like <literal>:set-mark</literal>.
+ </para></listitem>
+ </varlistentry>
+ </variablelist>
+ '';
+ example = literalExample ''
+ {
+ normal = {
+ "<Ctrl-v>" = "spawn mpv {url}";
+ ",p" = "spawn --userscript qute-pass";
+ ",l" = '''config-cycle spellcheck.languages ["en-GB"] ["en-US"]''';
+ };
+ prompt = {
+ "<Ctrl-y>" = "prompt-yes";
+ };
+ }
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra lines added to qutebrowser <filename>config.py</filename> file.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+
+ xdg.configFile."qutebrowser/config.py".text = concatStringsSep "\n" ([ ]
+ ++ mapAttrsToList (formatLine "c.") cfg.settings
+ ++ mapAttrsToList (formatDictLine "c.aliases") cfg.aliases
+ ++ mapAttrsToList (formatDictLine "c.url.searchengines") cfg.searchEngines
+ ++ mapAttrsToList (formatDictLine "c.bindings.key_mappings")
+ cfg.keyMappings
+ ++ optional (!cfg.enableDefaultBindings) "c.bindings.default = {}"
+ ++ mapAttrsToList formatKeyBindings cfg.keyBindings
+ ++ optional (cfg.extraConfig != "") cfg.extraConfig);
+ };
+}
diff --git a/home-manager/modules/programs/rofi.nix b/home-manager/modules/programs/rofi.nix
index f344e88e2ff..734bcc423e6 100644
--- a/home-manager/modules/programs/rofi.nix
+++ b/home-manager/modules/programs/rofi.nix
@@ -131,6 +131,17 @@ in {
enable = mkEnableOption
"Rofi: A window switcher, application launcher and dmenu replacement";
+ package = mkOption {
+ default = pkgs.rofi;
+ type = types.package;
+ description = ''
+ Package providing the <command>rofi</command> binary.
+ '';
+ example = literalExample ''
+ pkgs.rofi.override { plugins = [ pkgs.rofi-emoji ]; };
+ '';
+ };
+
width = mkOption {
default = null;
type = types.nullOr types.int;
@@ -295,7 +306,7 @@ in {
'';
}];
- home.packages = [ pkgs.rofi ];
+ home.packages = [ cfg.package ];
home.file."${cfg.configPath}".text = ''
${setOption "width" cfg.width}
diff --git a/home-manager/modules/programs/ssh.nix b/home-manager/modules/programs/ssh.nix
index 6b0747dd9b1..ae1f221803c 100644
--- a/home-manager/modules/programs/ssh.nix
+++ b/home-manager/modules/programs/ssh.nix
@@ -56,7 +56,7 @@ let
};
};
- matchBlockModule = types.submodule ({ name, ... }: {
+ matchBlockModule = types.submodule ({ dagName, ... }: {
options = {
host = mkOption {
type = types.str;
@@ -143,6 +143,15 @@ let
"Set timeout in seconds after which response will be requested.";
};
+ serverAliveCountMax = mkOption {
+ type = types.ints.positive;
+ default = 3;
+ description = ''
+ Sets the number of server alive messages which may be sent
+ without SSH receiving any messages back from the server.
+ '';
+ };
+
sendEnv = mkOption {
type = types.listOf types.str;
default = [];
@@ -266,7 +275,7 @@ let
};
};
- config.host = mkDefault name;
+ config.host = mkDefault dagName;
});
matchBlockStr = cf: concatStringsSep "\n" (
@@ -281,7 +290,9 @@ let
++ optional (cf.addressFamily != null) " AddressFamily ${cf.addressFamily}"
++ optional (cf.sendEnv != []) " SendEnv ${unwords cf.sendEnv}"
++ optional (cf.serverAliveInterval != 0)
- " ServerAliveInterval ${toString cf.serverAliveInterval}"
+ " ServerAliveInterval ${toString cf.serverAliveInterval}"
+ ++ optional (cf.serverAliveCountMax != 3)
+ " ServerAliveCountMax ${toString cf.serverAliveCountMax}"
++ optional (cf.compression != null) " Compression ${yn cf.compression}"
++ optional (!cf.checkHostIP) " CheckHostIP no"
++ optional (cf.proxyCommand != null) " ProxyCommand ${cf.proxyCommand}"
@@ -325,6 +336,15 @@ in
'';
};
+ serverAliveCountMax = mkOption {
+ type = types.ints.positive;
+ default = 3;
+ description = ''
+ Sets the default number of server alive messages which may be
+ sent without SSH receiving any messages back from the server.
+ '';
+ };
+
hashKnownHosts = mkOption {
default = false;
type = types.bool;
@@ -392,7 +412,7 @@ in
};
matchBlocks = mkOption {
- type = types.loaOf matchBlockModule;
+ type = hm.types.listOrDagOf matchBlockModule;
default = {};
example = literalExample ''
{
@@ -400,7 +420,7 @@ in
hostname = "example.com";
user = "john";
};
- foo = {
+ foo = lib.hm.dag.entryBefore ["john.example.com"] {
hostname = "example.com";
identityFile = "/home/john/.ssh/foo_rsa";
};
@@ -408,11 +428,15 @@ in
'';
description = ''
Specify per-host settings. Note, if the order of rules matter
- then this must be a list. See
+ then use the DAG functions to express the dependencies as
+ shown in the example.
+ </para><para>
+ See
<citerefentry>
<refentrytitle>ssh_config</refentrytitle>
<manvolnum>5</manvolnum>
- </citerefentry>.
+ </citerefentry>
+ for more information.
'';
};
};
@@ -432,23 +456,30 @@ in
checkLocal = block: any' checkBindAndHost block.localForwards;
checkRemote = block: any' checkBindAndHost block.remoteForwards;
checkMatchBlock = block: all (fn: fn block) [ checkLocal checkRemote checkDynamic ];
- in any' checkMatchBlock (builtins.attrValues cfg.matchBlocks);
+ in any' checkMatchBlock (map (block: block.data) (builtins.attrValues cfg.matchBlocks));
message = "Forwarded paths cannot have ports.";
}
];
- home.file.".ssh/config".text = ''
+ home.file.".ssh/config".text =
+ let
+ sortedMatchBlocks = hm.dag.topoSort cfg.matchBlocks;
+ sortedMatchBlocksStr = builtins.toJSON sortedMatchBlocks;
+ matchBlocks =
+ if sortedMatchBlocks ? result
+ then sortedMatchBlocks.result
+ else abort "Dependency cycle in SSH match blocks: ${sortedMatchBlocksStr}";
+ in ''
${concatStringsSep "\n" (
mapAttrsToList (n: v: "${n} ${v}") cfg.extraOptionOverrides)}
- ${concatStringsSep "\n\n" (
- map matchBlockStr (
- builtins.attrValues cfg.matchBlocks))}
+ ${concatStringsSep "\n\n" (map (block: matchBlockStr block.data) matchBlocks)}
Host *
ForwardAgent ${yn cfg.forwardAgent}
Compression ${yn cfg.compression}
ServerAliveInterval ${toString cfg.serverAliveInterval}
+ ServerAliveCountMax ${toString cfg.serverAliveCountMax}
HashKnownHosts ${yn cfg.hashKnownHosts}
UserKnownHostsFile ${cfg.userKnownHostsFile}
ControlMaster ${cfg.controlMaster}
diff --git a/home-manager/modules/programs/starship.nix b/home-manager/modules/programs/starship.nix
index 7c7819865f7..8462d331501 100644
--- a/home-manager/modules/programs/starship.nix
+++ b/home-manager/modules/programs/starship.nix
@@ -31,8 +31,23 @@ in {
};
settings = mkOption {
- type = types.attrs;
+ type = with types;
+ let
+ prim = either bool (either int str);
+ primOrPrimAttrs = either prim (attrsOf prim);
+ entry = either prim (listOf primOrPrimAttrs);
+ entryOrAttrsOf = t: either entry (attrsOf t);
+ entries = entryOrAttrsOf (entryOrAttrsOf entry);
+ in attrsOf entries // { description = "Starship configuration"; };
default = { };
+ example = literalExample ''
+ {
+ add_newline = false;
+ prompt_order = [ "line_break" "package" "line_break" "character" ];
+ scan_timeout = 10;
+ character.symbol = "➜";
+ }
+ '';
description = ''
Configuration written to
<filename>~/.config/starship.toml</filename>.
@@ -74,7 +89,7 @@ in {
mkIf (cfg.settings != { }) { source = configFile cfg.settings; };
programs.bash.initExtra = mkIf cfg.enableBashIntegration ''
- if [[ -z $INSIDE_EMACS ]]; then
+ if [[ $TERM != "dumb" && (-z $INSIDE_EMACS || $INSIDE_EMACS == "vterm") ]]; then
eval "$(${cfg.package}/bin/starship init bash)"
fi
'';
@@ -85,8 +100,8 @@ in {
fi
'';
- programs.fish.shellInit = mkIf cfg.enableFishIntegration ''
- if test -z "$INSIDE_EMACS"
+ programs.fish.promptInit = mkIf cfg.enableFishIntegration ''
+ if test "$TERM" != "dumb" -a \( -z "$INSIDE_EMACS" -o "$INSIDE_EMACS" = "vterm" \)
eval (${cfg.package}/bin/starship init fish)
end
'';
diff --git a/home-manager/modules/programs/termite.nix b/home-manager/modules/programs/termite.nix
index 8a05db03558..e3d704424e8 100644
--- a/home-manager/modules/programs/termite.nix
+++ b/home-manager/modules/programs/termite.nix
@@ -362,7 +362,7 @@ in {
${optionalString "cursor" cfg.cursorColor}
${optionalString "cursor_foreground" cfg.cursorForegroundColor}
${optionalString "foreground" cfg.foregroundColor}
- ${optionalString "foregroundBold" cfg.foregroundBoldColor}
+ ${optionalString "foreground_bold" cfg.foregroundBoldColor}
${optionalString "highlight" cfg.highlightColor}
${cfg.colorsExtra}
diff --git a/home-manager/modules/programs/tmux.nix b/home-manager/modules/programs/tmux.nix
index 766bc6238ba..a71c302ac6f 100644
--- a/home-manager/modules/programs/tmux.nix
+++ b/home-manager/modules/programs/tmux.nix
@@ -6,7 +6,7 @@ let
cfg = config.programs.tmux;
- pluginName = p: if types.package.check p then p.name else p.plugin.name;
+ pluginName = p: if types.package.check p then p.pname else p.plugin.pname;
pluginModule = types.submodule {
options = {
@@ -31,6 +31,13 @@ let
boolToStr = value: if value then "on" else "off";
tmuxConf = ''
+ ${optionalString cfg.sensibleOnTop ''
+ # ============================================= #
+ # Start with defaults from the Sensible plugin #
+ # --------------------------------------------- #
+ run-shell ${pkgs.tmuxPlugins.sensible.rtp}
+ # ============================================= #
+ ''}
set -g default-terminal "${cfg.terminal}"
set -g base-index ${toString cfg.baseIndex}
setw -g pane-base-index ${toString cfg.baseIndex}
@@ -74,10 +81,40 @@ let
setw -g clock-mode-style ${if cfg.clock24 then "24" else "12"}
set -s escape-time ${toString cfg.escapeTime}
set -g history-limit ${toString cfg.historyLimit}
-
- ${cfg.extraConfig}
'';
+ configPlugins = {
+ assertions = [(
+ let
+ hasBadPluginName = p: !(hasPrefix "tmuxplugin" (pluginName p));
+ badPlugins = filter hasBadPluginName cfg.plugins;
+ in
+ {
+ assertion = badPlugins == [];
+ message =
+ "Invalid tmux plugin (not prefixed with \"tmuxplugins\"): "
+ + concatMapStringsSep ", " pluginName badPlugins;
+ }
+ )];
+
+ home.file.".tmux.conf".text = ''
+ # ============================================= #
+ # Load plugins with Home Manager #
+ # --------------------------------------------- #
+
+ ${(concatMapStringsSep "\n\n" (p: ''
+ # ${pluginName p}
+ # ---------------------
+ ${p.extraConfig or ""}
+ run-shell ${
+ if types.package.check p
+ then p.rtp
+ else p.plugin.rtp
+ }
+ '') cfg.plugins)}
+ # ============================================= #
+ '';
+ };
in
{
@@ -214,7 +251,7 @@ in
};
secureSocket = mkOption {
- default = true;
+ default = pkgs.stdenv.isLinux;
type = types.bool;
description = ''
Store tmux socket under <filename>/run</filename>, which is more
@@ -258,63 +295,22 @@ in
};
config = mkIf cfg.enable (
- mkMerge [
+ mkMerge ([
{
home.packages = [ cfg.package ]
++ optional cfg.tmuxinator.enable pkgs.tmuxinator
++ optional cfg.tmuxp.enable pkgs.tmuxp;
-
- home.file.".tmux.conf".text = tmuxConf;
}
-
- (mkIf cfg.sensibleOnTop {
- home.file.".tmux.conf".text = mkBefore ''
- # ============================================= #
- # Start with defaults from the Sensible plugin #
- # --------------------------------------------- #
- run-shell ${pkgs.tmuxPlugins.sensible.rtp}
- # ============================================= #
- '';
- })
-
(mkIf cfg.secureSocket {
home.sessionVariables = {
TMUX_TMPDIR = ''''${XDG_RUNTIME_DIR:-"/run/user/\$(id -u)"}'';
};
})
- (mkIf (cfg.plugins != []) {
- assertions = [(
- let
- hasBadPluginName = p: !(hasPrefix "tmuxplugin" (pluginName p));
- badPlugins = filter hasBadPluginName cfg.plugins;
- in
- {
- assertion = badPlugins == [];
- message =
- "Invalid tmux plugin (not prefixed with \"tmuxplugins\"): "
- + concatMapStringsSep ", " pluginName badPlugins;
- }
- )];
-
- home.file.".tmux.conf".text = mkAfter ''
- # ============================================= #
- # Load plugins with Home Manager #
- # --------------------------------------------- #
-
- ${(concatMapStringsSep "\n\n" (p: ''
- # ${pluginName p}
- # ---------------------
- ${p.extraConfig or ""}
- run-shell ${
- if types.package.check p
- then p.rtp
- else p.plugin.rtp
- }
- '') cfg.plugins)}
- # ============================================= #
- '';
- })
- ]
+ # config file ~/.tmux.conf
+ { home.file.".tmux.conf".text = mkBefore tmuxConf; }
+ (mkIf (cfg.plugins != []) configPlugins)
+ { home.file.".tmux.conf".text = mkAfter cfg.extraConfig; }
+ ])
);
}
diff --git a/home-manager/modules/programs/vim.nix b/home-manager/modules/programs/vim.nix
index 39826a9a5d6..3325bf22516 100644
--- a/home-manager/modules/programs/vim.nix
+++ b/home-manager/modules/programs/vim.nix
@@ -5,7 +5,7 @@ with lib;
let
cfg = config.programs.vim;
- defaultPlugins = [ pkgs.vimPlugins.sensible ];
+ defaultPlugins = [ pkgs.vimPlugins.vim-sensible ];
knownSettings = {
background = types.enum [ "dark" "light" ];
diff --git a/home-manager/modules/programs/vscode.nix b/home-manager/modules/programs/vscode.nix
index cf7ac722210..099760c834a 100644
--- a/home-manager/modules/programs/vscode.nix
+++ b/home-manager/modules/programs/vscode.nix
@@ -20,11 +20,14 @@ let
"vscodium" = "vscode-oss";
}.${vscodePname};
- configFilePath =
+ userDir =
if pkgs.stdenv.hostPlatform.isDarwin then
- "Library/Application Support/${configDir}/User/settings.json"
+ "Library/Application Support/${configDir}/User"
else
- "${config.xdg.configHome}/${configDir}/User/settings.json";
+ "${config.xdg.configHome}/${configDir}/User";
+
+ configFilePath = "${userDir}/settings.json";
+ keybindingsFilePath = "${userDir}/keybindings.json";
# TODO: On Darwin where are the extensions?
extensionPath = ".${extensionDir}/extensions";
@@ -59,6 +62,45 @@ in
'';
};
+ keybindings = mkOption {
+ type = types.listOf (types.submodule {
+ options = {
+ key = mkOption {
+ type = types.str;
+ example = "ctrl+c";
+ description = "The key or key-combination to bind.";
+ };
+
+ command = mkOption {
+ type = types.str;
+ example = "editor.action.clipboardCopyAction";
+ description = "The VS Code command to execute.";
+ };
+
+ when = mkOption {
+ type = types.str;
+ default = "";
+ example = "textInputFocus";
+ description = "Optional context filter.";
+ };
+ };
+ });
+ default = [];
+ example = literalExample ''
+ [
+ {
+ key = "ctrl+c";
+ command = "editor.action.clipboardCopyAction";
+ when = "textInputFocus";
+ }
+ ]
+ '';
+ description = ''
+ Keybindings written to Visual Studio Code's
+ <filename>keybindings.json</filename>.
+ '';
+ };
+
extensions = mkOption {
type = types.listOf types.package;
default = [];
@@ -77,15 +119,13 @@ in
# Adapted from https://discourse.nixos.org/t/vscode-extensions-setup/1801/2
home.file =
let
+ subDir = "share/vscode/extensions";
toPaths = path:
- let
- p = "${path}/share/vscode/extensions";
- in
- # Links every dir in p to the extension path.
- mapAttrsToList (k: v:
- {
- "${extensionPath}/${k}".source = "${p}/${k}";
- }) (builtins.readDir p);
+ # Links every dir in path to the extension path.
+ mapAttrsToList (k: _:
+ {
+ "${extensionPath}/${k}".source = "${path}/${subDir}/${k}";
+ }) (builtins.readDir (path + "/${subDir}"));
toSymlink = concatMap toPaths cfg.extensions;
in
foldr
@@ -95,6 +135,10 @@ in
mkIf (cfg.userSettings != {}) {
text = builtins.toJSON cfg.userSettings;
};
+ "${keybindingsFilePath}" =
+ mkIf (cfg.keybindings != []) {
+ text = builtins.toJSON cfg.keybindings;
+ };
}
toSymlink;
};
diff --git a/home-manager/modules/programs/waybar.nix b/home-manager/modules/programs/waybar.nix
new file mode 100644
index 00000000000..369f6e32aba
--- /dev/null
+++ b/home-manager/modules/programs/waybar.nix
@@ -0,0 +1,363 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.programs.waybar;
+
+ # Used when generating warnings
+ modulesPath = "programs.waybar.settings.[].modules";
+
+ # Taken from <https://github.com/Alexays/Waybar/blob/adaf84304865e143e4e83984aaea6f6a7c9d4d96/src/factory.cpp>
+ defaultModuleNames = [
+ "sway/mode"
+ "sway/workspaces"
+ "sway/window"
+ "wlr/taskbar"
+ "idle_inhibitor"
+ "memory"
+ "cpu"
+ "clock"
+ "disk"
+ "tray"
+ "network"
+ "backlight"
+ "pulseaudio"
+ "mpd"
+ "temperature"
+ "bluetooth"
+ "battery"
+ ];
+
+ isValidCustomModuleName = x:
+ elem x defaultModuleNames || (hasPrefix "custom/" x && stringLength x > 7);
+
+ margins = let
+ mkMargin = name: {
+ "margin-${name}" = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 10;
+ description = "Margins value without unit.";
+ };
+ };
+ margins = map mkMargin [ "top" "left" "bottom" "right" ];
+ in foldl' mergeAttrs { } margins;
+
+ waybarBarConfig = with lib.types;
+ submodule {
+ options = {
+ layer = mkOption {
+ type = nullOr (enum [ "top" "bottom" ]);
+ default = null;
+ description = ''
+ Decide if the bar is displayed in front (<code>"top"</code>)
+ of the windows or behind (<code>"bottom"</code>).
+ '';
+ example = "top";
+ };
+
+ output = mkOption {
+ type = nullOr (either str (listOf str));
+ default = null;
+ example = literalExample ''
+ [ "DP-1" "!DP-2" "!DP-3" ]
+ '';
+ description = ''
+ Specifies on which screen this bar will be displayed.
+ Exclamation mark(!) can be used to exclude specific output.
+ '';
+ };
+
+ position = mkOption {
+ type = nullOr (enum [ "top" "bottom" "left" "right" ]);
+ default = null;
+ example = "right";
+ description = "Bar position relative to the output.";
+ };
+
+ height = mkOption {
+ type = nullOr ints.unsigned;
+ default = null;
+ example = 5;
+ description =
+ "Height to be used by the bar if possible. Leave blank for a dynamic value.";
+ };
+
+ width = mkOption {
+ type = nullOr ints.unsigned;
+ default = null;
+ example = 5;
+ description =
+ "Width to be used by the bar if possible. Leave blank for a dynamic value.";
+ };
+
+ modules-left = mkOption {
+ type = nullOr (listOf str);
+ default = null;
+ description = "Modules that will be displayed on the left.";
+ example = literalExample ''
+ [ "sway/workspaces" "sway/mode" "wlr/taskbar" ]
+ '';
+ };
+
+ modules-center = mkOption {
+ type = nullOr (listOf str);
+ default = null;
+ description = "Modules that will be displayed in the center.";
+ example = literalExample ''
+ [ "sway/window" ]
+ '';
+ };
+
+ modules-right = mkOption {
+ type = nullOr (listOf str);
+ default = null;
+ description = "Modules that will be displayed on the right.";
+ example = literalExample ''
+ [ "mpd" "custom/mymodule#with-css-id" "temperature" ]
+ '';
+ };
+
+ modules = mkOption {
+ type = attrsOf unspecified;
+ default = { };
+ description = "Modules configuration.";
+ example = literalExample ''
+ {
+ "sway/window" = {
+ max-length = 50;
+ };
+ "clock" = {
+ format-alt = "{:%a, %d. %b %H:%M}";
+ };
+ }
+ '';
+ };
+
+ margin = mkOption {
+ type = nullOr str;
+ default = null;
+ description = "Margins value using the CSS format without units.";
+ example = "20 5";
+ };
+
+ inherit (margins) margin-top margin-left margin-bottom margin-right;
+
+ name = mkOption {
+ type = nullOr str;
+ default = null;
+ description =
+ "Optional name added as a CSS class, for styling multiple waybars.";
+ example = "waybar-1";
+ };
+
+ gtk-layer-shell = mkOption {
+ type = nullOr bool;
+ default = null;
+ example = false;
+ description =
+ "Option to disable the use of gtk-layer-shell for popups.";
+ };
+ };
+ };
+in {
+ meta.maintainers = [ hm.maintainers.berbiche ];
+
+ options.programs.waybar = with lib.types; {
+ enable = mkEnableOption "Waybar";
+
+ package = mkOption {
+ type = package;
+ default = pkgs.waybar;
+ defaultText = literalExample "${pkgs.waybar}";
+ description = ''
+ Waybar package to use. Set to <code>null</code> to use the default module.
+ '';
+ };
+
+ settings = mkOption {
+ type = listOf waybarBarConfig;
+ default = [ ];
+ description = ''
+ Configuration for Waybar, see <link
+ xlink:href="https://github.com/Alexays/Waybar/wiki/Configuration"/>
+ for supported values.
+ '';
+ example = literalExample ''
+ [
+ {
+ layer = "top";
+ position = "top";
+ height = 30;
+ output = [
+ "eDP-1"
+ "HDMI-A-1"
+ ];
+ modules-left = [ "sway/workspaces" "sway/mode" "wlr/taskbar" ];
+ modules-center = [ "sway/window" "custom/hello-from-waybar" ];
+ modules-right = [ "mpd" "custom/mymodule#with-css-id" "temperature" ];
+ modules = {
+ "sway/workspaces" = {
+ disable-scroll = true;
+ all-outputs = true;
+ };
+ "custom/hello-from-waybar" = {
+ format = "hello {}";
+ max-length = 40;
+ interval = "once";
+ exec = pkgs.writeShellScript "hello-from-waybar" '''
+ echo "from within waybar"
+ ''';
+ };
+ };
+ }
+ ]
+ '';
+ };
+
+ systemd.enable = mkEnableOption "Waybar systemd integration";
+
+ style = mkOption {
+ type = nullOr str;
+ default = null;
+ description = ''
+ CSS style of the bar.
+ See <link xlink:href="https://github.com/Alexays/Waybar/wiki/Configuration"/>
+ for the documentation.
+ '';
+ example = ''
+ * {
+ border: none;
+ border-radius: 0;
+ font-family: Source Code Pro;
+ }
+ window#waybar {
+ background: #16191C;
+ color: #AAB2BF;
+ }
+ #workspaces button {
+ padding: 0 5px;
+ }
+ '';
+ };
+ };
+
+ config = let
+ # Inspired by https://github.com/NixOS/nixpkgs/pull/89781
+ writePrettyJSON = name: x:
+ pkgs.runCommandLocal name { } ''
+ ${pkgs.jq}/bin/jq . > $out <<<${escapeShellArg (builtins.toJSON x)}
+ '';
+
+ configSource = let
+ # Removes nulls because Waybar ignores them for most values
+ removeNulls = filterAttrs (_: v: v != null);
+
+ # Makes the actual valid configuration Waybar accepts
+ # (strips our custom settings before converting to JSON)
+ makeConfiguration = configuration:
+ let
+ # The "modules" option is not valid in the JSON
+ # as its descendants have to live at the top-level
+ settingsWithoutModules =
+ filterAttrs (n: _: n != "modules") configuration;
+ settingsModules =
+ optionalAttrs (configuration.modules != { }) configuration.modules;
+ in removeNulls (settingsWithoutModules // settingsModules);
+ # The clean list of configurations
+ finalConfiguration = map makeConfiguration cfg.settings;
+ in writePrettyJSON "waybar-config.json" finalConfiguration;
+
+ warnings = let
+ mkUnreferencedModuleWarning = name:
+ "The module '${name}' defined in '${modulesPath}' is not referenced "
+ + "in either `modules-left`, `modules-center` or `modules-right` of Waybar's options";
+ mkUndefinedModuleWarning = settings: name:
+ let
+ # Locations where the module is undefined (a combination modules-{left,center,right})
+ locations = flip filter [ "left" "center" "right" ]
+ (x: elem name settings."modules-${x}");
+ mkPath = loc: "'${modulesPath}-${loc}'";
+ # The modules-{left,center,right} configuration that includes
+ # an undefined module
+ path = concatMapStringsSep " and " mkPath locations;
+ in "The module '${name}' defined in ${path} is neither "
+ + "a default module or a custom module declared in '${modulesPath}'";
+ mkInvalidModuleNameWarning = name:
+ "The custom module '${name}' defined in '${modulesPath}' is not a valid "
+ + "module name. A custom module's name must start with 'custom/' "
+ + "like 'custom/mymodule' for instance";
+
+ # Find all modules in `modules-{left,center,right}` and `modules` not declared/referenced.
+ # `cfg.settings` is a list of Waybar configurations
+ # and we need to preserve the index for appropriate warnings
+ allFaultyModules = flip map cfg.settings (settings:
+ let
+ allModules = unique
+ (concatMap (x: attrByPath [ "modules-${x}" ] [ ] settings) [
+ "left"
+ "center"
+ "right"
+ ]);
+ declaredModules = attrNames settings.modules;
+ # Modules declared in `modules` but not referenced in `modules-{left,center,right}`
+ unreferencedModules = subtractLists allModules declaredModules;
+ # Modules listed in modules-{left,center,right} that are not default modules
+ nonDefaultModules = subtractLists defaultModuleNames allModules;
+ # Modules referenced in `modules-{left,center,right}` but not declared in `modules`
+ undefinedModules = subtractLists declaredModules nonDefaultModules;
+ # Check for invalid module names
+ invalidModuleNames =
+ filter (m: !isValidCustomModuleName m) (attrNames settings.modules);
+ in {
+ # The Waybar bar configuration (since config.settings is a list)
+ settings = settings;
+ undef = undefinedModules;
+ unref = unreferencedModules;
+ invalidName = invalidModuleNames;
+ });
+
+ allWarnings = flip concatMap allFaultyModules
+ ({ settings, undef, unref, invalidName }:
+ let
+ unreferenced = map mkUnreferencedModuleWarning unref;
+ undefined = map (mkUndefinedModuleWarning settings) undef;
+ invalid = map mkInvalidModuleNameWarning invalidName;
+ in undefined ++ unreferenced ++ invalid);
+ in allWarnings;
+
+ in mkIf cfg.enable (mkMerge [
+ { home.packages = [ cfg.package ]; }
+ (mkIf (cfg.settings != [ ]) {
+ # Generate warnings about defined but unreferenced modules
+ inherit warnings;
+
+ xdg.configFile."waybar/config".source = configSource;
+ })
+ (mkIf (cfg.style != null) {
+ xdg.configFile."waybar/style.css".text = cfg.style;
+ })
+ (mkIf cfg.systemd.enable {
+ systemd.user.services.waybar = {
+ Unit = {
+ Description =
+ "Highly customizable Wayland bar for Sway and Wlroots based compositors.";
+ Documentation = "https://github.com/Alexays/Waybar/wiki";
+ PartOf = [ "graphical-session.target" ];
+ Requisite = [ "dbus.service" ];
+ After = [ "dbus.service" ];
+ };
+
+ Service = {
+ Type = "dbus";
+ BusName = "fr.arouillard.waybar";
+ ExecStart = "${cfg.package}/bin/waybar";
+ Restart = "always";
+ RestartSec = "1sec";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ })
+ ]);
+}
diff --git a/home-manager/modules/programs/zoxide.nix b/home-manager/modules/programs/zoxide.nix
new file mode 100644
index 00000000000..842ff109294
--- /dev/null
+++ b/home-manager/modules/programs/zoxide.nix
@@ -0,0 +1,79 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.zoxide;
+
+in {
+ meta.maintainers = [ maintainers.marsam ];
+
+ options.programs.zoxide = {
+ enable = mkEnableOption "zoxide";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.zoxide;
+ defaultText = literalExample "pkgs.zoxide";
+ description = ''
+ Zoxide package to install.
+ '';
+ };
+
+ options = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "--no-aliases" ];
+ description = ''
+ List of options to pass to zoxide.
+ '';
+ };
+
+ enableBashIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Bash integration.
+ '';
+ };
+
+ enableZshIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Zsh integration.
+ '';
+ };
+
+ enableFishIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Fish integration.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+
+ programs.bash.initExtra = mkIf cfg.enableBashIntegration ''
+ eval "$(${cfg.package}/bin/zoxide init bash ${
+ concatStringsSep " " cfg.options
+ })"
+ '';
+
+ programs.zsh.initExtra = mkIf cfg.enableZshIntegration ''
+ eval "$(${cfg.package}/bin/zoxide init zsh ${
+ concatStringsSep " " cfg.options
+ })"
+ '';
+
+ programs.fish.shellInit = mkIf cfg.enableFishIntegration ''
+ ${cfg.package}/bin/zoxide init fish ${
+ concatStringsSep " " cfg.options
+ } | source
+ '';
+ };
+}
diff --git a/home-manager/modules/programs/zplug.nix b/home-manager/modules/programs/zplug.nix
new file mode 100644
index 00000000000..6cb5e98e313
--- /dev/null
+++ b/home-manager/modules/programs/zplug.nix
@@ -0,0 +1,60 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.zsh.zplug;
+
+ pluginModule = types.submodule ({ config, ... }: {
+ options = {
+ name = mkOption {
+ type = types.str;
+ description = "The name of the plugin.";
+ };
+
+ tags = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = "The plugin tags.";
+ };
+ };
+
+ });
+
+in {
+ options.programs.zsh.zplug = {
+ enable = mkEnableOption "zplug - a zsh plugin manager";
+
+ plugins = mkOption {
+ default = [ ];
+ type = types.listOf pluginModule;
+ description = "List of zplug plugins.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.zplug ];
+
+ programs.zsh.initExtraBeforeCompInit = ''
+ source ${pkgs.zplug}/init.zsh
+
+ ${optionalString (cfg.plugins != [ ]) ''
+ ${concatStrings (map (plugin: ''
+ zplug "${plugin.name}"${
+ optionalString (plugin.tags != [ ]) ''
+ ${concatStrings (map (tag: ", ${tag}") plugin.tags)}
+ ''
+ }
+ '') cfg.plugins)}
+ ''}
+
+ if ! zplug check; then
+ zplug install
+ fi
+
+ zplug load
+ '';
+
+ };
+}
diff --git a/home-manager/modules/programs/zsh.nix b/home-manager/modules/programs/zsh.nix
index c5694e1f704..ed65d5fe487 100644
--- a/home-manager/modules/programs/zsh.nix
+++ b/home-manager/modules/programs/zsh.nix
@@ -18,6 +18,10 @@ let
mapAttrsToList (k: v: "alias ${k}=${lib.escapeShellArg v}") cfg.shellAliases
);
+ globalAliasesStr = concatStringsSep "\n" (
+ mapAttrsToList (k: v: "alias -g ${k}=${lib.escapeShellArg v}") cfg.shellGlobalAliases
+ );
+
zdotdir = "$HOME/" + cfg.dotDir;
bindkeyCommands = {
@@ -152,6 +156,17 @@ let
Name of the theme to be used by oh-my-zsh.
'';
};
+
+ extraConfig = mkOption {
+ default = "";
+ example = ''
+ zstyle :omz:plugins:ssh-agent identities id_rsa id_rsa2 id_github
+ '';
+ type = types.lines;
+ description = ''
+ Extra settings for plugins.
+ '';
+ };
};
};
@@ -170,6 +185,14 @@ in
type = types.nullOr types.bool;
};
+ cdpath = mkOption {
+ default = [];
+ description = ''
+ List of paths to autocomplete calls to `cd`.
+ '';
+ type = types.listOf types.str;
+ };
+
dotDir = mkOption {
default = null;
example = ".config/zsh";
@@ -183,7 +206,12 @@ in
shellAliases = mkOption {
default = {};
- example = { ll = "ls -l"; ".." = "cd .."; };
+ example = literalExample ''
+ {
+ ll = "ls -l";
+ ".." = "cd ..";
+ }
+ '';
description = ''
An attribute set that maps aliases (the top level attribute names in
this option) to command strings or directly to build outputs.
@@ -191,6 +219,21 @@ in
type = types.attrsOf types.str;
};
+ shellGlobalAliases = mkOption {
+ default = {};
+ example = literalExample ''
+ {
+ UUID = "$(uuidgen | tr -d \\n)";
+ G = "| grep";
+ }
+ '';
+ description = ''
+ Similar to <varname><link linkend="opt-programs.zsh.shellAliases">opt-programs.zsh.shellAliases</link></varname>,
+ but are substituted anywhere on a line.
+ '';
+ type = types.attrsOf types.str;
+ };
+
enableCompletion = mkOption {
default = true;
description = ''
@@ -357,6 +400,10 @@ in
home.file."${relToDotDir ".zshrc"}".text = ''
typeset -U path cdpath fpath manpath
+ ${optionalString (cfg.cdpath != []) ''
+ cdpath+=(${concatStringsSep " " cfg.cdpath})
+ ''}
+
for profile in ''${(z)NIX_PROFILES}; do
fpath+=($profile/share/zsh/site-functions $profile/share/zsh/$ZSH_VERSION/functions $profile/share/zsh/vendor-completions)
done
@@ -393,6 +440,8 @@ in
${envVarsStr}
${optionalString cfg.oh-my-zsh.enable ''
+ # oh-my-zsh extra settings for plugins
+ ${cfg.oh-my-zsh.extraConfig}
# oh-my-zsh configuration generated by NixOS
${optionalString (cfg.oh-my-zsh.plugins != [])
"plugins=(${concatStringsSep " " cfg.oh-my-zsh.plugins})"
@@ -433,6 +482,9 @@ in
# Aliases
${aliasesStr}
+
+ # Global Aliases
+ ${globalAliasesStr}
'';
}
diff --git a/home-manager/modules/services/clipmenu.nix b/home-manager/modules/services/clipmenu.nix
new file mode 100644
index 00000000000..2e1c10e43d8
--- /dev/null
+++ b/home-manager/modules/services/clipmenu.nix
@@ -0,0 +1,43 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.clipmenu;
+
+in {
+ meta.maintainers = [ maintainers.DamienCassou ];
+
+ options.services.clipmenu = {
+ enable = mkEnableOption "clipmenu, the clipboard management daemon";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.clipmenu;
+ defaultText = "pkgs.clipmenu";
+ description = "clipmenu derivation to use.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+
+ systemd.user.services.clipmenu = {
+ Unit = {
+ Description = "Clipboard management daemon";
+ After = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ ExecStart = "${cfg.package}/bin/clipmenud";
+ Environment = "PATH=${
+ makeBinPath
+ (with pkgs; [ coreutils findutils gnugrep gnused systemd ])
+ }";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+}
diff --git a/home-manager/modules/services/compton.nix b/home-manager/modules/services/compton.nix
index c5b96af34da..0b8e7232b45 100644
--- a/home-manager/modules/services/compton.nix
+++ b/home-manager/modules/services/compton.nix
@@ -1,291 +1,43 @@
{ config, lib, pkgs, ... }:
-with lib;
-with builtins;
-
-let
-
- cfg = config.services.compton;
-
- configFile = pkgs.writeText "compton.conf" (optionalString cfg.fade ''
- # fading
- fading = true;
- fade-delta = ${toString cfg.fadeDelta};
- fade-in-step = ${elemAt cfg.fadeSteps 0};
- fade-out-step = ${elemAt cfg.fadeSteps 1};
- fade-exclude = ${toJSON cfg.fadeExclude};
- '' + optionalString cfg.shadow ''
-
- # shadows
- shadow = true;
- shadow-offset-x = ${toString (elemAt cfg.shadowOffsets 0)};
- shadow-offset-y = ${toString (elemAt cfg.shadowOffsets 1)};
- shadow-opacity = ${cfg.shadowOpacity};
- shadow-exclude = ${toJSON cfg.shadowExclude};
- no-dock-shadow = ${toJSON cfg.noDockShadow};
- no-dnd-shadow = ${toJSON cfg.noDNDShadow};
- '' + optionalString cfg.blur ''
-
- # blur
- blur-background = true;
- blur-background-exclude = ${toJSON cfg.blurExclude};
- '' + ''
-
- # opacity
- active-opacity = ${cfg.activeOpacity};
- inactive-opacity = ${cfg.inactiveOpacity};
- menu-opacity = ${cfg.menuOpacity};
- opacity-rule = ${toJSON cfg.opacityRule};
-
- # other options
- backend = ${toJSON cfg.backend};
- vsync = ${toJSON cfg.vSync};
- refresh-rate = ${toString cfg.refreshRate};
- '' + cfg.extraOptions);
-
-in {
-
- options.services.compton = {
- enable = mkEnableOption "Compton X11 compositor";
-
- blur = mkOption {
- type = types.bool;
- default = false;
- description = ''
- Enable background blur on transparent windows.
- '';
- };
-
- blurExclude = mkOption {
- type = types.listOf types.str;
- default = [ ];
- example = [ "class_g = 'slop'" "class_i = 'polybar'" ];
- description = ''
- List of windows to exclude background blur.
- See the
- <citerefentry>
- <refentrytitle>compton</refentrytitle>
- <manvolnum>1</manvolnum>
- </citerefentry>
- man page for more examples.
- '';
- };
-
- fade = mkOption {
- type = types.bool;
- default = false;
- description = ''
- Fade windows in and out.
- '';
- };
-
- fadeDelta = mkOption {
- type = types.int;
- default = 10;
- example = 5;
- description = ''
- Time between fade animation step (in ms).
- '';
- };
-
- fadeSteps = mkOption {
- type = types.listOf types.str;
- default = [ "0.028" "0.03" ];
- example = [ "0.04" "0.04" ];
- description = ''
- Opacity change between fade steps (in and out).
- '';
- };
-
- fadeExclude = mkOption {
- type = types.listOf types.str;
- default = [ ];
- example = [ "window_type *= 'menu'" "name ~= 'Firefox$'" "focused = 1" ];
- description = ''
- List of conditions of windows that should not be faded.
- See the
- <citerefentry>
- <refentrytitle>compton</refentrytitle>
- <manvolnum>1</manvolnum>
- </citerefentry>
- man page for more examples.
- '';
- };
-
- shadow = mkOption {
- type = types.bool;
- default = false;
- description = ''
- Draw window shadows.
- '';
- };
-
- shadowOffsets = mkOption {
- type = types.listOf types.int;
- default = [ (-15) (-15) ];
- example = [ (-10) (-15) ];
- description = ''
- Horizontal and vertical offsets for shadows (in pixels).
- '';
- };
-
- shadowOpacity = mkOption {
- type = types.str;
- default = "0.75";
- example = "0.8";
- description = ''
- Window shadows opacity (number in range 0 - 1).
- '';
- };
-
- shadowExclude = mkOption {
- type = types.listOf types.str;
- default = [ ];
- example = [ "window_type *= 'menu'" "name ~= 'Firefox$'" "focused = 1" ];
- description = ''
- List of conditions of windows that should have no shadow.
- See the
- <citerefentry>
- <refentrytitle>compton</refentrytitle>
- <manvolnum>1</manvolnum>
- </citerefentry>
- man page for more examples.
- '';
- };
-
- noDockShadow = mkOption {
- type = types.bool;
- default = true;
- description = ''
- Avoid shadow on docks.
- '';
- };
-
- noDNDShadow = mkOption {
- type = types.bool;
- default = true;
- description = ''
- Avoid shadow on drag-and-drop windows.
- '';
- };
-
- activeOpacity = mkOption {
- type = types.str;
- default = "1.0";
- example = "0.8";
- description = ''
- Opacity of active windows.
- '';
- };
-
- inactiveOpacity = mkOption {
- type = types.str;
- default = "1.0";
- example = "0.8";
- description = ''
- Opacity of inactive windows.
- '';
- };
-
- menuOpacity = mkOption {
- type = types.str;
- default = "1.0";
- example = "0.8";
- description = ''
- Opacity of dropdown and popup menu.
- '';
- };
-
- opacityRule = mkOption {
- type = types.listOf types.str;
- default = [ ];
- example = [ "87:class_i ?= 'scratchpad'" "91:class_i ?= 'xterm'" ];
- description = ''
- List of opacity rules.
- See the
- <citerefentry>
- <refentrytitle>compton</refentrytitle>
- <manvolnum>1</manvolnum>
- </citerefentry>
- man page for more examples.
- '';
- };
-
- backend = mkOption {
- type = types.str;
- default = "glx";
- description = ''
- Backend to use: <literal>glx</literal> or <literal>xrender</literal>.
- '';
- };
-
- vSync = mkOption {
- type = types.str;
- default = "none";
- example = "opengl-swc";
- description = ''
- Enable vertical synchronization using the specified method.
- See the
- <citerefentry>
- <refentrytitle>compton</refentrytitle>
- <manvolnum>1</manvolnum>
- </citerefentry>
- man page for available methods.
- '';
- };
-
- refreshRate = mkOption {
- type = types.int;
- default = 0;
- example = 60;
- description = ''
- Screen refresh rate (0 = automatically detect).
- '';
- };
-
- package = mkOption {
- type = types.package;
- default = pkgs.compton;
- defaultText = literalExample "pkgs.compton";
- example = literalExample "pkgs.compton";
- description = ''
- Compton derivation to use.
- '';
- };
-
- extraOptions = mkOption {
- type = types.str;
- default = "";
- example = ''
- unredir-if-possible = true;
- dbe = true;
- '';
- description = ''
- Additional Compton configuration.
- '';
- };
+with lib; {
+ imports = let
+ old = n: [ "services" "compton" n ];
+ new = n: [ "services" "picom" n ];
+ in [
+ (mkRenamedOptionModule (old "activeOpacity") (new "activeOpacity"))
+ (mkRenamedOptionModule (old "backend") (new "backend"))
+ (mkRenamedOptionModule (old "blur") (new "blur"))
+ (mkRenamedOptionModule (old "blurExclude") (new "blurExclude"))
+ (mkRenamedOptionModule (old "extraOptions") (new "extraOptions"))
+ (mkRenamedOptionModule (old "fade") (new "fade"))
+ (mkRenamedOptionModule (old "fadeDelta") (new "fadeDelta"))
+ (mkRenamedOptionModule (old "fadeExclude") (new "fadeExclude"))
+ (mkRenamedOptionModule (old "fadeSteps") (new "fadeSteps"))
+ (mkRenamedOptionModule (old "inactiveDim") (new "inactiveDim"))
+ (mkRenamedOptionModule (old "inactiveOpacity") (new "inactiveOpacity"))
+ (mkRenamedOptionModule (old "menuOpacity") (new "menuOpacity"))
+ (mkRenamedOptionModule (old "noDNDShadow") (new "noDNDShadow"))
+ (mkRenamedOptionModule (old "noDockShadow") (new "noDockShadow"))
+ (mkRenamedOptionModule (old "opacityRule") (new "opacityRule"))
+ (mkRenamedOptionModule (old "package") (new "package"))
+ (mkRenamedOptionModule (old "refreshRate") (new "refreshRate"))
+ (mkRenamedOptionModule (old "shadow") (new "shadow"))
+ (mkRenamedOptionModule (old "shadowExclude") (new "shadowExclude"))
+ (mkRenamedOptionModule (old "shadowOffsets") (new "shadowOffsets"))
+ (mkRenamedOptionModule (old "shadowOpacity") (new "shadowOpacity"))
+ (mkChangedOptionModule (old "vSync") (new "vSync") (v: v != "none"))
+ ];
+
+ options.services.compton.enable = mkEnableOption "Compton X11 compositor" // {
+ visible = false;
};
- config = mkIf cfg.enable {
- home.packages = [ cfg.package ];
-
- systemd.user.services.compton = {
- Unit = {
- Description = "Compton X11 compositor";
- After = [ "graphical-session-pre.target" ];
- PartOf = [ "graphical-session.target" ];
- };
-
- Install = { WantedBy = [ "graphical-session.target" ]; };
+ config = mkIf config.services.compton.enable {
+ warnings = [
+ "Obsolete option `services.compton.enable' is used. It was renamed to `services.picom.enable'."
+ ];
- Service = {
- ExecStart = "${cfg.package}/bin/compton --config ${configFile}";
- Restart = "always";
- RestartSec = 3;
- } // optionalAttrs (cfg.backend == "glx") {
- # Temporarily fixes corrupt colours with Mesa 18.
- Environment = [ "allow_rgb10_configs=false" ];
- };
- };
+ services.picom.enable = true;
};
}
diff --git a/home-manager/modules/services/dropbox.nix b/home-manager/modules/services/dropbox.nix
new file mode 100644
index 00000000000..bcf3ba2b457
--- /dev/null
+++ b/home-manager/modules/services/dropbox.nix
@@ -0,0 +1,77 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.dropbox;
+ baseDir = ".dropbox-hm";
+ dropboxCmd = "${pkgs.dropbox-cli}/bin/dropbox";
+ homeBaseDir = "${config.home.homeDirectory}/${baseDir}";
+
+in {
+ meta.maintainers = [ maintainers.eyjhb ];
+
+ options = {
+ services.dropbox = {
+ enable = mkEnableOption "Dropbox daemon";
+
+ path = mkOption {
+ type = types.path;
+ default = "${config.home.homeDirectory}/Dropbox";
+ defaultText =
+ literalExample ''"''${config.home.homeDirectory}/Dropbox"'';
+ apply = toString; # Prevent copies to Nix store.
+ description = "Where to put the Dropbox directory.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.dropbox-cli ];
+
+ systemd.user.services.dropbox = {
+ Unit = { Description = "dropbox"; };
+
+ Install = { WantedBy = [ "default.target" ]; };
+
+ Service = {
+ Environment = [ "HOME=${homeBaseDir}" "DISPLAY=" ];
+
+ Type = "forking";
+ PIDFile = "${homeBaseDir}/.dropbox/dropbox.pid";
+
+ Restart = "on-failure";
+ PrivateTmp = true;
+ ProtectSystem = "full";
+ Nice = 10;
+
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ ExecStop = "${dropboxCmd} stop";
+ ExecStart = toString (pkgs.writeShellScript "dropbox-start" ''
+ # ensure we have the dirs we need
+ $DRY_RUN_CMD ${pkgs.coreutils}/bin/mkdir $VERBOSE_ARG -p \
+ ${homeBaseDir}/{.dropbox,.dropbox-dist,Dropbox}
+
+ # symlink them as needed
+ if [[ ! -d ${config.home.homeDirectory}/.dropbox ]]; then
+ $DRY_RUN_CMD ${pkgs.coreutils}/bin/ln $VERBOSE_ARG -s \
+ ${homeBaseDir}/.dropbox ${config.home.homeDirectory}/.dropbox
+ fi
+
+ if [[ ! -d ${escapeShellArg cfg.path} ]]; then
+ $DRY_RUN_CMD ${pkgs.coreutils}/bin/ln $VERBOSE_ARG -s \
+ ${homeBaseDir}/Dropbox ${escapeShellArg cfg.path}
+ fi
+
+ # get the dropbox bins if needed
+ if [[ ! -f $HOME/.dropbox-dist/VERSION ]]; then
+ ${pkgs.coreutils}/bin/yes | ${dropboxCmd} update
+ fi
+
+ ${dropboxCmd} start
+ '');
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/services/dunst.nix b/home-manager/modules/services/dunst.nix
index d32e875137b..5fbbb884a8e 100644
--- a/home-manager/modules/services/dunst.nix
+++ b/home-manager/modules/services/dunst.nix
@@ -45,7 +45,7 @@ let
};
hicolorTheme = {
- package = pkgs.hicolor_icon_theme;
+ package = pkgs.hicolor-icon-theme;
name = "hicolor";
size = "32x32";
};
@@ -89,6 +89,8 @@ in {
config = mkIf cfg.enable (mkMerge [
{
+ home.packages = [ (getOutput "man" pkgs.dunst) ];
+
xdg.dataFile."dbus-1/services/org.knopwob.dunst.service".source =
"${pkgs.dunst}/share/dbus-1/services/org.knopwob.dunst.service";
diff --git a/home-manager/modules/services/emacs.nix b/home-manager/modules/services/emacs.nix
index 5b0e88db72d..a73b750c513 100644
--- a/home-manager/modules/services/emacs.nix
+++ b/home-manager/modules/services/emacs.nix
@@ -7,36 +7,138 @@ let
cfg = config.services.emacs;
emacsCfg = config.programs.emacs;
emacsBinPath = "${emacsCfg.finalPackage}/bin";
+ emacsVersion = getVersion emacsCfg.finalPackage;
+
+ # Adapted from upstream emacs.desktop
+ clientDesktopItem = pkgs.makeDesktopItem rec {
+ name = "emacsclient";
+ desktopName = "Emacs Client";
+ genericName = "Text Editor";
+ comment = "Edit text";
+ mimeType =
+ "text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;";
+ exec = "${emacsBinPath}/emacsclient ${
+ concatStringsSep " " cfg.client.arguments
+ } %F";
+ icon = "emacs";
+ type = "Application";
+ terminal = "false";
+ categories = "Utility;TextEditor;";
+ extraEntries = ''
+ StartupWMClass=Emacs
+ '';
+ };
+
+ # Match the default socket path for the Emacs version so emacsclient continues
+ # to work without wrapping it. It might be worthwhile to allow customizing the
+ # socket path, but we would want to wrap emacsclient in the user profile to
+ # connect to the alternative socket by default for Emacs 26, and set
+ # EMACS_SOCKET_NAME for Emacs 27.
+ #
+ # As systemd doesn't perform variable expansion for the ListenStream param, we
+ # would also have to solve the problem of matching the shell path to the path
+ # used in the socket unit, which would likely involve templating. It seems of
+ # little value for the most common use case of one Emacs daemon per user
+ # session.
+ socketPath = if versionAtLeast emacsVersion "27" then
+ "%t/emacs/server"
+ else
+ "%T/emacs%U/server";
in {
- options.services.emacs = { enable = mkEnableOption "the Emacs daemon"; };
-
- config = mkIf cfg.enable {
- assertions = [{
- assertion = emacsCfg.enable;
- message = "The Emacs service module requires"
- + " 'programs.emacs.enable = true'.";
- }];
-
- systemd.user.services.emacs = {
- Unit = {
- Description = "Emacs: the extensible, self-documenting text editor";
- Documentation =
- "info:emacs man:emacs(1) https://gnu.org/software/emacs/";
-
- # Avoid killing the Emacs session, which may be full of
- # unsaved buffers.
- X-RestartIfChanged = false;
- };
+ meta.maintainers = [ maintainers.tadfisher ];
- Service = {
- ExecStart =
- "${pkgs.runtimeShell} -l -c 'exec ${emacsBinPath}/emacs --fg-daemon'";
- ExecStop = "${emacsBinPath}/emacsclient --eval '(kill-emacs)'";
- Restart = "on-failure";
+ options.services.emacs = {
+ enable = mkEnableOption "the Emacs daemon";
+
+ client = {
+ enable = mkEnableOption "generation of Emacs client desktop file";
+ arguments = mkOption {
+ type = with types; listOf str;
+ default = [ "-c" ];
+ description = ''
+ Command-line arguments to pass to <command>emacsclient</command>.
+ '';
};
+ };
- Install = { WantedBy = [ "default.target" ]; };
+ # Attrset for forward-compatibility; there may be a need to customize the
+ # socket path, though allowing for such is not easy to do as systemd socket
+ # units don't perform variable expansion for 'ListenStream'.
+ socketActivation = {
+ enable = mkEnableOption "systemd socket activation for the Emacs service";
};
};
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+ assertions = [
+ {
+ assertion = emacsCfg.enable;
+ message = "The Emacs service module requires"
+ + " 'programs.emacs.enable = true'.";
+ }
+ {
+ assertion = cfg.socketActivation.enable
+ -> versionAtLeast emacsVersion "26";
+ message = "Socket activation requires Emacs 26 or newer.";
+ }
+ ];
+
+ systemd.user.services.emacs = {
+ Unit = {
+ Description = "Emacs: the extensible, self-documenting text editor";
+ Documentation =
+ "info:emacs man:emacs(1) https://gnu.org/software/emacs/";
+
+ # Avoid killing the Emacs session, which may be full of
+ # unsaved buffers.
+ X-RestartIfChanged = false;
+ };
+
+ Service = {
+ # We wrap ExecStart in a login shell so Emacs starts with the user's
+ # environment, most importantly $PATH and $NIX_PROFILES. It may be
+ # worth investigating a more targeted approach for user services to
+ # import the user environment.
+ ExecStart = ''
+ ${pkgs.runtimeShell} -l -c "${emacsBinPath}/emacs --fg-daemon${
+ # In case the user sets 'server-directory' or 'server-name' in
+ # their Emacs config, we want to specify the socket path explicitly
+ # so launching 'emacs.service' manually doesn't break emacsclient
+ # when using socket activation.
+ optionalString cfg.socketActivation.enable
+ "=${escapeShellArg socketPath}"
+ }"'';
+ # We use '(kill-emacs 0)' to avoid exiting with a failure code, which
+ # would restart the service immediately.
+ ExecStop = "${emacsBinPath}/emacsclient --eval '(kill-emacs 0)'";
+ Restart = "on-failure";
+ };
+ } // optionalAttrs (!cfg.socketActivation.enable) {
+ Install = { WantedBy = [ "default.target" ]; };
+ };
+
+ home.packages = optional cfg.client.enable clientDesktopItem;
+ }
+
+ (mkIf cfg.socketActivation.enable {
+ systemd.user.sockets.emacs = {
+ Unit = {
+ Description = "Emacs: the extensible, self-documenting text editor";
+ Documentation =
+ "info:emacs man:emacs(1) https://gnu.org/software/emacs/";
+ };
+
+ Socket = {
+ ListenStream = socketPath;
+ FileDescriptorName = "server";
+ SocketMode = "0600";
+ DirectoryMode = "0700";
+ };
+
+ Install = { WantedBy = [ "sockets.target" ]; };
+ };
+ })
+ ]);
}
diff --git a/home-manager/modules/services/fluidsynth.nix b/home-manager/modules/services/fluidsynth.nix
new file mode 100644
index 00000000000..18913fe5426
--- /dev/null
+++ b/home-manager/modules/services/fluidsynth.nix
@@ -0,0 +1,57 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.fluidsynth;
+
+in {
+ meta.maintainers = [ maintainers.valodim ];
+
+ options = {
+ services.fluidsynth = {
+ enable = mkEnableOption "fluidsynth midi synthesizer";
+
+ soundFont = mkOption {
+ type = types.path;
+ default = "${pkgs.soundfont-fluid}/share/soundfonts/FluidR3_GM2-2.sf2";
+ description = ''
+ The soundfont file to use, in SoundFont 2 format.
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "--sample-rate 96000" ];
+ description = ''
+ Extra arguments, added verbatim to the fluidsynth command. See
+ <citerefentry>
+ <refentrytitle>fluidsynth.conf</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.fluidsynth = {
+ Unit = {
+ Description = "FluidSynth Daemon";
+ Documentation = "man:fluidsynth(1)";
+ BindsTo = [ "pulseaudio.service" ];
+ After = [ "pulseaudio.service" ];
+ };
+
+ Install = { WantedBy = [ "default.target" ]; };
+
+ Service = {
+ ExecStart = "${pkgs.fluidsynth}/bin/fluidsynth -a pulseaudio -si ${
+ lib.concatStringsSep " " cfg.extraOptions
+ } ${cfg.soundFont}";
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/services/gnome-keyring.nix b/home-manager/modules/services/gnome-keyring.nix
index 6d8317dcffc..ce39cea93f9 100644
--- a/home-manager/modules/services/gnome-keyring.nix
+++ b/home-manager/modules/services/gnome-keyring.nix
@@ -36,7 +36,7 @@ in {
args = concatStringsSep " " ([ "--start" "--foreground" ]
++ optional (cfg.components != [ ])
("--components=" + concatStringsSep "," cfg.components));
- in "${pkgs.gnome3.gnome_keyring}/bin/gnome-keyring-daemon ${args}";
+ in "${pkgs.gnome3.gnome-keyring}/bin/gnome-keyring-daemon ${args}";
Restart = "on-abort";
};
diff --git a/home-manager/modules/services/kanshi.nix b/home-manager/modules/services/kanshi.nix
new file mode 100644
index 00000000000..4e5e5f104e6
--- /dev/null
+++ b/home-manager/modules/services/kanshi.nix
@@ -0,0 +1,194 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.kanshi;
+
+ outputModule = types.submodule {
+ options = {
+
+ criteria = mkOption {
+ type = types.str;
+ description = ''
+ The criteria can either be an output name, an output description or "*".
+ The latter can be used to match any output.
+
+ On
+ <citerefentry>
+ <refentrytitle>sway</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>,
+ output names and descriptions can be obtained via
+ <literal>swaymsg -t get_outputs</literal>.
+ '';
+ };
+
+ status = mkOption {
+ type = types.nullOr (types.enum [ "enable" "disable" ]);
+ default = null;
+ description = ''
+ Enables or disables the specified output.
+ '';
+ };
+
+ mode = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "1920x1080@60Hz";
+ description = ''
+ &lt;width&gt;x&lt;height&gt;[@&lt;rate&gt;[Hz]]
+ </para><para>
+ Configures the specified output to use the specified mode.
+ Modes are a combination of width and height (in pixels) and
+ a refresh rate (in Hz) that your display can be configured to use.
+ '';
+ };
+
+ position = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "1600,0";
+ description = ''
+ &lt;x&gt;,&lt;y&gt;
+ </para><para>
+ Places the output at the specified position in the global coordinates
+ space.
+ '';
+ };
+
+ scale = mkOption {
+ type = types.nullOr types.float;
+ default = null;
+ example = 2;
+ description = ''
+ Scales the output by the specified scale factor.
+ '';
+ };
+
+ transform = mkOption {
+ type = types.nullOr (types.enum [
+ "normal"
+ "90"
+ "180"
+ "270"
+ "flipped"
+ "flipped-90"
+ "flipped-180"
+ "flipped-270"
+ ]);
+ default = null;
+ description = ''
+ Sets the output transform.
+ '';
+ };
+ };
+ };
+
+ outputStr = { criteria, status, mode, position, scale, transform, ... }:
+ ''output "${criteria}"'' + optionalString (status != null) " ${status}"
+ + optionalString (mode != null) " mode ${mode}"
+ + optionalString (position != null) " position ${position}"
+ + optionalString (scale != null) " scale ${toString scale}"
+ + optionalString (transform != null) " transform ${transform}";
+
+ profileModule = types.submodule {
+ options = {
+ outputs = mkOption {
+ type = types.listOf outputModule;
+ default = [ ];
+ description = ''
+ Outputs configuration.
+ '';
+ };
+
+ exec = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example =
+ "\${pkg.sway}/bin/swaymsg workspace 1, move workspace to eDP-1";
+ description = ''
+ Command executed after the profile is succesfully applied.
+ '';
+ };
+ };
+ };
+
+ profileStr = name:
+ { outputs, exec, ... }:
+ ''
+ profile ${name} {
+ ${concatStringsSep "\n " (map outputStr outputs)}
+ '' + optionalString (exec != null) " exec ${exec}\n" + ''
+ }
+ '';
+in {
+
+ meta.maintainers = [ maintainers.nurelin ];
+
+ options.services.kanshi = {
+ enable = mkEnableOption
+ "kanshi, a Wayland daemon that automatically configures outputs";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.kanshi;
+ defaultText = literalExample "pkgs.kanshi";
+ description = ''
+ kanshi derivation to use.
+ '';
+ };
+
+ profiles = mkOption {
+ type = types.attrsOf profileModule;
+ default = { };
+ description = ''
+ List of profiles.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration lines to append to the kanshi
+ configuration file.
+ '';
+ };
+
+ systemdTarget = mkOption {
+ type = types.str;
+ default = "sway-session.target";
+ description = ''
+ Systemd target to bind to.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ xdg.configFile."kanshi/config".text = ''
+ ${concatStringsSep "\n" (mapAttrsToList profileStr cfg.profiles)}
+ ${cfg.extraConfig}
+ '';
+
+ systemd.user.services.kanshi = {
+ Unit = {
+ Description = "Dynamic output configuration";
+ Documentation = "man:kanshi(1)";
+ PartOf = cfg.systemdTarget;
+ Requires = cfg.systemdTarget;
+ After = cfg.systemdTarget;
+ };
+
+ Service = {
+ Type = "simple";
+ ExecStart = "${cfg.package}/bin/kanshi";
+ Restart = "always";
+ };
+
+ Install = { WantedBy = [ cfg.systemdTarget ]; };
+ };
+ };
+}
diff --git a/home-manager/modules/services/keynav.nix b/home-manager/modules/services/keynav.nix
new file mode 100644
index 00000000000..c7f1df373b8
--- /dev/null
+++ b/home-manager/modules/services/keynav.nix
@@ -0,0 +1,29 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.keynav;
+
+in {
+ options.services.keynav = { enable = mkEnableOption "keynav"; };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.keynav = {
+ Unit = {
+ Description = "keynav";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ ExecStart = "${pkgs.keynav}/bin/keynav";
+ RestartSec = 3;
+ Restart = "always";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+}
diff --git a/home-manager/modules/services/lieer-accounts.nix b/home-manager/modules/services/lieer-accounts.nix
new file mode 100644
index 00000000000..187f7dff980
--- /dev/null
+++ b/home-manager/modules/services/lieer-accounts.nix
@@ -0,0 +1,25 @@
+{ lib, ... }:
+
+with lib;
+
+{
+ options.lieer.sync = {
+ enable = mkEnableOption "lieer synchronization service";
+
+ frequency = mkOption {
+ type = types.str;
+ default = "*:0/5";
+ description = ''
+ How often to synchronize the account.
+ </para><para>
+ This value is passed to the systemd timer configuration as the
+ onCalendar option. See
+ <citerefentry>
+ <refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum>
+ </citerefentry>
+ for more information about the format.
+ '';
+ };
+ };
+}
diff --git a/home-manager/modules/services/lieer.nix b/home-manager/modules/services/lieer.nix
new file mode 100644
index 00000000000..571e2af75c8
--- /dev/null
+++ b/home-manager/modules/services/lieer.nix
@@ -0,0 +1,66 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.lieer;
+
+ syncAccounts = filter (a: a.lieer.enable && a.lieer.sync.enable)
+ (attrValues config.accounts.email.accounts);
+
+ escapeUnitName = name:
+ let
+ good = upperChars ++ lowerChars ++ stringToCharacters "0123456789-_";
+ subst = c: if any (x: x == c) good then c else "-";
+ in stringAsChars subst name;
+
+ serviceUnit = account: {
+ name = escapeUnitName "lieer-${account.name}";
+ value = {
+ Unit = {
+ Description = "lieer Gmail synchronization for ${account.name}";
+ ConditionPathExists = "${account.maildir.absPath}/.gmailieer.json";
+ };
+
+ Service = {
+ Type = "oneshot";
+ ExecStart = "${pkgs.gmailieer}/bin/gmi sync";
+ WorkingDirectory = account.maildir.absPath;
+ };
+ };
+ };
+
+ timerUnit = account: {
+ name = escapeUnitName "lieer-${account.name}";
+ value = {
+ Unit = {
+ Description = "lieer Gmail synchronization for ${account.name}";
+ };
+
+ Timer = {
+ OnCalendar = account.lieer.sync.frequency;
+ RandomizedDelaySec = 30;
+ };
+
+ Install = { WantedBy = [ "timers.target" ]; };
+ };
+ };
+
+in {
+ meta.maintainers = [ maintainers.tadfisher ];
+
+ options = {
+ services.lieer.enable =
+ mkEnableOption "lieer Gmail synchronization service";
+
+ accounts.email.accounts = mkOption {
+ type = with types; attrsOf (submodule (import ./lieer-accounts.nix));
+ };
+ };
+
+ config = mkIf cfg.enable {
+ programs.lieer.enable = true;
+ systemd.user.services = listToAttrs (map serviceUnit syncAccounts);
+ systemd.user.timers = listToAttrs (map timerUnit syncAccounts);
+ };
+}
diff --git a/home-manager/modules/services/lorri.nix b/home-manager/modules/services/lorri.nix
index 3b2c244e3c0..6183699088b 100644
--- a/home-manager/modules/services/lorri.nix
+++ b/home-manager/modules/services/lorri.nix
@@ -9,10 +9,19 @@ let
in {
meta.maintainers = [ maintainers.gerschtli ];
- options = { services.lorri.enable = mkEnableOption "lorri build daemon"; };
+ options.services.lorri = {
+ enable = mkEnableOption "lorri build daemon";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.lorri;
+ defaultText = literalExample "pkgs.lorri";
+ description = "Which lorri package to install.";
+ };
+ };
config = mkIf cfg.enable {
- home.packages = [ pkgs.lorri ];
+ home.packages = [ cfg.package ];
systemd.user = {
services.lorri = {
@@ -24,7 +33,7 @@ in {
};
Service = {
- ExecStart = "${pkgs.lorri}/bin/lorri daemon";
+ ExecStart = "${cfg.package}/bin/lorri daemon";
PrivateTmp = true;
ProtectSystem = "strict";
ProtectHome = "read-only";
@@ -32,7 +41,7 @@ in {
Environment = let
path = with pkgs;
makeSearchPath "bin" [ nix gitMinimal gnutar gzip ];
- in "PATH=${path}";
+ in [ "PATH=${path}" ];
};
};
diff --git a/home-manager/modules/services/mako.nix b/home-manager/modules/services/mako.nix
new file mode 100644
index 00000000000..77ea3011678
--- /dev/null
+++ b/home-manager/modules/services/mako.nix
@@ -0,0 +1,316 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.mako;
+
+in {
+ meta.maintainers = [ maintainers.onny ];
+
+ options = {
+ programs.mako = {
+ enable = mkEnableOption ''
+ Mako, lightweight notification daemon for Wayland
+ '';
+
+ maxVisible = mkOption {
+ default = 5;
+ type = types.nullOr types.int;
+ description = ''
+ Set maximum number of visible notifications. Set -1 to show all.
+ '';
+ };
+
+ sort = mkOption {
+ default = "-time";
+ type =
+ types.nullOr (types.enum [ "+time" "-time" "+priority" "-priority" ]);
+ description = ''
+ Sorts incoming notifications by time and/or priority in ascending(+)
+ or descending(-) order.
+ '';
+ };
+
+ output = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ Show notifications on the specified output. If empty, notifications
+ will appear on the focused output. Requires the compositor to support
+ the Wayland protocol xdg-output-unstable-v1 version 2.
+ '';
+ };
+
+ layer = mkOption {
+ default = "top";
+ type =
+ types.nullOr (types.enum [ "background" "bottom" "top" "overlay" ]);
+ description = ''
+ Arrange mako at the specified layer, relative to normal windows.
+ Supported values are background, bottom, top, and overlay. Using
+ overlay will cause notifications to be displayed above fullscreen
+ windows, though this may also occur at top depending on your
+ compositor.
+ '';
+ };
+
+ anchor = mkOption {
+ default = "top-right";
+ type = types.nullOr (types.enum [
+ "top-right"
+ "top-center"
+ "top-left"
+ "bottom-right"
+ "bottom-center"
+ "bottom-left"
+ "center"
+ ]);
+ description = ''
+ Show notifications at the specified position on the output.
+ Supported values are top-right, top-center, top-left, bottom-right,
+ bottom-center, bottom-left, and center.
+ '';
+ };
+
+ font = mkOption {
+ default = "monospace 10";
+ type = types.nullOr types.str;
+ description = ''
+ Font to use, in Pango format.
+ '';
+ };
+
+ backgroundColor = mkOption {
+ default = "#285577FF";
+ type = types.nullOr types.str;
+ description = ''
+ Set popup background color to a specific color, represented in hex
+ color code.
+ '';
+ };
+
+ textColor = mkOption {
+ default = "#FFFFFFFF";
+ type = types.nullOr types.str;
+ description = ''
+ Set popup text color to a specific color, represented in hex color
+ code.
+ '';
+ };
+
+ width = mkOption {
+ default = 300;
+ type = types.nullOr types.int;
+ description = ''
+ Set width of notification popups in specified number of pixels.
+ '';
+ };
+
+ height = mkOption {
+ default = 100;
+ type = types.nullOr types.int;
+ description = ''
+ Set maximum height of notification popups. Notifications whose text
+ takes up less space are shrunk to fit.
+ '';
+ };
+
+ margin = mkOption {
+ default = "10";
+ type = types.nullOr types.str;
+ description = ''
+ Set margin of each edge specified in pixels. Specify single value to
+ apply margin on all sides. Two comma-seperated values will set
+ vertical and horizontal edges seperately. Four comma-seperated will
+ give each edge a seperate value.
+ For example: 10,20,5 will set top margin to 10, left and right to 20
+ and bottom to five.
+ '';
+ };
+
+ padding = mkOption {
+ default = "5";
+ type = types.nullOr types.str;
+ description = ''
+ Set padding of each edge specified in pixels. Specify single value to
+ apply margin on all sides. Two comma-seperated values will set
+ vertical and horizontal edges seperately. Four comma-seperated will
+ give each edge a seperate value.
+ For example: 10,20,5 will set top margin to 10, left and right to 20
+ and bottom to five.
+ '';
+ };
+
+ borderSize = mkOption {
+ default = 1;
+ type = types.nullOr types.int;
+ description = ''
+ Set popup border size to the specified number of pixels.
+ '';
+ };
+
+ borderColor = mkOption {
+ default = "#4C7899FF";
+ type = types.nullOr types.str;
+ description = ''
+ Set popup border color to a specific color, represented in hex color
+ code.
+ '';
+ };
+
+ borderRadius = mkOption {
+ default = 0;
+ type = types.nullOr types.int;
+ description = ''
+ Set popup corner radius to the specified number of pixels.
+ '';
+ };
+
+ progressColor = mkOption {
+ default = "over #5588AAFF";
+ type = types.nullOr types.str;
+ description = ''
+ Set popup progress indicator color to a specific color,
+ represented in hex color code. To draw the progress
+ indicator on top of the background color, use the
+ <literal>over</literal> attribute. To replace the background
+ color, use the <literal>source</literal> attribute (this can
+ be useful when the notification is semi-transparent).
+ '';
+ };
+
+ icons = mkOption {
+ default = true;
+ type = types.nullOr types.bool;
+ description = ''
+ Whether or not to show icons in notifications.
+ '';
+ };
+
+ maxIconSize = mkOption {
+ default = 64;
+ type = types.nullOr types.int;
+ description = ''
+ Set maximum icon size to the specified number of pixels.
+ '';
+ };
+
+ iconPath = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ Paths to search for icons when a notification specifies a name
+ instead of a full path. Colon-delimited. This approximates the search
+ algorithm used by the XDG Icon Theme Specification, but does not
+ support any of the theme metadata. Therefore, if you want to search
+ parent themes, you'll need to add them to the path manually.
+ </para><para>
+ The <filename>/usr/share/icons/hicolor</filename> and
+ <filename>/usr/share/pixmaps</filename> directories are
+ always searched.
+ '';
+ };
+
+ markup = mkOption {
+ default = true;
+ type = types.nullOr types.bool;
+ description = ''
+ If 1, enable Pango markup. If 0, disable Pango markup. If enabled,
+ Pango markup will be interpreted in your format specifier and in the
+ body of notifications.
+ '';
+ };
+
+ actions = mkOption {
+ default = true;
+ type = types.nullOr types.bool;
+ description = ''
+ Applications may request an action to be associated with activating a
+ notification. Disabling this will cause mako to ignore these requests.
+ '';
+ };
+
+ format = mkOption {
+ default = "<b>%s</b>\\n%b";
+ type = types.nullOr types.str;
+ description = ''
+ Set notification format string to format. See FORMAT SPECIFIERS for
+ more information. To change this for grouped notifications, set it
+ within a grouped criteria.
+ '';
+ };
+
+ defaultTimeout = mkOption {
+ default = 0;
+ type = types.nullOr types.int;
+ description = ''
+ Set the default timeout to timeout in milliseconds. To disable the
+ timeout, set it to zero.
+ '';
+ };
+
+ ignoreTimeout = mkOption {
+ default = false;
+ type = types.nullOr types.bool;
+ description = ''
+ If set, mako will ignore the expire timeout sent by notifications
+ and use the one provided by default-timeout instead.
+ '';
+ };
+
+ groupBy = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ A comma-separated list of criteria fields that will be compared to
+ other visible notifications to determine if this one should form a
+ group with them. All listed criteria must be exactly equal for two
+ notifications to group.
+ '';
+ };
+
+ };
+ };
+
+ config = let
+ boolToString = v: if v then "true" else "false";
+ optionalBoolean = name: val:
+ lib.optionalString (val != null) "${name}=${boolToString val}";
+ optionalInteger = name: val:
+ lib.optionalString (val != null) "${name}=${toString val}";
+ optionalString = name: val:
+ lib.optionalString (val != null) "${name}=${val}";
+ in mkIf cfg.enable {
+ home.packages = [ pkgs.mako ];
+ xdg.configFile."mako/config".text = ''
+ ${optionalInteger "max-visible" cfg.maxVisible}
+ ${optionalString "sort" cfg.sort}
+ ${optionalString "output" cfg.output}
+ ${optionalString "layer" cfg.layer}
+ ${optionalString "anchor" cfg.anchor}
+
+ ${optionalString "font" cfg.font}
+ ${optionalString "background-color" cfg.backgroundColor}
+ ${optionalString "text-color" cfg.textColor}
+ ${optionalInteger "width" cfg.width}
+ ${optionalInteger "height" cfg.height}
+ ${optionalString "margin" cfg.margin}
+ ${optionalString "padding" cfg.padding}
+ ${optionalInteger "border-size" cfg.borderSize}
+ ${optionalString "border-color" cfg.borderColor}
+ ${optionalInteger "border-radius" cfg.borderRadius}
+ ${optionalString "progress-color" cfg.progressColor}
+ ${optionalBoolean "icons" cfg.icons}
+ ${optionalInteger "max-icon-size" cfg.maxIconSize}
+ ${optionalString "icon-path" cfg.iconPath}
+ ${optionalBoolean "markup" cfg.markup}
+ ${optionalBoolean "actions" cfg.actions}
+ ${optionalString "format" cfg.format}
+ ${optionalInteger "default-timeout" cfg.defaultTimeout}
+ ${optionalBoolean "ignore-timeout" cfg.ignoreTimeout}
+ ${optionalString "group-by" cfg.groupBy}
+ '';
+ };
+}
diff --git a/home-manager/modules/services/mpd.nix b/home-manager/modules/services/mpd.nix
index 2aa1cd3a9fe..13b3ae78f26 100644
--- a/home-manager/modules/services/mpd.nix
+++ b/home-manager/modules/services/mpd.nix
@@ -42,7 +42,7 @@ in {
};
musicDirectory = mkOption {
- type = types.path;
+ type = with types; either path str;
default = "${config.home.homeDirectory}/music";
defaultText = "$HOME/music";
apply = toString; # Prevent copies to Nix store.
diff --git a/home-manager/modules/services/picom.nix b/home-manager/modules/services/picom.nix
new file mode 100644
index 00000000000..4c4da8de697
--- /dev/null
+++ b/home-manager/modules/services/picom.nix
@@ -0,0 +1,311 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+with builtins;
+
+let
+
+ cfg = config.services.picom;
+
+ configFile = pkgs.writeText "picom.conf" (optionalString cfg.fade ''
+ # fading
+ fading = true;
+ fade-delta = ${toString cfg.fadeDelta};
+ fade-in-step = ${elemAt cfg.fadeSteps 0};
+ fade-out-step = ${elemAt cfg.fadeSteps 1};
+ fade-exclude = ${toJSON cfg.fadeExclude};
+ '' + optionalString cfg.shadow ''
+
+ # shadows
+ shadow = true;
+ shadow-offset-x = ${toString (elemAt cfg.shadowOffsets 0)};
+ shadow-offset-y = ${toString (elemAt cfg.shadowOffsets 1)};
+ shadow-opacity = ${cfg.shadowOpacity};
+ shadow-exclude = ${toJSON cfg.shadowExclude};
+ '' + optionalString cfg.blur ''
+
+ # blur
+ blur-background = true;
+ blur-background-exclude = ${toJSON cfg.blurExclude};
+ '' + ''
+
+ # opacity
+ active-opacity = ${cfg.activeOpacity};
+ inactive-opacity = ${cfg.inactiveOpacity};
+ inactive-dim = ${cfg.inactiveDim};
+ opacity-rule = ${toJSON cfg.opacityRule};
+
+ wintypes:
+ {
+ dock = { shadow = ${toJSON (!cfg.noDockShadow)}; };
+ dnd = { shadow = ${toJSON (!cfg.noDNDShadow)}; };
+ popup_menu = { opacity = ${cfg.menuOpacity}; };
+ dropdown_menu = { opacity = ${cfg.menuOpacity}; };
+ };
+
+ # other options
+ backend = ${toJSON cfg.backend};
+ vsync = ${toJSON cfg.vSync};
+ refresh-rate = ${toString cfg.refreshRate};
+ '' + cfg.extraOptions);
+
+in {
+
+ options.services.picom = {
+ enable = mkEnableOption "Picom X11 compositor";
+
+ blur = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable background blur on transparent windows.
+ '';
+ };
+
+ blurExclude = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "class_g = 'slop'" "class_i = 'polybar'" ];
+ description = ''
+ List of windows to exclude background blur.
+ See the
+ <citerefentry>
+ <refentrytitle>picom</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ man page for more examples.
+ '';
+ };
+
+ experimentalBackends = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to use the new experimental backends.
+ '';
+ };
+
+ fade = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Fade windows in and out.
+ '';
+ };
+
+ fadeDelta = mkOption {
+ type = types.int;
+ default = 10;
+ example = 5;
+ description = ''
+ Time between fade animation step (in ms).
+ '';
+ };
+
+ fadeSteps = mkOption {
+ type = types.listOf types.str;
+ default = [ "0.028" "0.03" ];
+ example = [ "0.04" "0.04" ];
+ description = ''
+ Opacity change between fade steps (in and out).
+ '';
+ };
+
+ fadeExclude = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "window_type *= 'menu'" "name ~= 'Firefox$'" "focused = 1" ];
+ description = ''
+ List of conditions of windows that should not be faded.
+ See the
+ <citerefentry>
+ <refentrytitle>picom</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ man page for more examples.
+ '';
+ };
+
+ shadow = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Draw window shadows.
+ '';
+ };
+
+ shadowOffsets = mkOption {
+ type = types.listOf types.int;
+ default = [ (-15) (-15) ];
+ example = [ (-10) (-15) ];
+ description = ''
+ Horizontal and vertical offsets for shadows (in pixels).
+ '';
+ };
+
+ shadowOpacity = mkOption {
+ type = types.str;
+ default = "0.75";
+ example = "0.8";
+ description = ''
+ Window shadows opacity (number in range 0 - 1).
+ '';
+ };
+
+ shadowExclude = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "window_type *= 'menu'" "name ~= 'Firefox$'" "focused = 1" ];
+ description = ''
+ List of conditions of windows that should have no shadow.
+ See the
+ <citerefentry>
+ <refentrytitle>picom</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ man page for more examples.
+ '';
+ };
+
+ noDockShadow = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Avoid shadow on docks.
+ '';
+ };
+
+ noDNDShadow = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Avoid shadow on drag-and-drop windows.
+ '';
+ };
+
+ activeOpacity = mkOption {
+ type = types.str;
+ default = "1.0";
+ example = "0.8";
+ description = ''
+ Opacity of active windows.
+ '';
+ };
+
+ inactiveDim = mkOption {
+ type = types.str;
+ default = "0.0";
+ example = "0.2";
+ description = ''
+ Dim inactive windows.
+ '';
+ };
+
+ inactiveOpacity = mkOption {
+ type = types.str;
+ default = "1.0";
+ example = "0.8";
+ description = ''
+ Opacity of inactive windows.
+ '';
+ };
+
+ menuOpacity = mkOption {
+ type = types.str;
+ default = "1.0";
+ example = "0.8";
+ description = ''
+ Opacity of dropdown and popup menu.
+ '';
+ };
+
+ opacityRule = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "87:class_i ?= 'scratchpad'" "91:class_i ?= 'xterm'" ];
+ description = ''
+ List of opacity rules.
+ See the
+ <citerefentry>
+ <refentrytitle>picom</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ man page for more examples.
+ '';
+ };
+
+ backend = mkOption {
+ type = types.str;
+ default = "glx";
+ description = ''
+ Backend to use: <literal>glx</literal> or <literal>xrender</literal>.
+ '';
+ };
+
+ vSync = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable vertical synchronization.
+ '';
+ };
+
+ refreshRate = mkOption {
+ type = types.int;
+ default = 0;
+ example = 60;
+ description = ''
+ Screen refresh rate (0 = automatically detect).
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.picom;
+ defaultText = literalExample "pkgs.picom";
+ example = literalExample "pkgs.picom";
+ description = ''
+ picom derivation to use.
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = types.str;
+ default = "";
+ example = ''
+ unredir-if-possible = true;
+ dbe = true;
+ '';
+ description = ''
+ Additional Picom configuration.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+
+ systemd.user.services.picom = {
+ Unit = {
+ Description = "Picom X11 compositor";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = let
+ experimentalBackendsFlag =
+ if cfg.experimentalBackends then " --experimental-backends" else "";
+ in {
+ ExecStart = "${cfg.package}/bin/picom --config ${configFile}"
+ + experimentalBackendsFlag;
+ Restart = "always";
+ RestartSec = 3;
+ } // optionalAttrs (cfg.backend == "glx") {
+ # Temporarily fixes corrupt colours with Mesa 18.
+ Environment = [ "allow_rgb10_configs=false" ];
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/services/pulseeffects.nix b/home-manager/modules/services/pulseeffects.nix
new file mode 100644
index 00000000000..445b1c0a1f8
--- /dev/null
+++ b/home-manager/modules/services/pulseeffects.nix
@@ -0,0 +1,55 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.pulseeffects;
+
+ presetOpts = optionalString (cfg.preset != "") "--load-preset ${cfg.preset}";
+
+in {
+ meta.maintainers = [ maintainers.jonringer ];
+
+ options.services.pulseeffects = {
+ enable = mkEnableOption "Pulseeffects daemon";
+
+ preset = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Which preset to use when starting pulseeffects.
+ Will likely need to launch pulseeffects to initially create preset.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ # running pulseeffects will just attach itself to gapplication service
+ # at-spi2-core is to minimize journalctl noise of:
+ # "AT-SPI: Error retrieving accessibility bus address: org.freedesktop.DBus.Error.ServiceUnknown: The name org.a11y.Bus was not provided by any .service files"
+ home.packages = [ pkgs.pulseeffects pkgs.at-spi2-core ];
+
+ # Will need to add `services.dbus.packages = with pkgs; [ gnome3.dconf ];`
+ # to /etc/nixos/configuration.nix for daemon to work correctly
+
+ systemd.user.services.pulseeffects = {
+ Unit = {
+ Description = "Pulseeffects daemon";
+ Requires = [ "dbus.service" ];
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" "pulseaudio.service" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ ExecStart =
+ "${pkgs.pulseeffects}/bin/pulseeffects --gapplication-service ${presetOpts}";
+ ExecStop = "${pkgs.pulseeffects}/bin/pulseeffects --quit";
+ Restart = "on-failure";
+ RestartSec = 5;
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/services/screen-locker.nix b/home-manager/modules/services/screen-locker.nix
index 30591a7d1a5..554d64f9abe 100644
--- a/home-manager/modules/services/screen-locker.nix
+++ b/home-manager/modules/services/screen-locker.nix
@@ -17,6 +17,14 @@ in {
example = "\${pkgs.i3lock}/bin/i3lock -n -c 000000";
};
+ enableDetectSleep = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to reset timers when awaking from sleep.
+ '';
+ };
+
inactiveInterval = mkOption {
type = types.int;
default = 10;
@@ -42,7 +50,6 @@ in {
Extra command-line arguments to pass to <command>xss-lock</command>.
'';
};
-
};
config = mkIf cfg.enable {
@@ -58,10 +65,10 @@ in {
Service = {
ExecStart = concatStringsSep " " ([
"${pkgs.xautolock}/bin/xautolock"
- "-detectsleep"
"-time ${toString cfg.inactiveInterval}"
"-locker '${pkgs.systemd}/bin/loginctl lock-session $XDG_SESSION_ID'"
- ] ++ cfg.xautolockExtraOptions);
+ ] ++ optional cfg.enableDetectSleep "-detectsleep"
+ ++ cfg.xautolockExtraOptions);
};
};
diff --git a/home-manager/modules/services/spotifyd.nix b/home-manager/modules/services/spotifyd.nix
index bc231814eba..dfe0ecd318e 100644
--- a/home-manager/modules/services/spotifyd.nix
+++ b/home-manager/modules/services/spotifyd.nix
@@ -14,6 +14,18 @@ in {
options.services.spotifyd = {
enable = mkEnableOption "SpotifyD connect";
+ package = mkOption {
+ type = types.package;
+ default = pkgs.spotifyd;
+ defaultText = literalExample "pkgs.spotifyd";
+ example =
+ literalExample "(pkgs.spotifyd.override { withKeyring = true; })";
+ description = ''
+ The <literal>spotifyd</literal> package to use.
+ Can be used to specify extensions.
+ '';
+ };
+
settings = mkOption {
type = types.attrsOf (types.attrsOf types.str);
default = { };
@@ -21,7 +33,7 @@ in {
example = literalExample ''
{
global = {
- user = "Alex";
+ username = "Alex";
password = "foo";
device_name = "nix";
};
@@ -31,7 +43,7 @@ in {
};
config = mkIf cfg.enable {
- home.packages = [ pkgs.spotifyd ];
+ home.packages = [ cfg.package ];
systemd.user.services.spotifyd = {
Unit = {
@@ -43,7 +55,7 @@ in {
Service = {
ExecStart =
- "${pkgs.spotifyd}/bin/spotifyd --no-daemon --config-path ${configFile}";
+ "${cfg.package}/bin/spotifyd --no-daemon --config-path ${configFile}";
Restart = "always";
RestartSec = 12;
};
diff --git a/home-manager/modules/services/status-notifier-watcher.nix b/home-manager/modules/services/status-notifier-watcher.nix
index 3c3e54877b4..ed0537e22e1 100644
--- a/home-manager/modules/services/status-notifier-watcher.nix
+++ b/home-manager/modules/services/status-notifier-watcher.nix
@@ -34,7 +34,14 @@ in {
Before = [ "taffybar.service" ];
};
- Service = { ExecStart = "${cfg.package}/bin/status-notifier-watcher"; };
+ Service = {
+ ExecStart = "${cfg.package}/bin/status-notifier-watcher";
+ # Delay the unit start a bit to allow the program to get fully
+ # set up before letting dependent services start. This is
+ # brittle and a better solution using, e.g., `BusName=` might
+ # be possible.
+ ExecStartPost = "${pkgs.coreutils}/bin/sleep 1";
+ };
Install = {
WantedBy = [ "graphical-session.target" "taffybar.service" ];
diff --git a/home-manager/modules/services/syncthing.nix b/home-manager/modules/services/syncthing.nix
index 2ef10540164..4622ac2e941 100644
--- a/home-manager/modules/services/syncthing.nix
+++ b/home-manager/modules/services/syncthing.nix
@@ -19,6 +19,8 @@ with lib;
config = mkMerge [
(mkIf config.services.syncthing.enable {
+ home.packages = [ (getOutput "man" pkgs.syncthing) ];
+
systemd.user.services = {
syncthing = {
Unit = {
diff --git a/home-manager/modules/services/udiskie.nix b/home-manager/modules/services/udiskie.nix
index 2444d68ff93..ca31021cb5c 100644
--- a/home-manager/modules/services/udiskie.nix
+++ b/home-manager/modules/services/udiskie.nix
@@ -81,9 +81,7 @@ in {
PartOf = [ "graphical-session.target" ];
};
- Service = {
- ExecStart = "${pkgs.udiskie}/bin/udiskie -2 ${commandArgs}";
- };
+ Service = { ExecStart = "${pkgs.udiskie}/bin/udiskie ${commandArgs}"; };
Install = { WantedBy = [ "graphical-session.target" ]; };
};
diff --git a/home-manager/modules/services/unison.nix b/home-manager/modules/services/unison.nix
index 93c59e8fd62..a9cf23fb66e 100644
--- a/home-manager/modules/services/unison.nix
+++ b/home-manager/modules/services/unison.nix
@@ -60,7 +60,7 @@ let
};
};
- serialiseArg = key: val: "-${key}=${escapeShellArg val}";
+ serialiseArg = key: val: escapeShellArg "-${key}=${escape [ "=" ] val}";
serialiseArgs = args: concatStringsSep " " (mapAttrsToList serialiseArg args);
diff --git a/home-manager/modules/services/window-managers/i3-sway/i3.nix b/home-manager/modules/services/window-managers/i3-sway/i3.nix
new file mode 100644
index 00000000000..f7124e6fd23
--- /dev/null
+++ b/home-manager/modules/services/window-managers/i3-sway/i3.nix
@@ -0,0 +1,257 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.xsession.windowManager.i3;
+
+ commonOptions = import ./lib/options.nix {
+ inherit config lib cfg pkgs;
+ moduleName = "i3";
+ isGaps = cfg.package == pkgs.i3-gaps;
+ };
+
+ configModule = types.submodule {
+ options = {
+ inherit (commonOptions)
+ fonts window floating focus assigns modifier workspaceLayout
+ workspaceAutoBackAndForth keycodebindings colors bars startup gaps menu
+ terminal;
+
+ keybindings = mkOption {
+ type = types.attrsOf (types.nullOr types.str);
+ default = mapAttrs (n: mkOptionDefault) {
+ "${cfg.config.modifier}+Return" = "exec ${cfg.config.terminal}";
+ "${cfg.config.modifier}+Shift+q" = "kill";
+ "${cfg.config.modifier}+d" = "exec ${cfg.config.menu}";
+
+ "${cfg.config.modifier}+Left" = "focus left";
+ "${cfg.config.modifier}+Down" = "focus down";
+ "${cfg.config.modifier}+Up" = "focus up";
+ "${cfg.config.modifier}+Right" = "focus right";
+
+ "${cfg.config.modifier}+Shift+Left" = "move left";
+ "${cfg.config.modifier}+Shift+Down" = "move down";
+ "${cfg.config.modifier}+Shift+Up" = "move up";
+ "${cfg.config.modifier}+Shift+Right" = "move right";
+
+ "${cfg.config.modifier}+h" = "split h";
+ "${cfg.config.modifier}+v" = "split v";
+ "${cfg.config.modifier}+f" = "fullscreen toggle";
+
+ "${cfg.config.modifier}+s" = "layout stacking";
+ "${cfg.config.modifier}+w" = "layout tabbed";
+ "${cfg.config.modifier}+e" = "layout toggle split";
+
+ "${cfg.config.modifier}+Shift+space" = "floating toggle";
+ "${cfg.config.modifier}+space" = "focus mode_toggle";
+
+ "${cfg.config.modifier}+a" = "focus parent";
+
+ "${cfg.config.modifier}+Shift+minus" = "move scratchpad";
+ "${cfg.config.modifier}+minus" = "scratchpad show";
+
+ "${cfg.config.modifier}+1" = "workspace number 1";
+ "${cfg.config.modifier}+2" = "workspace number 2";
+ "${cfg.config.modifier}+3" = "workspace number 3";
+ "${cfg.config.modifier}+4" = "workspace number 4";
+ "${cfg.config.modifier}+5" = "workspace number 5";
+ "${cfg.config.modifier}+6" = "workspace number 6";
+ "${cfg.config.modifier}+7" = "workspace number 7";
+ "${cfg.config.modifier}+8" = "workspace number 8";
+ "${cfg.config.modifier}+9" = "workspace number 9";
+ "${cfg.config.modifier}+0" = "workspace number 10";
+
+ "${cfg.config.modifier}+Shift+1" =
+ "move container to workspace number 1";
+ "${cfg.config.modifier}+Shift+2" =
+ "move container to workspace number 2";
+ "${cfg.config.modifier}+Shift+3" =
+ "move container to workspace number 3";
+ "${cfg.config.modifier}+Shift+4" =
+ "move container to workspace number 4";
+ "${cfg.config.modifier}+Shift+5" =
+ "move container to workspace number 5";
+ "${cfg.config.modifier}+Shift+6" =
+ "move container to workspace number 6";
+ "${cfg.config.modifier}+Shift+7" =
+ "move container to workspace number 7";
+ "${cfg.config.modifier}+Shift+8" =
+ "move container to workspace number 8";
+ "${cfg.config.modifier}+Shift+9" =
+ "move container to workspace number 9";
+ "${cfg.config.modifier}+Shift+0" =
+ "move container to workspace number 10";
+
+ "${cfg.config.modifier}+Shift+c" = "reload";
+ "${cfg.config.modifier}+Shift+r" = "restart";
+ "${cfg.config.modifier}+Shift+e" =
+ "exec i3-nagbar -t warning -m 'Do you want to exit i3?' -b 'Yes' 'i3-msg exit'";
+
+ "${cfg.config.modifier}+r" = "mode resize";
+ };
+ defaultText = "Default i3 keybindings.";
+ description = ''
+ An attribute set that assigns a key press to an action using a key symbol.
+ See <link xlink:href="https://i3wm.org/docs/userguide.html#keybindings"/>.
+ </para><para>
+ Consider to use <code>lib.mkOptionDefault</code> function to extend or override
+ default keybindings instead of specifying all of them from scratch.
+ '';
+ example = literalExample ''
+ let
+ modifier = config.xsession.windowManager.i3.config.modifier;
+ in lib.mkOptionDefault {
+ "''${modifier}+Return" = "exec i3-sensible-terminal";
+ "''${modifier}+Shift+q" = "kill";
+ "''${modifier}+d" = "exec \${pkgs.dmenu}/bin/dmenu_run";
+ }
+ '';
+ };
+
+ modes = mkOption {
+ type = types.attrsOf (types.attrsOf types.str);
+ default = {
+ resize = {
+ "Left" = "resize shrink width 10 px or 10 ppt";
+ "Down" = "resize grow height 10 px or 10 ppt";
+ "Up" = "resize shrink height 10 px or 10 ppt";
+ "Right" = "resize grow width 10 px or 10 ppt";
+ "Escape" = "mode default";
+ "Return" = "mode default";
+ };
+ };
+ description = ''
+ An attribute set that defines binding modes and keybindings
+ inside them
+
+ Only basic keybinding is supported (bindsym keycomb action),
+ for more advanced setup use 'i3.extraConfig'.
+ '';
+ };
+ };
+ };
+
+ commonFunctions = import ./lib/functions.nix {
+ inherit cfg lib;
+ moduleName = "i3";
+ };
+
+ inherit (commonFunctions)
+ keybindingsStr keycodebindingsStr modeStr assignStr barStr gapsStr
+ floatingCriteriaStr windowCommandsStr colorSetStr;
+
+ startupEntryStr = { command, always, notification, workspace, ... }: ''
+ ${if always then "exec_always" else "exec"} ${
+ if (notification && workspace == null) then "" else "--no-startup-id"
+ } ${
+ if (workspace == null) then
+ command
+ else
+ "i3-msg 'workspace ${workspace}; exec ${command}'"
+ }
+ '';
+
+ configFile = pkgs.writeText "i3.conf" ((if cfg.config != null then
+ with cfg.config; ''
+ font pango:${concatStringsSep ", " fonts}
+ floating_modifier ${floating.modifier}
+ new_window ${if window.titlebar then "normal" else "pixel"} ${
+ toString window.border
+ }
+ new_float ${if floating.titlebar then "normal" else "pixel"} ${
+ toString floating.border
+ }
+ hide_edge_borders ${window.hideEdgeBorders}
+ force_focus_wrapping ${if focus.forceWrapping then "yes" else "no"}
+ focus_follows_mouse ${if focus.followMouse then "yes" else "no"}
+ focus_on_window_activation ${focus.newWindow}
+ mouse_warping ${if focus.mouseWarping then "output" else "none"}
+ workspace_layout ${workspaceLayout}
+ workspace_auto_back_and_forth ${
+ if workspaceAutoBackAndForth then "yes" else "no"
+ }
+
+ client.focused ${colorSetStr colors.focused}
+ client.focused_inactive ${colorSetStr colors.focusedInactive}
+ client.unfocused ${colorSetStr colors.unfocused}
+ client.urgent ${colorSetStr colors.urgent}
+ client.placeholder ${colorSetStr colors.placeholder}
+ client.background ${colors.background}
+
+ ${keybindingsStr { inherit keybindings; }}
+ ${keycodebindingsStr keycodebindings}
+ ${concatStringsSep "\n" (mapAttrsToList modeStr modes)}
+ ${concatStringsSep "\n" (mapAttrsToList assignStr assigns)}
+ ${concatStringsSep "\n" (map barStr bars)}
+ ${optionalString (gaps != null) gapsStr}
+ ${concatStringsSep "\n" (map floatingCriteriaStr floating.criteria)}
+ ${concatStringsSep "\n" (map windowCommandsStr window.commands)}
+ ${concatStringsSep "\n" (map startupEntryStr startup)}
+ ''
+ else
+ "") + "\n" + cfg.extraConfig);
+
+in {
+ options = {
+ xsession.windowManager.i3 = {
+ enable = mkEnableOption "i3 window manager.";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.i3;
+ defaultText = literalExample "pkgs.i3";
+ example = literalExample "pkgs.i3-gaps";
+ description = ''
+ i3 package to use.
+ If 'i3.config.gaps' settings are specified, 'pkgs.i3-gaps' will be set as a default package.
+ '';
+ };
+
+ config = mkOption {
+ type = types.nullOr configModule;
+ default = { };
+ description = "i3 configuration options.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description =
+ "Extra configuration lines to add to ~/.config/i3/config.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+ home.packages = [ cfg.package ];
+ xsession.windowManager.command = "${cfg.package}/bin/i3";
+ xdg.configFile."i3/config" = {
+ source = configFile;
+ onChange = ''
+ i3Socket=''${XDG_RUNTIME_DIR:-/run/user/$UID}/i3/ipc-socket.*
+ if [ -S $i3Socket ]; then
+ echo "Reloading i3"
+ $DRY_RUN_CMD ${cfg.package}/bin/i3-msg -s $i3Socket reload 1>/dev/null
+ fi
+ '';
+ };
+ }
+
+ (mkIf (cfg.config != null) {
+ xsession.windowManager.i3.package =
+ mkDefault (if (cfg.config.gaps != null) then pkgs.i3-gaps else pkgs.i3);
+ })
+
+ (mkIf (cfg.config != null
+ && (any (s: s.workspace != null) cfg.config.startup)) {
+ warnings = [
+ ("'xsession.windowManager.i3.config.startup.*.workspace' is deprecated, "
+ + "use 'xsession.windowManager.i3.config.assigns' instead."
+ + "See https://github.com/rycee/home-manager/issues/265.")
+ ];
+ })
+ ]);
+}
diff --git a/home-manager/modules/services/window-managers/i3-sway/lib/functions.nix b/home-manager/modules/services/window-managers/i3-sway/lib/functions.nix
new file mode 100644
index 00000000000..9391e6e92fc
--- /dev/null
+++ b/home-manager/modules/services/window-managers/i3-sway/lib/functions.nix
@@ -0,0 +1,127 @@
+{ cfg, lib, moduleName }:
+
+with lib;
+
+rec {
+ criteriaStr = criteria:
+ "[${
+ concatStringsSep " " (mapAttrsToList (k: v: ''${k}="${v}"'') criteria)
+ }]";
+
+ keybindingsStr = { keybindings, bindsymArgs ? "" }:
+ concatStringsSep "\n" (mapAttrsToList (keycomb: action:
+ optionalString (action != null) "bindsym ${
+ lib.optionalString (bindsymArgs != "") "${bindsymArgs} "
+ }${keycomb} ${action}") keybindings);
+
+ keycodebindingsStr = keycodebindings:
+ concatStringsSep "\n" (mapAttrsToList (keycomb: action:
+ optionalString (action != null) "bindcode ${keycomb} ${action}")
+ keycodebindings);
+
+ colorSetStr = c:
+ concatStringsSep " " [
+ c.border
+ c.background
+ c.text
+ c.indicator
+ c.childBorder
+ ];
+ barColorSetStr = c: concatStringsSep " " [ c.border c.background c.text ];
+
+ modeStr = name: keybindings: ''
+ mode "${name}" {
+ ${keybindingsStr { inherit keybindings; }}
+ }
+ '';
+
+ assignStr = workspace: criteria:
+ concatStringsSep "\n"
+ (map (c: "assign ${criteriaStr c} ${workspace}") criteria);
+
+ barStr = { id, fonts, mode, hiddenState, position, workspaceButtons
+ , workspaceNumbers, command, statusCommand, colors, trayOutput, extraConfig
+ , ... }:
+ let colorsNotNull = lib.filterAttrs (n: v: v != null) colors != { };
+ in ''
+ bar {
+ ${optionalString (id != null) "id ${id}"}
+ ${
+ optionalString (fonts != [ ])
+ "font pango:${concatStringsSep ", " fonts}"
+ }
+ ${optionalString (mode != null) "mode ${mode}"}
+ ${optionalString (hiddenState != null) "hidden_state ${hiddenState}"}
+ ${optionalString (position != null) "position ${position}"}
+ ${
+ optionalString (statusCommand != null)
+ "status_command ${statusCommand}"
+ }
+ ${moduleName}bar_command ${command}
+ ${
+ optionalString (workspaceButtons != null)
+ "workspace_buttons ${if workspaceButtons then "yes" else "no"}"
+ }
+ ${
+ optionalString (workspaceNumbers != null)
+ "strip_workspace_numbers ${if !workspaceNumbers then "yes" else "no"}"
+ }
+ ${optionalString (trayOutput != null) "tray_output ${trayOutput}"}
+ ${optionalString colorsNotNull "colors {"}
+ ${
+ optionalString (colors.background != null)
+ "background ${colors.background}"
+ }
+ ${
+ optionalString (colors.statusline != null)
+ "statusline ${colors.statusline}"
+ }
+ ${
+ optionalString (colors.separator != null)
+ "separator ${colors.separator}"
+ }
+ ${
+ optionalString (colors.focusedWorkspace != null)
+ "focused_workspace ${barColorSetStr colors.focusedWorkspace}"
+ }
+ ${
+ optionalString (colors.activeWorkspace != null)
+ "active_workspace ${barColorSetStr colors.activeWorkspace}"
+ }
+ ${
+ optionalString (colors.inactiveWorkspace != null)
+ "inactive_workspace ${barColorSetStr colors.inactiveWorkspace}"
+ }
+ ${
+ optionalString (colors.urgentWorkspace != null)
+ "urgent_workspace ${barColorSetStr colors.urgentWorkspace}"
+ }
+ ${
+ optionalString (colors.bindingMode != null)
+ "binding_mode ${barColorSetStr colors.bindingMode}"
+ }
+ ${optionalString colorsNotNull "}"}
+ ${extraConfig}
+ }
+ '';
+
+ gapsStr = with cfg.config.gaps; ''
+ ${optionalString (inner != null) "gaps inner ${toString inner}"}
+ ${optionalString (outer != null) "gaps outer ${toString outer}"}
+ ${optionalString (horizontal != null)
+ "gaps horizontal ${toString horizontal}"}
+ ${optionalString (vertical != null) "gaps vertical ${toString vertical}"}
+ ${optionalString (top != null) "gaps top ${toString top}"}
+ ${optionalString (bottom != null) "gaps bottom ${toString bottom}"}
+ ${optionalString (left != null) "gaps left ${toString left}"}
+ ${optionalString (right != null) "gaps right ${toString right}"}
+
+ ${optionalString smartGaps "smart_gaps on"}
+ ${optionalString (smartBorders != "off") "smart_borders ${smartBorders}"}
+ '';
+
+ floatingCriteriaStr = criteria:
+ "for_window ${criteriaStr criteria} floating enable";
+ windowCommandsStr = { command, criteria, ... }:
+ "for_window ${criteriaStr criteria} ${command}";
+}
diff --git a/home-manager/modules/services/window-managers/i3-sway/lib/options.nix b/home-manager/modules/services/window-managers/i3-sway/lib/options.nix
new file mode 100644
index 00000000000..edfdcd4feae
--- /dev/null
+++ b/home-manager/modules/services/window-managers/i3-sway/lib/options.nix
@@ -0,0 +1,763 @@
+{ config, lib, moduleName, cfg, pkgs, capitalModuleName ? moduleName
+, isGaps ? true }:
+
+with lib;
+
+let
+ fonts = mkOption {
+ type = types.listOf types.str;
+ default = [ "monospace 8" ];
+ description = ''
+ Font list used for window titles. Only FreeType fonts are supported.
+ The order here is important (e.g. icons font should go before the one used for text).
+ '';
+ example = [ "FontAwesome 10" "Terminus 10" ];
+ };
+
+ startupModule = types.submodule {
+ options = {
+ command = mkOption {
+ type = types.str;
+ description = "Command that will be executed on startup.";
+ };
+
+ always = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to run command on each ${moduleName} restart.";
+ };
+ } // optionalAttrs (moduleName == "i3") {
+ notification = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable startup-notification support for the command.
+ See <option>--no-startup-id</option> option description in the i3 user guide.
+ '';
+ };
+
+ workspace = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Launch application on a particular workspace. DEPRECATED:
+ Use <varname><link linkend="opt-xsession.windowManager.i3.config.assigns">xsession.windowManager.i3.config.assigns</link></varname>
+ instead. See <link xlink:href="https://github.com/rycee/home-manager/issues/265"/>.
+ '';
+ };
+ };
+
+ };
+
+ barModule = types.submodule {
+ options = let
+ versionAtLeast2009 = versionAtLeast config.home.stateVersion "20.09";
+ mkNullableOption = { type, default, ... }@args:
+ mkOption (args // optionalAttrs versionAtLeast2009 {
+ type = types.nullOr type;
+ default = null;
+ example = default;
+ } // {
+ defaultText = literalExample ''
+ ${
+ if isString default then default else "See code"
+ } for state version < 20.09,
+ null for state version β‰₯ 20.09
+ '';
+ });
+ in {
+ fonts = fonts // optionalAttrs versionAtLeast2009 { default = [ ]; };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Extra configuration lines for this bar.";
+ };
+
+ id = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Specifies the bar ID for the configured bar instance.
+ If this option is missing, the ID is set to bar-x, where x corresponds
+ to the position of the embedding bar block in the config file.
+ '';
+ };
+
+ mode = mkNullableOption {
+ type = types.enum [ "dock" "hide" "invisible" ];
+ default = "dock";
+ description = "Bar visibility mode.";
+ };
+
+ hiddenState = mkNullableOption {
+ type = types.enum [ "hide" "show" ];
+ default = "hide";
+ description = "The default bar mode when 'bar.mode' == 'hide'.";
+ };
+
+ position = mkNullableOption {
+ type = types.enum [ "top" "bottom" ];
+ default = "bottom";
+ description = "The edge of the screen ${moduleName}bar should show up.";
+ };
+
+ workspaceButtons = mkNullableOption {
+ type = types.bool;
+ default = true;
+ description = "Whether workspace buttons should be shown or not.";
+ };
+
+ workspaceNumbers = mkNullableOption {
+ type = types.bool;
+ default = true;
+ description =
+ "Whether workspace numbers should be displayed within the workspace buttons.";
+ };
+
+ command = mkOption {
+ type = types.str;
+ default = "${cfg.package}/bin/${moduleName}bar";
+ defaultText = "i3bar";
+ description = "Command that will be used to start a bar.";
+ example = if moduleName == "i3" then
+ "\${pkgs.i3-gaps}/bin/i3bar -t"
+ else
+ "\${pkgs.waybar}/bin/waybar";
+ };
+
+ statusCommand = mkOption {
+ type = types.nullOr types.str;
+ default =
+ if versionAtLeast2009 then null else "${pkgs.i3status}/bin/i3status";
+ example = "i3status";
+ description = "Command that will be used to get status lines.";
+ };
+
+ colors = mkOption {
+ type = types.submodule {
+ options = {
+ background = mkNullableOption {
+ type = types.str;
+ default = "#000000";
+ description = "Background color of the bar.";
+ };
+
+ statusline = mkNullableOption {
+ type = types.str;
+ default = "#ffffff";
+ description = "Text color to be used for the statusline.";
+ };
+
+ separator = mkNullableOption {
+ type = types.str;
+ default = "#666666";
+ description = "Text color to be used for the separator.";
+ };
+
+ focusedWorkspace = mkNullableOption {
+ type = barColorSetModule;
+ default = {
+ border = "#4c7899";
+ background = "#285577";
+ text = "#ffffff";
+ };
+ description = ''
+ Border, background and text color for a workspace button when the workspace has focus.
+ '';
+ };
+
+ activeWorkspace = mkNullableOption {
+ type = barColorSetModule;
+ default = {
+ border = "#333333";
+ background = "#5f676a";
+ text = "#ffffff";
+ };
+ description = ''
+ Border, background and text color for a workspace button when the workspace is active.
+ '';
+ };
+
+ inactiveWorkspace = mkNullableOption {
+ type = barColorSetModule;
+ default = {
+ border = "#333333";
+ background = "#222222";
+ text = "#888888";
+ };
+ description = ''
+ Border, background and text color for a workspace button when the workspace does not
+ have focus and is not active.
+ '';
+ };
+
+ urgentWorkspace = mkNullableOption {
+ type = barColorSetModule;
+ default = {
+ border = "#2f343a";
+ background = "#900000";
+ text = "#ffffff";
+ };
+ description = ''
+ Border, background and text color for a workspace button when the workspace contains
+ a window with the urgency hint set.
+ '';
+ };
+
+ bindingMode = mkNullableOption {
+ type = barColorSetModule;
+ default = {
+ border = "#2f343a";
+ background = "#900000";
+ text = "#ffffff";
+ };
+ description =
+ "Border, background and text color for the binding mode indicator";
+ };
+ };
+ };
+ default = { };
+ description = ''
+ Bar color settings. All color classes can be specified using submodules
+ with 'border', 'background', 'text', fields and RGB color hex-codes as values.
+ See default values for the reference.
+ Note that 'background', 'status', and 'separator' parameters take a single RGB value.
+
+ See <link xlink:href="https://i3wm.org/docs/userguide.html#_colors"/>.
+ '';
+ };
+
+ trayOutput = mkNullableOption {
+ type = types.str;
+ default = "primary";
+ description = "Where to output tray.";
+ };
+ };
+ };
+
+ barColorSetModule = types.submodule {
+ options = {
+ border = mkOption {
+ type = types.str;
+ visible = false;
+ };
+
+ background = mkOption {
+ type = types.str;
+ visible = false;
+ };
+
+ text = mkOption {
+ type = types.str;
+ visible = false;
+ };
+ };
+ };
+
+ colorSetModule = types.submodule {
+ options = {
+ border = mkOption {
+ type = types.str;
+ visible = false;
+ };
+
+ childBorder = mkOption {
+ type = types.str;
+ visible = false;
+ };
+
+ background = mkOption {
+ type = types.str;
+ visible = false;
+ };
+
+ text = mkOption {
+ type = types.str;
+ visible = false;
+ };
+
+ indicator = mkOption {
+ type = types.str;
+ visible = false;
+ };
+ };
+ };
+
+ windowCommandModule = types.submodule {
+ options = {
+ command = mkOption {
+ type = types.str;
+ description = "${capitalModuleName}wm command to execute.";
+ example = "border pixel 1";
+ };
+
+ criteria = mkOption {
+ type = criteriaModule;
+ description =
+ "Criteria of the windows on which command should be executed.";
+ example = { title = "x200: ~/work"; };
+ };
+ };
+ };
+
+ criteriaModule = types.attrsOf types.str;
+in {
+ inherit fonts;
+
+ window = mkOption {
+ type = types.submodule {
+ options = {
+ titlebar = mkOption {
+ type = types.bool;
+ default = !isGaps;
+ defaultText = if moduleName == "i3" then
+ "xsession.windowManager.i3.package != nixpkgs.i3-gaps (titlebar should be disabled for i3-gaps)"
+ else
+ "false";
+ description = "Whether to show window titlebars.";
+ };
+
+ border = mkOption {
+ type = types.int;
+ default = 2;
+ description = "Window border width.";
+ };
+
+ hideEdgeBorders = mkOption {
+ type = types.enum [ "none" "vertical" "horizontal" "both" "smart" ];
+ default = "none";
+ description = "Hide window borders adjacent to the screen edges.";
+ };
+
+ commands = mkOption {
+ type = types.listOf windowCommandModule;
+ default = [ ];
+ description = ''
+ List of commands that should be executed on specific windows.
+ See <option>for_window</option> ${moduleName}wm option documentation.
+ '';
+ example = [{
+ command = "border pixel 1";
+ criteria = { class = "XTerm"; };
+ }];
+ };
+ };
+ };
+ default = { };
+ description = "Window titlebar and border settings.";
+ };
+
+ floating = mkOption {
+ type = types.submodule {
+ options = {
+ titlebar = mkOption {
+ type = types.bool;
+ default = !isGaps;
+ defaultText = if moduleName == "i3" then
+ "xsession.windowManager.i3.package != nixpkgs.i3-gaps (titlebar should be disabled for i3-gaps)"
+ else
+ "false";
+ description = "Whether to show floating window titlebars.";
+ };
+
+ border = mkOption {
+ type = types.int;
+ default = 2;
+ description = "Floating windows border width.";
+ };
+
+ modifier = mkOption {
+ type =
+ types.enum [ "Shift" "Control" "Mod1" "Mod2" "Mod3" "Mod4" "Mod5" ];
+ default = cfg.config.modifier;
+ defaultText = "${moduleName}.config.modifier";
+ description =
+ "Modifier key that can be used to drag floating windows.";
+ example = "Mod4";
+ };
+
+ criteria = mkOption {
+ type = types.listOf criteriaModule;
+ default = [ ];
+ description =
+ "List of criteria for windows that should be opened in a floating mode.";
+ example = [
+ { "title" = "Steam - Update News"; }
+ { "class" = "Pavucontrol"; }
+ ];
+ };
+ };
+ };
+ default = { };
+ description = "Floating window settings.";
+ };
+
+ focus = mkOption {
+ type = types.submodule {
+ options = {
+ newWindow = mkOption {
+ type = types.enum [ "smart" "urgent" "focus" "none" ];
+ default = "smart";
+ description = ''
+ This option modifies focus behavior on new window activation.
+
+ See <link xlink:href="https://i3wm.org/docs/userguide.html#focus_on_window_activation"/>
+ '';
+ example = "none";
+ };
+
+ followMouse = mkOption {
+ type = if moduleName == "sway" then
+ types.either (types.enum [ "yes" "no" "always" ]) types.bool
+ else
+ types.bool;
+ default = if moduleName == "sway" then "yes" else true;
+ description = "Whether focus should follow the mouse.";
+ apply = val:
+ if (moduleName == "sway" && isBool val) then
+ (if val then "yes" else "no")
+ else
+ val;
+ };
+
+ forceWrapping = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to force focus wrapping in tabbed or stacked container.
+
+ See <link xlink:href="https://i3wm.org/docs/userguide.html#_focus_wrapping"/>
+ '';
+ };
+
+ mouseWarping = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether mouse cursor should be warped to the center of the window when switching focus
+ to a window on a different output.
+ '';
+ };
+ };
+ };
+ default = { };
+ description = "Focus related settings.";
+ };
+
+ assigns = mkOption {
+ type = types.attrsOf (types.listOf criteriaModule);
+ default = { };
+ description = ''
+ An attribute set that assigns applications to workspaces based
+ on criteria.
+ '';
+ example = literalExample ''
+ {
+ "1: web" = [{ class = "^Firefox$"; }];
+ "0: extra" = [{ class = "^Firefox$"; window_role = "About"; }];
+ }
+ '';
+ };
+
+ modifier = mkOption {
+ type = types.enum [ "Shift" "Control" "Mod1" "Mod2" "Mod3" "Mod4" "Mod5" ];
+ default = "Mod1";
+ description = "Modifier key that is used for all default keybindings.";
+ example = "Mod4";
+ };
+
+ workspaceLayout = mkOption {
+ type = types.enum [ "default" "stacked" "tabbed" ];
+ default = "default";
+ example = "tabbed";
+ description = ''
+ The mode in which new containers on workspace level will
+ start.
+ '';
+ };
+
+ workspaceAutoBackAndForth = mkOption {
+ type = types.bool;
+ default = false;
+ example = true;
+ description = ''
+ Assume you are on workspace "1: www" and switch to "2: IM" using
+ mod+2 because somebody sent you a message. You don’t need to remember
+ where you came from now, you can just press $mod+2 again to switch
+ back to "1: www".
+ '';
+ };
+
+ keycodebindings = mkOption {
+ type = types.attrsOf (types.nullOr types.str);
+ default = { };
+ description = ''
+ An attribute set that assigns keypress to an action using key code.
+ See <link xlink:href="https://i3wm.org/docs/userguide.html#keybindings"/>.
+ '';
+ example = { "214" = "exec /bin/script.sh"; };
+ };
+
+ colors = mkOption {
+ type = types.submodule {
+ options = {
+ background = mkOption {
+ type = types.str;
+ default = "#ffffff";
+ description = ''
+ Background color of the window. Only applications which do not cover
+ the whole area expose the color.
+ '';
+ };
+
+ focused = mkOption {
+ type = colorSetModule;
+ default = {
+ border = "#4c7899";
+ background = "#285577";
+ text = "#ffffff";
+ indicator = "#2e9ef4";
+ childBorder = "#285577";
+ };
+ description = "A window which currently has the focus.";
+ };
+
+ focusedInactive = mkOption {
+ type = colorSetModule;
+ default = {
+ border = "#333333";
+ background = "#5f676a";
+ text = "#ffffff";
+ indicator = "#484e50";
+ childBorder = "#5f676a";
+ };
+ description = ''
+ A window which is the focused one of its container,
+ but it does not have the focus at the moment.
+ '';
+ };
+
+ unfocused = mkOption {
+ type = colorSetModule;
+ default = {
+ border = "#333333";
+ background = "#222222";
+ text = "#888888";
+ indicator = "#292d2e";
+ childBorder = "#222222";
+ };
+ description = "A window which is not focused.";
+ };
+
+ urgent = mkOption {
+ type = colorSetModule;
+ default = {
+ border = "#2f343a";
+ background = "#900000";
+ text = "#ffffff";
+ indicator = "#900000";
+ childBorder = "#900000";
+ };
+ description = "A window which has its urgency hint activated.";
+ };
+
+ placeholder = mkOption {
+ type = colorSetModule;
+ default = {
+ border = "#000000";
+ background = "#0c0c0c";
+ text = "#ffffff";
+ indicator = "#000000";
+ childBorder = "#0c0c0c";
+ };
+ description = ''
+ Background and text color are used to draw placeholder window
+ contents (when restoring layouts). Border and indicator are ignored.
+ '';
+ };
+ };
+ };
+ default = { };
+ description = ''
+ Color settings. All color classes can be specified using submodules
+ with 'border', 'background', 'text', 'indicator' and 'childBorder' fields
+ and RGB color hex-codes as values. See default values for the reference.
+ Note that '${moduleName}.config.colors.background' parameter takes a single RGB value.
+
+ See <link xlink:href="https://i3wm.org/docs/userguide.html#_changing_colors"/>.
+ '';
+ };
+
+ bars = mkOption {
+ type = types.listOf barModule;
+ default = if versionAtLeast config.home.stateVersion "20.09" then [{
+ mode = "dock";
+ hiddenState = "hide";
+ position = "bottom";
+ workspaceButtons = true;
+ workspaceNumbers = true;
+ statusCommand = "${pkgs.i3status}/bin/i3status";
+ fonts = [ "monospace 8" ];
+ trayOutput = "primary";
+ colors = {
+ background = "#000000";
+ statusline = "#ffffff";
+ separator = "#666666";
+ focusedWorkspace = {
+ border = "#4c7899";
+ background = "#285577";
+ text = "#ffffff";
+ };
+ activeWorkspace = {
+ border = "#333333";
+ background = "#5f676a";
+ text = "#ffffff";
+ };
+ inactiveWorkspace = {
+ border = "#333333";
+ background = "#222222";
+ text = "#888888";
+ };
+ urgentWorkspace = {
+ border = "#2f343a";
+ background = "#900000";
+ text = "#ffffff";
+ };
+ bindingMode = {
+ border = "#2f343a";
+ background = "#900000";
+ text = "#ffffff";
+ };
+ };
+ }] else
+ [ { } ];
+ description = ''
+ ${capitalModuleName} bars settings blocks. Set to empty list to remove bars completely.
+ '';
+ };
+
+ startup = mkOption {
+ type = types.listOf startupModule;
+ default = [ ];
+ description = ''
+ Commands that should be executed at startup.
+
+ See <link xlink:href="https://i3wm.org/docs/userguide.html#_automatically_starting_applications_on_i3_startup"/>.
+ '';
+ example = literalExample ''
+ [
+ { command = "systemctl --user restart polybar"; always = true; notification = false; }
+ { command = "dropbox start"; notification = false; }
+ { command = "firefox"; workspace = "1: web"; }
+ ];
+ '';
+ };
+
+ gaps = mkOption {
+ type = types.nullOr (types.submodule {
+ options = {
+ inner = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "Inner gaps value.";
+ example = 12;
+ };
+
+ outer = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "Outer gaps value.";
+ example = 5;
+ };
+
+ horizontal = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "Horizontal gaps value.";
+ example = 5;
+ };
+
+ vertical = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "Vertical gaps value.";
+ example = 5;
+ };
+
+ top = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "Top gaps value.";
+ example = 5;
+ };
+
+ left = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "Left gaps value.";
+ example = 5;
+ };
+
+ bottom = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "Bottom gaps value.";
+ example = 5;
+ };
+
+ right = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "Right gaps value.";
+ example = 5;
+ };
+
+ smartGaps = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ This option controls whether to disable all gaps (outer and inner)
+ on workspace with a single container.
+ '';
+ example = true;
+ };
+
+ smartBorders = mkOption {
+ type = types.enum [ "on" "off" "no_gaps" ];
+ default = "off";
+ description = ''
+ This option controls whether to disable container borders on
+ workspace with a single container.
+ '';
+ };
+ };
+ });
+ default = null;
+ description = if moduleName == "sway" then ''
+ Gaps related settings.
+ '' else ''
+ i3Gaps related settings. The i3-gaps package must be used for these features to work.
+ '';
+ };
+
+ terminal = mkOption {
+ type = types.str;
+ default = if moduleName == "i3" then
+ "i3-sensible-terminal"
+ else
+ "${pkgs.rxvt-unicode-unwrapped}/bin/urxvt";
+ description = "Default terminal to run.";
+ example = "alacritty";
+ };
+
+ menu = mkOption {
+ type = types.str;
+ default = if moduleName == "sway" then
+ "${pkgs.dmenu}/bin/dmenu_path | ${pkgs.dmenu}/bin/dmenu | ${pkgs.findutils}/bin/xargs swaymsg exec --"
+ else
+ "${pkgs.dmenu}/bin/dmenu_run";
+ description = "Default launcher to use.";
+ example = "bemenu-run";
+ };
+}
diff --git a/home-manager/modules/services/window-managers/i3-sway/sway.nix b/home-manager/modules/services/window-managers/i3-sway/sway.nix
new file mode 100644
index 00000000000..8f0ee608104
--- /dev/null
+++ b/home-manager/modules/services/window-managers/i3-sway/sway.nix
@@ -0,0 +1,416 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.wayland.windowManager.sway;
+
+ commonOptions = import ./lib/options.nix {
+ inherit config lib cfg pkgs;
+ moduleName = "sway";
+ capitalModuleName = "Sway";
+ };
+
+ configModule = types.submodule {
+ options = {
+ inherit (commonOptions)
+ fonts window floating focus assigns workspaceLayout
+ workspaceAutoBackAndForth modifier keycodebindings colors bars startup
+ gaps menu terminal;
+
+ left = mkOption {
+ type = types.str;
+ default = "h";
+ description = "Home row direction key for moving left.";
+ };
+
+ down = mkOption {
+ type = types.str;
+ default = "j";
+ description = "Home row direction key for moving down.";
+ };
+
+ up = mkOption {
+ type = types.str;
+ default = "k";
+ description = "Home row direction key for moving up.";
+ };
+
+ right = mkOption {
+ type = types.str;
+ default = "l";
+ description = "Home row direction key for moving right.";
+ };
+
+ keybindings = mkOption {
+ type = types.attrsOf (types.nullOr types.str);
+ default = mapAttrs (n: mkOptionDefault) {
+ "${cfg.config.modifier}+Return" = "exec ${cfg.config.terminal}";
+ "${cfg.config.modifier}+Shift+q" = "kill";
+ "${cfg.config.modifier}+d" = "exec ${cfg.config.menu}";
+
+ "${cfg.config.modifier}+${cfg.config.left}" = "focus left";
+ "${cfg.config.modifier}+${cfg.config.down}" = "focus down";
+ "${cfg.config.modifier}+${cfg.config.up}" = "focus up";
+ "${cfg.config.modifier}+${cfg.config.right}" = "focus right";
+
+ "${cfg.config.modifier}+Left" = "focus left";
+ "${cfg.config.modifier}+Down" = "focus down";
+ "${cfg.config.modifier}+Up" = "focus up";
+ "${cfg.config.modifier}+Right" = "focus right";
+
+ "${cfg.config.modifier}+Shift+${cfg.config.left}" = "move left";
+ "${cfg.config.modifier}+Shift+${cfg.config.down}" = "move down";
+ "${cfg.config.modifier}+Shift+${cfg.config.up}" = "move up";
+ "${cfg.config.modifier}+Shift+${cfg.config.right}" = "move right";
+
+ "${cfg.config.modifier}+Shift+Left" = "move left";
+ "${cfg.config.modifier}+Shift+Down" = "move down";
+ "${cfg.config.modifier}+Shift+Up" = "move up";
+ "${cfg.config.modifier}+Shift+Right" = "move right";
+
+ "${cfg.config.modifier}+b" = "splith";
+ "${cfg.config.modifier}+v" = "splitv";
+ "${cfg.config.modifier}+f" = "fullscreen toggle";
+ "${cfg.config.modifier}+a" = "focus parent";
+
+ "${cfg.config.modifier}+s" = "layout stacking";
+ "${cfg.config.modifier}+w" = "layout tabbed";
+ "${cfg.config.modifier}+e" = "layout toggle split";
+
+ "${cfg.config.modifier}+Shift+space" = "floating toggle";
+ "${cfg.config.modifier}+space" = "focus mode_toggle";
+
+ "${cfg.config.modifier}+1" = "workspace number 1";
+ "${cfg.config.modifier}+2" = "workspace number 2";
+ "${cfg.config.modifier}+3" = "workspace number 3";
+ "${cfg.config.modifier}+4" = "workspace number 4";
+ "${cfg.config.modifier}+5" = "workspace number 5";
+ "${cfg.config.modifier}+6" = "workspace number 6";
+ "${cfg.config.modifier}+7" = "workspace number 7";
+ "${cfg.config.modifier}+8" = "workspace number 8";
+ "${cfg.config.modifier}+9" = "workspace number 9";
+
+ "${cfg.config.modifier}+Shift+1" =
+ "move container to workspace number 1";
+ "${cfg.config.modifier}+Shift+2" =
+ "move container to workspace number 2";
+ "${cfg.config.modifier}+Shift+3" =
+ "move container to workspace number 3";
+ "${cfg.config.modifier}+Shift+4" =
+ "move container to workspace number 4";
+ "${cfg.config.modifier}+Shift+5" =
+ "move container to workspace number 5";
+ "${cfg.config.modifier}+Shift+6" =
+ "move container to workspace number 6";
+ "${cfg.config.modifier}+Shift+7" =
+ "move container to workspace number 7";
+ "${cfg.config.modifier}+Shift+8" =
+ "move container to workspace number 8";
+ "${cfg.config.modifier}+Shift+9" =
+ "move container to workspace number 9";
+
+ "${cfg.config.modifier}+Shift+minus" = "move scratchpad";
+ "${cfg.config.modifier}+minus" = "scratchpad show";
+
+ "${cfg.config.modifier}+Shift+c" = "reload";
+ "${cfg.config.modifier}+Shift+e" =
+ "exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -b 'Yes, exit sway' 'swaymsg exit'";
+
+ "${cfg.config.modifier}+r" = "mode resize";
+ };
+ defaultText = "Default sway keybindings.";
+ description = ''
+ An attribute set that assigns a key press to an action using a key symbol.
+ See <link xlink:href="https://i3wm.org/docs/userguide.html#keybindings"/>.
+ </para><para>
+ Consider to use <code>lib.mkOptionDefault</code> function to extend or override
+ default keybindings instead of specifying all of them from scratch.
+ '';
+ example = literalExample ''
+ let
+ modifier = config.wayland.windowManager.sway.config.modifier;
+ in lib.mkOptionDefault {
+ "''${modifier}+Return" = "exec ${cfg.config.terminal}";
+ "''${modifier}+Shift+q" = "kill";
+ "''${modifier}+d" = "exec ${cfg.config.menu}";
+ }
+ '';
+ };
+
+ bindkeysToCode = mkOption {
+ type = types.bool;
+ default = false;
+ example = true;
+ description = ''
+ Whether to make use of <option>--to-code</option> in keybindings.
+ '';
+ };
+
+ input = mkOption {
+ type = types.attrsOf (types.attrsOf types.str);
+ default = { };
+ example = { "*" = { xkb_variant = "dvorak"; }; };
+ description = ''
+ An attribute set that defines input modules. See man sway_input for options.
+ '';
+ };
+
+ output = mkOption {
+ type = types.attrsOf (types.attrsOf types.str);
+ default = { };
+ example = { "HDMI-A-2" = { bg = "~/path/to/background.png fill"; }; };
+ description = ''
+ An attribute set that defines output modules. See man sway_output for options.
+ '';
+ };
+
+ modes = mkOption {
+ type = types.attrsOf (types.attrsOf types.str);
+ default = {
+ resize = {
+ "${cfg.config.left}" = "resize shrink width 10 px";
+ "${cfg.config.down}" = "resize grow height 10 px";
+ "${cfg.config.up}" = "resize shrink height 10 px";
+ "${cfg.config.right}" = "resize grow width 10 px";
+ "Left" = "resize shrink width 10 px";
+ "Down" = "resize grow height 10 px";
+ "Up" = "resize shrink height 10 px";
+ "Right" = "resize grow width 10 px";
+ "Escape" = "mode default";
+ "Return" = "mode default";
+ };
+ };
+ description = ''
+ An attribute set that defines binding modes and keybindings
+ inside them
+
+ Only basic keybinding is supported (bindsym keycomb action),
+ for more advanced setup use 'sway.extraConfig'.
+ '';
+ };
+ };
+ };
+
+ wrapperOptions = types.submodule {
+ options = let
+ mkWrapperFeature = default: description:
+ mkOption {
+ type = types.bool;
+ inherit default;
+ example = !default;
+ description = "Whether to make use of the ${description}";
+ };
+ in {
+ base = mkWrapperFeature true ''
+ base wrapper to execute extra session commands and prepend a
+ dbus-run-session to the sway command.
+ '';
+ gtk = mkWrapperFeature false ''
+ wrapGAppsHook wrapper to execute sway with required environment
+ variables for GTK applications.
+ '';
+ };
+ };
+
+ commonFunctions = import ./lib/functions.nix {
+ inherit cfg lib;
+ moduleName = "sway";
+ };
+
+ inherit (commonFunctions)
+ keybindingsStr keycodebindingsStr modeStr assignStr barStr gapsStr
+ floatingCriteriaStr windowCommandsStr colorSetStr;
+
+ startupEntryStr = { command, always, ... }: ''
+ ${if always then "exec_always" else "exec"} ${command}
+ '';
+
+ inputStr = name: attrs: ''
+ input "${name}" {
+ ${concatStringsSep "\n"
+ (mapAttrsToList (name: value: "${name} ${value}") attrs)}
+ }
+ '';
+
+ outputStr = name: attrs: ''
+ output "${name}" {
+ ${concatStringsSep "\n"
+ (mapAttrsToList (name: value: "${name} ${value}") attrs)}
+ }
+ '';
+
+ configFile = pkgs.writeText "sway.conf" ((if cfg.config != null then
+ with cfg.config; ''
+ font pango:${concatStringsSep ", " fonts}
+ floating_modifier ${floating.modifier}
+ default_border ${if window.titlebar then "normal" else "pixel"} ${
+ toString window.border
+ }
+ default_floating_border ${
+ if floating.titlebar then "normal" else "pixel"
+ } ${toString floating.border}
+ hide_edge_borders ${window.hideEdgeBorders}
+ focus_wrapping ${if focus.forceWrapping then "yes" else "no"}
+ focus_follows_mouse ${focus.followMouse}
+ focus_on_window_activation ${focus.newWindow}
+ mouse_warping ${if focus.mouseWarping then "output" else "none"}
+ workspace_layout ${workspaceLayout}
+ workspace_auto_back_and_forth ${
+ if workspaceAutoBackAndForth then "yes" else "no"
+ }
+
+ client.focused ${colorSetStr colors.focused}
+ client.focused_inactive ${colorSetStr colors.focusedInactive}
+ client.unfocused ${colorSetStr colors.unfocused}
+ client.urgent ${colorSetStr colors.urgent}
+ client.placeholder ${colorSetStr colors.placeholder}
+ client.background ${colors.background}
+
+ ${keybindingsStr {
+ inherit keybindings;
+ bindsymArgs =
+ lib.optionalString (cfg.config.bindkeysToCode) "--to-code";
+ }}
+ ${keycodebindingsStr keycodebindings}
+ ${concatStringsSep "\n" (mapAttrsToList inputStr input)}
+ ${concatStringsSep "\n" (mapAttrsToList outputStr output)}
+ ${concatStringsSep "\n" (mapAttrsToList modeStr modes)}
+ ${concatStringsSep "\n" (mapAttrsToList assignStr assigns)}
+ ${concatStringsSep "\n" (map barStr bars)}
+ ${optionalString (gaps != null) gapsStr}
+ ${concatStringsSep "\n" (map floatingCriteriaStr floating.criteria)}
+ ${concatStringsSep "\n" (map windowCommandsStr window.commands)}
+ ${concatStringsSep "\n" (map startupEntryStr startup)}
+ ''
+ else
+ "") + "\n" + (if cfg.systemdIntegration then ''
+ exec "systemctl --user import-environment; systemctl --user start sway-session.target"
+ '' else
+ "") + cfg.extraConfig);
+
+ defaultSwayPackage = pkgs.sway.override {
+ extraSessionCommands = cfg.extraSessionCommands;
+ extraOptions = cfg.extraOptions;
+ withBaseWrapper = cfg.wrapperFeatures.base;
+ withGtkWrapper = cfg.wrapperFeatures.gtk;
+ };
+
+in {
+ meta.maintainers = [ maintainers.alexarice ];
+
+ options.wayland.windowManager.sway = {
+ enable = mkEnableOption "sway wayland compositor";
+
+ package = mkOption {
+ type = with types; nullOr package;
+ default = defaultSwayPackage;
+ defaultText = literalExample "${pkgs.sway}";
+ description = ''
+ Sway package to use. Will override the options
+ 'wrapperFeatures', 'extraSessionCommands', and 'extraOptions'.
+ Set to <code>null</code> to not add any Sway package to your
+ path. This should be done if you want to use the NixOS Sway
+ module to install Sway.
+ '';
+ };
+
+ systemdIntegration = mkOption {
+ type = types.bool;
+ default = pkgs.stdenv.isLinux;
+ example = false;
+ description = ''
+ Whether to enable <filename>sway-session.target</filename> on
+ sway startup. This links to
+ <filename>graphical-session.target</filename>.
+ '';
+ };
+
+ xwayland = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Enable xwayland, which is needed for the default configuration of sway.
+ '';
+ };
+
+ wrapperFeatures = mkOption {
+ type = wrapperOptions;
+ default = { };
+ example = { gtk = true; };
+ description = ''
+ Attribute set of features to enable in the wrapper.
+ '';
+ };
+
+ extraSessionCommands = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ export SDL_VIDEODRIVER=wayland
+ # needs qt5.qtwayland in systemPackages
+ export QT_QPA_PLATFORM=wayland
+ export QT_WAYLAND_DISABLE_WINDOWDECORATION="1"
+ # Fix for some Java AWT applications (e.g. Android Studio),
+ # use this if they aren't displayed properly:
+ export _JAVA_AWT_WM_NONREPARENTING=1
+ '';
+ description = ''
+ Shell commands executed just before Sway is started.
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [
+ "--verbose"
+ "--debug"
+ "--unsupported-gpu"
+ "--my-next-gpu-wont-be-nvidia"
+ ];
+ description = ''
+ Command line arguments passed to launch Sway. Please DO NOT report
+ issues if you use an unsupported GPU (proprietary drivers).
+ '';
+ };
+
+ config = mkOption {
+ type = types.nullOr configModule;
+ default = { };
+ description = "Sway configuration options.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description =
+ "Extra configuration lines to add to ~/.config/sway/config.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = optional (cfg.package != null) cfg.package
+ ++ optional cfg.xwayland pkgs.xwayland;
+ xdg.configFile."sway/config" = {
+ source = configFile;
+ onChange = ''
+ swaySocket=''${XDG_RUNTIME_DIR:-/run/user/$UID}/sway-ipc.$UID.$(${pkgs.procps}/bin/pgrep -x sway).sock
+ if [ -S $swaySocket ]; then
+ echo "Reloading sway"
+ $DRY_RUN_CMD ${pkgs.sway}/bin/swaymsg -s $swaySocket reload
+ fi
+ '';
+ };
+ systemd.user.targets.sway-session = mkIf cfg.systemdIntegration {
+ Unit = {
+ Description = "sway compositor session";
+ Documentation = [ "man:systemd.special(7)" ];
+ BindsTo = [ "graphical-session.target" ];
+ Wants = [ "graphical-session-pre.target" ];
+ After = [ "graphical-session-pre.target" ];
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/services/window-managers/i3.nix b/home-manager/modules/services/window-managers/i3.nix
deleted file mode 100644
index 7a4ec90b1cd..00000000000
--- a/home-manager/modules/services/window-managers/i3.nix
+++ /dev/null
@@ -1,856 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
- cfg = config.xsession.windowManager.i3;
-
- commonOptions = {
- fonts = mkOption {
- type = types.listOf types.str;
- default = ["monospace 8"];
- description = ''
- Font list used for window titles. Only FreeType fonts are supported.
- The order here is improtant (e.g. icons font should go before the one used for text).
- '';
- example = [ "FontAwesome 10" "Terminus 10" ];
- };
- };
-
- startupModule = types.submodule {
- options = {
- command = mkOption {
- type = types.str;
- description = "Command that will be executed on startup.";
- };
-
- always = mkOption {
- type = types.bool;
- default = false;
- description = "Whether to run command on each i3 restart.";
- };
-
- notification = mkOption {
- type = types.bool;
- default = true;
- description = ''
- Whether to enable startup-notification support for the command.
- See <option>--no-startup-id</option> option description in the i3 user guide.
- '';
- };
-
- workspace = mkOption {
- type = types.nullOr types.str;
- default = null;
- description = ''
- Launch application on a particular workspace. DEPRECATED:
- Use <varname><link linkend="opt-xsession.windowManager.i3.config.assigns">xsession.windowManager.i3.config.assigns</link></varname>
- instead. See <link xlink:href="https://github.com/rycee/home-manager/issues/265"/>.
- '';
- };
- };
- };
-
- barColorSetModule = types.submodule {
- options = {
- border = mkOption {
- type = types.str;
- visible = false;
- };
-
- background = mkOption {
- type = types.str;
- visible = false;
- };
-
- text = mkOption {
- type = types.str;
- visible = false;
- };
- };
- };
-
- colorSetModule = types.submodule {
- options = {
- border = mkOption {
- type = types.str;
- visible = false;
- };
-
- childBorder = mkOption {
- type = types.str;
- visible = false;
- };
-
- background = mkOption {
- type = types.str;
- visible = false;
- };
-
- text = mkOption {
- type = types.str;
- visible = false;
- };
-
- indicator = mkOption {
- type = types.str;
- visible = false;
- };
- };
- };
-
- barModule = types.submodule {
- options = {
- inherit (commonOptions) fonts;
-
- extraConfig = mkOption {
- type = types.lines;
- default = "";
- description = "Extra configuration lines for this bar.";
- };
-
- id = mkOption {
- type = types.nullOr types.str;
- default = null;
- description = ''
- Specifies the bar ID for the configured bar instance.
- If this option is missing, the ID is set to bar-x, where x corresponds
- to the position of the embedding bar block in the config file.
- '';
- };
-
- mode = mkOption {
- type = types.enum [ "dock" "hide" "invisible" ];
- default = "dock";
- description = "Bar visibility mode.";
- };
-
- hiddenState = mkOption {
- type = types.enum [ "hide" "show" ];
- default = "hide";
- description = "The default bar mode when 'bar.mode' == 'hide'.";
- };
-
- position = mkOption {
- type = types.enum [ "top" "bottom" ];
- default = "bottom";
- description = "The edge of the screen i3bar should show up.";
- };
-
- workspaceButtons = mkOption {
- type = types.bool;
- default = true;
- description = "Whether workspace buttons should be shown or not.";
- };
-
- workspaceNumbers = mkOption {
- type = types.bool;
- default = true;
- description = "Whether workspace numbers should be displayed within the workspace buttons.";
- };
-
- command = mkOption {
- type = types.str;
- default = "${cfg.package}/bin/i3bar";
- defaultText = "i3bar";
- description = "Command that will be used to start a bar.";
- example = "\${pkgs.i3-gaps}/bin/i3bar -t";
- };
-
- statusCommand = mkOption {
- type = types.str;
- default = "${pkgs.i3status}/bin/i3status";
- description = "Command that will be used to get status lines.";
- };
-
- colors = mkOption {
- type = types.submodule {
- options = {
- background = mkOption {
- type = types.str;
- default = "#000000";
- description = "Background color of the bar.";
- };
-
- statusline = mkOption {
- type = types.str;
- default = "#ffffff";
- description = "Text color to be used for the statusline.";
- };
-
- separator = mkOption {
- type = types.str;
- default = "#666666";
- description = "Text color to be used for the separator.";
- };
-
- focusedWorkspace = mkOption {
- type = barColorSetModule;
- default = { border = "#4c7899"; background = "#285577"; text = "#ffffff"; };
- description = ''
- Border, background and text color for a workspace button when the workspace has focus.
- '';
- };
-
- activeWorkspace = mkOption {
- type = barColorSetModule;
- default = { border = "#333333"; background = "#5f676a"; text = "#ffffff"; };
- description = ''
- Border, background and text color for a workspace button when the workspace is active.
- '';
- };
-
- inactiveWorkspace = mkOption {
- type = barColorSetModule;
- default = { border = "#333333"; background = "#222222"; text = "#888888"; };
- description = ''
- Border, background and text color for a workspace button when the workspace does not
- have focus and is not active.
- '';
- };
-
- urgentWorkspace = mkOption {
- type = barColorSetModule;
- default = { border = "#2f343a"; background = "#900000"; text = "#ffffff"; };
- description = ''
- Border, background and text color for a workspace button when the workspace contains
- a window with the urgency hint set.
- '';
- };
-
- bindingMode = mkOption {
- type = barColorSetModule;
- default = { border = "#2f343a"; background = "#900000"; text = "#ffffff"; };
- description = "Border, background and text color for the binding mode indicator";
- };
- };
- };
- default = {};
- description = ''
- Bar color settings. All color classes can be specified using submodules
- with 'border', 'background', 'text', fields and RGB color hex-codes as values.
- See default values for the reference.
- Note that 'background', 'status', and 'separator' parameters take a single RGB value.
-
- See <link xlink:href="https://i3wm.org/docs/userguide.html#_colors"/>.
- '';
- };
-
- trayOutput = mkOption {
- type = types.str;
- default = "primary";
- description = "Where to output tray.";
- };
- };
- };
-
- windowCommandModule = types.submodule {
- options = {
- command = mkOption {
- type = types.str;
- description = "i3wm command to execute.";
- example = "border pixel 1";
- };
-
- criteria = mkOption {
- type = criteriaModule;
- description = "Criteria of the windows on which command should be executed.";
- example = { title = "x200: ~/work"; };
- };
- };
- };
-
- criteriaModule = types.attrsOf types.str;
-
- configModule = types.submodule {
- options = {
- inherit (commonOptions) fonts;
-
- window = mkOption {
- type = types.submodule {
- options = {
- titlebar = mkOption {
- type = types.bool;
- default = cfg.package != pkgs.i3-gaps;
- defaultText = "xsession.windowManager.i3.package != nixpkgs.i3-gaps (titlebar should be disabled for i3-gaps)";
- description = "Whether to show window titlebars.";
- };
-
- border = mkOption {
- type = types.int;
- default = 2;
- description = "Window border width.";
- };
-
- hideEdgeBorders = mkOption {
- type = types.enum [ "none" "vertical" "horizontal" "both" "smart" ];
- default = "none";
- description = "Hide window borders adjacent to the screen edges.";
- };
-
- commands = mkOption {
- type = types.listOf windowCommandModule;
- default = [];
- description = ''
- List of commands that should be executed on specific windows.
- See <option>for_window</option> i3wm option documentation.
- '';
- example = [ { command = "border pixel 1"; criteria = { class = "XTerm"; }; } ];
- };
- };
- };
- default = {};
- description = "Window titlebar and border settings.";
- };
-
- floating = mkOption {
- type = types.submodule {
- options = {
- titlebar = mkOption {
- type = types.bool;
- default = cfg.package != pkgs.i3-gaps;
- defaultText = "xsession.windowManager.i3.package != nixpkgs.i3-gaps (titlebar should be disabled for i3-gaps)";
- description = "Whether to show floating window titlebars.";
- };
-
- border = mkOption {
- type = types.int;
- default = 2;
- description = "Floating windows border width.";
- };
-
- modifier = mkOption {
- type = types.enum [ "Shift" "Control" "Mod1" "Mod2" "Mod3" "Mod4" "Mod5" ];
- default = cfg.config.modifier;
- defaultText = "i3.config.modifier";
- description = "Modifier key that can be used to drag floating windows.";
- example = "Mod4";
- };
-
- criteria = mkOption {
- type = types.listOf criteriaModule;
- default = [];
- description = "List of criteria for windows that should be opened in a floating mode.";
- example = [ {"title" = "Steam - Update News";} {"class" = "Pavucontrol";} ];
- };
- };
- };
- default = {};
- description = "Floating window settings.";
- };
-
- focus = mkOption {
- type = types.submodule {
- options = {
- newWindow = mkOption {
- type = types.enum [ "smart" "urgent" "focus" "none" ];
- default = "smart";
- description = ''
- This option modifies focus behavior on new window activation.
-
- See <link xlink:href="https://i3wm.org/docs/userguide.html#focus_on_window_activation"/>
- '';
- example = "none";
- };
-
- followMouse = mkOption {
- type = types.bool;
- default = true;
- description = "Whether focus should follow the mouse.";
- };
-
- forceWrapping = mkOption {
- type = types.bool;
- default = false;
- description = ''
- Whether to force focus wrapping in tabbed or stacked container.
-
- See <link xlink:href="https://i3wm.org/docs/userguide.html#_focus_wrapping"/>
- '';
- };
-
- mouseWarping = mkOption {
- type = types.bool;
- default = true;
- description = ''
- Whether mouse cursor should be warped to the center of the window when switching focus
- to a window on a different output.
- '';
- };
- };
- };
- default = {};
- description = "Focus related settings.";
- };
-
- assigns = mkOption {
- type = types.attrsOf (types.listOf criteriaModule);
- default = {};
- description = ''
- An attribute set that assigns applications to workspaces based
- on criteria.
- '';
- example = literalExample ''
- {
- "1: web" = [{ class = "^Firefox$"; }];
- "0: extra" = [{ class = "^Firefox$"; window_role = "About"; }];
- }
- '';
- };
-
- modifier = mkOption {
- type = types.enum [ "Shift" "Control" "Mod1" "Mod2" "Mod3" "Mod4" "Mod5" ];
- default = "Mod1";
- description = "Modifier key that is used for all default keybindings.";
- example = "Mod4";
- };
-
- workspaceLayout = mkOption {
- type = types.enum [ "default" "stacked" "tabbed" ];
- default = "default";
- example = "tabbed";
- description = ''
- The mode in which new containers on workspace level will
- start.
- '';
- };
-
- workspaceAutoBackAndForth = mkOption {
- type = types.bool;
- default = false;
- example = true;
- description = ''
- Assume you are on workspace "1: www" and switch to "2: IM" using
- mod+2 because somebody sent you a message. You don’t need to remember
- where you came from now, you can just press $mod+2 again to switch
- back to "1: www".
- '';
- };
-
- keybindings = mkOption {
- type = types.attrsOf (types.nullOr types.str);
- default = mapAttrs (n: mkOptionDefault) {
- "${cfg.config.modifier}+Return" = "exec i3-sensible-terminal";
- "${cfg.config.modifier}+Shift+q" = "kill";
- "${cfg.config.modifier}+d" = "exec ${pkgs.dmenu}/bin/dmenu_run";
-
- "${cfg.config.modifier}+Left" = "focus left";
- "${cfg.config.modifier}+Down" = "focus down";
- "${cfg.config.modifier}+Up" = "focus up";
- "${cfg.config.modifier}+Right" = "focus right";
-
- "${cfg.config.modifier}+Shift+Left" = "move left";
- "${cfg.config.modifier}+Shift+Down" = "move down";
- "${cfg.config.modifier}+Shift+Up" = "move up";
- "${cfg.config.modifier}+Shift+Right" = "move right";
-
- "${cfg.config.modifier}+h" = "split h";
- "${cfg.config.modifier}+v" = "split v";
- "${cfg.config.modifier}+f" = "fullscreen toggle";
-
- "${cfg.config.modifier}+s" = "layout stacking";
- "${cfg.config.modifier}+w" = "layout tabbed";
- "${cfg.config.modifier}+e" = "layout toggle split";
-
- "${cfg.config.modifier}+Shift+space" = "floating toggle";
- "${cfg.config.modifier}+space" = "focus mode_toggle";
-
- "${cfg.config.modifier}+a" = "focus parent";
-
- "${cfg.config.modifier}+Shift+minus" = "move scratchpad";
- "${cfg.config.modifier}+minus" = "scratchpad show";
-
- "${cfg.config.modifier}+1" = "workspace number 1";
- "${cfg.config.modifier}+2" = "workspace number 2";
- "${cfg.config.modifier}+3" = "workspace number 3";
- "${cfg.config.modifier}+4" = "workspace number 4";
- "${cfg.config.modifier}+5" = "workspace number 5";
- "${cfg.config.modifier}+6" = "workspace number 6";
- "${cfg.config.modifier}+7" = "workspace number 7";
- "${cfg.config.modifier}+8" = "workspace number 8";
- "${cfg.config.modifier}+9" = "workspace number 9";
- "${cfg.config.modifier}+0" = "workspace number 10";
-
- "${cfg.config.modifier}+Shift+1" = "move container to workspace number 1";
- "${cfg.config.modifier}+Shift+2" = "move container to workspace number 2";
- "${cfg.config.modifier}+Shift+3" = "move container to workspace number 3";
- "${cfg.config.modifier}+Shift+4" = "move container to workspace number 4";
- "${cfg.config.modifier}+Shift+5" = "move container to workspace number 5";
- "${cfg.config.modifier}+Shift+6" = "move container to workspace number 6";
- "${cfg.config.modifier}+Shift+7" = "move container to workspace number 7";
- "${cfg.config.modifier}+Shift+8" = "move container to workspace number 8";
- "${cfg.config.modifier}+Shift+9" = "move container to workspace number 9";
- "${cfg.config.modifier}+Shift+0" = "move container to workspace number 10";
-
- "${cfg.config.modifier}+Shift+c" = "reload";
- "${cfg.config.modifier}+Shift+r" = "restart";
- "${cfg.config.modifier}+Shift+e" = "exec i3-nagbar -t warning -m 'Do you want to exit i3?' -b 'Yes' 'i3-msg exit'";
-
- "${cfg.config.modifier}+r" = "mode resize";
- };
- defaultText = "Default i3 keybindings.";
- description = ''
- An attribute set that assigns a key press to an action using a key symbol.
- See <link xlink:href="https://i3wm.org/docs/userguide.html#keybindings"/>.
- </para><para>
- Consider to use <code>lib.mkOptionDefault</code> function to extend or override
- default keybindings instead of specifying all of them from scratch.
- '';
- example = literalExample ''
- let
- modifier = xsession.windowManager.i3.config.modifier;
- in
-
- lib.mkOptionDefault {
- "''${modifier}+Return" = "exec i3-sensible-terminal";
- "''${modifier}+Shift+q" = "kill";
- "''${modifier}+d" = "exec \${pkgs.dmenu}/bin/dmenu_run";
- }
- '';
- };
-
- keycodebindings = mkOption {
- type = types.attrsOf (types.nullOr types.str);
- default = {};
- description = ''
- An attribute set that assigns keypress to an action using key code.
- See <link xlink:href="https://i3wm.org/docs/userguide.html#keybindings"/>.
- '';
- example = { "214" = "exec --no-startup-id /bin/script.sh"; };
- };
-
- colors = mkOption {
- type = types.submodule {
- options = {
- background = mkOption {
- type = types.str;
- default = "#ffffff";
- description = ''
- Background color of the window. Only applications which do not cover
- the whole area expose the color.
- '';
- };
-
- focused = mkOption {
- type = colorSetModule;
- default = {
- border = "#4c7899"; background = "#285577"; text = "#ffffff";
- indicator = "#2e9ef4"; childBorder = "#285577";
- };
- description = "A window which currently has the focus.";
- };
-
- focusedInactive = mkOption {
- type = colorSetModule;
- default = {
- border = "#333333"; background = "#5f676a"; text = "#ffffff";
- indicator = "#484e50"; childBorder = "#5f676a";
- };
- description = ''
- A window which is the focused one of its container,
- but it does not have the focus at the moment.
- '';
- };
-
- unfocused = mkOption {
- type = colorSetModule;
- default = {
- border = "#333333"; background = "#222222"; text = "#888888";
- indicator = "#292d2e"; childBorder = "#222222";
- };
- description = "A window which is not focused.";
- };
-
- urgent = mkOption {
- type = colorSetModule;
- default = {
- border = "#2f343a"; background = "#900000"; text = "#ffffff";
- indicator = "#900000"; childBorder = "#900000";
- };
- description = "A window which has its urgency hint activated.";
- };
-
- placeholder = mkOption {
- type = colorSetModule;
- default = {
- border = "#000000"; background = "#0c0c0c"; text = "#ffffff";
- indicator = "#000000"; childBorder = "#0c0c0c";
- };
- description = ''
- Background and text color are used to draw placeholder window
- contents (when restoring layouts). Border and indicator are ignored.
- '';
- };
- };
- };
- default = {};
- description = ''
- Color settings. All color classes can be specified using submodules
- with 'border', 'background', 'text', 'indicator' and 'childBorder' fields
- and RGB color hex-codes as values. See default values for the reference.
- Note that 'i3.config.colors.background' parameter takes a single RGB value.
-
- See <link xlink:href="https://i3wm.org/docs/userguide.html#_changing_colors"/>.
- '';
- };
-
- modes = mkOption {
- type = types.attrsOf (types.attrsOf types.str);
- default = {
- resize = {
- "Left" = "resize shrink width 10 px or 10 ppt";
- "Down" = "resize grow height 10 px or 10 ppt";
- "Up" = "resize shrink height 10 px or 10 ppt";
- "Right" = "resize grow width 10 px or 10 ppt";
- "Escape" = "mode default";
- "Return" = "mode default";
- };
- };
- description = ''
- An attribute set that defines binding modes and keybindings
- inside them
-
- Only basic keybinding is supported (bindsym keycomb action),
- for more advanced setup use 'i3.extraConfig'.
- '';
- };
-
- bars = mkOption {
- type = types.listOf barModule;
- default = [{}];
- description = ''
- i3 bars settings blocks. Set to empty list to remove bars completely.
- '';
- };
-
- startup = mkOption {
- type = types.listOf startupModule;
- default = [];
- description = ''
- Commands that should be executed at startup.
-
- See <link xlink:href="https://i3wm.org/docs/userguide.html#_automatically_starting_applications_on_i3_startup"/>.
- '';
- example = literalExample ''
- [
- { command = "systemctl --user restart polybar"; always = true; notification = false; }
- { command = "dropbox start"; notification = false; }
- { command = "firefox"; workspace = "1: web"; }
- ];
- '';
- };
-
- gaps = mkOption {
- type = types.nullOr (types.submodule {
- options = {
- inner = mkOption {
- type = types.nullOr types.int;
- default = null;
- description = "Inner gaps value.";
- example = 12;
- };
-
- outer = mkOption {
- type = types.nullOr types.int;
- default = null;
- description = "Outer gaps value.";
- example = 5;
- };
-
- smartGaps = mkOption {
- type = types.bool;
- default = false;
- description = ''
- This option controls whether to disable all gaps (outer and inner)
- on workspace with a single container.
- '';
- example = true;
- };
-
- smartBorders = mkOption {
- type = types.enum [ "on" "off" "no_gaps" ];
- default = "off";
- description = ''
- This option controls whether to disable container borders on
- workspace with a single container.
- '';
- };
- };
- });
- default = null;
- description = ''
- i3gaps related settings.
- Note that i3-gaps package should be set for this options to take effect.
- '';
- };
- };
- };
-
- keybindingsStr = keybindings: concatStringsSep "\n" (
- mapAttrsToList (keycomb: action: optionalString (action != null) "bindsym ${keycomb} ${action}") keybindings
- );
-
- keycodebindingsStr = keycodebindings: concatStringsSep "\n" (
- mapAttrsToList (keycomb: action: optionalString (action != null) "bindcode ${keycomb} ${action}") keycodebindings
- );
-
- colorSetStr = c: concatStringsSep " " [ c.border c.background c.text c.indicator c.childBorder ];
- barColorSetStr = c: concatStringsSep " " [ c.border c.background c.text ];
-
- criteriaStr = criteria: "[${concatStringsSep " " (mapAttrsToList (k: v: ''${k}="${v}"'') criteria)}]";
-
- modeStr = name: keybindings: ''
- mode "${name}" {
- ${keybindingsStr keybindings}
- }
- '';
-
- assignStr = workspace: criteria: concatStringsSep "\n" (
- map (c: "assign ${criteriaStr c} ${workspace}") criteria
- );
-
- barStr = {
- id, fonts, mode, hiddenState, position, workspaceButtons,
- workspaceNumbers, command, statusCommand, colors, trayOutput, extraConfig, ...
- }: ''
- bar {
- ${optionalString (id != null) "id ${id}"}
- font pango:${concatStringsSep ", " fonts}
- mode ${mode}
- hidden_state ${hiddenState}
- position ${position}
- status_command ${statusCommand}
- i3bar_command ${command}
- workspace_buttons ${if workspaceButtons then "yes" else "no"}
- strip_workspace_numbers ${if !workspaceNumbers then "yes" else "no"}
- tray_output ${trayOutput}
- colors {
- background ${colors.background}
- statusline ${colors.statusline}
- separator ${colors.separator}
- focused_workspace ${barColorSetStr colors.focusedWorkspace}
- active_workspace ${barColorSetStr colors.activeWorkspace}
- inactive_workspace ${barColorSetStr colors.inactiveWorkspace}
- urgent_workspace ${barColorSetStr colors.urgentWorkspace}
- binding_mode ${barColorSetStr colors.bindingMode}
- }
- ${extraConfig}
- }
- '';
-
- gapsStr = with cfg.config.gaps; ''
- ${optionalString (inner != null) "gaps inner ${toString inner}"}
- ${optionalString (outer != null) "gaps outer ${toString outer}"}
- ${optionalString smartGaps "smart_gaps on"}
- ${optionalString (smartBorders != "off") "smart_borders ${smartBorders}"}
- '';
-
- floatingCriteriaStr = criteria: "for_window ${criteriaStr criteria} floating enable";
- windowCommandsStr = { command, criteria, ... }: "for_window ${criteriaStr criteria} ${command}";
-
- startupEntryStr = { command, always, notification, workspace, ... }: ''
- ${if always then "exec_always" else "exec"} ${
- if (notification && workspace == null) then "" else "--no-startup-id"
- } ${
- if (workspace == null) then
- command
- else
- "i3-msg 'workspace ${workspace}; exec ${command}'"
- }
- '';
-
- configFile = pkgs.writeText "i3.conf" ((if cfg.config != null then with cfg.config; ''
- font pango:${concatStringsSep ", " fonts}
- floating_modifier ${floating.modifier}
- new_window ${if window.titlebar then "normal" else "pixel"} ${toString window.border}
- new_float ${if floating.titlebar then "normal" else "pixel"} ${toString floating.border}
- hide_edge_borders ${window.hideEdgeBorders}
- force_focus_wrapping ${if focus.forceWrapping then "yes" else "no"}
- focus_follows_mouse ${if focus.followMouse then "yes" else "no"}
- focus_on_window_activation ${focus.newWindow}
- mouse_warping ${if focus.mouseWarping then "output" else "none"}
- workspace_layout ${workspaceLayout}
- workspace_auto_back_and_forth ${if workspaceAutoBackAndForth then "yes" else "no"}
-
- client.focused ${colorSetStr colors.focused}
- client.focused_inactive ${colorSetStr colors.focusedInactive}
- client.unfocused ${colorSetStr colors.unfocused}
- client.urgent ${colorSetStr colors.urgent}
- client.placeholder ${colorSetStr colors.placeholder}
- client.background ${colors.background}
-
- ${keybindingsStr keybindings}
- ${keycodebindingsStr keycodebindings}
- ${concatStringsSep "\n" (mapAttrsToList modeStr modes)}
- ${concatStringsSep "\n" (mapAttrsToList assignStr assigns)}
- ${concatStringsSep "\n" (map barStr bars)}
- ${optionalString (gaps != null) gapsStr}
- ${concatStringsSep "\n" (map floatingCriteriaStr floating.criteria)}
- ${concatStringsSep "\n" (map windowCommandsStr window.commands)}
- ${concatStringsSep "\n" (map startupEntryStr startup)}
- '' else "") + "\n" + cfg.extraConfig);
-
-in
-
-{
- options = {
- xsession.windowManager.i3 = {
- enable = mkEnableOption "i3 window manager.";
-
- package = mkOption {
- type = types.package;
- default = pkgs.i3;
- defaultText = literalExample "pkgs.i3";
- example = literalExample "pkgs.i3-gaps";
- description = ''
- i3 package to use.
- If 'i3.config.gaps' settings are specified, 'pkgs.i3-gaps' will be set as a default package.
- '';
- };
-
- config = mkOption {
- type = types.nullOr configModule;
- default = {};
- description = "i3 configuration options.";
- };
-
- extraConfig = mkOption {
- type = types.lines;
- default = "";
- description = "Extra configuration lines to add to ~/.config/i3/config.";
- };
- };
- };
-
- config = mkIf cfg.enable (mkMerge [
- {
- home.packages = [ cfg.package ];
- xsession.windowManager.command = "${cfg.package}/bin/i3";
- xdg.configFile."i3/config" = {
- source = configFile;
- onChange = ''
- i3Socket=''${XDG_RUNTIME_DIR:-/run/user/$UID}/i3/ipc-socket.*
- if [ -S $i3Socket ]; then
- echo "Reloading i3"
- $DRY_RUN_CMD ${cfg.package}/bin/i3-msg -s $i3Socket reload 1>/dev/null
- fi
- '';
- };
- }
-
- (mkIf (cfg.config != null) {
- xsession.windowManager.i3.package = mkDefault (
- if (cfg.config.gaps != null) then pkgs.i3-gaps else pkgs.i3
- );
- })
-
- (mkIf (cfg.config != null && (any (s: s.workspace != null) cfg.config.startup)) {
- warnings = [
- ("'xsession.windowManager.i3.config.startup.*.workspace' is deprecated, "
- + "use 'xsession.windowManager.i3.config.assigns' instead."
- + "See https://github.com/rycee/home-manager/issues/265.")
- ];
- })
- ]);
-}
diff --git a/home-manager/modules/services/xscreensaver.nix b/home-manager/modules/services/xscreensaver.nix
index 73a365aa730..ac6194e70c1 100644
--- a/home-manager/modules/services/xscreensaver.nix
+++ b/home-manager/modules/services/xscreensaver.nix
@@ -40,6 +40,10 @@ in {
Description = "XScreenSaver";
After = [ "graphical-session-pre.target" ];
PartOf = [ "graphical-session.target" ];
+
+ # Make sure the service is restarted if the settings change.
+ X-Restart-Triggers =
+ [ (builtins.hashString "md5" (builtins.toJSON cfg.settings)) ];
};
Service = {
diff --git a/home-manager/modules/systemd-activate.rb b/home-manager/modules/systemd-activate.rb
index 8382c840e93..31d06d8fc19 100644
--- a/home-manager/modules/systemd-activate.rb
+++ b/home-manager/modules/systemd-activate.rb
@@ -1,6 +1,5 @@
require 'set'
require 'open3'
-require 'shellwords'
@dry_run = ENV['DRY_RUN']
@verbose = ENV['VERBOSE']
@@ -28,33 +27,36 @@ def setup_services(old_gen_path, new_gen_path, start_timeout_ms_string)
exit if old_services.empty? && new_services.empty?
+ all_services = get_active_targets_units(new_units_path)
+ maybe_changed = all_services & old_services
+ changed_services = get_changed_services(old_units_path, new_units_path, maybe_changed)
+ unchanged_oneshots = get_oneshot_services(maybe_changed - changed_services)
+
# These services should be running when this script is finished
- services_to_run = get_services_to_run(new_units_path)
- maybe_changed_services = services_to_run & old_services
+ services_to_run = all_services - unchanged_oneshots
# Only stop active services, otherwise we might get a 'service not loaded' error
# for inactive services that were removed in the current generation.
to_stop = get_active_units(old_services - new_services)
- to_restart = get_changed_services(old_units_path, new_units_path, maybe_changed_services)
+ to_restart = changed_services
to_start = get_inactive_units(services_to_run - to_restart)
- raise "daemon-reload failed" unless run_cmd('systemctl --user daemon-reload')
+ raise "daemon-reload failed" unless run_cmd('systemctl', '--user', 'daemon-reload')
- # Exclude services that aren't allowed to be manually started or stopped
+ # Exclude units that shouldn't be (re)started or stopped
no_manual_start, no_manual_stop, no_restart = get_restricted_units(to_stop + to_restart + to_start)
- to_stop -= no_manual_stop + no_restart
+ notify_skipped_units(to_restart & no_restart)
+ to_stop -= no_manual_stop
to_restart -= no_manual_stop + no_manual_start + no_restart
to_start -= no_manual_start
- puts "Not restarting: #{no_restart.join(' ')}" unless no_restart.empty?
-
if to_stop.empty? && to_start.empty? && to_restart.empty?
print_service_msg("All services are already running", services_to_run)
else
puts "Setting up services" if @verbose
- systemctl('stop', to_stop)
- systemctl('start', to_start)
- systemctl('restart', to_restart)
+ systemctl_action('stop', to_stop)
+ systemctl_action('start', to_start)
+ systemctl_action('restart', to_restart)
started_services = to_start + to_restart
if start_timeout_ms > 0 && !started_services.empty? && !@dry_run
failed = wait_and_get_failed_services(started_services, start_timeout_ms)
@@ -76,7 +78,7 @@ def get_services(dir)
end
def get_service_files(dir)
- Dir.chdir(dir) { Dir['*.{service,socket}'] }
+ Dir.chdir(dir) { Dir['*[^@].{service,socket,timer}'] }
end
def get_changed_services(dir_a, dir_b, services)
@@ -89,24 +91,24 @@ end
TargetDirRegexp = /^(.*\.target)\.wants$/
-# @return all services wanted by active targets
-def get_services_to_run(units_dir)
+# @return all units wanted by active targets
+def get_active_targets_units(units_dir)
return Set.new unless Dir.exists?(units_dir)
targets = Dir.entries(units_dir).map { |entry| entry[TargetDirRegexp, 1] }.compact
active_targets = get_active_units(targets)
- services_to_run = active_targets.map do |target|
+ active_units = active_targets.map do |target|
get_service_files(File.join(units_dir, "#{target}.wants"))
end.flatten
- Set.new(services_to_run)
+ Set.new(active_units)
end
# @return true on success
-def run_cmd(cmd)
+def run_cmd(*cmd)
print_cmd cmd
- @dry_run || system(cmd)
+ @dry_run || system(*cmd)
end
-def systemctl(cmd, services)
+def systemctl_action(cmd, services)
return if services.empty?
verb = (cmd == 'stop') ? 'Stopping' : "#{cmd.capitalize}ing"
@@ -114,7 +116,7 @@ def systemctl(cmd, services)
cmd = ['systemctl', '--user', cmd, *services]
if @dry_run
- puts cmd
+ puts cmd.join(' ')
return
end
@@ -131,32 +133,44 @@ def systemctl(cmd, services)
end
end
+def systemctl(*cmd)
+ output, _ = Open3.capture2('systemctl', '--user', *cmd)
+ output
+end
+
def print_cmd(cmd)
- puts cmd if @verbose || @dry_run
+ puts [*cmd].join(' ') if @verbose || @dry_run
end
def get_active_units(units)
- get_units_by_activity(units, true)
+ filter_units(units) { |state| state == 'active' }
end
def get_inactive_units(units)
- get_units_by_activity(units, false)
+ filter_units(units) { |state| state != 'active' }
+end
+
+def get_failed_units(units)
+ filter_units(units) { |state| state == 'failed' }
end
-def get_units_by_activity(units, active)
+def filter_units(units)
return [] if units.empty?
- units = units.to_a
- is_active = `systemctl --user is-active #{units.shelljoin}`.split
+ states = systemctl('is-active', *units).split
+ units.select.with_index { |_, i| yield states[i] }
+end
+
+def get_oneshot_services(units)
+ return [] if units.empty?
+ types = systemctl('show', '-p', 'Type', *units).split
units.select.with_index do |_, i|
- (is_active[i] == 'active') == active
+ types[i] == 'Type=oneshot'
end
end
def get_restricted_units(units)
- units = units.to_a
- infos = `systemctl --user show -p RefuseManualStart -p RefuseManualStop #{units.shelljoin}`
+ infos = systemctl('show', '-p', 'RefuseManualStart', '-p', 'RefuseManualStop', *units)
.split("\n\n")
- no_restart = []
no_manual_start = []
no_manual_stop = []
infos.zip(units).each do |info, unit|
@@ -164,14 +178,9 @@ def get_restricted_units(units)
no_manual_start << unit if no_start.end_with?('yes')
no_manual_stop << unit if no_stop.end_with?('yes')
end
- # Regular expression that indicates that a service should not be
- # restarted even if a change has been detected.
- restartRe = /^[ \t]*X-RestartIfChanged[ \t]*=[ \t]*false[ \t]*(?:#.*)?$/
- units.each do |unit|
- if `systemctl --user cat #{unit.shellescape}` =~ restartRe
- no_restart << unit
- end
- end
+ # Get units that should not be restarted even if a change has been detected.
+ no_restart_regexp = /^\s*X-RestartIfChanged\s*=\s*false\b/
+ no_restart = units.select { |unit| systemctl('cat', unit) =~ no_restart_regexp }
[no_manual_start, no_manual_stop, no_restart]
end
@@ -180,13 +189,13 @@ def wait_and_get_failed_services(services, start_timeout_ms)
# Force the previous message to always be visible before sleeping
STDOUT.flush
sleep(start_timeout_ms / 1000.0)
- get_inactive_units(services)
+ get_failed_units(services)
end
def show_failed_services_status(services)
puts
services.each do |service|
- run_cmd("systemctl --user status #{service.shellescape}")
+ run_cmd('systemctl', '--user', 'status', service)
puts
end
end
@@ -200,4 +209,8 @@ def print_service_msg(msg, services)
end
end
+def notify_skipped_units(no_restart)
+ puts "Not restarting: #{no_restart.join(' ')}" unless no_restart.empty?
+end
+
setup_services(*ARGV)
diff --git a/home-manager/modules/systemd.nix b/home-manager/modules/systemd.nix
index 56164020577..66fffadf737 100644
--- a/home-manager/modules/systemd.nix
+++ b/home-manager/modules/systemd.nix
@@ -228,7 +228,7 @@ in
# running this from the NixOS module then XDG_RUNTIME_DIR is not
# set and systemd commands will fail. We'll therefore have to
# set it ourselves in that case.
- home.activation.reloadSystemD = hm.dag.entryAfter ["linkGeneration"] (
+ home.activation.reloadSystemd = hm.dag.entryAfter ["linkGeneration"] (
let
autoReloadCmd = ''
${pkgs.ruby}/bin/ruby ${./systemd-activate.rb} \
@@ -249,7 +249,7 @@ in
if [[ $systemdStatus == 'running' || $systemdStatus == 'degraded' ]]; then
if [[ $systemdStatus == 'degraded' ]]; then
warnEcho "The user systemd session is degraded:"
- ${systemctl} --user --state=failed
+ ${systemctl} --user --no-pager --state=failed
warnEcho "Attempting to reload services anyway..."
fi
diff --git a/home-manager/modules/targets/darwin.nix b/home-manager/modules/targets/darwin.nix
new file mode 100644
index 00000000000..cd7d8e289cf
--- /dev/null
+++ b/home-manager/modules/targets/darwin.nix
@@ -0,0 +1,16 @@
+{ config, lib, pkgs, ... }:
+
+{
+ # Disabled for now due to conflicting behavior with nix-darwin. See
+ # https://github.com/rycee/home-manager/issues/1341#issuecomment-687286866
+ config = lib.mkIf (false && pkgs.stdenv.hostPlatform.isDarwin) {
+ # Install MacOS applications to the user environment.
+ home.file."Applications/Home Manager Apps".source = let
+ apps = pkgs.buildEnv {
+ name = "home-manager-applications";
+ paths = config.home.packages;
+ pathsToLink = "/Applications";
+ };
+ in "${apps}/Applications";
+ };
+}
diff --git a/home-manager/modules/targets/generic-linux.nix b/home-manager/modules/targets/generic-linux.nix
new file mode 100644
index 00000000000..47fcc87b3c0
--- /dev/null
+++ b/home-manager/modules/targets/generic-linux.nix
@@ -0,0 +1,52 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ profileDirectory = config.home.profileDirectory;
+
+in {
+ options.targets.genericLinux = {
+ enable = mkEnableOption "" // {
+ description = ''
+ Whether to enable settings that make Home Manager work better on
+ GNU/Linux distributions other than NixOS.
+ '';
+ };
+
+ extraXdgDataDirs = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "/usr/share" "/usr/local/share" ];
+ description = ''
+ List of directory names to add to <envar>XDG_DATA_DIRS</envar>.
+ '';
+ };
+ };
+
+ config = mkIf config.targets.genericLinux.enable {
+ home.sessionVariables = let
+ profiles =
+ [ "\${NIX_STATE_DIR:-/nix/var/nix}/profiles/default" profileDirectory ];
+ dataDirs = concatStringsSep ":"
+ (map (profile: "${profile}/share") profiles
+ ++ config.targets.genericLinux.extraXdgDataDirs);
+ in { XDG_DATA_DIRS = "${dataDirs}\${XDG_DATA_DIRS:+:}$XDG_DATA_DIRS"; };
+
+ home.sessionVariablesExtra = ''
+ . "${pkgs.nix}/etc/profile.d/nix.sh"
+ '';
+
+ # We need to source both nix.sh and hm-session-vars.sh as noted in
+ # https://github.com/rycee/home-manager/pull/797#issuecomment-544783247
+ programs.bash.initExtra = ''
+ . "${pkgs.nix}/etc/profile.d/nix.sh"
+ . "${profileDirectory}/etc/profile.d/hm-session-vars.sh"
+ '';
+
+ systemd.user.sessionVariables = {
+ NIX_PATH = "$HOME/.nix-defexpr/channels\${NIX_PATH:+:}$NIX_PATH";
+ };
+ };
+}
diff --git a/home-manager/modules/xresources.nix b/home-manager/modules/xresources.nix
index b74d671befb..dc59e50c46e 100644
--- a/home-manager/modules/xresources.nix
+++ b/home-manager/modules/xresources.nix
@@ -28,7 +28,11 @@ in {
options = {
xresources.properties = mkOption {
- type = types.nullOr types.attrs;
+ type = with types;
+ let
+ prim = either bool (either int str);
+ entry = either prim (listOf prim);
+ in nullOr (attrsOf entry);
default = null;
example = literalExample ''
{
@@ -71,17 +75,18 @@ in {
};
};
- config = mkIf (cfg.properties != null || cfg.extraConfig != "") {
- home.file.".Xresources" = {
- text = concatStringsSep "\n" ([ ]
- ++ optional (cfg.extraConfig != "") cfg.extraConfig
- ++ optionals (cfg.properties != null)
- (mapAttrsToList formatLine cfg.properties)) + "\n";
- onChange = ''
- if [[ -v DISPLAY ]] ; then
- $DRY_RUN_CMD ${pkgs.xorg.xrdb}/bin/xrdb -merge $HOME/.Xresources
- fi
- '';
+ config = mkIf ((cfg.properties != null && cfg.properties != { })
+ || cfg.extraConfig != "") {
+ home.file.".Xresources" = {
+ text = concatStringsSep "\n" ([ ]
+ ++ optional (cfg.extraConfig != "") cfg.extraConfig
+ ++ optionals (cfg.properties != null)
+ (mapAttrsToList formatLine cfg.properties)) + "\n";
+ onChange = ''
+ if [[ -v DISPLAY ]] ; then
+ $DRY_RUN_CMD ${pkgs.xorg.xrdb}/bin/xrdb -merge $HOME/.Xresources
+ fi
+ '';
+ };
};
- };
}