aboutsummaryrefslogtreecommitdiff
path: root/home-manager/modules/services
diff options
context:
space:
mode:
Diffstat (limited to 'home-manager/modules/services')
-rw-r--r--home-manager/modules/services/blueman-applet.nix36
-rw-r--r--home-manager/modules/services/cbatticon.nix118
-rw-r--r--home-manager/modules/services/compton.nix291
-rw-r--r--home-manager/modules/services/dunst.nix159
-rw-r--r--home-manager/modules/services/dwm-status.nix65
-rw-r--r--home-manager/modules/services/emacs.nix42
-rw-r--r--home-manager/modules/services/flameshot.nix39
-rw-r--r--home-manager/modules/services/getmail.nix55
-rw-r--r--home-manager/modules/services/gnome-keyring.nix46
-rw-r--r--home-manager/modules/services/gpg-agent.nix281
-rw-r--r--home-manager/modules/services/grobi.nix97
-rw-r--r--home-manager/modules/services/hound.nix74
-rw-r--r--home-manager/modules/services/imapnotify-accounts.nix33
-rw-r--r--home-manager/modules/services/imapnotify.nix90
-rw-r--r--home-manager/modules/services/kbfs.nix67
-rw-r--r--home-manager/modules/services/kdeconnect.nix72
-rw-r--r--home-manager/modules/services/keepassx.nix27
-rw-r--r--home-manager/modules/services/keybase.nix37
-rw-r--r--home-manager/modules/services/lorri.nix51
-rw-r--r--home-manager/modules/services/mbsync.nix104
-rw-r--r--home-manager/modules/services/mpd.nix150
-rw-r--r--home-manager/modules/services/mpdris2.nix101
-rw-r--r--home-manager/modules/services/muchsync.nix203
-rw-r--r--home-manager/modules/services/network-manager-applet.nix36
-rw-r--r--home-manager/modules/services/nextcloud-client.nix26
-rw-r--r--home-manager/modules/services/owncloud-client.nix26
-rw-r--r--home-manager/modules/services/parcellite.nix35
-rw-r--r--home-manager/modules/services/password-store-sync.nix71
-rw-r--r--home-manager/modules/services/pasystray.nix30
-rw-r--r--home-manager/modules/services/polybar.nix135
-rw-r--r--home-manager/modules/services/random-background.nix97
-rw-r--r--home-manager/modules/services/redshift.nix164
-rw-r--r--home-manager/modules/services/rsibreak.nix32
-rw-r--r--home-manager/modules/services/screen-locker.nix85
-rw-r--r--home-manager/modules/services/spotifyd.nix52
-rw-r--r--home-manager/modules/services/stalonetray.nix90
-rw-r--r--home-manager/modules/services/status-notifier-watcher.nix44
-rw-r--r--home-manager/modules/services/sxhkd.nix86
-rw-r--r--home-manager/modules/services/syncthing.nix68
-rw-r--r--home-manager/modules/services/taffybar.nix44
-rw-r--r--home-manager/modules/services/tahoe-lafs.nix19
-rw-r--r--home-manager/modules/services/taskwarrior-sync.nix50
-rw-r--r--home-manager/modules/services/udiskie.nix91
-rw-r--r--home-manager/modules/services/unclutter.nix61
-rw-r--r--home-manager/modules/services/unison.nix121
-rw-r--r--home-manager/modules/services/window-managers/awesome.nix52
-rw-r--r--home-manager/modules/services/window-managers/bspwm/default.nix74
-rw-r--r--home-manager/modules/services/window-managers/bspwm/options.nix214
-rw-r--r--home-manager/modules/services/window-managers/i3.nix856
-rw-r--r--home-manager/modules/services/window-managers/xmonad.nix101
-rw-r--r--home-manager/modules/services/xcape.nix76
-rw-r--r--home-manager/modules/services/xembed-sni-proxy.nix45
-rw-r--r--home-manager/modules/services/xscreensaver.nix52
-rw-r--r--home-manager/modules/services/xsuspender.nix192
54 files changed, 5363 insertions, 0 deletions
diff --git a/home-manager/modules/services/blueman-applet.nix b/home-manager/modules/services/blueman-applet.nix
new file mode 100644
index 00000000000..5a57acccc27
--- /dev/null
+++ b/home-manager/modules/services/blueman-applet.nix
@@ -0,0 +1,36 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ options = {
+ services.blueman-applet = {
+ enable = mkEnableOption "" // {
+ description = ''
+ Whether to enable the Blueman applet.
+ </para><para>
+ Note, for the applet to work, the 'blueman' service should
+ be enabled system-wide. You can enable it in the system
+ configuration using
+ <programlisting language="nix">
+ services.blueman.enable = true;
+ </programlisting>
+ '';
+ };
+ };
+ };
+
+ config = mkIf config.services.blueman-applet.enable {
+ systemd.user.services.blueman-applet = {
+ Unit = {
+ Description = "Blueman applet";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = { ExecStart = "${pkgs.blueman}/bin/blueman-applet"; };
+ };
+ };
+}
diff --git a/home-manager/modules/services/cbatticon.nix b/home-manager/modules/services/cbatticon.nix
new file mode 100644
index 00000000000..0de69c5f9ec
--- /dev/null
+++ b/home-manager/modules/services/cbatticon.nix
@@ -0,0 +1,118 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.cbatticon;
+
+ package = pkgs.cbatticon;
+
+ makeCommand = commandName: commandArg:
+ optional (commandArg != null)
+ (let cmd = pkgs.writeShellScript commandName commandArg;
+ in "--${commandName} ${cmd}");
+
+ commandLine = concatStringsSep " " ([ "${package}/bin/cbatticon" ]
+ ++ makeCommand "command-critical-level" cfg.commandCriticalLevel
+ ++ makeCommand "command-left-click" cfg.commandLeftClick
+ ++ optional (cfg.iconType != null) "--icon-type ${cfg.iconType}"
+ ++ optional (cfg.lowLevelPercent != null)
+ "--low-level ${toString cfg.lowLevelPercent}"
+ ++ optional (cfg.criticalLevelPercent != null)
+ "--critical-level ${toString cfg.criticalLevelPercent}"
+ ++ optional (cfg.updateIntervalSeconds != null)
+ "--update-interval ${toString cfg.updateIntervalSeconds}"
+ ++ optional (cfg.hideNotification != null && cfg.hideNotification)
+ "--hide-notification");
+
+in {
+ meta.maintainers = [ maintainers.pmiddend ];
+
+ options = {
+ services.cbatticon = {
+ enable = mkEnableOption "cbatticon";
+
+ commandCriticalLevel = mkOption {
+ type = types.nullOr types.lines;
+ default = null;
+ example = ''
+ notify-send "battery critical!"
+ '';
+ description = ''
+ Command to execute when the critical battery level is reached.
+ '';
+ };
+
+ commandLeftClick = mkOption {
+ type = types.nullOr types.lines;
+ default = null;
+ description = ''
+ Command to execute when left clicking on the tray icon.
+ '';
+ };
+
+ iconType = mkOption {
+ type =
+ types.nullOr (types.enum [ "standard" "notification" "symbolic" ]);
+ default = null;
+ example = "symbolic";
+ description = "Icon type to display in the system tray.";
+ };
+
+ lowLevelPercent = mkOption {
+ type = types.nullOr (types.ints.between 0 100);
+ default = null;
+ example = 20;
+ description = ''
+ Low level percentage of the battery in percent (without the
+ percent symbol).
+ '';
+ };
+
+ criticalLevelPercent = mkOption {
+ type = types.nullOr (types.ints.between 0 100);
+ default = null;
+ example = 5;
+ description = ''
+ Critical level percentage of the battery in percent (without
+ the percent symbol).
+ '';
+ };
+
+ updateIntervalSeconds = mkOption {
+ type = types.nullOr types.ints.positive;
+ default = null;
+ example = 5;
+ description = ''
+ Number of seconds between updates of the battery information.
+ '';
+ };
+
+ hideNotification = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = "Hide the notification popups.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ package ];
+
+ systemd.user.services.cbatticon = {
+ Unit = {
+ Description = "cbatticon system tray battery icon";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ ExecStart = commandLine;
+ Restart = "on-abort";
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/services/compton.nix b/home-manager/modules/services/compton.nix
new file mode 100644
index 00000000000..c5b96af34da
--- /dev/null
+++ b/home-manager/modules/services/compton.nix
@@ -0,0 +1,291 @@
+{ 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.
+ '';
+ };
+ };
+
+ 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" ]; };
+
+ 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" ];
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/services/dunst.nix b/home-manager/modules/services/dunst.nix
new file mode 100644
index 00000000000..d32e875137b
--- /dev/null
+++ b/home-manager/modules/services/dunst.nix
@@ -0,0 +1,159 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.dunst;
+
+ eitherStrBoolIntList = with types;
+ either str (either bool (either int (listOf str)));
+
+ toDunstIni = generators.toINI {
+ mkKeyValue = key: value:
+ let
+ value' = if isBool value then
+ (if value then "yes" else "no")
+ else if isString value then
+ ''"${value}"''
+ else
+ toString value;
+ in "${key}=${value'}";
+ };
+
+ themeType = types.submodule {
+ options = {
+ package = mkOption {
+ type = types.package;
+ example = literalExample "pkgs.gnome3.adwaita-icon-theme";
+ description = "Package providing the theme.";
+ };
+
+ name = mkOption {
+ type = types.str;
+ example = "Adwaita";
+ description = "The name of the theme within the package.";
+ };
+
+ size = mkOption {
+ type = types.str;
+ default = "32x32";
+ example = "16x16";
+ description = "The desired icon size.";
+ };
+ };
+ };
+
+ hicolorTheme = {
+ package = pkgs.hicolor_icon_theme;
+ name = "hicolor";
+ size = "32x32";
+ };
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ services.dunst = {
+ enable = mkEnableOption "the dunst notification daemon";
+
+ iconTheme = mkOption {
+ type = themeType;
+ default = hicolorTheme;
+ description = "Set the icon theme.";
+ };
+
+ settings = mkOption {
+ type = with types; attrsOf (attrsOf eitherStrBoolIntList);
+ default = { };
+ description = "Configuration written to ~/.config/dunstrc";
+ example = literalExample ''
+ {
+ global = {
+ geometry = "300x5-30+50";
+ transparency = 10;
+ frame_color = "#eceff1";
+ font = "Droid Sans 9";
+ };
+
+ urgency_normal = {
+ background = "#37474f";
+ foreground = "#eceff1";
+ timeout = 10;
+ };
+ };
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+ xdg.dataFile."dbus-1/services/org.knopwob.dunst.service".source =
+ "${pkgs.dunst}/share/dbus-1/services/org.knopwob.dunst.service";
+
+ services.dunst.settings.global.icon_path = let
+ useCustomTheme = cfg.iconTheme.package != hicolorTheme.package
+ || cfg.iconTheme.name != hicolorTheme.name || cfg.iconTheme.size
+ != hicolorTheme.size;
+
+ basePaths = [
+ "/run/current-system/sw"
+ config.home.profileDirectory
+ cfg.iconTheme.package
+ ] ++ optional useCustomTheme hicolorTheme.package;
+
+ themes = [ cfg.iconTheme ] ++ optional useCustomTheme
+ (hicolorTheme // { size = cfg.iconTheme.size; });
+
+ categories = [
+ "actions"
+ "animations"
+ "apps"
+ "categories"
+ "devices"
+ "emblems"
+ "emotes"
+ "filesystem"
+ "intl"
+ "mimetypes"
+ "places"
+ "status"
+ "stock"
+ ];
+ in concatStringsSep ":" (concatMap (theme:
+ concatMap (basePath:
+ map (category:
+ "${basePath}/share/icons/${theme.name}/${theme.size}/${category}")
+ categories) basePaths) themes);
+
+ systemd.user.services.dunst = {
+ Unit = {
+ Description = "Dunst notification daemon";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ Type = "dbus";
+ BusName = "org.freedesktop.Notifications";
+ ExecStart = "${pkgs.dunst}/bin/dunst";
+ };
+ };
+ }
+
+ (mkIf (cfg.settings != { }) {
+ xdg.configFile."dunst/dunstrc" = {
+ text = toDunstIni cfg.settings;
+ onChange = ''
+ pkillVerbose=""
+ if [[ -v VERBOSE ]]; then
+ pkillVerbose="-e"
+ fi
+ $DRY_RUN_CMD ${pkgs.procps}/bin/pkill -u $USER $pkillVerbose dunst || true
+ unset pkillVerbose
+ '';
+ };
+ })
+ ]);
+}
diff --git a/home-manager/modules/services/dwm-status.nix b/home-manager/modules/services/dwm-status.nix
new file mode 100644
index 00000000000..7a19e5e5fc9
--- /dev/null
+++ b/home-manager/modules/services/dwm-status.nix
@@ -0,0 +1,65 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.dwm-status;
+
+ features = [ "audio" "backlight" "battery" "cpu_load" "network" "time" ];
+
+ configText = builtins.toJSON ({ inherit (cfg) order; } // cfg.extraConfig);
+
+ configFile = pkgs.writeText "dwm-status.json" configText;
+
+in {
+ options = {
+ services.dwm-status = {
+ enable = mkEnableOption "dwm-status user service";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.dwm-status;
+ defaultText = literalExample "pkgs.dwm-status";
+ example = "pkgs.dwm-status.override { enableAlsaUtils = false; }";
+ description = "Which dwm-status package to use.";
+ };
+
+ order = mkOption {
+ type = types.listOf (types.enum features);
+ description = "List of enabled features in order.";
+ };
+
+ extraConfig = mkOption {
+ type = types.attrs;
+ default = { };
+ example = literalExample ''
+ {
+ separator = "#";
+
+ battery = {
+ notifier_levels = [ 2 5 10 15 20 ];
+ };
+
+ time = {
+ format = "%H:%M";
+ };
+ }
+ '';
+ description = "Extra config of dwm-status.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.dwm-status = {
+ Unit = {
+ Description = "DWM status service";
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = { ExecStart = "${cfg.package}/bin/dwm-status ${configFile}"; };
+ };
+ };
+}
diff --git a/home-manager/modules/services/emacs.nix b/home-manager/modules/services/emacs.nix
new file mode 100644
index 00000000000..5b0e88db72d
--- /dev/null
+++ b/home-manager/modules/services/emacs.nix
@@ -0,0 +1,42 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.emacs;
+ emacsCfg = config.programs.emacs;
+ emacsBinPath = "${emacsCfg.finalPackage}/bin";
+
+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;
+ };
+
+ Service = {
+ ExecStart =
+ "${pkgs.runtimeShell} -l -c 'exec ${emacsBinPath}/emacs --fg-daemon'";
+ ExecStop = "${emacsBinPath}/emacsclient --eval '(kill-emacs)'";
+ Restart = "on-failure";
+ };
+
+ Install = { WantedBy = [ "default.target" ]; };
+ };
+ };
+}
diff --git a/home-manager/modules/services/flameshot.nix b/home-manager/modules/services/flameshot.nix
new file mode 100644
index 00000000000..c8659d51d1e
--- /dev/null
+++ b/home-manager/modules/services/flameshot.nix
@@ -0,0 +1,39 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.flameshot;
+ package = pkgs.flameshot;
+
+in {
+ meta.maintainers = [ maintainers.hamhut1066 ];
+
+ options = { services.flameshot = { enable = mkEnableOption "Flameshot"; }; };
+
+ config = mkIf cfg.enable {
+ home.packages = [ package ];
+
+ systemd.user.services.flameshot = {
+ Unit = {
+ Description = "Flameshot screenshot tool";
+ After = [
+ "graphical-session-pre.target"
+ "polybar.service"
+ "stalonetray.service"
+ "taffybar.service"
+ ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ Environment = "PATH=${config.home.profileDirectory}/bin";
+ ExecStart = "${package}/bin/flameshot";
+ Restart = "on-abort";
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/services/getmail.nix b/home-manager/modules/services/getmail.nix
new file mode 100644
index 00000000000..e7a1b1a4627
--- /dev/null
+++ b/home-manager/modules/services/getmail.nix
@@ -0,0 +1,55 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.getmail;
+
+ accounts =
+ filter (a: a.getmail.enable) (attrValues config.accounts.email.accounts);
+
+ # Note: The getmail service does not expect a path, but just the filename!
+ renderConfigFilepath = a:
+ if a.primary then "getmailrc" else "getmail${a.name}";
+ configFiles =
+ concatMapStringsSep " " (a: " --rcfile ${renderConfigFilepath a}") accounts;
+in {
+ options = {
+ services.getmail = {
+ enable = mkEnableOption
+ "the getmail systemd service to automatically retrieve mail";
+
+ frequency = mkOption {
+ type = types.str;
+ default = "*:0/5";
+ example = "hourly";
+ description = ''
+ The refresh frequency. Check <literal>man systemd.time</literal> for
+ more information on the syntax. If you use a gpg-agent in
+ combination with the passwordCommand, keep the poll
+ frequency below the cache-ttl value (as set by the
+ <literal>default</literal>) to avoid pinentry asking
+ permanently for a password.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.getmail = {
+ Unit = { Description = "getmail email fetcher"; };
+ Service = { ExecStart = "${pkgs.getmail}/bin/getmail ${configFiles}"; };
+ };
+
+ systemd.user.timers.getmail = {
+ Unit = { Description = "getmail email fetcher"; };
+ Timer = {
+ OnCalendar = "${cfg.frequency}";
+ Unit = "getmail.service";
+ };
+ Install = { WantedBy = [ "timers.target" ]; };
+ };
+
+ };
+}
diff --git a/home-manager/modules/services/gnome-keyring.nix b/home-manager/modules/services/gnome-keyring.nix
new file mode 100644
index 00000000000..6d8317dcffc
--- /dev/null
+++ b/home-manager/modules/services/gnome-keyring.nix
@@ -0,0 +1,46 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.gnome-keyring;
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ services.gnome-keyring = {
+ enable = mkEnableOption "GNOME Keyring";
+
+ components = mkOption {
+ type = types.listOf (types.enum [ "pkcs11" "secrets" "ssh" ]);
+ default = [ ];
+ description = ''
+ The GNOME keyring components to start. If empty then the
+ default set of components will be started.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.gnome-keyring = {
+ Unit = {
+ Description = "GNOME Keyring";
+ PartOf = [ "graphical-session-pre.target" ];
+ };
+
+ Service = {
+ ExecStart = let
+ args = concatStringsSep " " ([ "--start" "--foreground" ]
+ ++ optional (cfg.components != [ ])
+ ("--components=" + concatStringsSep "," cfg.components));
+ in "${pkgs.gnome3.gnome_keyring}/bin/gnome-keyring-daemon ${args}";
+ Restart = "on-abort";
+ };
+
+ Install = { WantedBy = [ "graphical-session-pre.target" ]; };
+ };
+ };
+}
diff --git a/home-manager/modules/services/gpg-agent.nix b/home-manager/modules/services/gpg-agent.nix
new file mode 100644
index 00000000000..16a4723fea7
--- /dev/null
+++ b/home-manager/modules/services/gpg-agent.nix
@@ -0,0 +1,281 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.gpg-agent;
+
+ gpgInitStr = ''
+ GPG_TTY="$(tty)"
+ export GPG_TTY
+ ''
+ + optionalString cfg.enableSshSupport
+ "${pkgs.gnupg}/bin/gpg-connect-agent updatestartuptty /bye > /dev/null";
+
+in
+
+{
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ services.gpg-agent = {
+ enable = mkEnableOption "GnuPG private key agent";
+
+ defaultCacheTtl = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ Set the time a cache entry is valid to the given number of
+ seconds.
+ '';
+ };
+
+ defaultCacheTtlSsh = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ Set the time a cache entry used for SSH keys is valid to the
+ given number of seconds.
+ '';
+ };
+
+ maxCacheTtl = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ Set the maximum time a cache entry is valid to n seconds. After this
+ time a cache entry will be expired even if it has been accessed
+ recently or has been set using gpg-preset-passphrase. The default is
+ 2 hours (7200 seconds).
+ '';
+ };
+
+ maxCacheTtlSsh = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ Set the maximum time a cache entry used for SSH keys is valid to n
+ seconds. After this time a cache entry will be expired even if it has
+ been accessed recently or has been set using gpg-preset-passphrase.
+ The default is 2 hours (7200 seconds).
+ '';
+ };
+
+ enableSshSupport = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to use the GnuPG key agent for SSH keys.
+ '';
+ };
+
+ sshKeys = mkOption {
+ type = types.nullOr (types.listOf types.str);
+ default = null;
+ description = ''
+ Which GPG keys (by keygrip) to expose as SSH keys.
+ '';
+ };
+
+ enableExtraSocket = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable extra socket of the GnuPG key agent (useful for GPG
+ Agent forwarding).
+ '';
+ };
+
+ verbose = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to produce verbose output.
+ '';
+ };
+
+ grabKeyboardAndMouse = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Tell the pinentry to grab the keyboard and mouse. This
+ option should in general be used to avoid X-sniffing
+ attacks. When disabled, this option passes
+ <option>no-grab</option> setting to gpg-agent.
+ '';
+ };
+
+ enableScDaemon = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Make use of the scdaemon tool. This option has the effect of
+ enabling the ability to do smartcard operations. When
+ disabled, this option passes
+ <option>disable-scdaemon</option> setting to gpg-agent.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ allow-emacs-pinentry
+ allow-loopback-pinentry
+ '';
+ description = ''
+ Extra configuration lines to append to the gpg-agent
+ configuration file.
+ '';
+ };
+
+ pinentryFlavor = mkOption {
+ type = types.nullOr (types.enum pkgs.pinentry.flavors);
+ example = "gnome3";
+ default = "gtk2";
+ description = ''
+ Which pinentry interface to use. If not
+ <literal>null</literal>, it sets
+ <option>pinentry-program</option> in
+ <filename>gpg-agent.conf</filename>. Beware that
+ <literal>pinentry-gnome3</literal> may not work on non-Gnome
+ systems. You can fix it by adding the following to your
+ system configuration:
+ <programlisting language="nix">
+ services.dbus.packages = [ pkgs.gcr ];
+ </programlisting>
+ For this reason, the default is <literal>gtk2</literal> for
+ now.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+ home.file.".gnupg/gpg-agent.conf".text = concatStringsSep "\n" (
+ optional (cfg.enableSshSupport) "enable-ssh-support"
+ ++
+ optional (!cfg.grabKeyboardAndMouse) "no-grab"
+ ++
+ optional (!cfg.enableScDaemon) "disable-scdaemon"
+ ++
+ optional (cfg.defaultCacheTtl != null)
+ "default-cache-ttl ${toString cfg.defaultCacheTtl}"
+ ++
+ optional (cfg.defaultCacheTtlSsh != null)
+ "default-cache-ttl-ssh ${toString cfg.defaultCacheTtlSsh}"
+ ++
+ optional (cfg.maxCacheTtl != null)
+ "max-cache-ttl ${toString cfg.maxCacheTtl}"
+ ++
+ optional (cfg.maxCacheTtlSsh != null)
+ "max-cache-ttl-ssh ${toString cfg.maxCacheTtlSsh}"
+ ++
+ optional (cfg.pinentryFlavor != null)
+ "pinentry-program ${pkgs.pinentry.${cfg.pinentryFlavor}}/bin/pinentry"
+ ++
+ [ cfg.extraConfig ]
+ );
+
+ home.sessionVariables =
+ optionalAttrs cfg.enableSshSupport {
+ SSH_AUTH_SOCK = "$(${pkgs.gnupg}/bin/gpgconf --list-dirs agent-ssh-socket)";
+ };
+
+ programs.bash.initExtra = gpgInitStr;
+ programs.zsh.initExtra = gpgInitStr;
+ }
+
+ (mkIf (cfg.sshKeys != null) {
+ # Trailing newlines are important
+ home.file.".gnupg/sshcontrol".text = concatMapStrings (s: "${s}\n") cfg.sshKeys;
+ })
+
+ # The systemd units below are direct translations of the
+ # descriptions in the
+ #
+ # ${pkgs.gnupg}/share/doc/gnupg/examples/systemd-user
+ #
+ # directory.
+ {
+ systemd.user.services.gpg-agent = {
+ Unit = {
+ Description = "GnuPG cryptographic agent and passphrase cache";
+ Documentation = "man:gpg-agent(1)";
+ Requires = "gpg-agent.socket";
+ After = "gpg-agent.socket";
+ # This is a socket-activated service:
+ RefuseManualStart = true;
+ };
+
+ Service = {
+ ExecStart = "${pkgs.gnupg}/bin/gpg-agent --supervised"
+ + optionalString cfg.verbose " --verbose";
+ ExecReload = "${pkgs.gnupg}/bin/gpgconf --reload gpg-agent";
+ };
+ };
+
+ systemd.user.sockets.gpg-agent = {
+ Unit = {
+ Description = "GnuPG cryptographic agent and passphrase cache";
+ Documentation = "man:gpg-agent(1)";
+ };
+
+ Socket = {
+ ListenStream = "%t/gnupg/S.gpg-agent";
+ FileDescriptorName = "std";
+ SocketMode = "0600";
+ DirectoryMode = "0700";
+ };
+
+ Install = {
+ WantedBy = [ "sockets.target" ];
+ };
+ };
+ }
+
+ (mkIf cfg.enableSshSupport {
+ systemd.user.sockets.gpg-agent-ssh = {
+ Unit = {
+ Description = "GnuPG cryptographic agent (ssh-agent emulation)";
+ Documentation = "man:gpg-agent(1) man:ssh-add(1) man:ssh-agent(1) man:ssh(1)";
+ };
+
+ Socket = {
+ ListenStream = "%t/gnupg/S.gpg-agent.ssh";
+ FileDescriptorName = "ssh";
+ Service = "gpg-agent.service";
+ SocketMode = "0600";
+ DirectoryMode = "0700";
+ };
+
+ Install = {
+ WantedBy = [ "sockets.target" ];
+ };
+ };
+ })
+
+ (mkIf cfg.enableExtraSocket {
+ systemd.user.sockets.gpg-agent-extra = {
+ Unit = {
+ Description = "GnuPG cryptographic agent and passphrase cache (restricted)";
+ Documentation = "man:gpg-agent(1) man:ssh(1)";
+ };
+
+ Socket = {
+ ListenStream = "%t/gnupg/S.gpg-agent.extra";
+ FileDescriptorName = "extra";
+ Service = "gpg-agent.service";
+ SocketMode = "0600";
+ DirectoryMode = "0700";
+ };
+
+ Install = {
+ WantedBy = [ "sockets.target" ];
+ };
+ };
+ })
+ ]);
+}
diff --git a/home-manager/modules/services/grobi.nix b/home-manager/modules/services/grobi.nix
new file mode 100644
index 00000000000..4dfc5d6331f
--- /dev/null
+++ b/home-manager/modules/services/grobi.nix
@@ -0,0 +1,97 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.grobi;
+
+ eitherStrBoolIntList = with types;
+ either str (either bool (either int (listOf str)));
+
+in {
+ meta.maintainers = [ maintainers.mbrgm ];
+
+ options = {
+ services.grobi = {
+ enable = mkEnableOption "the grobi display setup daemon";
+
+ executeAfter = mkOption {
+ type = with types; listOf str;
+ default = [ ];
+ example = [ "setxkbmap dvorak" ];
+ description = ''
+ Commands to be run after an output configuration was
+ changed. The Nix value declared here will be translated to
+ JSON and written to the <option>execute_after</option> key
+ in <filename>~/.config/grobi.conf</filename>.
+ '';
+ };
+
+ rules = mkOption {
+ type = with types; listOf (attrsOf eitherStrBoolIntList);
+ default = [ ];
+ example = literalExample ''
+ [
+ {
+ name = "Home";
+ outputs_connected = [ "DP-2" ];
+ configure_single = "DP-2";
+ primary = true;
+ atomic = true;
+ execute_after = [
+ "${pkgs.xorg.xrandr}/bin/xrandr --dpi 96"
+ "${pkgs.xmonad-with-packages}/bin/xmonad --restart";
+ ];
+ }
+ {
+ name = "Mobile";
+ outputs_disconnected = [ "DP-2" ];
+ configure_single = "eDP-1";
+ primary = true;
+ atomic = true;
+ execute_after = [
+ "${pkgs.xorg.xrandr}/bin/xrandr --dpi 120"
+ "${pkgs.xmonad-with-packages}/bin/xmonad --restart";
+ ];
+ }
+ ]
+ '';
+ description = ''
+ These are the rules grobi tries to match to the current
+ output configuration. The rules are evaluated top to bottom,
+ the first matching rule is applied and processing stops. See
+ <link xlink:href="https://github.com/fd0/grobi/blob/master/doc/grobi.conf"/>
+ for more information. The Nix value declared here will be
+ translated to JSON and written to the <option>rules</option>
+ key in <filename>~/.config/grobi.conf</filename>.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.grobi = {
+ Unit = {
+ Description = "grobi display auto config daemon";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ Type = "simple";
+ ExecStart = "${pkgs.grobi}/bin/grobi watch -v";
+ Restart = "always";
+ RestartSec = "2s";
+ Environment = "PATH=${pkgs.xorg.xrandr}/bin:${pkgs.bash}/bin";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+
+ xdg.configFile."grobi.conf".text = builtins.toJSON {
+ execute_after = cfg.executeAfter;
+ rules = cfg.rules;
+ };
+ };
+}
diff --git a/home-manager/modules/services/hound.nix b/home-manager/modules/services/hound.nix
new file mode 100644
index 00000000000..00589f3405f
--- /dev/null
+++ b/home-manager/modules/services/hound.nix
@@ -0,0 +1,74 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.hound;
+
+ configFile = pkgs.writeText "hound-config.json" (builtins.toJSON {
+ max-concurrent-indexers = cfg.maxConcurrentIndexers;
+ dbpath = cfg.databasePath;
+ repos = cfg.repositories;
+ health-check-url = "/healthz";
+ });
+
+ houndOptions = [ "--addr ${cfg.listenAddress}" "--conf ${configFile}" ];
+
+in {
+ meta.maintainers = [ maintainers.adisbladis ];
+
+ options.services.hound = {
+ enable = mkEnableOption "hound";
+
+ maxConcurrentIndexers = mkOption {
+ type = types.ints.positive;
+ default = 2;
+ description = "Limit the amount of concurrent indexers.";
+ };
+
+ databasePath = mkOption {
+ type = types.path;
+ default = "${config.xdg.dataHome}/hound";
+ defaultText = "$XDG_DATA_HOME/hound";
+ description = "The Hound database path.";
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "localhost:6080";
+ description = "Listen address of the Hound daemon.";
+ };
+
+ repositories = mkOption {
+ type = types.attrsOf (types.uniq types.attrs);
+ default = { };
+ example = literalExample ''
+ {
+ SomeGitRepo = {
+ url = "https://www.github.com/YourOrganization/RepoOne.git";
+ ms-between-poll = 10000;
+ exclude-dot-files = true;
+ };
+ }
+ '';
+ description = "The repository configuration.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.hound ];
+
+ systemd.user.services.hound = {
+ Unit = { Description = "Hound source code search engine"; };
+
+ Install = { WantedBy = [ "default.target" ]; };
+
+ Service = {
+ Environment = "PATH=${makeBinPath [ pkgs.mercurial pkgs.git ]}";
+ ExecStart =
+ "${pkgs.hound}/bin/houndd ${concatStringsSep " " houndOptions}";
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/services/imapnotify-accounts.nix b/home-manager/modules/services/imapnotify-accounts.nix
new file mode 100644
index 00000000000..94bdce5dfb4
--- /dev/null
+++ b/home-manager/modules/services/imapnotify-accounts.nix
@@ -0,0 +1,33 @@
+{ lib, ... }:
+
+with lib;
+
+{
+ options.imapnotify = {
+ enable = mkEnableOption "imapnotify";
+
+ onNotify = mkOption {
+ type = with types; either str (attrsOf str);
+ default = "";
+ example = "\${pkgs.isync}/bin/mbsync test-%s";
+ description = "Shell commands to run on any event.";
+ };
+
+ onNotifyPost = mkOption {
+ type = with types; either str (attrsOf str);
+ default = "";
+ example = {
+ mail =
+ "\${pkgs.notmuch}/bin/notmuch new && \${pkgs.libnotify}/bin/notify-send 'New mail arrived'";
+ };
+ description = "Shell commands to run after onNotify event.";
+ };
+
+ boxes = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "Inbox" "[Gmail]/MyLabel" ];
+ description = "IMAP folders to watch.";
+ };
+ };
+}
diff --git a/home-manager/modules/services/imapnotify.nix b/home-manager/modules/services/imapnotify.nix
new file mode 100644
index 00000000000..b59b006e335
--- /dev/null
+++ b/home-manager/modules/services/imapnotify.nix
@@ -0,0 +1,90 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.imapnotify;
+
+ safeName = lib.replaceChars [ "@" ":" "\\" "[" "]" ] [ "-" "-" "-" "" "" ];
+
+ imapnotifyAccounts =
+ filter (a: a.imapnotify.enable) (attrValues config.accounts.email.accounts);
+
+ genAccountUnit = account:
+ let name = safeName account.name;
+ in {
+ name = "imapnotify-${name}";
+ value = {
+ Unit = { Description = "imapnotify for ${name}"; };
+
+ Service = {
+ ExecStart =
+ "${pkgs.imapnotify}/bin/imapnotify -c ${genAccountConfig account}";
+ } // optionalAttrs account.notmuch.enable {
+ Environment =
+ "NOTMUCH_CONFIG=${config.xdg.configHome}/notmuch/notmuchrc";
+ };
+
+ Install = { WantedBy = [ "default.target" ]; };
+ };
+ };
+
+ genAccountConfig = account:
+ pkgs.writeText "imapnotify-${safeName account.name}-config.js" (let
+ port = if account.imap.port != null then
+ account.imap.port
+ else if account.imap.tls.enable then
+ 993
+ else
+ 143;
+
+ toJSON = builtins.toJSON;
+ in ''
+ var child_process = require('child_process');
+
+ function getStdout(cmd) {
+ var stdout = child_process.execSync(cmd);
+ return stdout.toString().trim();
+ }
+
+ exports.host = ${toJSON account.imap.host}
+ exports.port = ${toJSON port};
+ exports.tls = ${toJSON account.imap.tls.enable};
+ exports.username = ${toJSON account.userName};
+ exports.password = getStdout("${toString account.passwordCommand}");
+ exports.onNotify = ${toJSON account.imapnotify.onNotify};
+ exports.onNotifyPost = ${toJSON account.imapnotify.onNotifyPost};
+ exports.boxes = ${toJSON account.imapnotify.boxes};
+ '');
+
+in {
+ meta.maintainers = [ maintainers.nickhu ];
+
+ options = {
+ services.imapnotify = { enable = mkEnableOption "imapnotify"; };
+
+ accounts.email.accounts = mkOption {
+ type = with types; attrsOf (submodule (import ./imapnotify-accounts.nix));
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = let
+ checkAccounts = pred: msg:
+ let badAccounts = filter pred imapnotifyAccounts;
+ in {
+ assertion = badAccounts == [ ];
+ message = "imapnotify: Missing ${msg} for accounts: "
+ + concatMapStringsSep ", " (a: a.name) badAccounts;
+ };
+ in [
+ (checkAccounts (a: a.maildir == null) "maildir configuration")
+ (checkAccounts (a: a.imap == null) "IMAP configuration")
+ (checkAccounts (a: a.passwordCommand == null) "password command")
+ (checkAccounts (a: a.userName == null) "username")
+ ];
+
+ systemd.user.services = listToAttrs (map genAccountUnit imapnotifyAccounts);
+ };
+}
diff --git a/home-manager/modules/services/kbfs.nix b/home-manager/modules/services/kbfs.nix
new file mode 100644
index 00000000000..863f4feea3b
--- /dev/null
+++ b/home-manager/modules/services/kbfs.nix
@@ -0,0 +1,67 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.kbfs;
+
+in
+
+{
+ options = {
+ services.kbfs = {
+ enable = mkEnableOption "Keybase File System";
+
+ mountPoint = mkOption {
+ type = types.str;
+ default = "keybase";
+ description = ''
+ Mount point for the Keybase filesystem, relative to
+ <envar>HOME</envar>.
+ '';
+ };
+
+ extraFlags = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [
+ "-label kbfs"
+ "-mount-type normal"
+ ];
+ description = ''
+ Additional flags to pass to the Keybase filesystem on launch.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.kbfs = {
+ Unit = {
+ Description = "Keybase File System";
+ Requires = [ "keybase.service" ];
+ After = [ "keybase.service" ];
+ };
+
+ Service =
+ let
+ mountPoint = "\"%h/${cfg.mountPoint}\"";
+ in {
+ Environment = "PATH=/run/wrappers/bin KEYBASE_SYSTEMD=1";
+ ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p ${mountPoint}";
+ ExecStart ="${pkgs.kbfs}/bin/kbfsfuse ${toString cfg.extraFlags} ${mountPoint}";
+ ExecStopPost = "/run/wrappers/bin/fusermount -u ${mountPoint}";
+ Restart = "on-failure";
+ PrivateTmp = true;
+ };
+
+ Install = {
+ WantedBy = [ "default.target" ];
+ };
+ };
+
+ home.packages = [ pkgs.kbfs ];
+ services.keybase.enable = true;
+ };
+}
diff --git a/home-manager/modules/services/kdeconnect.nix b/home-manager/modules/services/kdeconnect.nix
new file mode 100644
index 00000000000..82de1f0eb7c
--- /dev/null
+++ b/home-manager/modules/services/kdeconnect.nix
@@ -0,0 +1,72 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.kdeconnect;
+ package = pkgs.kdeconnect;
+
+in {
+ meta.maintainers = [ maintainers.adisbladis ];
+
+ options = {
+ services.kdeconnect = {
+ enable = mkEnableOption "KDE connect";
+
+ indicator = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable kdeconnect-indicator service.";
+ };
+
+ };
+ };
+
+ config = mkMerge [
+ (mkIf cfg.enable {
+ home.packages = [ package ];
+
+ systemd.user.services.kdeconnect = {
+ Unit = {
+ Description =
+ "Adds communication between your desktop and your smartphone";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ Environment = "PATH=${config.home.profileDirectory}/bin";
+ ExecStart = "${package}/libexec/kdeconnectd";
+ Restart = "on-abort";
+ };
+ };
+ })
+
+ (mkIf cfg.indicator {
+ systemd.user.services.kdeconnect-indicator = {
+ Unit = {
+ Description = "kdeconnect-indicator";
+ After = [
+ "graphical-session-pre.target"
+ "polybar.service"
+ "taffybar.service"
+ "stalonetray.service"
+ ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ Environment = "PATH=${config.home.profileDirectory}/bin";
+ ExecStart = "${package}/bin/kdeconnect-indicator";
+ Restart = "on-abort";
+ };
+ };
+ })
+
+ ];
+}
diff --git a/home-manager/modules/services/keepassx.nix b/home-manager/modules/services/keepassx.nix
new file mode 100644
index 00000000000..dc37066e20c
--- /dev/null
+++ b/home-manager/modules/services/keepassx.nix
@@ -0,0 +1,27 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ services.keepassx = {
+ enable = mkEnableOption "the KeePassX password manager";
+ };
+ };
+
+ config = mkIf config.services.keepassx.enable {
+ systemd.user.services.keepassx = {
+ Unit = {
+ Description = "KeePassX password manager";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = { ExecStart = "${pkgs.keepassx}/bin/keepassx -min -lock"; };
+ };
+ };
+}
diff --git a/home-manager/modules/services/keybase.nix b/home-manager/modules/services/keybase.nix
new file mode 100644
index 00000000000..2d0a06b06a7
--- /dev/null
+++ b/home-manager/modules/services/keybase.nix
@@ -0,0 +1,37 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.keybase;
+
+in
+
+{
+ options = {
+ services.keybase = {
+ enable = mkEnableOption "Keybase";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.keybase ];
+
+ systemd.user.services.keybase = {
+ Unit = {
+ Description = "Keybase service";
+ };
+
+ Service = {
+ ExecStart = "${pkgs.keybase}/bin/keybase service --auto-forked";
+ Restart = "on-failure";
+ PrivateTmp = true;
+ };
+
+ Install = {
+ WantedBy = [ "default.target" ];
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/services/lorri.nix b/home-manager/modules/services/lorri.nix
new file mode 100644
index 00000000000..3b2c244e3c0
--- /dev/null
+++ b/home-manager/modules/services/lorri.nix
@@ -0,0 +1,51 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.lorri;
+
+in {
+ meta.maintainers = [ maintainers.gerschtli ];
+
+ options = { services.lorri.enable = mkEnableOption "lorri build daemon"; };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.lorri ];
+
+ systemd.user = {
+ services.lorri = {
+ Unit = {
+ Description = "lorri build daemon";
+ Requires = "lorri.socket";
+ After = "lorri.socket";
+ RefuseManualStart = true;
+ };
+
+ Service = {
+ ExecStart = "${pkgs.lorri}/bin/lorri daemon";
+ PrivateTmp = true;
+ ProtectSystem = "strict";
+ ProtectHome = "read-only";
+ Restart = "on-failure";
+ Environment = let
+ path = with pkgs;
+ makeSearchPath "bin" [ nix gitMinimal gnutar gzip ];
+ in "PATH=${path}";
+ };
+ };
+
+ sockets.lorri = {
+ Unit = { Description = "Socket for lorri build daemon"; };
+
+ Socket = {
+ ListenStream = "%t/lorri/daemon.socket";
+ RuntimeDirectory = "lorri";
+ };
+
+ Install = { WantedBy = [ "sockets.target" ]; };
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/services/mbsync.nix b/home-manager/modules/services/mbsync.nix
new file mode 100644
index 00000000000..ac6ac1ef78a
--- /dev/null
+++ b/home-manager/modules/services/mbsync.nix
@@ -0,0 +1,104 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.mbsync;
+
+ mbsyncOptions = [ "--all" ] ++ optional (cfg.verbose) "--verbose"
+ ++ optional (cfg.configFile != null) "--config ${cfg.configFile}";
+
+in {
+ meta.maintainers = [ maintainers.pjones ];
+
+ options.services.mbsync = {
+ enable = mkEnableOption "mbsync";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.isync;
+ defaultText = literalExample "pkgs.isync";
+ example = literalExample "pkgs.isync";
+ description = "The package to use for the mbsync binary.";
+ };
+
+ frequency = mkOption {
+ type = types.str;
+ default = "*:0/5";
+ description = ''
+ How often to run mbsync. 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.
+ '';
+ };
+
+ verbose = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether mbsync should produce verbose output.
+ '';
+ };
+
+ configFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ Optional configuration file to link to use instead of
+ the default file (<filename>~/.mbsyncrc</filename>).
+ '';
+ };
+
+ preExec = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "mkdir -p %h/mail";
+ description = ''
+ An optional command to run before mbsync executes. This is
+ useful for creating the directories mbsync is going to use.
+ '';
+ };
+
+ postExec = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "\${pkgs.mu}/bin/mu index";
+ description = ''
+ An optional command to run after mbsync executes successfully.
+ This is useful for running mailbox indexing tools.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.mbsync = {
+ Unit = { Description = "mbsync mailbox synchronization"; };
+
+ Service = {
+ Type = "oneshot";
+ ExecStart =
+ "${cfg.package}/bin/mbsync ${concatStringsSep " " mbsyncOptions}";
+ } // (optionalAttrs (cfg.postExec != null) {
+ ExecStartPost = cfg.postExec;
+ }) // (optionalAttrs (cfg.preExec != null) {
+ ExecStartPre = cfg.preExec;
+ });
+ };
+
+ systemd.user.timers.mbsync = {
+ Unit = { Description = "mbsync mailbox synchronization"; };
+
+ Timer = {
+ OnCalendar = cfg.frequency;
+ Unit = "mbsync.service";
+ };
+
+ Install = { WantedBy = [ "timers.target" ]; };
+ };
+ };
+}
diff --git a/home-manager/modules/services/mpd.nix b/home-manager/modules/services/mpd.nix
new file mode 100644
index 00000000000..2aa1cd3a9fe
--- /dev/null
+++ b/home-manager/modules/services/mpd.nix
@@ -0,0 +1,150 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ name = "mpd";
+
+ cfg = config.services.mpd;
+
+ mpdConf = pkgs.writeText "mpd.conf" ''
+ music_directory "${cfg.musicDirectory}"
+ playlist_directory "${cfg.playlistDirectory}"
+ ${lib.optionalString (cfg.dbFile != null) ''
+ db_file "${cfg.dbFile}"
+ ''}
+ state_file "${cfg.dataDir}/state"
+ sticker_file "${cfg.dataDir}/sticker.sql"
+
+ ${optionalString (cfg.network.listenAddress != "any")
+ ''bind_to_address "${cfg.network.listenAddress}"''}
+ ${optionalString (cfg.network.port != 6600)
+ ''port "${toString cfg.network.port}"''}
+
+ ${cfg.extraConfig}
+ '';
+
+in {
+
+ ###### interface
+
+ options = {
+
+ services.mpd = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable MPD, the music player daemon.
+ '';
+ };
+
+ musicDirectory = mkOption {
+ type = types.path;
+ default = "${config.home.homeDirectory}/music";
+ defaultText = "$HOME/music";
+ apply = toString; # Prevent copies to Nix store.
+ description = ''
+ The directory where mpd reads music from.
+ '';
+ };
+
+ playlistDirectory = mkOption {
+ type = types.path;
+ default = "${cfg.dataDir}/playlists";
+ defaultText = ''''${dataDir}/playlists'';
+ apply = toString; # Prevent copies to Nix store.
+ description = ''
+ The directory where mpd stores playlists.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra directives added to to the end of MPD's configuration
+ file, <filename>mpd.conf</filename>. Basic configuration
+ like file location and uid/gid is added automatically to the
+ beginning of the file. For available options see
+ <citerefentry>
+ <refentrytitle>mpd.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry>.
+ '';
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "${config.xdg.dataHome}/${name}";
+ defaultText = "$XDG_DATA_HOME/mpd";
+ apply = toString; # Prevent copies to Nix store.
+ description = ''
+ The directory where MPD stores its state, tag cache,
+ playlists etc.
+ '';
+ };
+
+ network = {
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ example = "any";
+ description = ''
+ The address for the daemon to listen on.
+ Use <literal>any</literal> to listen on all addresses.
+ '';
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = 6600;
+ description = ''
+ The TCP port on which the the daemon will listen.
+ '';
+ };
+
+ };
+
+ dbFile = mkOption {
+ type = types.nullOr types.str;
+ default = "${cfg.dataDir}/tag_cache";
+ defaultText = ''''${dataDir}/tag_cache'';
+ description = ''
+ The path to MPD's database. If set to
+ <literal>null</literal> the parameter is omitted from the
+ configuration.
+ '';
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.user.services.mpd = {
+ Unit = {
+ After = [ "network.target" "sound.target" ];
+ Description = "Music Player Daemon";
+ };
+
+ Install = {
+ WantedBy = [ "default.target" ];
+ };
+
+ Service = {
+ Environment = "PATH=${config.home.profileDirectory}/bin";
+ ExecStart = "${pkgs.mpd}/bin/mpd --no-daemon ${mpdConf}";
+ Type = "notify";
+ ExecStartPre = ''${pkgs.bash}/bin/bash -c "${pkgs.coreutils}/bin/mkdir -p '${cfg.dataDir}' '${cfg.playlistDirectory}'"'';
+ };
+ };
+ };
+
+}
diff --git a/home-manager/modules/services/mpdris2.nix b/home-manager/modules/services/mpdris2.nix
new file mode 100644
index 00000000000..cb8cefba6bd
--- /dev/null
+++ b/home-manager/modules/services/mpdris2.nix
@@ -0,0 +1,101 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.mpdris2;
+
+ toIni = generators.toINI {
+ mkKeyValue = key: value:
+ let
+ value' = if isBool value then
+ (if value then "True" else "False")
+ else
+ toString value;
+ in "${key} = ${value'}";
+ };
+
+ mpdris2Conf = {
+ Connection = {
+ host = cfg.mpd.host;
+ port = cfg.mpd.port;
+ music_dir = cfg.mpd.musicDirectory;
+ };
+
+ Bling = {
+ notify = cfg.notifications;
+ mmkeys = cfg.multimediaKeys;
+ };
+ };
+
+in {
+ meta.maintainers = [ maintainers.pjones ];
+
+ options.services.mpdris2 = {
+ enable = mkEnableOption "mpDris2 the MPD to MPRIS2 bridge";
+ notifications = mkEnableOption "song change notifications";
+ multimediaKeys = mkEnableOption "multimedia key support";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.mpdris2;
+ defaultText = literalExample "pkgs.mpdris2";
+ description = "The mpDris2 package to use.";
+ };
+
+ mpd = {
+ host = mkOption {
+ type = types.str;
+ default = config.services.mpd.network.listenAddress;
+ defaultText = "config.services.mpd.network.listenAddress";
+ example = "192.168.1.1";
+ description = "The address where MPD is listening for connections.";
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = config.services.mpd.network.port;
+ defaultText = "config.services.mpd.network.port";
+ description = ''
+ The port number where MPD is listening for connections.
+ '';
+ };
+
+ musicDirectory = mkOption {
+ type = types.nullOr types.path;
+ default = config.services.mpd.musicDirectory;
+ defaultText = "config.services.mpd.musicDirectory";
+ description = ''
+ If set, mpDris2 will use this directory to access music artwork.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [{
+ assertion = config.services.mpd.enable;
+ message = "The mpdris2 module requires 'services.mpd.enable = true'.";
+ }];
+
+ xdg.configFile."mpDris2/mpDris2.conf".text = toIni mpdris2Conf;
+
+ systemd.user.services.mpdris2 = {
+ Install = { WantedBy = [ "default.target" ]; };
+
+ Unit = {
+ Description = "MPRIS 2 support for MPD";
+ After = [ "mpd.service" ];
+ };
+
+ Service = {
+ Type = "simple";
+ Restart = "on-failure";
+ RestartSec = "5s";
+ ExecStart = "${cfg.package}/bin/mpDris2";
+ BusName = "org.mpris.MediaPlayer2.mpd";
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/services/muchsync.nix b/home-manager/modules/services/muchsync.nix
new file mode 100644
index 00000000000..b7004418d35
--- /dev/null
+++ b/home-manager/modules/services/muchsync.nix
@@ -0,0 +1,203 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+# Documentation was partially copied from the muchsync manual.
+# See http://www.muchsync.org/muchsync.html
+
+let
+ cfg = config.services.muchsync;
+ syncOptions = {
+ options = {
+ frequency = mkOption {
+ type = types.str;
+ default = "*:0/5";
+ description = ''
+ How often to run <command>muchsync</command>. This
+ value is passed to the systemd timer configuration as the
+ <literal>OnCalendar</literal> option. See
+ <citerefentry>
+ <refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum>
+ </citerefentry>
+ for more information about the format.
+ '';
+ };
+
+ sshCommand = mkOption {
+ type = types.str;
+ default = "${pkgs.openssh}/bin/ssh -CTaxq";
+ defaultText = "ssh -CTaxq";
+ description = ''
+ Specifies a command line to pass to <command>/bin/sh</command>
+ to execute a command on another machine.
+ </para><para>
+ Note that because this string is passed to the shell,
+ special characters including spaces may need to be escaped.
+ '';
+ };
+
+ upload = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to propagate local changes to the remote.
+ '';
+ };
+
+ local = {
+ checkForModifiedFiles = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Check for locally modified files.
+ Without this option, muchsync assumes that files in a maildir are
+ never edited.
+ </para><para>
+ <option>checkForModifiedFiles</option> disables certain
+ optimizations so as to make muchsync at least check the timestamp on
+ every file, which will detect modified files at the cost of a longer
+ startup time.
+ </para><para>
+ This option is useful if your software regularly modifies the
+ contents of mail files (e.g., because you are running offlineimap
+ with "synclabels = yes").
+ '';
+ };
+
+ importNew = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to begin the synchronisation by running
+ <command>notmuch new</command> locally.
+ '';
+ };
+ };
+
+ remote = {
+ host = mkOption {
+ type = types.str;
+ description = ''
+ Remote SSHΒ host to synchronize with.
+ '';
+ };
+
+ muchsyncPath = mkOption {
+ type = types.str;
+ default = "";
+ defaultText = "$PATH/muchsync";
+ description = ''
+ Specifies the path to muchsync on the server.
+ Ordinarily, muchsync should be in the default PATH on the server
+ so this option is not required.
+ However, this option is useful if you have to install muchsync in
+ a non-standard place or wish to test development versions of the
+ code.
+ '';
+ };
+
+ checkForModifiedFiles = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Check for modified files on the remote side.
+ Without this option, muchsync assumes that files in a maildir are
+ never edited.
+ </para><para>
+ <option>checkForModifiedFiles</option> disables certain
+ optimizations so as to make muchsync at least check the timestamp on
+ every file, which will detect modified files at the cost of a longer
+ startup time.
+ </para><para>
+ This option is useful if your software regularly modifies the
+ contents of mail files (e.g., because you are running offlineimap
+ with "synclabels = yes").
+ '';
+ };
+
+ importNew = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to begin the synchronisation by running
+ <command>notmuch new</command> on the remote side.
+ '';
+ };
+ };
+ };
+ };
+
+in {
+ meta.maintainers = with maintainers; [ pacien ];
+
+ options.services.muchsync = {
+ remotes = mkOption {
+ type = with types; attrsOf (submodule syncOptions);
+ default = { };
+ example = literalExample ''
+ {
+ server = {
+ frequency = "*:0/10";
+ remote.host = "server.tld";
+ };
+ }
+ '';
+ description = ''
+ Muchsync remotes to synchronise with.
+ '';
+ };
+ };
+
+ config = let
+ mapRemotes = gen:
+ with attrsets;
+ mapAttrs'
+ (name: remoteCfg: nameValuePair "muchsync-${name}" (gen name remoteCfg))
+ cfg.remotes;
+ in mkIf (cfg.remotes != { }) {
+ assertions = [{
+ assertion = config.programs.notmuch.enable;
+ message = ''
+ The muchsync module requires 'programs.notmuch.enable = true'.
+ '';
+ }];
+
+ systemd.user.services = mapRemotes (name: remoteCfg: {
+ Unit = { Description = "muchsync sync service (${name})"; };
+ Service = {
+ CPUSchedulingPolicy = "idle";
+ IOSchedulingClass = "idle";
+ Environment = [
+ ''"PATH=${pkgs.notmuch}/bin"''
+ ''"NOTMUCH_CONFIG=${config.home.sessionVariables.NOTMUCH_CONFIG}"''
+ ''"NMBGIT=${config.home.sessionVariables.NMBGIT}"''
+ ];
+ ExecStart = concatStringsSep " " ([ "${pkgs.muchsync}/bin/muchsync" ]
+ ++ [ "-s ${escapeShellArg remoteCfg.sshCommand}" ]
+ ++ optional (!remoteCfg.upload) "--noup"
+
+ # local configuration
+ ++ optional remoteCfg.local.checkForModifiedFiles "-F"
+ ++ optional (!remoteCfg.local.importNew) "--nonew"
+
+ # remote configuration
+ ++ [ (escapeShellArg remoteCfg.remote.host) ]
+ ++ optional (remoteCfg.remote.muchsyncPath != "")
+ "-r ${escapeShellArg remoteCfg.remote.muchsyncPath}"
+ ++ optional remoteCfg.remote.checkForModifiedFiles "-F"
+ ++ optional (!remoteCfg.remote.importNew) "--nonew");
+ };
+ });
+
+ systemd.user.timers = mapRemotes (name: remoteCfg: {
+ Unit = { Description = "muchsync periodic sync (${name})"; };
+ Timer = {
+ Unit = "muchsync-${name}.service";
+ OnCalendar = remoteCfg.frequency;
+ Persistent = true;
+ };
+ Install = { WantedBy = [ "timers.target" ]; };
+ });
+ };
+}
diff --git a/home-manager/modules/services/network-manager-applet.nix b/home-manager/modules/services/network-manager-applet.nix
new file mode 100644
index 00000000000..bf57ed65091
--- /dev/null
+++ b/home-manager/modules/services/network-manager-applet.nix
@@ -0,0 +1,36 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.network-manager-applet;
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ services.network-manager-applet = {
+ enable = mkEnableOption "the Network Manager applet";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.network-manager-applet = {
+ Unit = {
+ Description = "Network Manager applet";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ ExecStart = toString
+ ([ "${pkgs.networkmanagerapplet}/bin/nm-applet" "--sm-disable" ]
+ ++ optional config.xsession.preferStatusNotifierItems
+ "--indicator");
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/services/nextcloud-client.nix b/home-manager/modules/services/nextcloud-client.nix
new file mode 100644
index 00000000000..555ca11ad64
--- /dev/null
+++ b/home-manager/modules/services/nextcloud-client.nix
@@ -0,0 +1,26 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ options = {
+ services.nextcloud-client = { enable = mkEnableOption "Nextcloud Client"; };
+ };
+
+ config = mkIf config.services.nextcloud-client.enable {
+ systemd.user.services.nextcloud-client = {
+ Unit = {
+ Description = "Nextcloud Client";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ Environment = "PATH=${config.home.profileDirectory}/bin";
+ ExecStart = "${pkgs.nextcloud-client}/bin/nextcloud";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+}
diff --git a/home-manager/modules/services/owncloud-client.nix b/home-manager/modules/services/owncloud-client.nix
new file mode 100644
index 00000000000..d55d8ffa2a4
--- /dev/null
+++ b/home-manager/modules/services/owncloud-client.nix
@@ -0,0 +1,26 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ options = {
+ services.owncloud-client = { enable = mkEnableOption "Owncloud Client"; };
+ };
+
+ config = mkIf config.services.owncloud-client.enable {
+ systemd.user.services.owncloud-client = {
+ Unit = {
+ Description = "Owncloud Client";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ Environment = "PATH=${config.home.profileDirectory}/bin";
+ ExecStart = "${pkgs.owncloud-client}/bin/owncloud";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+}
diff --git a/home-manager/modules/services/parcellite.nix b/home-manager/modules/services/parcellite.nix
new file mode 100644
index 00000000000..ce04238613b
--- /dev/null
+++ b/home-manager/modules/services/parcellite.nix
@@ -0,0 +1,35 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.parcellite;
+ package = pkgs.parcellite;
+
+in {
+ meta.maintainers = [ maintainers.gleber ];
+
+ options = {
+ services.parcellite = { enable = mkEnableOption "Parcellite"; };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ package ];
+
+ systemd.user.services.parcellite = {
+ Unit = {
+ Description = "Lightweight GTK+ clipboard manager";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ ExecStart = "${package}/bin/parcellite";
+ Restart = "on-abort";
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/services/password-store-sync.nix b/home-manager/modules/services/password-store-sync.nix
new file mode 100644
index 00000000000..81933914980
--- /dev/null
+++ b/home-manager/modules/services/password-store-sync.nix
@@ -0,0 +1,71 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ serviceCfg = config.services.password-store-sync;
+ programCfg = config.programs.password-store;
+
+in {
+ meta.maintainers = with maintainers; [ pacien ];
+
+ options.services.password-store-sync = {
+ enable = mkEnableOption "Password store periodic sync";
+
+ frequency = mkOption {
+ type = types.str;
+ default = "*:0/5";
+ description = ''
+ How often to synchronise the password store git repository with its
+ default upstream.
+ </para><para>
+ This value is passed to the systemd timer configuration as the
+ <literal>onCalendar</literal> option.
+ See
+ <citerefentry>
+ <refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum>
+ </citerefentry>
+ for more information about the format.
+ '';
+ };
+ };
+
+ config = mkIf serviceCfg.enable {
+ assertions = [{
+ assertion = programCfg.enable;
+ message = "The 'services.password-store-sync' module requires"
+ + " 'programs.password-store.enable = true'.";
+ }];
+
+ systemd.user.services.password-store-sync = {
+ Unit = { Description = "Password store sync"; };
+
+ Service = {
+ CPUSchedulingPolicy = "idle";
+ IOSchedulingClass = "idle";
+ Environment = let
+ makeEnvironmentPairs =
+ mapAttrsToList (key: value: "${key}=${builtins.toJSON value}");
+ in makeEnvironmentPairs programCfg.settings;
+ ExecStart = toString (pkgs.writeShellScript "password-store-sync" ''
+ ${pkgs.pass}/bin/pass git pull --rebase && \
+ ${pkgs.pass}/bin/pass git push
+ '');
+ };
+ };
+
+ systemd.user.timers.password-store-sync = {
+ Unit = { Description = "Password store periodic sync"; };
+
+ Timer = {
+ Unit = "password-store-sync.service";
+ OnCalendar = serviceCfg.frequency;
+ Persistent = true;
+ };
+
+ Install = { WantedBy = [ "timers.target" ]; };
+ };
+ };
+}
diff --git a/home-manager/modules/services/pasystray.nix b/home-manager/modules/services/pasystray.nix
new file mode 100644
index 00000000000..7c6651d9499
--- /dev/null
+++ b/home-manager/modules/services/pasystray.nix
@@ -0,0 +1,30 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ meta.maintainers = [ maintainers.pltanton ];
+
+ options = {
+ services.pasystray = { enable = mkEnableOption "PulseAudio system tray"; };
+ };
+
+ config = mkIf config.services.pasystray.enable {
+ systemd.user.services.pasystray = {
+ Unit = {
+ Description = "PulseAudio system tray";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ Environment =
+ let toolPaths = makeBinPath [ pkgs.paprefs pkgs.pavucontrol ];
+ in [ "PATH=${toolPaths}" ];
+ ExecStart = "${pkgs.pasystray}/bin/pasystray";
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/services/polybar.nix b/home-manager/modules/services/polybar.nix
new file mode 100644
index 00000000000..934a990638f
--- /dev/null
+++ b/home-manager/modules/services/polybar.nix
@@ -0,0 +1,135 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.polybar;
+
+ eitherStrBoolIntList = with types;
+ either str (either bool (either int (listOf str)));
+
+ toPolybarIni = generators.toINI {
+ mkKeyValue = key: value:
+ let
+ quoted = v:
+ if hasPrefix " " v || hasSuffix " " v then ''"${v}"'' else v;
+
+ value' = if isBool value then
+ (if value then "true" else "false")
+ else if (isString value && key != "include-file") then
+ quoted value
+ else
+ toString value;
+ in "${key}=${value'}";
+ };
+
+ configFile = pkgs.writeText "polybar.conf"
+ (toPolybarIni cfg.config + "\n" + cfg.extraConfig);
+
+in {
+ options = {
+ services.polybar = {
+ enable = mkEnableOption "Polybar status bar";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.polybar;
+ defaultText = literalExample "pkgs.polybar";
+ description = "Polybar package to install.";
+ example = literalExample ''
+ pkgs.polybar.override {
+ i3GapsSupport = true;
+ alsaSupport = true;
+ iwSupport = true;
+ githubSupport = true;
+ }
+ '';
+ };
+
+ config = mkOption {
+ type = types.coercedTo types.path
+ (p: { "section/base" = { include-file = "${p}"; }; })
+ (types.attrsOf (types.attrsOf eitherStrBoolIntList));
+ description = ''
+ Polybar configuration. Can be either path to a file, or set of attributes
+ that will be used to create the final configuration.
+ '';
+ default = { };
+ example = literalExample ''
+ {
+ "bar/top" = {
+ monitor = "\''${env:MONITOR:eDP1}";
+ width = "100%";
+ height = "3%";
+ radius = 0;
+ modules-center = "date";
+ };
+
+ "module/date" = {
+ type = "internal/date";
+ internal = 5;
+ date = "%d.%m.%y";
+ time = "%H:%M";
+ label = "%time% %date%";
+ };
+ }
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ description = "Additional configuration to add.";
+ default = "";
+ example = ''
+ [module/date]
+ type = internal/date
+ interval = 5
+ date = "%d.%m.%y"
+ time = %H:%M
+ format-prefix-foreground = \''${colors.foreground-alt}
+ label = %time% %date%
+ '';
+ };
+
+ script = mkOption {
+ type = types.lines;
+ description = ''
+ This script will be used to start the polybars.
+ Set all necessary environment variables here and start all bars.
+ It can be assumed that <command>polybar</command> executable is in the <envar>PATH</envar>.
+
+ Note, this script must start all bars in the background and then terminate.
+ '';
+ example = "polybar bar &";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+ xdg.configFile."polybar/config".source = configFile;
+
+ systemd.user.services.polybar = {
+ Unit = {
+ Description = "Polybar status bar";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ X-Restart-Triggers =
+ [ "${config.xdg.configFile."polybar/config".source}" ];
+ };
+
+ Service = {
+ Type = "forking";
+ Environment = "PATH=${cfg.package}/bin:/run/wrappers/bin";
+ ExecStart =
+ let scriptPkg = pkgs.writeShellScriptBin "polybar-start" cfg.script;
+ in "${scriptPkg}/bin/polybar-start";
+ Restart = "on-failure";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+
+}
diff --git a/home-manager/modules/services/random-background.nix b/home-manager/modules/services/random-background.nix
new file mode 100644
index 00000000000..9deee8deb5c
--- /dev/null
+++ b/home-manager/modules/services/random-background.nix
@@ -0,0 +1,97 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.random-background;
+
+ flags = lib.concatStringsSep " "
+ ([ "--bg-${cfg.display}" "--no-fehbg" "--randomize" ]
+ ++ lib.optional (!cfg.enableXinerama) "--no-xinerama");
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ services.random-background = {
+ enable = mkEnableOption "" // {
+ description = ''
+ Whether to enable random desktop background.
+ </para><para>
+ Note, if you are using NixOS and have set up a custom
+ desktop manager session for Home Manager, then the session
+ configuration must have the <option>bgSupport</option>
+ option set to <literal>true</literal> or the background
+ image set by this module may be overwritten.
+ '';
+ };
+
+ imageDirectory = mkOption {
+ type = types.str;
+ example = "%h/backgrounds";
+ description = ''
+ The directory of images from which a background should be
+ chosen. Should be formatted in a way understood by systemd,
+ e.g., '%h' is the home directory.
+ '';
+ };
+
+ display = mkOption {
+ type = types.enum [ "center" "fill" "max" "scale" "tile" ];
+ default = "fill";
+ description = "Display background images according to this option.";
+ };
+
+ interval = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ example = "1h";
+ description = ''
+ The duration between changing background image, set to null
+ to only set background when logging in. Should be formatted
+ as a duration understood by systemd.
+ '';
+ };
+
+ enableXinerama = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Will place a separate image per screen when enabled,
+ otherwise a single image will be stretched across all
+ screens.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge ([
+ {
+ systemd.user.services.random-background = {
+ Unit = {
+ Description = "Set random desktop background using feh";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ Type = "oneshot";
+ ExecStart = "${pkgs.feh}/bin/feh ${flags} ${cfg.imageDirectory}";
+ IOSchedulingClass = "idle";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ }
+ (mkIf (cfg.interval != null) {
+ systemd.user.timers.random-background = {
+ Unit = { Description = "Set random desktop background using feh"; };
+
+ Timer = { OnUnitActiveSec = cfg.interval; };
+
+ Install = { WantedBy = [ "timers.target" ]; };
+ };
+ })
+ ]));
+}
diff --git a/home-manager/modules/services/redshift.nix b/home-manager/modules/services/redshift.nix
new file mode 100644
index 00000000000..86cbab205f6
--- /dev/null
+++ b/home-manager/modules/services/redshift.nix
@@ -0,0 +1,164 @@
+# Adapted from Nixpkgs.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.redshift;
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options.services.redshift = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ example = true;
+ description = ''
+ Enable Redshift to change your screen's colour temperature depending on
+ the time of day.
+ '';
+ };
+
+ latitude = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Your current latitude, between <literal>-90.0</literal> and
+ <literal>90.0</literal>. Must be provided along with
+ longitude.
+ '';
+ };
+
+ longitude = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Your current longitude, between <literal>-180.0</literal> and
+ <literal>180.0</literal>. Must be provided along with
+ latitude.
+ '';
+ };
+
+ provider = mkOption {
+ type = types.enum [ "manual" "geoclue2" ];
+ default = "manual";
+ description = ''
+ The location provider to use for determining your location. If set to
+ <literal>manual</literal> you must also provide latitude/longitude.
+ If set to <literal>geoclue2</literal>, you must also enable the global
+ geoclue2 service.
+ '';
+ };
+
+ temperature = {
+ day = mkOption {
+ type = types.int;
+ default = 5500;
+ description = ''
+ Colour temperature to use during the day, between
+ <literal>1000</literal> and <literal>25000</literal> K.
+ '';
+ };
+ night = mkOption {
+ type = types.int;
+ default = 3700;
+ description = ''
+ Colour temperature to use at night, between
+ <literal>1000</literal> and <literal>25000</literal> K.
+ '';
+ };
+ };
+
+ brightness = {
+ day = mkOption {
+ type = types.str;
+ default = "1";
+ description = ''
+ Screen brightness to apply during the day,
+ between <literal>0.1</literal> and <literal>1.0</literal>.
+ '';
+ };
+ night = mkOption {
+ type = types.str;
+ default = "1";
+ description = ''
+ Screen brightness to apply during the night,
+ between <literal>0.1</literal> and <literal>1.0</literal>.
+ '';
+ };
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.redshift;
+ defaultText = literalExample "pkgs.redshift";
+ description = ''
+ redshift derivation to use.
+ '';
+ };
+
+ tray = mkOption {
+ type = types.bool;
+ default = false;
+ example = true;
+ description = ''
+ Start the redshift-gtk tray applet.
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "-v" "-m randr" ];
+ description = ''
+ Additional command-line arguments to pass to
+ <command>redshift</command>.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [{
+ assertion = cfg.provider == "manual" -> cfg.latitude != null
+ && cfg.longitude != null;
+ message = "Must provide services.redshift.latitude and"
+ + " services.redshift.latitude when"
+ + " services.redshift.provider is set to \"manual\".";
+ }];
+
+ systemd.user.services.redshift = {
+ Unit = {
+ Description = "Redshift colour temperature adjuster";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ ExecStart = let
+ providerString = if cfg.provider == "manual" then
+ "${cfg.latitude}:${cfg.longitude}"
+ else
+ cfg.provider;
+
+ args = [
+ "-l ${providerString}"
+ "-t ${toString cfg.temperature.day}:${
+ toString cfg.temperature.night
+ }"
+ "-b ${toString cfg.brightness.day}:${toString cfg.brightness.night}"
+ ] ++ cfg.extraOptions;
+
+ command = if cfg.tray then "redshift-gtk" else "redshift";
+ in "${cfg.package}/bin/${command} ${concatStringsSep " " args}";
+ RestartSec = 3;
+ Restart = "always";
+ };
+ };
+ };
+
+}
diff --git a/home-manager/modules/services/rsibreak.nix b/home-manager/modules/services/rsibreak.nix
new file mode 100644
index 00000000000..77eaa71f958
--- /dev/null
+++ b/home-manager/modules/services/rsibreak.nix
@@ -0,0 +1,32 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.rsibreak;
+
+in {
+ options.services.rsibreak = {
+
+ enable = mkEnableOption "rsibreak";
+
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.rsibreak = {
+ Unit = {
+ Description = "RSI break timer";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ Environment = "PATH=${config.home.profileDirectory}/bin";
+ ExecStart = "${pkgs.rsibreak}/bin/rsibreak";
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/services/screen-locker.nix b/home-manager/modules/services/screen-locker.nix
new file mode 100644
index 00000000000..30591a7d1a5
--- /dev/null
+++ b/home-manager/modules/services/screen-locker.nix
@@ -0,0 +1,85 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.screen-locker;
+
+in {
+
+ options.services.screen-locker = {
+ enable = mkEnableOption "screen locker for X session";
+
+ lockCmd = mkOption {
+ type = types.str;
+ description = "Locker command to run.";
+ example = "\${pkgs.i3lock}/bin/i3lock -n -c 000000";
+ };
+
+ inactiveInterval = mkOption {
+ type = types.int;
+ default = 10;
+ description = ''
+ Inactive time interval in minutes after which session will be locked.
+ The minimum is 1 minute, and the maximum is 1 hour.
+ See <link xlink:href="https://linux.die.net/man/1/xautolock"/>.
+ '';
+ };
+
+ xautolockExtraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = ''
+ Extra command-line arguments to pass to <command>xautolock</command>.
+ '';
+ };
+
+ xssLockExtraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = ''
+ Extra command-line arguments to pass to <command>xss-lock</command>.
+ '';
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.xautolock-session = {
+ Unit = {
+ Description = "xautolock, session locker service";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ ExecStart = concatStringsSep " " ([
+ "${pkgs.xautolock}/bin/xautolock"
+ "-detectsleep"
+ "-time ${toString cfg.inactiveInterval}"
+ "-locker '${pkgs.systemd}/bin/loginctl lock-session $XDG_SESSION_ID'"
+ ] ++ cfg.xautolockExtraOptions);
+ };
+ };
+
+ systemd.user.services.xss-lock = {
+ Unit = {
+ Description = "xss-lock, session locker service";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ ExecStart = concatStringsSep " "
+ ([ "${pkgs.xss-lock}/bin/xss-lock" "-s \${XDG_SESSION_ID}" ]
+ ++ cfg.xssLockExtraOptions ++ [ "-- ${cfg.lockCmd}" ]);
+ };
+ };
+ };
+
+}
diff --git a/home-manager/modules/services/spotifyd.nix b/home-manager/modules/services/spotifyd.nix
new file mode 100644
index 00000000000..bc231814eba
--- /dev/null
+++ b/home-manager/modules/services/spotifyd.nix
@@ -0,0 +1,52 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.spotifyd;
+
+ configFile = pkgs.writeText "spotifyd.conf" ''
+ ${generators.toINI { } cfg.settings}
+ '';
+
+in {
+ options.services.spotifyd = {
+ enable = mkEnableOption "SpotifyD connect";
+
+ settings = mkOption {
+ type = types.attrsOf (types.attrsOf types.str);
+ default = { };
+ description = "Configuration for spotifyd";
+ example = literalExample ''
+ {
+ global = {
+ user = "Alex";
+ password = "foo";
+ device_name = "nix";
+ };
+ }
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.spotifyd ];
+
+ systemd.user.services.spotifyd = {
+ Unit = {
+ Description = "spotify daemon";
+ Documentation = "https://github.com/Spotifyd/spotifyd";
+ };
+
+ Install.WantedBy = [ "default.target" ];
+
+ Service = {
+ ExecStart =
+ "${pkgs.spotifyd}/bin/spotifyd --no-daemon --config-path ${configFile}";
+ Restart = "always";
+ RestartSec = 12;
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/services/stalonetray.nix b/home-manager/modules/services/stalonetray.nix
new file mode 100644
index 00000000000..cca60498963
--- /dev/null
+++ b/home-manager/modules/services/stalonetray.nix
@@ -0,0 +1,90 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.stalonetray;
+
+in {
+ options = {
+ services.stalonetray = {
+ enable = mkEnableOption "Stalonetray system tray";
+
+ package = mkOption {
+ default = pkgs.stalonetray;
+ defaultText = literalExample "pkgs.stalonetray";
+ type = types.package;
+ example = literalExample "pkgs.stalonetray";
+ description = "The package to use for the Stalonetray binary.";
+ };
+
+ config = mkOption {
+ type = with types; attrsOf (nullOr (either str (either bool int)));
+ description = ''
+ Stalonetray configuration as a set of attributes.
+ '';
+ default = { };
+ example = {
+ geometry = "3x1-600+0";
+ decorations = null;
+ icon_size = 30;
+ sticky = true;
+ background = "#cccccc";
+ };
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ description = "Additional configuration lines for stalonetrayrc.";
+ default = "";
+ example = ''
+ geometry 3x1-600+0
+ decorations none
+ icon_size 30
+ sticky true
+ background "#cccccc"
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+ home.packages = [ cfg.package ];
+
+ systemd.user.services.stalonetray = {
+ Unit = {
+ Description = "Stalonetray system tray";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ ExecStart = "${cfg.package}/bin/stalonetray";
+ Restart = "on-failure";
+ };
+ };
+ }
+
+ (mkIf (cfg.config != { }) {
+ home.file.".stalonetrayrc".text = let
+ valueToString = v:
+ if isBool v then
+ (if v then "true" else "false")
+ else if (v == null) then
+ "none"
+ else
+ ''"${toString v}"'';
+ in concatStrings (mapAttrsToList (k: v: ''
+ ${k} ${valueToString v}
+ '') cfg.config);
+ })
+
+ (mkIf (cfg.extraConfig != "") {
+ home.file.".stalonetrayrc".text = cfg.extraConfig;
+ })
+ ]);
+}
diff --git a/home-manager/modules/services/status-notifier-watcher.nix b/home-manager/modules/services/status-notifier-watcher.nix
new file mode 100644
index 00000000000..3c3e54877b4
--- /dev/null
+++ b/home-manager/modules/services/status-notifier-watcher.nix
@@ -0,0 +1,44 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.status-notifier-watcher;
+
+in {
+ meta.maintainers = [ maintainers.pltanton ];
+
+ options = {
+ services.status-notifier-watcher = {
+ enable = mkEnableOption "Status Notifier Watcher";
+
+ package = mkOption {
+ default = pkgs.haskellPackages.status-notifier-item;
+ defaultText =
+ literalExample "pkgs.haskellPackages.status-notifier-item";
+ type = types.package;
+ example = literalExample "pkgs.haskellPackages.status-notifier-item";
+ description =
+ "The package to use for the status notifier watcher binary.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.status-notifier-watcher = {
+ Unit = {
+ Description = "SNI watcher";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ Before = [ "taffybar.service" ];
+ };
+
+ Service = { ExecStart = "${cfg.package}/bin/status-notifier-watcher"; };
+
+ Install = {
+ WantedBy = [ "graphical-session.target" "taffybar.service" ];
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/services/sxhkd.nix b/home-manager/modules/services/sxhkd.nix
new file mode 100644
index 00000000000..d9f0a968515
--- /dev/null
+++ b/home-manager/modules/services/sxhkd.nix
@@ -0,0 +1,86 @@
+{config, lib, pkgs, ...}:
+
+with lib;
+
+let
+
+ cfg = config.services.sxhkd;
+
+ keybindingsStr = concatStringsSep "\n" (
+ mapAttrsToList (hotkey: command:
+ optionalString (command != null) ''
+ ${hotkey}
+ ${command}
+ ''
+ )
+ cfg.keybindings
+ );
+
+in
+
+{
+ options.services.sxhkd = {
+ enable = mkEnableOption "simple X hotkey daemon";
+
+ keybindings = mkOption {
+ type = types.attrsOf (types.nullOr types.str);
+ default = {};
+ description = "An attribute set that assigns hotkeys to commands.";
+ example = literalExample ''
+ {
+ "super + shift + {r,c}" = "i3-msg {restart,reload}";
+ "super + {s,w}" = "i3-msg {stacking,tabbed}";
+ }
+ '';
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = "Additional configuration to add.";
+ example = literalExample ''
+ super + {_,shift +} {1-9,0}
+ i3-msg {workspace,move container to workspace} {1-10}
+ '';
+ };
+
+ extraPath = mkOption {
+ default = "";
+ type = types.envVar;
+ description = ''
+ Additional <envar>PATH</envar> entries to search for commands.
+ '';
+ example = "/home/some-user/bin:/extra/path/bin";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.sxhkd ];
+
+ xdg.configFile."sxhkd/sxhkdrc".text = concatStringsSep "\n" [
+ keybindingsStr
+ cfg.extraConfig
+ ];
+
+ systemd.user.services.sxhkd = {
+ Unit = {
+ Description = "simple X hotkey daemon";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ Environment =
+ "PATH="
+ + "${config.home.profileDirectory}/bin"
+ + optionalString (cfg.extraPath != "") ":"
+ + cfg.extraPath;
+ ExecStart = "${pkgs.sxhkd}/bin/sxhkd";
+ };
+
+ Install = {
+ WantedBy = [ "graphical-session.target" ];
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/services/syncthing.nix b/home-manager/modules/services/syncthing.nix
new file mode 100644
index 00000000000..2ef10540164
--- /dev/null
+++ b/home-manager/modules/services/syncthing.nix
@@ -0,0 +1,68 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ services.syncthing = {
+ enable = mkEnableOption "Syncthing continuous file synchronization";
+
+ tray = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable QSyncthingTray service.";
+ };
+ };
+ };
+
+ config = mkMerge [
+ (mkIf config.services.syncthing.enable {
+ systemd.user.services = {
+ syncthing = {
+ Unit = {
+ Description =
+ "Syncthing - Open Source Continuous File Synchronization";
+ Documentation = "man:syncthing(1)";
+ After = [ "network.target" ];
+ };
+
+ Service = {
+ ExecStart =
+ "${pkgs.syncthing}/bin/syncthing -no-browser -no-restart -logflags=0";
+ Restart = "on-failure";
+ SuccessExitStatus = [ 3 4 ];
+ RestartForceExitStatus = [ 3 4 ];
+ };
+
+ Install = { WantedBy = [ "default.target" ]; };
+ };
+ };
+ })
+
+ (mkIf config.services.syncthing.tray {
+ systemd.user.services = {
+ qsyncthingtray = {
+ Unit = {
+ Description = "QSyncthingTray";
+ After = [
+ "graphical-session-pre.target"
+ "polybar.service"
+ "taffybar.service"
+ "stalonetray.service"
+ ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ Environment = "PATH=${config.home.profileDirectory}/bin";
+ ExecStart = "${pkgs.qsyncthingtray}/bin/QSyncthingTray";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+ })
+ ];
+}
diff --git a/home-manager/modules/services/taffybar.nix b/home-manager/modules/services/taffybar.nix
new file mode 100644
index 00000000000..5392755423d
--- /dev/null
+++ b/home-manager/modules/services/taffybar.nix
@@ -0,0 +1,44 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.taffybar;
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ services.taffybar = {
+ enable = mkEnableOption "Taffybar";
+
+ package = mkOption {
+ default = pkgs.taffybar;
+ defaultText = literalExample "pkgs.taffybar";
+ type = types.package;
+ example = literalExample "pkgs.taffybar";
+ description = "The package to use for the Taffybar binary.";
+ };
+ };
+ };
+
+ config = mkIf config.services.taffybar.enable {
+ systemd.user.services.taffybar = {
+ Unit = {
+ Description = "Taffybar desktop bar";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ ExecStart = "${cfg.package}/bin/taffybar";
+ Restart = "on-failure";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+
+ xsession.importedVariables = [ "GDK_PIXBUF_MODULE_FILE" ];
+ };
+}
diff --git a/home-manager/modules/services/tahoe-lafs.nix b/home-manager/modules/services/tahoe-lafs.nix
new file mode 100644
index 00000000000..742b779b270
--- /dev/null
+++ b/home-manager/modules/services/tahoe-lafs.nix
@@ -0,0 +1,19 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ services.tahoe-lafs = { enable = mkEnableOption "Tahoe-LAFS"; };
+ };
+
+ config = mkIf config.services.tahoe-lafs.enable {
+ systemd.user.services.tahoe-lafs = {
+ Unit = { Description = "Tahoe-LAFS"; };
+
+ Service = { ExecStart = "${pkgs.tahoelafs}/bin/tahoe run -C %h/.tahoe"; };
+ };
+ };
+}
diff --git a/home-manager/modules/services/taskwarrior-sync.nix b/home-manager/modules/services/taskwarrior-sync.nix
new file mode 100644
index 00000000000..d16c0681bee
--- /dev/null
+++ b/home-manager/modules/services/taskwarrior-sync.nix
@@ -0,0 +1,50 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.taskwarrior-sync;
+
+in {
+ meta.maintainers = with maintainers; [ minijackson pacien ];
+
+ options.services.taskwarrior-sync = {
+ enable = mkEnableOption "Taskwarrior periodic sync";
+
+ frequency = mkOption {
+ type = types.str;
+ default = "*:0/5";
+ description = ''
+ How often to run <literal>taskwarrior sync</literal>. This
+ value is passed to the systemd timer configuration as the
+ <literal>OnCalendar</literal> option. See
+ <citerefentry>
+ <refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum>
+ </citerefentry>
+ for more information about the format.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.taskwarrior-sync = {
+ Unit = { Description = "Taskwarrior sync"; };
+ Service = {
+ CPUSchedulingPolicy = "idle";
+ IOSchedulingClass = "idle";
+ ExecStart = "${pkgs.taskwarrior}/bin/task synchronize";
+ };
+ };
+
+ systemd.user.timers.taskwarrior-sync = {
+ Unit = { Description = "Taskwarrior periodic sync"; };
+ Timer = {
+ Unit = "taskwarrior-sync.service";
+ OnCalendar = cfg.frequency;
+ };
+ Install = { WantedBy = [ "timers.target" ]; };
+ };
+ };
+}
diff --git a/home-manager/modules/services/udiskie.nix b/home-manager/modules/services/udiskie.nix
new file mode 100644
index 00000000000..2444d68ff93
--- /dev/null
+++ b/home-manager/modules/services/udiskie.nix
@@ -0,0 +1,91 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.udiskie;
+
+ commandArgs = concatStringsSep " " (map (opt: "-" + opt) [
+ (if cfg.automount then "a" else "A")
+ (if cfg.notify then "n" else "N")
+ ({
+ always = "t";
+ auto = "s";
+ never = "T";
+ }.${cfg.tray})
+ ] ++ optional config.xsession.preferStatusNotifierItems "--appindicator");
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ imports = [
+ (mkRemovedOptionModule [ "services" "udiskie" "sni" ] ''
+ Support for Status Notifier Items is now configured globally through the
+
+ xsession.preferStatusNotifierItems
+
+ option. Please change to use that instead.
+ '')
+ ];
+
+ options = {
+ services.udiskie = {
+ enable = mkEnableOption "udiskie mount daemon";
+
+ automount = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to automatically mount new devices.";
+ };
+
+ notify = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to show pop-up notifications.";
+ };
+
+ tray = mkOption {
+ type = types.enum [ "always" "auto" "never" ];
+ default = "auto";
+ description = ''
+ Whether to display tray icon.
+ </para><para>
+ The options are
+ <variablelist>
+ <varlistentry>
+ <term><literal>always</literal></term>
+ <listitem><para>Always show tray icon.</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>auto</literal></term>
+ <listitem><para>
+ Show tray icon only when there is a device available.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>never</literal></term>
+ <listitem><para>Never show tray icon.</para></listitem>
+ </varlistentry>
+ </variablelist>
+ '';
+ };
+ };
+ };
+
+ config = mkIf config.services.udiskie.enable {
+ systemd.user.services.udiskie = {
+ Unit = {
+ Description = "udiskie mount daemon";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ ExecStart = "${pkgs.udiskie}/bin/udiskie -2 ${commandArgs}";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+}
diff --git a/home-manager/modules/services/unclutter.nix b/home-manager/modules/services/unclutter.nix
new file mode 100644
index 00000000000..5e760639591
--- /dev/null
+++ b/home-manager/modules/services/unclutter.nix
@@ -0,0 +1,61 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.unclutter;
+
+in {
+ options.services.unclutter = {
+
+ enable = mkEnableOption "unclutter";
+
+ package = mkOption {
+ description = "unclutter derivation to use.";
+ type = types.package;
+ default = pkgs.unclutter-xfixes;
+ defaultText = literalExample "pkgs.unclutter-xfixes";
+ };
+
+ timeout = mkOption {
+ description = "Number of seconds before the cursor is marked inactive.";
+ type = types.int;
+ default = 1;
+ };
+
+ threshold = mkOption {
+ description = "Minimum number of pixels considered cursor movement.";
+ type = types.int;
+ default = 1;
+ };
+
+ extraOptions = mkOption {
+ description = "More arguments to pass to the unclutter command.";
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "exclude-root" "ignore-scrolling" ];
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.unclutter = {
+ Unit = {
+ Description = "unclutter";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ ExecStart = ''
+ ${cfg.package}/bin/unclutter \
+ --timeout ${toString cfg.timeout} \
+ --jitter ${toString (cfg.threshold - 1)} \
+ ${concatMapStrings (x: " --${x}") cfg.extraOptions}
+ '';
+ RestartSec = 3;
+ Restart = "always";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+}
diff --git a/home-manager/modules/services/unison.nix b/home-manager/modules/services/unison.nix
new file mode 100644
index 00000000000..93c59e8fd62
--- /dev/null
+++ b/home-manager/modules/services/unison.nix
@@ -0,0 +1,121 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.unison;
+
+ pairOf = t:
+ let list = types.addCheck (types.listOf t) (l: length l == 2);
+ in list // { description = list.description + " of length 2"; };
+
+ pairOptions = {
+ options = {
+ stateDirectory = mkOption {
+ type = types.path;
+ default = "${config.xdg.dataHome}/unison";
+ defaultText = "$XDG_DATA_HOME/unison";
+ description = ''
+ Unison state directory to use.
+ '';
+ };
+
+ commandOptions = mkOption rec {
+ type = with types; attrsOf str;
+ apply = mergeAttrs default;
+ default = {
+ repeat = "watch";
+ sshcmd = "${pkgs.openssh}/bin/ssh";
+ ui = "text";
+ auto = "true";
+ batch = "true";
+ log = "false"; # don't log to file, handled by systemd
+ };
+ description = ''
+ Additional command line options as a dictionary to pass to the
+ <literal>unison</literal> program.
+ </para><para>
+ See
+ <citerefentry>
+ <refentrytitle>unison</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ for a list of available options.
+ '';
+ };
+
+ roots = mkOption {
+ type = pairOf types.str;
+ example = literalExample ''
+ [
+ "/home/user/documents"
+ "ssh://remote/documents"
+ ]
+ '';
+ description = ''
+ Pair of roots to synchronise.
+ '';
+ };
+ };
+ };
+
+ serialiseArg = key: val: "-${key}=${escapeShellArg val}";
+
+ serialiseArgs = args: concatStringsSep " " (mapAttrsToList serialiseArg args);
+
+ makeDefs = gen:
+ mapAttrs'
+ (name: pairCfg: nameValuePair "unison-pair-${name}" (gen name pairCfg))
+ cfg.pairs;
+
+in {
+ meta.maintainers = with maintainers; [ pacien ];
+
+ options.services.unison = {
+ enable = mkEnableOption "Unison synchronisation";
+
+ pairs = mkOption {
+ type = with types; attrsOf (submodule pairOptions);
+ default = { };
+ example = literalExample ''
+ {
+ roots = [
+ "/home/user/documents"
+ "ssh://remote/documents"
+ ];
+ }
+ '';
+ description = ''
+ Unison root pairs to keep synchronised.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services = makeDefs (name: pairCfg: {
+ Unit = {
+ Description = "Unison pair sync (${name})";
+ # Retry forever, useful in case of network disruption.
+ StartLimitIntervalSec = 0;
+ };
+
+ Service = {
+ Restart = "always";
+ RestartSec = 60;
+
+ CPUSchedulingPolicy = "idle";
+ IOSchedulingClass = "idle";
+
+ Environment = [ "UNISON='${toString pairCfg.stateDirectory}'" ];
+ ExecStart = ''
+ ${pkgs.unison}/bin/unison \
+ ${serialiseArgs pairCfg.commandOptions} \
+ ${strings.concatMapStringsSep " " escapeShellArg pairCfg.roots}
+ '';
+ };
+
+ Install = { WantedBy = [ "default.target" ]; };
+ });
+ };
+}
diff --git a/home-manager/modules/services/window-managers/awesome.nix b/home-manager/modules/services/window-managers/awesome.nix
new file mode 100644
index 00000000000..d2e2903f83b
--- /dev/null
+++ b/home-manager/modules/services/window-managers/awesome.nix
@@ -0,0 +1,52 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.xsession.windowManager.awesome;
+ awesome = cfg.package;
+ getLuaPath = lib: dir: "${lib}/${dir}/lua/${pkgs.luaPackages.lua.luaversion}";
+ makeSearchPath = lib.concatMapStrings (path:
+ " --search ${getLuaPath path "share"}"
+ + " --search ${getLuaPath path "lib"}");
+
+in {
+ options = {
+ xsession.windowManager.awesome = {
+ enable = mkEnableOption "Awesome window manager.";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.awesome;
+ defaultText = literalExample "pkgs.awesome";
+ description = "Package to use for running the Awesome WM.";
+ };
+
+ luaModules = mkOption {
+ default = [ ];
+ type = types.listOf types.package;
+ description = ''
+ List of lua packages available for being
+ used in the Awesome configuration.
+ '';
+ example = literalExample "[ luaPackages.oocairo ]";
+ };
+
+ noArgb = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Disable client transparency support, which can be greatly
+ detrimental to performance in some setups
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ awesome ];
+ xsession.windowManager.command = "${awesome}/bin/awesome "
+ + optionalString cfg.noArgb "--no-argb " + makeSearchPath cfg.luaModules;
+ };
+}
diff --git a/home-manager/modules/services/window-managers/bspwm/default.nix b/home-manager/modules/services/window-managers/bspwm/default.nix
new file mode 100644
index 00000000000..9ea5adbc880
--- /dev/null
+++ b/home-manager/modules/services/window-managers/bspwm/default.nix
@@ -0,0 +1,74 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.xsession.windowManager.bspwm;
+ bspwm = cfg.package;
+
+ camelToSnake = s:
+ builtins.replaceStrings lib.upperChars (map (c: "_${c}") lib.lowerChars) s;
+
+ formatConfig = n: v:
+ let
+ formatList = x:
+ if isList x then
+ throw "can not convert 2-dimensional lists to bspwm format"
+ else
+ formatValue x;
+
+ formatValue = v:
+ if isBool v then
+ (if v then "true" else "false")
+ else if isList v then
+ concatMapStringsSep ", " formatList v
+ else if isString v then
+ "${lib.strings.escapeShellArg v}"
+ else
+ toString v;
+ in "bspc config ${n} ${formatValue v}";
+
+ formatMonitors = n: v: "bspc monitor ${n} -d ${concatStringsSep " " v}";
+
+ formatRules = target: directiveOptions:
+ let
+ formatDirective = n: v:
+ if isBool v then
+ (if v then "${camelToSnake n}=on" else "${camelToSnake n}=off")
+ else if (n == "desktop" || n == "node") then
+ "${camelToSnake n}='${v}'"
+ else
+ "${camelToSnake n}=${lib.strings.escapeShellArg v}";
+
+ directives =
+ filterAttrs (n: v: v != null && !(lib.strings.hasPrefix "_" n))
+ directiveOptions;
+ directivesStr = builtins.concatStringsSep " "
+ (mapAttrsToList formatDirective directives);
+ in "bspc rule -a ${target} ${directivesStr}";
+
+ formatStartupPrograms = map (s: "${s} &");
+
+in {
+ options = import ./options.nix {
+ inherit pkgs;
+ inherit lib;
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ bspwm ];
+ xsession.windowManager.command = let
+ configFile = pkgs.writeShellScript "bspwmrc" (concatStringsSep "\n"
+ ((mapAttrsToList formatMonitors cfg.monitors)
+ ++ (mapAttrsToList formatConfig cfg.settings)
+ ++ (mapAttrsToList formatRules cfg.rules) ++ [''
+ # java gui fixes
+ export _JAVA_AWT_WM_NONREPARENTING=1
+ bspc rule -a sun-awt-X11-XDialogPeer state=floating
+ ''] ++ [ cfg.extraConfig ]
+ ++ (formatStartupPrograms cfg.startupPrograms)));
+ configCmdOpt = optionalString (cfg.settings != null) "-c ${configFile}";
+ in "${cfg.package}/bin/bspwm ${configCmdOpt}";
+ };
+}
diff --git a/home-manager/modules/services/window-managers/bspwm/options.nix b/home-manager/modules/services/window-managers/bspwm/options.nix
new file mode 100644
index 00000000000..58a58a1a782
--- /dev/null
+++ b/home-manager/modules/services/window-managers/bspwm/options.nix
@@ -0,0 +1,214 @@
+{ pkgs, lib }:
+
+with lib;
+
+let
+
+ rule = types.submodule {
+ options = {
+ monitor = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "The monitor where the rule should be applied.";
+ example = "HDMI-0";
+ };
+
+ desktop = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "The desktop where the rule should be applied.";
+ example = "^8";
+ };
+
+ node = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "The node where the rule should be applied.";
+ example = "1";
+ };
+
+ state = mkOption {
+ type = types.nullOr
+ (types.enum [ "tiled" "pseudo_tiled" "floating" "fullscreen" ]);
+ default = null;
+ description = "The state in which a new window should spawn.";
+ example = "floating";
+ };
+
+ layer = mkOption {
+ type = types.nullOr (types.enum [ "below" "normal" "above" ]);
+ default = null;
+ description = "The layer where a new window should spawn.";
+ example = "above";
+ };
+
+ splitDir = mkOption {
+ type = types.nullOr (types.enum [ "north" "west" "south" "east" ]);
+ default = null;
+ description = "The direction where the container is going to be split.";
+ example = "south";
+ };
+
+ splitRatio = mkOption {
+ type = types.nullOr types.float;
+ default = null;
+ description = ''
+ The ratio between the new window and the previous existing window in
+ the desktop.
+ '';
+ example = 0.65;
+ };
+
+ hidden = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = "Whether the node should occupy any space.";
+ example = true;
+ };
+
+ sticky = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = "Whether the node should stay on the focused desktop.";
+ example = true;
+ };
+
+ private = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = ''
+ Whether the node should stay in the same tiling position and size.
+ '';
+ example = true;
+ };
+
+ locked = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = ''
+ Whether the node should ignore <command>node --close</command>
+ messages.
+ '';
+ example = true;
+ };
+
+ marked = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = "Whether the node will be marked for deferred actions.";
+ example = true;
+ };
+
+ center = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = ''
+ Whether the node will be put in the center, in floating mode.
+ '';
+ example = true;
+ };
+
+ follow = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = "Whether focus should follow the node when it is moved.";
+ example = true;
+ };
+
+ manage = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = ''
+ Whether the window should be managed by bspwm. If false, the window
+ will be ignored by bspwm entirely. This is useful for overlay apps,
+ e.g. screenshot tools.
+ '';
+ example = true;
+ };
+
+ focus = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = "Whether the node should gain focus on creation.";
+ example = true;
+ };
+
+ border = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = "Whether the node should have border.";
+ example = true;
+ };
+ };
+ };
+
+in {
+ xsession.windowManager.bspwm = {
+ enable = mkEnableOption "bspwm window manager.";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.bspwm;
+ defaultText = literalExample "pkgs.bspwm";
+ description = "bspwm package to use.";
+ example = literalExample "pkgs.bspwm-unstable";
+ };
+
+ settings = mkOption {
+ type = with types;
+ let primitive = either bool (either int (either float str));
+ in attrsOf (either primitive (listOf primitive));
+ default = { };
+ description = "bspwm configuration";
+ example = {
+ "border_width" = 2;
+ "split_ratio" = 0.52;
+ "gapless_monocle" = true;
+ };
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Additional configuration to add.";
+ example = ''
+ bspc subscribe all > ~/bspc-report.log &
+ '';
+ };
+
+ monitors = mkOption {
+ type = types.attrsOf (types.listOf types.str);
+ default = { };
+ description = "bspc monitor configurations";
+ example = { "HDMI-0" = [ "web" "terminal" "III" "IV" ]; };
+ };
+
+ rules = mkOption {
+ type = types.attrsOf rule;
+ default = { };
+ description = "bspc rules";
+ example = literalExample ''
+ {
+ "Gimp" = {
+ desktop = "^8";
+ state = "floating";
+ follow = true;
+ };
+ "Kupfer.py" = {
+ focus = true;
+ };
+ "Screenkey" = {
+ manage = false;
+ };
+ }
+ '';
+ };
+
+ startupPrograms = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = "Programs to be executed during startup.";
+ example = [ "numlockx on" "tilda" ];
+ };
+ };
+}
diff --git a/home-manager/modules/services/window-managers/i3.nix b/home-manager/modules/services/window-managers/i3.nix
new file mode 100644
index 00000000000..7a4ec90b1cd
--- /dev/null
+++ b/home-manager/modules/services/window-managers/i3.nix
@@ -0,0 +1,856 @@
+{ 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/window-managers/xmonad.nix b/home-manager/modules/services/window-managers/xmonad.nix
new file mode 100644
index 00000000000..7be03874a89
--- /dev/null
+++ b/home-manager/modules/services/window-managers/xmonad.nix
@@ -0,0 +1,101 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.xsession.windowManager.xmonad;
+
+ xmonad = pkgs.xmonad-with-packages.override {
+ ghcWithPackages = cfg.haskellPackages.ghcWithPackages;
+ packages = self:
+ cfg.extraPackages self ++ optionals cfg.enableContribAndExtras [
+ self.xmonad-contrib
+ self.xmonad-extras
+ ];
+ };
+
+in {
+ options = {
+ xsession.windowManager.xmonad = {
+ enable = mkEnableOption "xmonad window manager";
+
+ haskellPackages = mkOption {
+ default = pkgs.haskellPackages;
+ defaultText = literalExample "pkgs.haskellPackages";
+ example = literalExample "pkgs.haskell.packages.ghc784";
+ description = ''
+ The <varname>haskellPackages</varname> used to build xmonad
+ and other packages. This can be used to change the GHC
+ version used to build xmonad and the packages listed in
+ <varname>extraPackages</varname>.
+ '';
+ };
+
+ extraPackages = mkOption {
+ default = self: [ ];
+ defaultText = "self: []";
+ example = literalExample ''
+ haskellPackages: [
+ haskellPackages.xmonad-contrib
+ haskellPackages.monad-logger
+ ]
+ '';
+ description = ''
+ Extra packages available to GHC when rebuilding xmonad. The
+ value must be a function which receives the attribute set
+ defined in <varname>haskellPackages</varname> as the sole
+ argument.
+ '';
+ };
+
+ enableContribAndExtras = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Enable xmonad-{contrib,extras} in xmonad.";
+ };
+
+ config = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = literalExample ''
+ pkgs.writeText "xmonad.hs" '''
+ import XMonad
+ main = xmonad defaultConfig
+ { terminal = "urxvt"
+ , modMask = mod4Mask
+ , borderWidth = 3
+ }
+ '''
+ '';
+ description = ''
+ The configuration file to be used for xmonad. This must be
+ an absolute path or <literal>null</literal> in which case
+ <filename>~/.xmonad/xmonad.hs</filename> will not be managed
+ by Home Manager.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+ home.packages = [ (lowPrio xmonad) ];
+ xsession.windowManager.command = "${xmonad}/bin/xmonad";
+ }
+
+ (mkIf (cfg.config != null) {
+ home.file.".xmonad/xmonad.hs".source = cfg.config;
+ home.file.".xmonad/xmonad.hs".onChange = ''
+ echo "Recompiling xmonad"
+ $DRY_RUN_CMD ${config.xsession.windowManager.command} --recompile
+
+ # Attempt to restart xmonad if X is running.
+ if [[ -v DISPLAY ]] ; then
+ echo "Restarting xmonad"
+ $DRY_RUN_CMD ${config.xsession.windowManager.command} --restart
+ fi
+ '';
+ })
+ ]);
+}
diff --git a/home-manager/modules/services/xcape.nix b/home-manager/modules/services/xcape.nix
new file mode 100644
index 00000000000..f4f77caa331
--- /dev/null
+++ b/home-manager/modules/services/xcape.nix
@@ -0,0 +1,76 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.xcape;
+
+in {
+ meta.maintainers = [ maintainers.nickhu ];
+
+ options = {
+ services.xcape = {
+ enable = mkEnableOption "xcape";
+
+ timeout = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 500;
+ description = ''
+ If you hold a key longer than this timeout, xcape will not
+ generate a key event. Default is 500 ms.
+ '';
+ };
+
+ mapExpression = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ example = {
+ Shift_L = "Escape";
+ Control_L = "Control_L|O";
+ };
+ description = ''
+ The value has the grammar <literal>Key[|OtherKey]</literal>.
+ </para>
+ <para>
+ The list of key names is found in the header file
+ <filename>X11/keysymdef.h</filename> (remove the
+ <literal>XK_</literal> prefix). Note that due to limitations
+ of X11 shifted keys must be specified as a shift key
+ followed by the key to be pressed rather than the actual
+ name of the character. For example to generate "{" the
+ expression <literal>Shift_L|bracketleft</literal> could be
+ used (assuming that you have a key with "{" above "[").
+ </para>
+ <para>
+ You can also specify keys in decimal (prefix #), octal (#0),
+ or hexadecimal (#0x). They will be interpreted as keycodes
+ unless no corresponding key name is found.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.xcape = {
+ Unit = {
+ Description = "xcape";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ Type = "forking";
+ ExecStart = "${pkgs.xcape}/bin/xcape"
+ + optionalString (cfg.timeout != null) " -t ${toString cfg.timeout}"
+ + optionalString (cfg.mapExpression != { }) " -e '${
+ builtins.concatStringsSep ";"
+ (attrsets.mapAttrsToList (n: v: "${n}=${v}") cfg.mapExpression)
+ }'";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+}
diff --git a/home-manager/modules/services/xembed-sni-proxy.nix b/home-manager/modules/services/xembed-sni-proxy.nix
new file mode 100644
index 00000000000..ff63d108b77
--- /dev/null
+++ b/home-manager/modules/services/xembed-sni-proxy.nix
@@ -0,0 +1,45 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.xembed-sni-proxy;
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ services.xembed-sni-proxy = {
+ enable = mkEnableOption "XEmbed SNI Proxy";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.plasma-workspace;
+ defaultText = literalExample "pkgs.plasma-workspace";
+ description = ''
+ Package containing the <command>xembedsniproxy</command>
+ program.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.xembed-sni-proxy = {
+ Unit = {
+ Description = "XEmbed SNI Proxy";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ Environment = "PATH=${config.home.profileDirectory}/bin";
+ ExecStart = "${cfg.package}/bin/xembedsniproxy";
+ Restart = "on-abort";
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/services/xscreensaver.nix b/home-manager/modules/services/xscreensaver.nix
new file mode 100644
index 00000000000..73a365aa730
--- /dev/null
+++ b/home-manager/modules/services/xscreensaver.nix
@@ -0,0 +1,52 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.xscreensaver;
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ services.xscreensaver = {
+ enable = mkEnableOption "XScreenSaver";
+
+ settings = mkOption {
+ type = with types; attrsOf (either bool (either int str));
+ default = { };
+ example = {
+ mode = "blank";
+ lock = false;
+ fadeTicks = 20;
+ };
+ description = ''
+ The settings to use for XScreenSaver.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ # To make the xscreensaver-command tool available.
+ home.packages = [ pkgs.xscreensaver ];
+
+ xresources.properties =
+ mapAttrs' (n: nameValuePair "xscreensaver.${n}") cfg.settings;
+
+ systemd.user.services.xscreensaver = {
+ Unit = {
+ Description = "XScreenSaver";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ ExecStart = "${pkgs.xscreensaver}/bin/xscreensaver -no-splash";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+}
diff --git a/home-manager/modules/services/xsuspender.nix b/home-manager/modules/services/xsuspender.nix
new file mode 100644
index 00000000000..2eb40f5dd34
--- /dev/null
+++ b/home-manager/modules/services/xsuspender.nix
@@ -0,0 +1,192 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.xsuspender;
+
+ xsuspenderOptions = types.submodule {
+ options = {
+ matchWmClassContains = mkOption {
+ description = "Match windows that wm class contains string.";
+ type = types.nullOr types.str;
+ default = null;
+ };
+
+ matchWmClassGroupContains = mkOption {
+ description = "Match windows where wm class group contains string.";
+ type = types.nullOr types.str;
+ default = null;
+ };
+
+ matchWmNameContains = mkOption {
+ description = "Match windows where wm name contains string.";
+ type = types.nullOr types.str;
+ default = null;
+ };
+
+ suspendDelay = mkOption {
+ description = "Initial suspend delay in seconds.";
+ type = types.int;
+ default = 5;
+ };
+
+ resumeEvery = mkOption {
+ description = "Resume interval in seconds.";
+ type = types.int;
+ default = 50;
+ };
+
+ resumeFor = mkOption {
+ description = "Resume duration in seconds.";
+ type = types.int;
+ default = 5;
+ };
+
+ execSuspend = mkOption {
+ description = ''
+ Before suspending, execute this shell script. If it fails,
+ abort suspension.
+ '';
+ type = types.nullOr types.str;
+ default = null;
+ example = ''echo "suspending window $XID of process $PID"'';
+ };
+
+ execResume = mkOption {
+ description = ''
+ Before resuming, execute this shell script. Resume the
+ process regardless script failure.
+ '';
+ type = types.nullOr types.str;
+ default = null;
+ example = "echo resuming ...";
+ };
+
+ sendSignals = mkOption {
+ description = ''
+ Whether to send SIGSTOP / SIGCONT signals or not.
+ If false just the exec scripts are run.
+ '';
+ type = types.bool;
+ default = true;
+ };
+
+ suspendSubtreePattern = mkOption {
+ description =
+ "Also suspend descendant processes that match this regex.";
+ type = types.nullOr types.str;
+ default = null;
+ };
+
+ onlyOnBattery = mkOption {
+ description = "Whether to enable process suspend only on battery.";
+ type = types.bool;
+ default = false;
+ };
+
+ autoSuspendOnBattery = mkOption {
+ description = ''
+ Whether to auto-apply rules when switching to battery
+ power even if the window(s) didn't just lose focus.
+ '';
+ type = types.bool;
+ default = true;
+ };
+
+ downclockOnBattery = mkOption {
+ description = ''
+ Limit CPU consumption for this factor when on battery power.
+ Value 1 means 50% decrease, 2 means 66%, 3 means 75% etc.
+ '';
+ type = types.int;
+ default = 0;
+ };
+ };
+ };
+
+in {
+ meta.maintainers = [ maintainers.offline ];
+
+ options = {
+ services.xsuspender = {
+ enable = mkEnableOption "XSuspender";
+
+ defaults = mkOption {
+ description = "XSuspender defaults.";
+ type = xsuspenderOptions;
+ default = { };
+ };
+
+ rules = mkOption {
+ description = "Attribute set of XSuspender rules.";
+ type = types.attrsOf xsuspenderOptions;
+ default = { };
+ example = {
+ Chromium = {
+ suspendDelay = 10;
+ matchWmClassContains = "chromium-browser";
+ suspendSubtreePattern = "chromium";
+ };
+ };
+ };
+
+ debug = mkOption {
+ description = "Whether to enable debug output.";
+ type = types.bool;
+ default = false;
+ };
+
+ iniContent = mkOption {
+ type = types.attrsOf types.attrs;
+ internal = true;
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.xsuspender.iniContent = let
+ mkSection = values:
+ filterAttrs (_: v: v != null) {
+ match_wm_class_contains = values.matchWmClassContains;
+ match_wm_class_group_contains = values.matchWmClassGroupContains;
+ match_wm_name_contains = values.matchWmNameContains;
+ suspend_delay = values.suspendDelay;
+ resume_every = values.resumeEvery;
+ resume_for = values.resumeFor;
+ exec_suspend = values.execSuspend;
+ exec_resume = values.execResume;
+ send_signals = values.sendSignals;
+ suspend_subtree_pattern = values.suspendSubtreePattern;
+ only_on_battery = values.onlyOnBattery;
+ auto_suspend_on_battery = values.autoSuspendOnBattery;
+ downclock_on_battery = values.downclockOnBattery;
+ };
+ in {
+ Default = mkSection cfg.defaults;
+ } // mapAttrs (_: mkSection) cfg.rules;
+
+ # To make the xsuspender tool available.
+ home.packages = [ pkgs.xsuspender ];
+
+ xdg.configFile."xsuspender.conf".text = generators.toINI { } cfg.iniContent;
+
+ systemd.user.services.xsuspender = {
+ Unit = {
+ Description = "XSuspender";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ X-Restart-Triggers =
+ [ "${config.xdg.configFile."xsuspender.conf".source}" ];
+ };
+
+ Service = {
+ ExecStart = "${pkgs.xsuspender}/bin/xsuspender";
+ Environment = mkIf cfg.debug [ "G_MESSAGE_DEBUG=all" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+}