diff options
Diffstat (limited to 'home-manager/modules/services')
46 files changed, 4735 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..186dc7454f9 --- /dev/null +++ b/home-manager/modules/services/blueman-applet.nix @@ -0,0 +1,37 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + options = { + services.blueman-applet = { + enable = mkEnableOption '' + Blueman applet. + + Note, for the applet to work, 'blueman' package should also be installed system-wide + since it requires running 'blueman-mechanism' service activated via dbus. + You can add it to the dbus packages in system configuration: + + services.dbus.packages = [ pkgs.blueman ]; + ''; + }; + }; + + 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/compton.nix b/home-manager/modules/services/compton.nix new file mode 100644 index 00000000000..c227f0a8c07 --- /dev/null +++ b/home-manager/modules/services/compton.nix @@ -0,0 +1,311 @@ +{ 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..96b1f71a2fa --- /dev/null +++ b/home-manager/modules/services/dunst.nix @@ -0,0 +1,172 @@ +{ 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..2b010cec1e1 --- /dev/null +++ b/home-manager/modules/services/dwm-status.nix @@ -0,0 +1,70 @@ +{ 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..33d6871c61b --- /dev/null +++ b/home-manager/modules/services/emacs.nix @@ -0,0 +1,48 @@ +{ 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..87b494d0fcd --- /dev/null +++ b/home-manager/modules/services/flameshot.nix @@ -0,0 +1,47 @@ +{ 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..46d4c1752d4 --- /dev/null +++ b/home-manager/modules/services/getmail.nix @@ -0,0 +1,61 @@ +{ 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..4ca6c7cacf2 --- /dev/null +++ b/home-manager/modules/services/gnome-keyring.nix @@ -0,0 +1,55 @@ +{ 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..5dc942fef63 --- /dev/null +++ b/home-manager/modules/services/gpg-agent.nix @@ -0,0 +1,258 @@ +{ 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. + ''; + }; + }; + }; + + 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}" + ++ + [ 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/hound.nix b/home-manager/modules/services/hound.nix new file mode 100644 index 00000000000..a252a68d271 --- /dev/null +++ b/home-manager/modules/services/hound.nix @@ -0,0 +1,84 @@ +{ 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..1c780bf28c3 --- /dev/null +++ b/home-manager/modules/services/imapnotify-accounts.nix @@ -0,0 +1,30 @@ +{ 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..fbb0713e978 --- /dev/null +++ b/home-manager/modules/services/imapnotify.nix @@ -0,0 +1,107 @@ +{ 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..bd698fcf836 --- /dev/null +++ b/home-manager/modules/services/kdeconnect.nix @@ -0,0 +1,75 @@ +{ 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..ad791786f05 --- /dev/null +++ b/home-manager/modules/services/keepassx.nix @@ -0,0 +1,31 @@ +{ 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/mbsync.nix b/home-manager/modules/services/mbsync.nix new file mode 100644 index 00000000000..73c3b326695 --- /dev/null +++ b/home-manager/modules/services/mbsync.nix @@ -0,0 +1,110 @@ +{ 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..450f84c5912 --- /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 = { + Unit = { + Description = "MPRIS 2 support for MPD"; + After = [ "graphical-session-pre.target" "mpd.service" ]; + PartOf = [ "graphical-session.target" ]; + }; + + Service = { + Type = "simple"; + ExecStart = "${cfg.package}/bin/mpDris2"; + }; + }; + }; +} diff --git a/home-manager/modules/services/muchsync.nix b/home-manager/modules/services/muchsync.nix new file mode 100644 index 00000000000..72bf737c27d --- /dev/null +++ b/home-manager/modules/services/muchsync.nix @@ -0,0 +1,211 @@ +{ 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..72a4711e39a --- /dev/null +++ b/home-manager/modules/services/network-manager-applet.nix @@ -0,0 +1,42 @@ +{ 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..3d8dc0bc80b --- /dev/null +++ b/home-manager/modules/services/nextcloud-client.nix @@ -0,0 +1,30 @@ +{ 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..d98a508f088 --- /dev/null +++ b/home-manager/modules/services/owncloud-client.nix @@ -0,0 +1,30 @@ +{ 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..455989ffe07 --- /dev/null +++ b/home-manager/modules/services/parcellite.nix @@ -0,0 +1,47 @@ +{ 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 = { + # PATH have been added in nixpkgs.parcellite, keeping it here for + # backward compatibility. XDG_DATA_DIRS is necessary to make it pick up + # icons correctly. + Environment = '' + PATH=${package}/bin:${pkgs.which}/bin:${pkgs.xdotool}/bin XDG_DATA_DIRS=${pkgs.hicolor_icon_theme}/share + ''; + ExecStart = "${package}/bin/parcellite"; + Restart = "on-abort"; + }; + }; + }; +} diff --git a/home-manager/modules/services/pasystray.nix b/home-manager/modules/services/pasystray.nix new file mode 100644 index 00000000000..8f92f34c091 --- /dev/null +++ b/home-manager/modules/services/pasystray.nix @@ -0,0 +1,36 @@ +{ 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..4225ed9b38c --- /dev/null +++ b/home-manager/modules/services/polybar.nix @@ -0,0 +1,143 @@ +{ 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..cbec97ae7cb --- /dev/null +++ b/home-manager/modules/services/random-background.nix @@ -0,0 +1,103 @@ +{ 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 "random desktop background"; + + 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..1452fcc95ed --- /dev/null +++ b/home-manager/modules/services/redshift.nix @@ -0,0 +1,172 @@ +# 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..242e03432e8 --- /dev/null +++ b/home-manager/modules/services/rsibreak.nix @@ -0,0 +1,36 @@ +{ 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..e3da14069bc --- /dev/null +++ b/home-manager/modules/services/screen-locker.nix @@ -0,0 +1,76 @@ +{ 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); + }; + }; + + # xss-lock will run specified screen locker when the session is locked via loginctl + # can't be started as a systemd service, + # see https://bitbucket.org/raymonad/xss-lock/issues/13/allow-operation-as-systemd-user-unit + xsession.initExtra = "${pkgs.xss-lock}/bin/xss-lock ${concatStringsSep " " cfg.xssLockExtraOptions} -- ${cfg.lockCmd} &"; + }; + +} diff --git a/home-manager/modules/services/stalonetray.nix b/home-manager/modules/services/stalonetray.nix new file mode 100644 index 00000000000..934e78c99a1 --- /dev/null +++ b/home-manager/modules/services/stalonetray.nix @@ -0,0 +1,94 @@ +{ 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}\n") 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..8a2ded8720a --- /dev/null +++ b/home-manager/modules/services/status-notifier-watcher.nix @@ -0,0 +1,46 @@ +{ 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..7fc556c5234 --- /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..69531a19dc9 --- /dev/null +++ b/home-manager/modules/services/taffybar.nix @@ -0,0 +1,48 @@ +{ 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..bb7be8d7db9 --- /dev/null +++ b/home-manager/modules/services/tahoe-lafs.nix @@ -0,0 +1,25 @@ +{ 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..4179ac8aa85 --- /dev/null +++ b/home-manager/modules/services/taskwarrior-sync.nix @@ -0,0 +1,58 @@ +{ 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..c058a23de6c --- /dev/null +++ b/home-manager/modules/services/udiskie.nix @@ -0,0 +1,95 @@ +{ 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..6b5ac866ec5 --- /dev/null +++ b/home-manager/modules/services/unclutter.nix @@ -0,0 +1,63 @@ +{ 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/window-managers/awesome.nix b/home-manager/modules/services/window-managers/awesome.nix new file mode 100644 index 00000000000..fe914864e2a --- /dev/null +++ b/home-manager/modules/services/window-managers/awesome.nix @@ -0,0 +1,57 @@ +{ 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/i3.nix b/home-manager/modules/services/window-managers/i3.nix new file mode 100644 index 00000000000..6c52ff6c335 --- /dev/null +++ b/home-manager/modules/services/window-managers/i3.nix @@ -0,0 +1,836 @@ +{ 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. + ''; + }; + + 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}+1" = "workspace 1"; + "${cfg.config.modifier}+2" = "workspace 2"; + "${cfg.config.modifier}+3" = "workspace 3"; + "${cfg.config.modifier}+4" = "workspace 4"; + "${cfg.config.modifier}+5" = "workspace 5"; + "${cfg.config.modifier}+6" = "workspace 6"; + "${cfg.config.modifier}+7" = "workspace 7"; + "${cfg.config.modifier}+8" = "workspace 8"; + "${cfg.config.modifier}+9" = "workspace 9"; + + "${cfg.config.modifier}+Shift+1" = "move container to workspace 1"; + "${cfg.config.modifier}+Shift+2" = "move container to workspace 2"; + "${cfg.config.modifier}+Shift+3" = "move container to workspace 3"; + "${cfg.config.modifier}+Shift+4" = "move container to workspace 4"; + "${cfg.config.modifier}+Shift+5" = "move container to workspace 5"; + "${cfg.config.modifier}+Shift+6" = "move container to workspace 6"; + "${cfg.config.modifier}+Shift+7" = "move container to workspace 7"; + "${cfg.config.modifier}+Shift+8" = "move container to workspace 8"; + "${cfg.config.modifier}+Shift+9" = "move container to workspace 9"; + + "${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} + + 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..6b3426b963b --- /dev/null +++ b/home-manager/modules/services/window-managers/xmonad.nix @@ -0,0 +1,103 @@ +{ 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..26115a93062 --- /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..d9e5ae783f9 --- /dev/null +++ b/home-manager/modules/services/xembed-sni-proxy.nix @@ -0,0 +1,49 @@ +{ 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..4001c294e86 --- /dev/null +++ b/home-manager/modules/services/xscreensaver.nix @@ -0,0 +1,56 @@ +{ 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..22a5ca536a5 --- /dev/null +++ b/home-manager/modules/services/xsuspender.nix @@ -0,0 +1,198 @@ +{ 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" ]; + }; + }; + }; +} |