aboutsummaryrefslogtreecommitdiff
path: root/home-manager/modules/services
diff options
context:
space:
mode:
Diffstat (limited to 'home-manager/modules/services')
-rw-r--r--home-manager/modules/services/clipmenu.nix43
-rw-r--r--home-manager/modules/services/compton.nix320
-rw-r--r--home-manager/modules/services/dropbox.nix77
-rw-r--r--home-manager/modules/services/dunst.nix4
-rw-r--r--home-manager/modules/services/emacs.nix152
-rw-r--r--home-manager/modules/services/fluidsynth.nix57
-rw-r--r--home-manager/modules/services/gnome-keyring.nix2
-rw-r--r--home-manager/modules/services/kanshi.nix194
-rw-r--r--home-manager/modules/services/keynav.nix29
-rw-r--r--home-manager/modules/services/lieer-accounts.nix25
-rw-r--r--home-manager/modules/services/lieer.nix66
-rw-r--r--home-manager/modules/services/lorri.nix17
-rw-r--r--home-manager/modules/services/mako.nix316
-rw-r--r--home-manager/modules/services/mpd.nix2
-rw-r--r--home-manager/modules/services/picom.nix311
-rw-r--r--home-manager/modules/services/pulseeffects.nix55
-rw-r--r--home-manager/modules/services/screen-locker.nix13
-rw-r--r--home-manager/modules/services/spotifyd.nix18
-rw-r--r--home-manager/modules/services/status-notifier-watcher.nix9
-rw-r--r--home-manager/modules/services/syncthing.nix2
-rw-r--r--home-manager/modules/services/udiskie.nix4
-rw-r--r--home-manager/modules/services/unison.nix2
-rw-r--r--home-manager/modules/services/window-managers/i3-sway/i3.nix257
-rw-r--r--home-manager/modules/services/window-managers/i3-sway/lib/functions.nix127
-rw-r--r--home-manager/modules/services/window-managers/i3-sway/lib/options.nix763
-rw-r--r--home-manager/modules/services/window-managers/i3-sway/sway.nix416
-rw-r--r--home-manager/modules/services/window-managers/i3.nix856
-rw-r--r--home-manager/modules/services/xscreensaver.nix4
28 files changed, 2958 insertions, 1183 deletions
diff --git a/home-manager/modules/services/clipmenu.nix b/home-manager/modules/services/clipmenu.nix
new file mode 100644
index 00000000000..2e1c10e43d8
--- /dev/null
+++ b/home-manager/modules/services/clipmenu.nix
@@ -0,0 +1,43 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.clipmenu;
+
+in {
+ meta.maintainers = [ maintainers.DamienCassou ];
+
+ options.services.clipmenu = {
+ enable = mkEnableOption "clipmenu, the clipboard management daemon";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.clipmenu;
+ defaultText = "pkgs.clipmenu";
+ description = "clipmenu derivation to use.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+
+ systemd.user.services.clipmenu = {
+ Unit = {
+ Description = "Clipboard management daemon";
+ After = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ ExecStart = "${cfg.package}/bin/clipmenud";
+ Environment = "PATH=${
+ makeBinPath
+ (with pkgs; [ coreutils findutils gnugrep gnused systemd ])
+ }";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+}
diff --git a/home-manager/modules/services/compton.nix b/home-manager/modules/services/compton.nix
index c5b96af34da..0b8e7232b45 100644
--- a/home-manager/modules/services/compton.nix
+++ b/home-manager/modules/services/compton.nix
@@ -1,291 +1,43 @@
{ config, lib, pkgs, ... }:
-with lib;
-with builtins;
-
-let
-
- cfg = config.services.compton;
-
- configFile = pkgs.writeText "compton.conf" (optionalString cfg.fade ''
- # fading
- fading = true;
- fade-delta = ${toString cfg.fadeDelta};
- fade-in-step = ${elemAt cfg.fadeSteps 0};
- fade-out-step = ${elemAt cfg.fadeSteps 1};
- fade-exclude = ${toJSON cfg.fadeExclude};
- '' + optionalString cfg.shadow ''
-
- # shadows
- shadow = true;
- shadow-offset-x = ${toString (elemAt cfg.shadowOffsets 0)};
- shadow-offset-y = ${toString (elemAt cfg.shadowOffsets 1)};
- shadow-opacity = ${cfg.shadowOpacity};
- shadow-exclude = ${toJSON cfg.shadowExclude};
- no-dock-shadow = ${toJSON cfg.noDockShadow};
- no-dnd-shadow = ${toJSON cfg.noDNDShadow};
- '' + optionalString cfg.blur ''
-
- # blur
- blur-background = true;
- blur-background-exclude = ${toJSON cfg.blurExclude};
- '' + ''
-
- # opacity
- active-opacity = ${cfg.activeOpacity};
- inactive-opacity = ${cfg.inactiveOpacity};
- menu-opacity = ${cfg.menuOpacity};
- opacity-rule = ${toJSON cfg.opacityRule};
-
- # other options
- backend = ${toJSON cfg.backend};
- vsync = ${toJSON cfg.vSync};
- refresh-rate = ${toString cfg.refreshRate};
- '' + cfg.extraOptions);
-
-in {
-
- options.services.compton = {
- enable = mkEnableOption "Compton X11 compositor";
-
- blur = mkOption {
- type = types.bool;
- default = false;
- description = ''
- Enable background blur on transparent windows.
- '';
- };
-
- blurExclude = mkOption {
- type = types.listOf types.str;
- default = [ ];
- example = [ "class_g = 'slop'" "class_i = 'polybar'" ];
- description = ''
- List of windows to exclude background blur.
- See the
- <citerefentry>
- <refentrytitle>compton</refentrytitle>
- <manvolnum>1</manvolnum>
- </citerefentry>
- man page for more examples.
- '';
- };
-
- fade = mkOption {
- type = types.bool;
- default = false;
- description = ''
- Fade windows in and out.
- '';
- };
-
- fadeDelta = mkOption {
- type = types.int;
- default = 10;
- example = 5;
- description = ''
- Time between fade animation step (in ms).
- '';
- };
-
- fadeSteps = mkOption {
- type = types.listOf types.str;
- default = [ "0.028" "0.03" ];
- example = [ "0.04" "0.04" ];
- description = ''
- Opacity change between fade steps (in and out).
- '';
- };
-
- fadeExclude = mkOption {
- type = types.listOf types.str;
- default = [ ];
- example = [ "window_type *= 'menu'" "name ~= 'Firefox$'" "focused = 1" ];
- description = ''
- List of conditions of windows that should not be faded.
- See the
- <citerefentry>
- <refentrytitle>compton</refentrytitle>
- <manvolnum>1</manvolnum>
- </citerefentry>
- man page for more examples.
- '';
- };
-
- shadow = mkOption {
- type = types.bool;
- default = false;
- description = ''
- Draw window shadows.
- '';
- };
-
- shadowOffsets = mkOption {
- type = types.listOf types.int;
- default = [ (-15) (-15) ];
- example = [ (-10) (-15) ];
- description = ''
- Horizontal and vertical offsets for shadows (in pixels).
- '';
- };
-
- shadowOpacity = mkOption {
- type = types.str;
- default = "0.75";
- example = "0.8";
- description = ''
- Window shadows opacity (number in range 0 - 1).
- '';
- };
-
- shadowExclude = mkOption {
- type = types.listOf types.str;
- default = [ ];
- example = [ "window_type *= 'menu'" "name ~= 'Firefox$'" "focused = 1" ];
- description = ''
- List of conditions of windows that should have no shadow.
- See the
- <citerefentry>
- <refentrytitle>compton</refentrytitle>
- <manvolnum>1</manvolnum>
- </citerefentry>
- man page for more examples.
- '';
- };
-
- noDockShadow = mkOption {
- type = types.bool;
- default = true;
- description = ''
- Avoid shadow on docks.
- '';
- };
-
- noDNDShadow = mkOption {
- type = types.bool;
- default = true;
- description = ''
- Avoid shadow on drag-and-drop windows.
- '';
- };
-
- activeOpacity = mkOption {
- type = types.str;
- default = "1.0";
- example = "0.8";
- description = ''
- Opacity of active windows.
- '';
- };
-
- inactiveOpacity = mkOption {
- type = types.str;
- default = "1.0";
- example = "0.8";
- description = ''
- Opacity of inactive windows.
- '';
- };
-
- menuOpacity = mkOption {
- type = types.str;
- default = "1.0";
- example = "0.8";
- description = ''
- Opacity of dropdown and popup menu.
- '';
- };
-
- opacityRule = mkOption {
- type = types.listOf types.str;
- default = [ ];
- example = [ "87:class_i ?= 'scratchpad'" "91:class_i ?= 'xterm'" ];
- description = ''
- List of opacity rules.
- See the
- <citerefentry>
- <refentrytitle>compton</refentrytitle>
- <manvolnum>1</manvolnum>
- </citerefentry>
- man page for more examples.
- '';
- };
-
- backend = mkOption {
- type = types.str;
- default = "glx";
- description = ''
- Backend to use: <literal>glx</literal> or <literal>xrender</literal>.
- '';
- };
-
- vSync = mkOption {
- type = types.str;
- default = "none";
- example = "opengl-swc";
- description = ''
- Enable vertical synchronization using the specified method.
- See the
- <citerefentry>
- <refentrytitle>compton</refentrytitle>
- <manvolnum>1</manvolnum>
- </citerefentry>
- man page for available methods.
- '';
- };
-
- refreshRate = mkOption {
- type = types.int;
- default = 0;
- example = 60;
- description = ''
- Screen refresh rate (0 = automatically detect).
- '';
- };
-
- package = mkOption {
- type = types.package;
- default = pkgs.compton;
- defaultText = literalExample "pkgs.compton";
- example = literalExample "pkgs.compton";
- description = ''
- Compton derivation to use.
- '';
- };
-
- extraOptions = mkOption {
- type = types.str;
- default = "";
- example = ''
- unredir-if-possible = true;
- dbe = true;
- '';
- description = ''
- Additional Compton configuration.
- '';
- };
+with lib; {
+ imports = let
+ old = n: [ "services" "compton" n ];
+ new = n: [ "services" "picom" n ];
+ in [
+ (mkRenamedOptionModule (old "activeOpacity") (new "activeOpacity"))
+ (mkRenamedOptionModule (old "backend") (new "backend"))
+ (mkRenamedOptionModule (old "blur") (new "blur"))
+ (mkRenamedOptionModule (old "blurExclude") (new "blurExclude"))
+ (mkRenamedOptionModule (old "extraOptions") (new "extraOptions"))
+ (mkRenamedOptionModule (old "fade") (new "fade"))
+ (mkRenamedOptionModule (old "fadeDelta") (new "fadeDelta"))
+ (mkRenamedOptionModule (old "fadeExclude") (new "fadeExclude"))
+ (mkRenamedOptionModule (old "fadeSteps") (new "fadeSteps"))
+ (mkRenamedOptionModule (old "inactiveDim") (new "inactiveDim"))
+ (mkRenamedOptionModule (old "inactiveOpacity") (new "inactiveOpacity"))
+ (mkRenamedOptionModule (old "menuOpacity") (new "menuOpacity"))
+ (mkRenamedOptionModule (old "noDNDShadow") (new "noDNDShadow"))
+ (mkRenamedOptionModule (old "noDockShadow") (new "noDockShadow"))
+ (mkRenamedOptionModule (old "opacityRule") (new "opacityRule"))
+ (mkRenamedOptionModule (old "package") (new "package"))
+ (mkRenamedOptionModule (old "refreshRate") (new "refreshRate"))
+ (mkRenamedOptionModule (old "shadow") (new "shadow"))
+ (mkRenamedOptionModule (old "shadowExclude") (new "shadowExclude"))
+ (mkRenamedOptionModule (old "shadowOffsets") (new "shadowOffsets"))
+ (mkRenamedOptionModule (old "shadowOpacity") (new "shadowOpacity"))
+ (mkChangedOptionModule (old "vSync") (new "vSync") (v: v != "none"))
+ ];
+
+ options.services.compton.enable = mkEnableOption "Compton X11 compositor" // {
+ visible = false;
};
- config = mkIf cfg.enable {
- home.packages = [ cfg.package ];
-
- systemd.user.services.compton = {
- Unit = {
- Description = "Compton X11 compositor";
- After = [ "graphical-session-pre.target" ];
- PartOf = [ "graphical-session.target" ];
- };
-
- Install = { WantedBy = [ "graphical-session.target" ]; };
+ config = mkIf config.services.compton.enable {
+ warnings = [
+ "Obsolete option `services.compton.enable' is used. It was renamed to `services.picom.enable'."
+ ];
- Service = {
- ExecStart = "${cfg.package}/bin/compton --config ${configFile}";
- Restart = "always";
- RestartSec = 3;
- } // optionalAttrs (cfg.backend == "glx") {
- # Temporarily fixes corrupt colours with Mesa 18.
- Environment = [ "allow_rgb10_configs=false" ];
- };
- };
+ services.picom.enable = true;
};
}
diff --git a/home-manager/modules/services/dropbox.nix b/home-manager/modules/services/dropbox.nix
new file mode 100644
index 00000000000..bcf3ba2b457
--- /dev/null
+++ b/home-manager/modules/services/dropbox.nix
@@ -0,0 +1,77 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.dropbox;
+ baseDir = ".dropbox-hm";
+ dropboxCmd = "${pkgs.dropbox-cli}/bin/dropbox";
+ homeBaseDir = "${config.home.homeDirectory}/${baseDir}";
+
+in {
+ meta.maintainers = [ maintainers.eyjhb ];
+
+ options = {
+ services.dropbox = {
+ enable = mkEnableOption "Dropbox daemon";
+
+ path = mkOption {
+ type = types.path;
+ default = "${config.home.homeDirectory}/Dropbox";
+ defaultText =
+ literalExample ''"''${config.home.homeDirectory}/Dropbox"'';
+ apply = toString; # Prevent copies to Nix store.
+ description = "Where to put the Dropbox directory.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.dropbox-cli ];
+
+ systemd.user.services.dropbox = {
+ Unit = { Description = "dropbox"; };
+
+ Install = { WantedBy = [ "default.target" ]; };
+
+ Service = {
+ Environment = [ "HOME=${homeBaseDir}" "DISPLAY=" ];
+
+ Type = "forking";
+ PIDFile = "${homeBaseDir}/.dropbox/dropbox.pid";
+
+ Restart = "on-failure";
+ PrivateTmp = true;
+ ProtectSystem = "full";
+ Nice = 10;
+
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ ExecStop = "${dropboxCmd} stop";
+ ExecStart = toString (pkgs.writeShellScript "dropbox-start" ''
+ # ensure we have the dirs we need
+ $DRY_RUN_CMD ${pkgs.coreutils}/bin/mkdir $VERBOSE_ARG -p \
+ ${homeBaseDir}/{.dropbox,.dropbox-dist,Dropbox}
+
+ # symlink them as needed
+ if [[ ! -d ${config.home.homeDirectory}/.dropbox ]]; then
+ $DRY_RUN_CMD ${pkgs.coreutils}/bin/ln $VERBOSE_ARG -s \
+ ${homeBaseDir}/.dropbox ${config.home.homeDirectory}/.dropbox
+ fi
+
+ if [[ ! -d ${escapeShellArg cfg.path} ]]; then
+ $DRY_RUN_CMD ${pkgs.coreutils}/bin/ln $VERBOSE_ARG -s \
+ ${homeBaseDir}/Dropbox ${escapeShellArg cfg.path}
+ fi
+
+ # get the dropbox bins if needed
+ if [[ ! -f $HOME/.dropbox-dist/VERSION ]]; then
+ ${pkgs.coreutils}/bin/yes | ${dropboxCmd} update
+ fi
+
+ ${dropboxCmd} start
+ '');
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/services/dunst.nix b/home-manager/modules/services/dunst.nix
index d32e875137b..5fbbb884a8e 100644
--- a/home-manager/modules/services/dunst.nix
+++ b/home-manager/modules/services/dunst.nix
@@ -45,7 +45,7 @@ let
};
hicolorTheme = {
- package = pkgs.hicolor_icon_theme;
+ package = pkgs.hicolor-icon-theme;
name = "hicolor";
size = "32x32";
};
@@ -89,6 +89,8 @@ in {
config = mkIf cfg.enable (mkMerge [
{
+ home.packages = [ (getOutput "man" pkgs.dunst) ];
+
xdg.dataFile."dbus-1/services/org.knopwob.dunst.service".source =
"${pkgs.dunst}/share/dbus-1/services/org.knopwob.dunst.service";
diff --git a/home-manager/modules/services/emacs.nix b/home-manager/modules/services/emacs.nix
index 5b0e88db72d..a73b750c513 100644
--- a/home-manager/modules/services/emacs.nix
+++ b/home-manager/modules/services/emacs.nix
@@ -7,36 +7,138 @@ let
cfg = config.services.emacs;
emacsCfg = config.programs.emacs;
emacsBinPath = "${emacsCfg.finalPackage}/bin";
+ emacsVersion = getVersion emacsCfg.finalPackage;
+
+ # Adapted from upstream emacs.desktop
+ clientDesktopItem = pkgs.makeDesktopItem rec {
+ name = "emacsclient";
+ desktopName = "Emacs Client";
+ genericName = "Text Editor";
+ comment = "Edit text";
+ mimeType =
+ "text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;";
+ exec = "${emacsBinPath}/emacsclient ${
+ concatStringsSep " " cfg.client.arguments
+ } %F";
+ icon = "emacs";
+ type = "Application";
+ terminal = "false";
+ categories = "Utility;TextEditor;";
+ extraEntries = ''
+ StartupWMClass=Emacs
+ '';
+ };
+
+ # Match the default socket path for the Emacs version so emacsclient continues
+ # to work without wrapping it. It might be worthwhile to allow customizing the
+ # socket path, but we would want to wrap emacsclient in the user profile to
+ # connect to the alternative socket by default for Emacs 26, and set
+ # EMACS_SOCKET_NAME for Emacs 27.
+ #
+ # As systemd doesn't perform variable expansion for the ListenStream param, we
+ # would also have to solve the problem of matching the shell path to the path
+ # used in the socket unit, which would likely involve templating. It seems of
+ # little value for the most common use case of one Emacs daemon per user
+ # session.
+ socketPath = if versionAtLeast emacsVersion "27" then
+ "%t/emacs/server"
+ else
+ "%T/emacs%U/server";
in {
- options.services.emacs = { enable = mkEnableOption "the Emacs daemon"; };
-
- config = mkIf cfg.enable {
- assertions = [{
- assertion = emacsCfg.enable;
- message = "The Emacs service module requires"
- + " 'programs.emacs.enable = true'.";
- }];
-
- systemd.user.services.emacs = {
- Unit = {
- Description = "Emacs: the extensible, self-documenting text editor";
- Documentation =
- "info:emacs man:emacs(1) https://gnu.org/software/emacs/";
-
- # Avoid killing the Emacs session, which may be full of
- # unsaved buffers.
- X-RestartIfChanged = false;
- };
+ meta.maintainers = [ maintainers.tadfisher ];
- Service = {
- ExecStart =
- "${pkgs.runtimeShell} -l -c 'exec ${emacsBinPath}/emacs --fg-daemon'";
- ExecStop = "${emacsBinPath}/emacsclient --eval '(kill-emacs)'";
- Restart = "on-failure";
+ options.services.emacs = {
+ enable = mkEnableOption "the Emacs daemon";
+
+ client = {
+ enable = mkEnableOption "generation of Emacs client desktop file";
+ arguments = mkOption {
+ type = with types; listOf str;
+ default = [ "-c" ];
+ description = ''
+ Command-line arguments to pass to <command>emacsclient</command>.
+ '';
};
+ };
- Install = { WantedBy = [ "default.target" ]; };
+ # Attrset for forward-compatibility; there may be a need to customize the
+ # socket path, though allowing for such is not easy to do as systemd socket
+ # units don't perform variable expansion for 'ListenStream'.
+ socketActivation = {
+ enable = mkEnableOption "systemd socket activation for the Emacs service";
};
};
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+ assertions = [
+ {
+ assertion = emacsCfg.enable;
+ message = "The Emacs service module requires"
+ + " 'programs.emacs.enable = true'.";
+ }
+ {
+ assertion = cfg.socketActivation.enable
+ -> versionAtLeast emacsVersion "26";
+ message = "Socket activation requires Emacs 26 or newer.";
+ }
+ ];
+
+ systemd.user.services.emacs = {
+ Unit = {
+ Description = "Emacs: the extensible, self-documenting text editor";
+ Documentation =
+ "info:emacs man:emacs(1) https://gnu.org/software/emacs/";
+
+ # Avoid killing the Emacs session, which may be full of
+ # unsaved buffers.
+ X-RestartIfChanged = false;
+ };
+
+ Service = {
+ # We wrap ExecStart in a login shell so Emacs starts with the user's
+ # environment, most importantly $PATH and $NIX_PROFILES. It may be
+ # worth investigating a more targeted approach for user services to
+ # import the user environment.
+ ExecStart = ''
+ ${pkgs.runtimeShell} -l -c "${emacsBinPath}/emacs --fg-daemon${
+ # In case the user sets 'server-directory' or 'server-name' in
+ # their Emacs config, we want to specify the socket path explicitly
+ # so launching 'emacs.service' manually doesn't break emacsclient
+ # when using socket activation.
+ optionalString cfg.socketActivation.enable
+ "=${escapeShellArg socketPath}"
+ }"'';
+ # We use '(kill-emacs 0)' to avoid exiting with a failure code, which
+ # would restart the service immediately.
+ ExecStop = "${emacsBinPath}/emacsclient --eval '(kill-emacs 0)'";
+ Restart = "on-failure";
+ };
+ } // optionalAttrs (!cfg.socketActivation.enable) {
+ Install = { WantedBy = [ "default.target" ]; };
+ };
+
+ home.packages = optional cfg.client.enable clientDesktopItem;
+ }
+
+ (mkIf cfg.socketActivation.enable {
+ systemd.user.sockets.emacs = {
+ Unit = {
+ Description = "Emacs: the extensible, self-documenting text editor";
+ Documentation =
+ "info:emacs man:emacs(1) https://gnu.org/software/emacs/";
+ };
+
+ Socket = {
+ ListenStream = socketPath;
+ FileDescriptorName = "server";
+ SocketMode = "0600";
+ DirectoryMode = "0700";
+ };
+
+ Install = { WantedBy = [ "sockets.target" ]; };
+ };
+ })
+ ]);
}
diff --git a/home-manager/modules/services/fluidsynth.nix b/home-manager/modules/services/fluidsynth.nix
new file mode 100644
index 00000000000..18913fe5426
--- /dev/null
+++ b/home-manager/modules/services/fluidsynth.nix
@@ -0,0 +1,57 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.fluidsynth;
+
+in {
+ meta.maintainers = [ maintainers.valodim ];
+
+ options = {
+ services.fluidsynth = {
+ enable = mkEnableOption "fluidsynth midi synthesizer";
+
+ soundFont = mkOption {
+ type = types.path;
+ default = "${pkgs.soundfont-fluid}/share/soundfonts/FluidR3_GM2-2.sf2";
+ description = ''
+ The soundfont file to use, in SoundFont 2 format.
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "--sample-rate 96000" ];
+ description = ''
+ Extra arguments, added verbatim to the fluidsynth command. See
+ <citerefentry>
+ <refentrytitle>fluidsynth.conf</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.fluidsynth = {
+ Unit = {
+ Description = "FluidSynth Daemon";
+ Documentation = "man:fluidsynth(1)";
+ BindsTo = [ "pulseaudio.service" ];
+ After = [ "pulseaudio.service" ];
+ };
+
+ Install = { WantedBy = [ "default.target" ]; };
+
+ Service = {
+ ExecStart = "${pkgs.fluidsynth}/bin/fluidsynth -a pulseaudio -si ${
+ lib.concatStringsSep " " cfg.extraOptions
+ } ${cfg.soundFont}";
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/services/gnome-keyring.nix b/home-manager/modules/services/gnome-keyring.nix
index 6d8317dcffc..ce39cea93f9 100644
--- a/home-manager/modules/services/gnome-keyring.nix
+++ b/home-manager/modules/services/gnome-keyring.nix
@@ -36,7 +36,7 @@ in {
args = concatStringsSep " " ([ "--start" "--foreground" ]
++ optional (cfg.components != [ ])
("--components=" + concatStringsSep "," cfg.components));
- in "${pkgs.gnome3.gnome_keyring}/bin/gnome-keyring-daemon ${args}";
+ in "${pkgs.gnome3.gnome-keyring}/bin/gnome-keyring-daemon ${args}";
Restart = "on-abort";
};
diff --git a/home-manager/modules/services/kanshi.nix b/home-manager/modules/services/kanshi.nix
new file mode 100644
index 00000000000..4e5e5f104e6
--- /dev/null
+++ b/home-manager/modules/services/kanshi.nix
@@ -0,0 +1,194 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.kanshi;
+
+ outputModule = types.submodule {
+ options = {
+
+ criteria = mkOption {
+ type = types.str;
+ description = ''
+ The criteria can either be an output name, an output description or "*".
+ The latter can be used to match any output.
+
+ On
+ <citerefentry>
+ <refentrytitle>sway</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>,
+ output names and descriptions can be obtained via
+ <literal>swaymsg -t get_outputs</literal>.
+ '';
+ };
+
+ status = mkOption {
+ type = types.nullOr (types.enum [ "enable" "disable" ]);
+ default = null;
+ description = ''
+ Enables or disables the specified output.
+ '';
+ };
+
+ mode = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "1920x1080@60Hz";
+ description = ''
+ &lt;width&gt;x&lt;height&gt;[@&lt;rate&gt;[Hz]]
+ </para><para>
+ Configures the specified output to use the specified mode.
+ Modes are a combination of width and height (in pixels) and
+ a refresh rate (in Hz) that your display can be configured to use.
+ '';
+ };
+
+ position = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "1600,0";
+ description = ''
+ &lt;x&gt;,&lt;y&gt;
+ </para><para>
+ Places the output at the specified position in the global coordinates
+ space.
+ '';
+ };
+
+ scale = mkOption {
+ type = types.nullOr types.float;
+ default = null;
+ example = 2;
+ description = ''
+ Scales the output by the specified scale factor.
+ '';
+ };
+
+ transform = mkOption {
+ type = types.nullOr (types.enum [
+ "normal"
+ "90"
+ "180"
+ "270"
+ "flipped"
+ "flipped-90"
+ "flipped-180"
+ "flipped-270"
+ ]);
+ default = null;
+ description = ''
+ Sets the output transform.
+ '';
+ };
+ };
+ };
+
+ outputStr = { criteria, status, mode, position, scale, transform, ... }:
+ ''output "${criteria}"'' + optionalString (status != null) " ${status}"
+ + optionalString (mode != null) " mode ${mode}"
+ + optionalString (position != null) " position ${position}"
+ + optionalString (scale != null) " scale ${toString scale}"
+ + optionalString (transform != null) " transform ${transform}";
+
+ profileModule = types.submodule {
+ options = {
+ outputs = mkOption {
+ type = types.listOf outputModule;
+ default = [ ];
+ description = ''
+ Outputs configuration.
+ '';
+ };
+
+ exec = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example =
+ "\${pkg.sway}/bin/swaymsg workspace 1, move workspace to eDP-1";
+ description = ''
+ Command executed after the profile is succesfully applied.
+ '';
+ };
+ };
+ };
+
+ profileStr = name:
+ { outputs, exec, ... }:
+ ''
+ profile ${name} {
+ ${concatStringsSep "\n " (map outputStr outputs)}
+ '' + optionalString (exec != null) " exec ${exec}\n" + ''
+ }
+ '';
+in {
+
+ meta.maintainers = [ maintainers.nurelin ];
+
+ options.services.kanshi = {
+ enable = mkEnableOption
+ "kanshi, a Wayland daemon that automatically configures outputs";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.kanshi;
+ defaultText = literalExample "pkgs.kanshi";
+ description = ''
+ kanshi derivation to use.
+ '';
+ };
+
+ profiles = mkOption {
+ type = types.attrsOf profileModule;
+ default = { };
+ description = ''
+ List of profiles.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration lines to append to the kanshi
+ configuration file.
+ '';
+ };
+
+ systemdTarget = mkOption {
+ type = types.str;
+ default = "sway-session.target";
+ description = ''
+ Systemd target to bind to.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ xdg.configFile."kanshi/config".text = ''
+ ${concatStringsSep "\n" (mapAttrsToList profileStr cfg.profiles)}
+ ${cfg.extraConfig}
+ '';
+
+ systemd.user.services.kanshi = {
+ Unit = {
+ Description = "Dynamic output configuration";
+ Documentation = "man:kanshi(1)";
+ PartOf = cfg.systemdTarget;
+ Requires = cfg.systemdTarget;
+ After = cfg.systemdTarget;
+ };
+
+ Service = {
+ Type = "simple";
+ ExecStart = "${cfg.package}/bin/kanshi";
+ Restart = "always";
+ };
+
+ Install = { WantedBy = [ cfg.systemdTarget ]; };
+ };
+ };
+}
diff --git a/home-manager/modules/services/keynav.nix b/home-manager/modules/services/keynav.nix
new file mode 100644
index 00000000000..c7f1df373b8
--- /dev/null
+++ b/home-manager/modules/services/keynav.nix
@@ -0,0 +1,29 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.keynav;
+
+in {
+ options.services.keynav = { enable = mkEnableOption "keynav"; };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.keynav = {
+ Unit = {
+ Description = "keynav";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ ExecStart = "${pkgs.keynav}/bin/keynav";
+ RestartSec = 3;
+ Restart = "always";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+}
diff --git a/home-manager/modules/services/lieer-accounts.nix b/home-manager/modules/services/lieer-accounts.nix
new file mode 100644
index 00000000000..187f7dff980
--- /dev/null
+++ b/home-manager/modules/services/lieer-accounts.nix
@@ -0,0 +1,25 @@
+{ lib, ... }:
+
+with lib;
+
+{
+ options.lieer.sync = {
+ enable = mkEnableOption "lieer synchronization service";
+
+ frequency = mkOption {
+ type = types.str;
+ default = "*:0/5";
+ description = ''
+ How often to synchronize the account.
+ </para><para>
+ This value is passed to the systemd timer configuration as the
+ onCalendar option. See
+ <citerefentry>
+ <refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum>
+ </citerefentry>
+ for more information about the format.
+ '';
+ };
+ };
+}
diff --git a/home-manager/modules/services/lieer.nix b/home-manager/modules/services/lieer.nix
new file mode 100644
index 00000000000..571e2af75c8
--- /dev/null
+++ b/home-manager/modules/services/lieer.nix
@@ -0,0 +1,66 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.lieer;
+
+ syncAccounts = filter (a: a.lieer.enable && a.lieer.sync.enable)
+ (attrValues config.accounts.email.accounts);
+
+ escapeUnitName = name:
+ let
+ good = upperChars ++ lowerChars ++ stringToCharacters "0123456789-_";
+ subst = c: if any (x: x == c) good then c else "-";
+ in stringAsChars subst name;
+
+ serviceUnit = account: {
+ name = escapeUnitName "lieer-${account.name}";
+ value = {
+ Unit = {
+ Description = "lieer Gmail synchronization for ${account.name}";
+ ConditionPathExists = "${account.maildir.absPath}/.gmailieer.json";
+ };
+
+ Service = {
+ Type = "oneshot";
+ ExecStart = "${pkgs.gmailieer}/bin/gmi sync";
+ WorkingDirectory = account.maildir.absPath;
+ };
+ };
+ };
+
+ timerUnit = account: {
+ name = escapeUnitName "lieer-${account.name}";
+ value = {
+ Unit = {
+ Description = "lieer Gmail synchronization for ${account.name}";
+ };
+
+ Timer = {
+ OnCalendar = account.lieer.sync.frequency;
+ RandomizedDelaySec = 30;
+ };
+
+ Install = { WantedBy = [ "timers.target" ]; };
+ };
+ };
+
+in {
+ meta.maintainers = [ maintainers.tadfisher ];
+
+ options = {
+ services.lieer.enable =
+ mkEnableOption "lieer Gmail synchronization service";
+
+ accounts.email.accounts = mkOption {
+ type = with types; attrsOf (submodule (import ./lieer-accounts.nix));
+ };
+ };
+
+ config = mkIf cfg.enable {
+ programs.lieer.enable = true;
+ systemd.user.services = listToAttrs (map serviceUnit syncAccounts);
+ systemd.user.timers = listToAttrs (map timerUnit syncAccounts);
+ };
+}
diff --git a/home-manager/modules/services/lorri.nix b/home-manager/modules/services/lorri.nix
index 3b2c244e3c0..6183699088b 100644
--- a/home-manager/modules/services/lorri.nix
+++ b/home-manager/modules/services/lorri.nix
@@ -9,10 +9,19 @@ let
in {
meta.maintainers = [ maintainers.gerschtli ];
- options = { services.lorri.enable = mkEnableOption "lorri build daemon"; };
+ options.services.lorri = {
+ enable = mkEnableOption "lorri build daemon";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.lorri;
+ defaultText = literalExample "pkgs.lorri";
+ description = "Which lorri package to install.";
+ };
+ };
config = mkIf cfg.enable {
- home.packages = [ pkgs.lorri ];
+ home.packages = [ cfg.package ];
systemd.user = {
services.lorri = {
@@ -24,7 +33,7 @@ in {
};
Service = {
- ExecStart = "${pkgs.lorri}/bin/lorri daemon";
+ ExecStart = "${cfg.package}/bin/lorri daemon";
PrivateTmp = true;
ProtectSystem = "strict";
ProtectHome = "read-only";
@@ -32,7 +41,7 @@ in {
Environment = let
path = with pkgs;
makeSearchPath "bin" [ nix gitMinimal gnutar gzip ];
- in "PATH=${path}";
+ in [ "PATH=${path}" ];
};
};
diff --git a/home-manager/modules/services/mako.nix b/home-manager/modules/services/mako.nix
new file mode 100644
index 00000000000..77ea3011678
--- /dev/null
+++ b/home-manager/modules/services/mako.nix
@@ -0,0 +1,316 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.mako;
+
+in {
+ meta.maintainers = [ maintainers.onny ];
+
+ options = {
+ programs.mako = {
+ enable = mkEnableOption ''
+ Mako, lightweight notification daemon for Wayland
+ '';
+
+ maxVisible = mkOption {
+ default = 5;
+ type = types.nullOr types.int;
+ description = ''
+ Set maximum number of visible notifications. Set -1 to show all.
+ '';
+ };
+
+ sort = mkOption {
+ default = "-time";
+ type =
+ types.nullOr (types.enum [ "+time" "-time" "+priority" "-priority" ]);
+ description = ''
+ Sorts incoming notifications by time and/or priority in ascending(+)
+ or descending(-) order.
+ '';
+ };
+
+ output = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ Show notifications on the specified output. If empty, notifications
+ will appear on the focused output. Requires the compositor to support
+ the Wayland protocol xdg-output-unstable-v1 version 2.
+ '';
+ };
+
+ layer = mkOption {
+ default = "top";
+ type =
+ types.nullOr (types.enum [ "background" "bottom" "top" "overlay" ]);
+ description = ''
+ Arrange mako at the specified layer, relative to normal windows.
+ Supported values are background, bottom, top, and overlay. Using
+ overlay will cause notifications to be displayed above fullscreen
+ windows, though this may also occur at top depending on your
+ compositor.
+ '';
+ };
+
+ anchor = mkOption {
+ default = "top-right";
+ type = types.nullOr (types.enum [
+ "top-right"
+ "top-center"
+ "top-left"
+ "bottom-right"
+ "bottom-center"
+ "bottom-left"
+ "center"
+ ]);
+ description = ''
+ Show notifications at the specified position on the output.
+ Supported values are top-right, top-center, top-left, bottom-right,
+ bottom-center, bottom-left, and center.
+ '';
+ };
+
+ font = mkOption {
+ default = "monospace 10";
+ type = types.nullOr types.str;
+ description = ''
+ Font to use, in Pango format.
+ '';
+ };
+
+ backgroundColor = mkOption {
+ default = "#285577FF";
+ type = types.nullOr types.str;
+ description = ''
+ Set popup background color to a specific color, represented in hex
+ color code.
+ '';
+ };
+
+ textColor = mkOption {
+ default = "#FFFFFFFF";
+ type = types.nullOr types.str;
+ description = ''
+ Set popup text color to a specific color, represented in hex color
+ code.
+ '';
+ };
+
+ width = mkOption {
+ default = 300;
+ type = types.nullOr types.int;
+ description = ''
+ Set width of notification popups in specified number of pixels.
+ '';
+ };
+
+ height = mkOption {
+ default = 100;
+ type = types.nullOr types.int;
+ description = ''
+ Set maximum height of notification popups. Notifications whose text
+ takes up less space are shrunk to fit.
+ '';
+ };
+
+ margin = mkOption {
+ default = "10";
+ type = types.nullOr types.str;
+ description = ''
+ Set margin of each edge specified in pixels. Specify single value to
+ apply margin on all sides. Two comma-seperated values will set
+ vertical and horizontal edges seperately. Four comma-seperated will
+ give each edge a seperate value.
+ For example: 10,20,5 will set top margin to 10, left and right to 20
+ and bottom to five.
+ '';
+ };
+
+ padding = mkOption {
+ default = "5";
+ type = types.nullOr types.str;
+ description = ''
+ Set padding of each edge specified in pixels. Specify single value to
+ apply margin on all sides. Two comma-seperated values will set
+ vertical and horizontal edges seperately. Four comma-seperated will
+ give each edge a seperate value.
+ For example: 10,20,5 will set top margin to 10, left and right to 20
+ and bottom to five.
+ '';
+ };
+
+ borderSize = mkOption {
+ default = 1;
+ type = types.nullOr types.int;
+ description = ''
+ Set popup border size to the specified number of pixels.
+ '';
+ };
+
+ borderColor = mkOption {
+ default = "#4C7899FF";
+ type = types.nullOr types.str;
+ description = ''
+ Set popup border color to a specific color, represented in hex color
+ code.
+ '';
+ };
+
+ borderRadius = mkOption {
+ default = 0;
+ type = types.nullOr types.int;
+ description = ''
+ Set popup corner radius to the specified number of pixels.
+ '';
+ };
+
+ progressColor = mkOption {
+ default = "over #5588AAFF";
+ type = types.nullOr types.str;
+ description = ''
+ Set popup progress indicator color to a specific color,
+ represented in hex color code. To draw the progress
+ indicator on top of the background color, use the
+ <literal>over</literal> attribute. To replace the background
+ color, use the <literal>source</literal> attribute (this can
+ be useful when the notification is semi-transparent).
+ '';
+ };
+
+ icons = mkOption {
+ default = true;
+ type = types.nullOr types.bool;
+ description = ''
+ Whether or not to show icons in notifications.
+ '';
+ };
+
+ maxIconSize = mkOption {
+ default = 64;
+ type = types.nullOr types.int;
+ description = ''
+ Set maximum icon size to the specified number of pixels.
+ '';
+ };
+
+ iconPath = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ Paths to search for icons when a notification specifies a name
+ instead of a full path. Colon-delimited. This approximates the search
+ algorithm used by the XDG Icon Theme Specification, but does not
+ support any of the theme metadata. Therefore, if you want to search
+ parent themes, you'll need to add them to the path manually.
+ </para><para>
+ The <filename>/usr/share/icons/hicolor</filename> and
+ <filename>/usr/share/pixmaps</filename> directories are
+ always searched.
+ '';
+ };
+
+ markup = mkOption {
+ default = true;
+ type = types.nullOr types.bool;
+ description = ''
+ If 1, enable Pango markup. If 0, disable Pango markup. If enabled,
+ Pango markup will be interpreted in your format specifier and in the
+ body of notifications.
+ '';
+ };
+
+ actions = mkOption {
+ default = true;
+ type = types.nullOr types.bool;
+ description = ''
+ Applications may request an action to be associated with activating a
+ notification. Disabling this will cause mako to ignore these requests.
+ '';
+ };
+
+ format = mkOption {
+ default = "<b>%s</b>\\n%b";
+ type = types.nullOr types.str;
+ description = ''
+ Set notification format string to format. See FORMAT SPECIFIERS for
+ more information. To change this for grouped notifications, set it
+ within a grouped criteria.
+ '';
+ };
+
+ defaultTimeout = mkOption {
+ default = 0;
+ type = types.nullOr types.int;
+ description = ''
+ Set the default timeout to timeout in milliseconds. To disable the
+ timeout, set it to zero.
+ '';
+ };
+
+ ignoreTimeout = mkOption {
+ default = false;
+ type = types.nullOr types.bool;
+ description = ''
+ If set, mako will ignore the expire timeout sent by notifications
+ and use the one provided by default-timeout instead.
+ '';
+ };
+
+ groupBy = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ A comma-separated list of criteria fields that will be compared to
+ other visible notifications to determine if this one should form a
+ group with them. All listed criteria must be exactly equal for two
+ notifications to group.
+ '';
+ };
+
+ };
+ };
+
+ config = let
+ boolToString = v: if v then "true" else "false";
+ optionalBoolean = name: val:
+ lib.optionalString (val != null) "${name}=${boolToString val}";
+ optionalInteger = name: val:
+ lib.optionalString (val != null) "${name}=${toString val}";
+ optionalString = name: val:
+ lib.optionalString (val != null) "${name}=${val}";
+ in mkIf cfg.enable {
+ home.packages = [ pkgs.mako ];
+ xdg.configFile."mako/config".text = ''
+ ${optionalInteger "max-visible" cfg.maxVisible}
+ ${optionalString "sort" cfg.sort}
+ ${optionalString "output" cfg.output}
+ ${optionalString "layer" cfg.layer}
+ ${optionalString "anchor" cfg.anchor}
+
+ ${optionalString "font" cfg.font}
+ ${optionalString "background-color" cfg.backgroundColor}
+ ${optionalString "text-color" cfg.textColor}
+ ${optionalInteger "width" cfg.width}
+ ${optionalInteger "height" cfg.height}
+ ${optionalString "margin" cfg.margin}
+ ${optionalString "padding" cfg.padding}
+ ${optionalInteger "border-size" cfg.borderSize}
+ ${optionalString "border-color" cfg.borderColor}
+ ${optionalInteger "border-radius" cfg.borderRadius}
+ ${optionalString "progress-color" cfg.progressColor}
+ ${optionalBoolean "icons" cfg.icons}
+ ${optionalInteger "max-icon-size" cfg.maxIconSize}
+ ${optionalString "icon-path" cfg.iconPath}
+ ${optionalBoolean "markup" cfg.markup}
+ ${optionalBoolean "actions" cfg.actions}
+ ${optionalString "format" cfg.format}
+ ${optionalInteger "default-timeout" cfg.defaultTimeout}
+ ${optionalBoolean "ignore-timeout" cfg.ignoreTimeout}
+ ${optionalString "group-by" cfg.groupBy}
+ '';
+ };
+}
diff --git a/home-manager/modules/services/mpd.nix b/home-manager/modules/services/mpd.nix
index 2aa1cd3a9fe..13b3ae78f26 100644
--- a/home-manager/modules/services/mpd.nix
+++ b/home-manager/modules/services/mpd.nix
@@ -42,7 +42,7 @@ in {
};
musicDirectory = mkOption {
- type = types.path;
+ type = with types; either path str;
default = "${config.home.homeDirectory}/music";
defaultText = "$HOME/music";
apply = toString; # Prevent copies to Nix store.
diff --git a/home-manager/modules/services/picom.nix b/home-manager/modules/services/picom.nix
new file mode 100644
index 00000000000..4c4da8de697
--- /dev/null
+++ b/home-manager/modules/services/picom.nix
@@ -0,0 +1,311 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+with builtins;
+
+let
+
+ cfg = config.services.picom;
+
+ configFile = pkgs.writeText "picom.conf" (optionalString cfg.fade ''
+ # fading
+ fading = true;
+ fade-delta = ${toString cfg.fadeDelta};
+ fade-in-step = ${elemAt cfg.fadeSteps 0};
+ fade-out-step = ${elemAt cfg.fadeSteps 1};
+ fade-exclude = ${toJSON cfg.fadeExclude};
+ '' + optionalString cfg.shadow ''
+
+ # shadows
+ shadow = true;
+ shadow-offset-x = ${toString (elemAt cfg.shadowOffsets 0)};
+ shadow-offset-y = ${toString (elemAt cfg.shadowOffsets 1)};
+ shadow-opacity = ${cfg.shadowOpacity};
+ shadow-exclude = ${toJSON cfg.shadowExclude};
+ '' + optionalString cfg.blur ''
+
+ # blur
+ blur-background = true;
+ blur-background-exclude = ${toJSON cfg.blurExclude};
+ '' + ''
+
+ # opacity
+ active-opacity = ${cfg.activeOpacity};
+ inactive-opacity = ${cfg.inactiveOpacity};
+ inactive-dim = ${cfg.inactiveDim};
+ opacity-rule = ${toJSON cfg.opacityRule};
+
+ wintypes:
+ {
+ dock = { shadow = ${toJSON (!cfg.noDockShadow)}; };
+ dnd = { shadow = ${toJSON (!cfg.noDNDShadow)}; };
+ popup_menu = { opacity = ${cfg.menuOpacity}; };
+ dropdown_menu = { opacity = ${cfg.menuOpacity}; };
+ };
+
+ # other options
+ backend = ${toJSON cfg.backend};
+ vsync = ${toJSON cfg.vSync};
+ refresh-rate = ${toString cfg.refreshRate};
+ '' + cfg.extraOptions);
+
+in {
+
+ options.services.picom = {
+ enable = mkEnableOption "Picom X11 compositor";
+
+ blur = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable background blur on transparent windows.
+ '';
+ };
+
+ blurExclude = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "class_g = 'slop'" "class_i = 'polybar'" ];
+ description = ''
+ List of windows to exclude background blur.
+ See the
+ <citerefentry>
+ <refentrytitle>picom</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ man page for more examples.
+ '';
+ };
+
+ experimentalBackends = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to use the new experimental backends.
+ '';
+ };
+
+ fade = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Fade windows in and out.
+ '';
+ };
+
+ fadeDelta = mkOption {
+ type = types.int;
+ default = 10;
+ example = 5;
+ description = ''
+ Time between fade animation step (in ms).
+ '';
+ };
+
+ fadeSteps = mkOption {
+ type = types.listOf types.str;
+ default = [ "0.028" "0.03" ];
+ example = [ "0.04" "0.04" ];
+ description = ''
+ Opacity change between fade steps (in and out).
+ '';
+ };
+
+ fadeExclude = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "window_type *= 'menu'" "name ~= 'Firefox$'" "focused = 1" ];
+ description = ''
+ List of conditions of windows that should not be faded.
+ See the
+ <citerefentry>
+ <refentrytitle>picom</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ man page for more examples.
+ '';
+ };
+
+ shadow = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Draw window shadows.
+ '';
+ };
+
+ shadowOffsets = mkOption {
+ type = types.listOf types.int;
+ default = [ (-15) (-15) ];
+ example = [ (-10) (-15) ];
+ description = ''
+ Horizontal and vertical offsets for shadows (in pixels).
+ '';
+ };
+
+ shadowOpacity = mkOption {
+ type = types.str;
+ default = "0.75";
+ example = "0.8";
+ description = ''
+ Window shadows opacity (number in range 0 - 1).
+ '';
+ };
+
+ shadowExclude = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "window_type *= 'menu'" "name ~= 'Firefox$'" "focused = 1" ];
+ description = ''
+ List of conditions of windows that should have no shadow.
+ See the
+ <citerefentry>
+ <refentrytitle>picom</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ man page for more examples.
+ '';
+ };
+
+ noDockShadow = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Avoid shadow on docks.
+ '';
+ };
+
+ noDNDShadow = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Avoid shadow on drag-and-drop windows.
+ '';
+ };
+
+ activeOpacity = mkOption {
+ type = types.str;
+ default = "1.0";
+ example = "0.8";
+ description = ''
+ Opacity of active windows.
+ '';
+ };
+
+ inactiveDim = mkOption {
+ type = types.str;
+ default = "0.0";
+ example = "0.2";
+ description = ''
+ Dim inactive windows.
+ '';
+ };
+
+ inactiveOpacity = mkOption {
+ type = types.str;
+ default = "1.0";
+ example = "0.8";
+ description = ''
+ Opacity of inactive windows.
+ '';
+ };
+
+ menuOpacity = mkOption {
+ type = types.str;
+ default = "1.0";
+ example = "0.8";
+ description = ''
+ Opacity of dropdown and popup menu.
+ '';
+ };
+
+ opacityRule = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "87:class_i ?= 'scratchpad'" "91:class_i ?= 'xterm'" ];
+ description = ''
+ List of opacity rules.
+ See the
+ <citerefentry>
+ <refentrytitle>picom</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ man page for more examples.
+ '';
+ };
+
+ backend = mkOption {
+ type = types.str;
+ default = "glx";
+ description = ''
+ Backend to use: <literal>glx</literal> or <literal>xrender</literal>.
+ '';
+ };
+
+ vSync = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable vertical synchronization.
+ '';
+ };
+
+ refreshRate = mkOption {
+ type = types.int;
+ default = 0;
+ example = 60;
+ description = ''
+ Screen refresh rate (0 = automatically detect).
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.picom;
+ defaultText = literalExample "pkgs.picom";
+ example = literalExample "pkgs.picom";
+ description = ''
+ picom derivation to use.
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = types.str;
+ default = "";
+ example = ''
+ unredir-if-possible = true;
+ dbe = true;
+ '';
+ description = ''
+ Additional Picom configuration.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+
+ systemd.user.services.picom = {
+ Unit = {
+ Description = "Picom X11 compositor";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = let
+ experimentalBackendsFlag =
+ if cfg.experimentalBackends then " --experimental-backends" else "";
+ in {
+ ExecStart = "${cfg.package}/bin/picom --config ${configFile}"
+ + experimentalBackendsFlag;
+ Restart = "always";
+ RestartSec = 3;
+ } // optionalAttrs (cfg.backend == "glx") {
+ # Temporarily fixes corrupt colours with Mesa 18.
+ Environment = [ "allow_rgb10_configs=false" ];
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/services/pulseeffects.nix b/home-manager/modules/services/pulseeffects.nix
new file mode 100644
index 00000000000..445b1c0a1f8
--- /dev/null
+++ b/home-manager/modules/services/pulseeffects.nix
@@ -0,0 +1,55 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.pulseeffects;
+
+ presetOpts = optionalString (cfg.preset != "") "--load-preset ${cfg.preset}";
+
+in {
+ meta.maintainers = [ maintainers.jonringer ];
+
+ options.services.pulseeffects = {
+ enable = mkEnableOption "Pulseeffects daemon";
+
+ preset = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Which preset to use when starting pulseeffects.
+ Will likely need to launch pulseeffects to initially create preset.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ # running pulseeffects will just attach itself to gapplication service
+ # at-spi2-core is to minimize journalctl noise of:
+ # "AT-SPI: Error retrieving accessibility bus address: org.freedesktop.DBus.Error.ServiceUnknown: The name org.a11y.Bus was not provided by any .service files"
+ home.packages = [ pkgs.pulseeffects pkgs.at-spi2-core ];
+
+ # Will need to add `services.dbus.packages = with pkgs; [ gnome3.dconf ];`
+ # to /etc/nixos/configuration.nix for daemon to work correctly
+
+ systemd.user.services.pulseeffects = {
+ Unit = {
+ Description = "Pulseeffects daemon";
+ Requires = [ "dbus.service" ];
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" "pulseaudio.service" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ ExecStart =
+ "${pkgs.pulseeffects}/bin/pulseeffects --gapplication-service ${presetOpts}";
+ ExecStop = "${pkgs.pulseeffects}/bin/pulseeffects --quit";
+ Restart = "on-failure";
+ RestartSec = 5;
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/services/screen-locker.nix b/home-manager/modules/services/screen-locker.nix
index 30591a7d1a5..554d64f9abe 100644
--- a/home-manager/modules/services/screen-locker.nix
+++ b/home-manager/modules/services/screen-locker.nix
@@ -17,6 +17,14 @@ in {
example = "\${pkgs.i3lock}/bin/i3lock -n -c 000000";
};
+ enableDetectSleep = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to reset timers when awaking from sleep.
+ '';
+ };
+
inactiveInterval = mkOption {
type = types.int;
default = 10;
@@ -42,7 +50,6 @@ in {
Extra command-line arguments to pass to <command>xss-lock</command>.
'';
};
-
};
config = mkIf cfg.enable {
@@ -58,10 +65,10 @@ in {
Service = {
ExecStart = concatStringsSep " " ([
"${pkgs.xautolock}/bin/xautolock"
- "-detectsleep"
"-time ${toString cfg.inactiveInterval}"
"-locker '${pkgs.systemd}/bin/loginctl lock-session $XDG_SESSION_ID'"
- ] ++ cfg.xautolockExtraOptions);
+ ] ++ optional cfg.enableDetectSleep "-detectsleep"
+ ++ cfg.xautolockExtraOptions);
};
};
diff --git a/home-manager/modules/services/spotifyd.nix b/home-manager/modules/services/spotifyd.nix
index bc231814eba..dfe0ecd318e 100644
--- a/home-manager/modules/services/spotifyd.nix
+++ b/home-manager/modules/services/spotifyd.nix
@@ -14,6 +14,18 @@ in {
options.services.spotifyd = {
enable = mkEnableOption "SpotifyD connect";
+ package = mkOption {
+ type = types.package;
+ default = pkgs.spotifyd;
+ defaultText = literalExample "pkgs.spotifyd";
+ example =
+ literalExample "(pkgs.spotifyd.override { withKeyring = true; })";
+ description = ''
+ The <literal>spotifyd</literal> package to use.
+ Can be used to specify extensions.
+ '';
+ };
+
settings = mkOption {
type = types.attrsOf (types.attrsOf types.str);
default = { };
@@ -21,7 +33,7 @@ in {
example = literalExample ''
{
global = {
- user = "Alex";
+ username = "Alex";
password = "foo";
device_name = "nix";
};
@@ -31,7 +43,7 @@ in {
};
config = mkIf cfg.enable {
- home.packages = [ pkgs.spotifyd ];
+ home.packages = [ cfg.package ];
systemd.user.services.spotifyd = {
Unit = {
@@ -43,7 +55,7 @@ in {
Service = {
ExecStart =
- "${pkgs.spotifyd}/bin/spotifyd --no-daemon --config-path ${configFile}";
+ "${cfg.package}/bin/spotifyd --no-daemon --config-path ${configFile}";
Restart = "always";
RestartSec = 12;
};
diff --git a/home-manager/modules/services/status-notifier-watcher.nix b/home-manager/modules/services/status-notifier-watcher.nix
index 3c3e54877b4..ed0537e22e1 100644
--- a/home-manager/modules/services/status-notifier-watcher.nix
+++ b/home-manager/modules/services/status-notifier-watcher.nix
@@ -34,7 +34,14 @@ in {
Before = [ "taffybar.service" ];
};
- Service = { ExecStart = "${cfg.package}/bin/status-notifier-watcher"; };
+ Service = {
+ ExecStart = "${cfg.package}/bin/status-notifier-watcher";
+ # Delay the unit start a bit to allow the program to get fully
+ # set up before letting dependent services start. This is
+ # brittle and a better solution using, e.g., `BusName=` might
+ # be possible.
+ ExecStartPost = "${pkgs.coreutils}/bin/sleep 1";
+ };
Install = {
WantedBy = [ "graphical-session.target" "taffybar.service" ];
diff --git a/home-manager/modules/services/syncthing.nix b/home-manager/modules/services/syncthing.nix
index 2ef10540164..4622ac2e941 100644
--- a/home-manager/modules/services/syncthing.nix
+++ b/home-manager/modules/services/syncthing.nix
@@ -19,6 +19,8 @@ with lib;
config = mkMerge [
(mkIf config.services.syncthing.enable {
+ home.packages = [ (getOutput "man" pkgs.syncthing) ];
+
systemd.user.services = {
syncthing = {
Unit = {
diff --git a/home-manager/modules/services/udiskie.nix b/home-manager/modules/services/udiskie.nix
index 2444d68ff93..ca31021cb5c 100644
--- a/home-manager/modules/services/udiskie.nix
+++ b/home-manager/modules/services/udiskie.nix
@@ -81,9 +81,7 @@ in {
PartOf = [ "graphical-session.target" ];
};
- Service = {
- ExecStart = "${pkgs.udiskie}/bin/udiskie -2 ${commandArgs}";
- };
+ Service = { ExecStart = "${pkgs.udiskie}/bin/udiskie ${commandArgs}"; };
Install = { WantedBy = [ "graphical-session.target" ]; };
};
diff --git a/home-manager/modules/services/unison.nix b/home-manager/modules/services/unison.nix
index 93c59e8fd62..a9cf23fb66e 100644
--- a/home-manager/modules/services/unison.nix
+++ b/home-manager/modules/services/unison.nix
@@ -60,7 +60,7 @@ let
};
};
- serialiseArg = key: val: "-${key}=${escapeShellArg val}";
+ serialiseArg = key: val: escapeShellArg "-${key}=${escape [ "=" ] val}";
serialiseArgs = args: concatStringsSep " " (mapAttrsToList serialiseArg args);
diff --git a/home-manager/modules/services/window-managers/i3-sway/i3.nix b/home-manager/modules/services/window-managers/i3-sway/i3.nix
new file mode 100644
index 00000000000..f7124e6fd23
--- /dev/null
+++ b/home-manager/modules/services/window-managers/i3-sway/i3.nix
@@ -0,0 +1,257 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.xsession.windowManager.i3;
+
+ commonOptions = import ./lib/options.nix {
+ inherit config lib cfg pkgs;
+ moduleName = "i3";
+ isGaps = cfg.package == pkgs.i3-gaps;
+ };
+
+ configModule = types.submodule {
+ options = {
+ inherit (commonOptions)
+ fonts window floating focus assigns modifier workspaceLayout
+ workspaceAutoBackAndForth keycodebindings colors bars startup gaps menu
+ terminal;
+
+ keybindings = mkOption {
+ type = types.attrsOf (types.nullOr types.str);
+ default = mapAttrs (n: mkOptionDefault) {
+ "${cfg.config.modifier}+Return" = "exec ${cfg.config.terminal}";
+ "${cfg.config.modifier}+Shift+q" = "kill";
+ "${cfg.config.modifier}+d" = "exec ${cfg.config.menu}";
+
+ "${cfg.config.modifier}+Left" = "focus left";
+ "${cfg.config.modifier}+Down" = "focus down";
+ "${cfg.config.modifier}+Up" = "focus up";
+ "${cfg.config.modifier}+Right" = "focus right";
+
+ "${cfg.config.modifier}+Shift+Left" = "move left";
+ "${cfg.config.modifier}+Shift+Down" = "move down";
+ "${cfg.config.modifier}+Shift+Up" = "move up";
+ "${cfg.config.modifier}+Shift+Right" = "move right";
+
+ "${cfg.config.modifier}+h" = "split h";
+ "${cfg.config.modifier}+v" = "split v";
+ "${cfg.config.modifier}+f" = "fullscreen toggle";
+
+ "${cfg.config.modifier}+s" = "layout stacking";
+ "${cfg.config.modifier}+w" = "layout tabbed";
+ "${cfg.config.modifier}+e" = "layout toggle split";
+
+ "${cfg.config.modifier}+Shift+space" = "floating toggle";
+ "${cfg.config.modifier}+space" = "focus mode_toggle";
+
+ "${cfg.config.modifier}+a" = "focus parent";
+
+ "${cfg.config.modifier}+Shift+minus" = "move scratchpad";
+ "${cfg.config.modifier}+minus" = "scratchpad show";
+
+ "${cfg.config.modifier}+1" = "workspace number 1";
+ "${cfg.config.modifier}+2" = "workspace number 2";
+ "${cfg.config.modifier}+3" = "workspace number 3";
+ "${cfg.config.modifier}+4" = "workspace number 4";
+ "${cfg.config.modifier}+5" = "workspace number 5";
+ "${cfg.config.modifier}+6" = "workspace number 6";
+ "${cfg.config.modifier}+7" = "workspace number 7";
+ "${cfg.config.modifier}+8" = "workspace number 8";
+ "${cfg.config.modifier}+9" = "workspace number 9";
+ "${cfg.config.modifier}+0" = "workspace number 10";
+
+ "${cfg.config.modifier}+Shift+1" =
+ "move container to workspace number 1";
+ "${cfg.config.modifier}+Shift+2" =
+ "move container to workspace number 2";
+ "${cfg.config.modifier}+Shift+3" =
+ "move container to workspace number 3";
+ "${cfg.config.modifier}+Shift+4" =
+ "move container to workspace number 4";
+ "${cfg.config.modifier}+Shift+5" =
+ "move container to workspace number 5";
+ "${cfg.config.modifier}+Shift+6" =
+ "move container to workspace number 6";
+ "${cfg.config.modifier}+Shift+7" =
+ "move container to workspace number 7";
+ "${cfg.config.modifier}+Shift+8" =
+ "move container to workspace number 8";
+ "${cfg.config.modifier}+Shift+9" =
+ "move container to workspace number 9";
+ "${cfg.config.modifier}+Shift+0" =
+ "move container to workspace number 10";
+
+ "${cfg.config.modifier}+Shift+c" = "reload";
+ "${cfg.config.modifier}+Shift+r" = "restart";
+ "${cfg.config.modifier}+Shift+e" =
+ "exec i3-nagbar -t warning -m 'Do you want to exit i3?' -b 'Yes' 'i3-msg exit'";
+
+ "${cfg.config.modifier}+r" = "mode resize";
+ };
+ defaultText = "Default i3 keybindings.";
+ description = ''
+ An attribute set that assigns a key press to an action using a key symbol.
+ See <link xlink:href="https://i3wm.org/docs/userguide.html#keybindings"/>.
+ </para><para>
+ Consider to use <code>lib.mkOptionDefault</code> function to extend or override
+ default keybindings instead of specifying all of them from scratch.
+ '';
+ example = literalExample ''
+ let
+ modifier = config.xsession.windowManager.i3.config.modifier;
+ in lib.mkOptionDefault {
+ "''${modifier}+Return" = "exec i3-sensible-terminal";
+ "''${modifier}+Shift+q" = "kill";
+ "''${modifier}+d" = "exec \${pkgs.dmenu}/bin/dmenu_run";
+ }
+ '';
+ };
+
+ modes = mkOption {
+ type = types.attrsOf (types.attrsOf types.str);
+ default = {
+ resize = {
+ "Left" = "resize shrink width 10 px or 10 ppt";
+ "Down" = "resize grow height 10 px or 10 ppt";
+ "Up" = "resize shrink height 10 px or 10 ppt";
+ "Right" = "resize grow width 10 px or 10 ppt";
+ "Escape" = "mode default";
+ "Return" = "mode default";
+ };
+ };
+ description = ''
+ An attribute set that defines binding modes and keybindings
+ inside them
+
+ Only basic keybinding is supported (bindsym keycomb action),
+ for more advanced setup use 'i3.extraConfig'.
+ '';
+ };
+ };
+ };
+
+ commonFunctions = import ./lib/functions.nix {
+ inherit cfg lib;
+ moduleName = "i3";
+ };
+
+ inherit (commonFunctions)
+ keybindingsStr keycodebindingsStr modeStr assignStr barStr gapsStr
+ floatingCriteriaStr windowCommandsStr colorSetStr;
+
+ startupEntryStr = { command, always, notification, workspace, ... }: ''
+ ${if always then "exec_always" else "exec"} ${
+ if (notification && workspace == null) then "" else "--no-startup-id"
+ } ${
+ if (workspace == null) then
+ command
+ else
+ "i3-msg 'workspace ${workspace}; exec ${command}'"
+ }
+ '';
+
+ configFile = pkgs.writeText "i3.conf" ((if cfg.config != null then
+ with cfg.config; ''
+ font pango:${concatStringsSep ", " fonts}
+ floating_modifier ${floating.modifier}
+ new_window ${if window.titlebar then "normal" else "pixel"} ${
+ toString window.border
+ }
+ new_float ${if floating.titlebar then "normal" else "pixel"} ${
+ toString floating.border
+ }
+ hide_edge_borders ${window.hideEdgeBorders}
+ force_focus_wrapping ${if focus.forceWrapping then "yes" else "no"}
+ focus_follows_mouse ${if focus.followMouse then "yes" else "no"}
+ focus_on_window_activation ${focus.newWindow}
+ mouse_warping ${if focus.mouseWarping then "output" else "none"}
+ workspace_layout ${workspaceLayout}
+ workspace_auto_back_and_forth ${
+ if workspaceAutoBackAndForth then "yes" else "no"
+ }
+
+ client.focused ${colorSetStr colors.focused}
+ client.focused_inactive ${colorSetStr colors.focusedInactive}
+ client.unfocused ${colorSetStr colors.unfocused}
+ client.urgent ${colorSetStr colors.urgent}
+ client.placeholder ${colorSetStr colors.placeholder}
+ client.background ${colors.background}
+
+ ${keybindingsStr { inherit keybindings; }}
+ ${keycodebindingsStr keycodebindings}
+ ${concatStringsSep "\n" (mapAttrsToList modeStr modes)}
+ ${concatStringsSep "\n" (mapAttrsToList assignStr assigns)}
+ ${concatStringsSep "\n" (map barStr bars)}
+ ${optionalString (gaps != null) gapsStr}
+ ${concatStringsSep "\n" (map floatingCriteriaStr floating.criteria)}
+ ${concatStringsSep "\n" (map windowCommandsStr window.commands)}
+ ${concatStringsSep "\n" (map startupEntryStr startup)}
+ ''
+ else
+ "") + "\n" + cfg.extraConfig);
+
+in {
+ options = {
+ xsession.windowManager.i3 = {
+ enable = mkEnableOption "i3 window manager.";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.i3;
+ defaultText = literalExample "pkgs.i3";
+ example = literalExample "pkgs.i3-gaps";
+ description = ''
+ i3 package to use.
+ If 'i3.config.gaps' settings are specified, 'pkgs.i3-gaps' will be set as a default package.
+ '';
+ };
+
+ config = mkOption {
+ type = types.nullOr configModule;
+ default = { };
+ description = "i3 configuration options.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description =
+ "Extra configuration lines to add to ~/.config/i3/config.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+ home.packages = [ cfg.package ];
+ xsession.windowManager.command = "${cfg.package}/bin/i3";
+ xdg.configFile."i3/config" = {
+ source = configFile;
+ onChange = ''
+ i3Socket=''${XDG_RUNTIME_DIR:-/run/user/$UID}/i3/ipc-socket.*
+ if [ -S $i3Socket ]; then
+ echo "Reloading i3"
+ $DRY_RUN_CMD ${cfg.package}/bin/i3-msg -s $i3Socket reload 1>/dev/null
+ fi
+ '';
+ };
+ }
+
+ (mkIf (cfg.config != null) {
+ xsession.windowManager.i3.package =
+ mkDefault (if (cfg.config.gaps != null) then pkgs.i3-gaps else pkgs.i3);
+ })
+
+ (mkIf (cfg.config != null
+ && (any (s: s.workspace != null) cfg.config.startup)) {
+ warnings = [
+ ("'xsession.windowManager.i3.config.startup.*.workspace' is deprecated, "
+ + "use 'xsession.windowManager.i3.config.assigns' instead."
+ + "See https://github.com/rycee/home-manager/issues/265.")
+ ];
+ })
+ ]);
+}
diff --git a/home-manager/modules/services/window-managers/i3-sway/lib/functions.nix b/home-manager/modules/services/window-managers/i3-sway/lib/functions.nix
new file mode 100644
index 00000000000..9391e6e92fc
--- /dev/null
+++ b/home-manager/modules/services/window-managers/i3-sway/lib/functions.nix
@@ -0,0 +1,127 @@
+{ cfg, lib, moduleName }:
+
+with lib;
+
+rec {
+ criteriaStr = criteria:
+ "[${
+ concatStringsSep " " (mapAttrsToList (k: v: ''${k}="${v}"'') criteria)
+ }]";
+
+ keybindingsStr = { keybindings, bindsymArgs ? "" }:
+ concatStringsSep "\n" (mapAttrsToList (keycomb: action:
+ optionalString (action != null) "bindsym ${
+ lib.optionalString (bindsymArgs != "") "${bindsymArgs} "
+ }${keycomb} ${action}") keybindings);
+
+ keycodebindingsStr = keycodebindings:
+ concatStringsSep "\n" (mapAttrsToList (keycomb: action:
+ optionalString (action != null) "bindcode ${keycomb} ${action}")
+ keycodebindings);
+
+ colorSetStr = c:
+ concatStringsSep " " [
+ c.border
+ c.background
+ c.text
+ c.indicator
+ c.childBorder
+ ];
+ barColorSetStr = c: concatStringsSep " " [ c.border c.background c.text ];
+
+ modeStr = name: keybindings: ''
+ mode "${name}" {
+ ${keybindingsStr { inherit keybindings; }}
+ }
+ '';
+
+ assignStr = workspace: criteria:
+ concatStringsSep "\n"
+ (map (c: "assign ${criteriaStr c} ${workspace}") criteria);
+
+ barStr = { id, fonts, mode, hiddenState, position, workspaceButtons
+ , workspaceNumbers, command, statusCommand, colors, trayOutput, extraConfig
+ , ... }:
+ let colorsNotNull = lib.filterAttrs (n: v: v != null) colors != { };
+ in ''
+ bar {
+ ${optionalString (id != null) "id ${id}"}
+ ${
+ optionalString (fonts != [ ])
+ "font pango:${concatStringsSep ", " fonts}"
+ }
+ ${optionalString (mode != null) "mode ${mode}"}
+ ${optionalString (hiddenState != null) "hidden_state ${hiddenState}"}
+ ${optionalString (position != null) "position ${position}"}
+ ${
+ optionalString (statusCommand != null)
+ "status_command ${statusCommand}"
+ }
+ ${moduleName}bar_command ${command}
+ ${
+ optionalString (workspaceButtons != null)
+ "workspace_buttons ${if workspaceButtons then "yes" else "no"}"
+ }
+ ${
+ optionalString (workspaceNumbers != null)
+ "strip_workspace_numbers ${if !workspaceNumbers then "yes" else "no"}"
+ }
+ ${optionalString (trayOutput != null) "tray_output ${trayOutput}"}
+ ${optionalString colorsNotNull "colors {"}
+ ${
+ optionalString (colors.background != null)
+ "background ${colors.background}"
+ }
+ ${
+ optionalString (colors.statusline != null)
+ "statusline ${colors.statusline}"
+ }
+ ${
+ optionalString (colors.separator != null)
+ "separator ${colors.separator}"
+ }
+ ${
+ optionalString (colors.focusedWorkspace != null)
+ "focused_workspace ${barColorSetStr colors.focusedWorkspace}"
+ }
+ ${
+ optionalString (colors.activeWorkspace != null)
+ "active_workspace ${barColorSetStr colors.activeWorkspace}"
+ }
+ ${
+ optionalString (colors.inactiveWorkspace != null)
+ "inactive_workspace ${barColorSetStr colors.inactiveWorkspace}"
+ }
+ ${
+ optionalString (colors.urgentWorkspace != null)
+ "urgent_workspace ${barColorSetStr colors.urgentWorkspace}"
+ }
+ ${
+ optionalString (colors.bindingMode != null)
+ "binding_mode ${barColorSetStr colors.bindingMode}"
+ }
+ ${optionalString colorsNotNull "}"}
+ ${extraConfig}
+ }
+ '';
+
+ gapsStr = with cfg.config.gaps; ''
+ ${optionalString (inner != null) "gaps inner ${toString inner}"}
+ ${optionalString (outer != null) "gaps outer ${toString outer}"}
+ ${optionalString (horizontal != null)
+ "gaps horizontal ${toString horizontal}"}
+ ${optionalString (vertical != null) "gaps vertical ${toString vertical}"}
+ ${optionalString (top != null) "gaps top ${toString top}"}
+ ${optionalString (bottom != null) "gaps bottom ${toString bottom}"}
+ ${optionalString (left != null) "gaps left ${toString left}"}
+ ${optionalString (right != null) "gaps right ${toString right}"}
+
+ ${optionalString smartGaps "smart_gaps on"}
+ ${optionalString (smartBorders != "off") "smart_borders ${smartBorders}"}
+ '';
+
+ floatingCriteriaStr = criteria:
+ "for_window ${criteriaStr criteria} floating enable";
+ windowCommandsStr = { command, criteria, ... }:
+ "for_window ${criteriaStr criteria} ${command}";
+}
diff --git a/home-manager/modules/services/window-managers/i3-sway/lib/options.nix b/home-manager/modules/services/window-managers/i3-sway/lib/options.nix
new file mode 100644
index 00000000000..edfdcd4feae
--- /dev/null
+++ b/home-manager/modules/services/window-managers/i3-sway/lib/options.nix
@@ -0,0 +1,763 @@
+{ config, lib, moduleName, cfg, pkgs, capitalModuleName ? moduleName
+, isGaps ? true }:
+
+with lib;
+
+let
+ fonts = mkOption {
+ type = types.listOf types.str;
+ default = [ "monospace 8" ];
+ description = ''
+ Font list used for window titles. Only FreeType fonts are supported.
+ The order here is important (e.g. icons font should go before the one used for text).
+ '';
+ example = [ "FontAwesome 10" "Terminus 10" ];
+ };
+
+ startupModule = types.submodule {
+ options = {
+ command = mkOption {
+ type = types.str;
+ description = "Command that will be executed on startup.";
+ };
+
+ always = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to run command on each ${moduleName} restart.";
+ };
+ } // optionalAttrs (moduleName == "i3") {
+ notification = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable startup-notification support for the command.
+ See <option>--no-startup-id</option> option description in the i3 user guide.
+ '';
+ };
+
+ workspace = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Launch application on a particular workspace. DEPRECATED:
+ Use <varname><link linkend="opt-xsession.windowManager.i3.config.assigns">xsession.windowManager.i3.config.assigns</link></varname>
+ instead. See <link xlink:href="https://github.com/rycee/home-manager/issues/265"/>.
+ '';
+ };
+ };
+
+ };
+
+ barModule = types.submodule {
+ options = let
+ versionAtLeast2009 = versionAtLeast config.home.stateVersion "20.09";
+ mkNullableOption = { type, default, ... }@args:
+ mkOption (args // optionalAttrs versionAtLeast2009 {
+ type = types.nullOr type;
+ default = null;
+ example = default;
+ } // {
+ defaultText = literalExample ''
+ ${
+ if isString default then default else "See code"
+ } for state version < 20.09,
+ null for state version β‰₯ 20.09
+ '';
+ });
+ in {
+ fonts = fonts // optionalAttrs versionAtLeast2009 { default = [ ]; };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Extra configuration lines for this bar.";
+ };
+
+ id = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Specifies the bar ID for the configured bar instance.
+ If this option is missing, the ID is set to bar-x, where x corresponds
+ to the position of the embedding bar block in the config file.
+ '';
+ };
+
+ mode = mkNullableOption {
+ type = types.enum [ "dock" "hide" "invisible" ];
+ default = "dock";
+ description = "Bar visibility mode.";
+ };
+
+ hiddenState = mkNullableOption {
+ type = types.enum [ "hide" "show" ];
+ default = "hide";
+ description = "The default bar mode when 'bar.mode' == 'hide'.";
+ };
+
+ position = mkNullableOption {
+ type = types.enum [ "top" "bottom" ];
+ default = "bottom";
+ description = "The edge of the screen ${moduleName}bar should show up.";
+ };
+
+ workspaceButtons = mkNullableOption {
+ type = types.bool;
+ default = true;
+ description = "Whether workspace buttons should be shown or not.";
+ };
+
+ workspaceNumbers = mkNullableOption {
+ type = types.bool;
+ default = true;
+ description =
+ "Whether workspace numbers should be displayed within the workspace buttons.";
+ };
+
+ command = mkOption {
+ type = types.str;
+ default = "${cfg.package}/bin/${moduleName}bar";
+ defaultText = "i3bar";
+ description = "Command that will be used to start a bar.";
+ example = if moduleName == "i3" then
+ "\${pkgs.i3-gaps}/bin/i3bar -t"
+ else
+ "\${pkgs.waybar}/bin/waybar";
+ };
+
+ statusCommand = mkOption {
+ type = types.nullOr types.str;
+ default =
+ if versionAtLeast2009 then null else "${pkgs.i3status}/bin/i3status";
+ example = "i3status";
+ description = "Command that will be used to get status lines.";
+ };
+
+ colors = mkOption {
+ type = types.submodule {
+ options = {
+ background = mkNullableOption {
+ type = types.str;
+ default = "#000000";
+ description = "Background color of the bar.";
+ };
+
+ statusline = mkNullableOption {
+ type = types.str;
+ default = "#ffffff";
+ description = "Text color to be used for the statusline.";
+ };
+
+ separator = mkNullableOption {
+ type = types.str;
+ default = "#666666";
+ description = "Text color to be used for the separator.";
+ };
+
+ focusedWorkspace = mkNullableOption {
+ type = barColorSetModule;
+ default = {
+ border = "#4c7899";
+ background = "#285577";
+ text = "#ffffff";
+ };
+ description = ''
+ Border, background and text color for a workspace button when the workspace has focus.
+ '';
+ };
+
+ activeWorkspace = mkNullableOption {
+ type = barColorSetModule;
+ default = {
+ border = "#333333";
+ background = "#5f676a";
+ text = "#ffffff";
+ };
+ description = ''
+ Border, background and text color for a workspace button when the workspace is active.
+ '';
+ };
+
+ inactiveWorkspace = mkNullableOption {
+ type = barColorSetModule;
+ default = {
+ border = "#333333";
+ background = "#222222";
+ text = "#888888";
+ };
+ description = ''
+ Border, background and text color for a workspace button when the workspace does not
+ have focus and is not active.
+ '';
+ };
+
+ urgentWorkspace = mkNullableOption {
+ type = barColorSetModule;
+ default = {
+ border = "#2f343a";
+ background = "#900000";
+ text = "#ffffff";
+ };
+ description = ''
+ Border, background and text color for a workspace button when the workspace contains
+ a window with the urgency hint set.
+ '';
+ };
+
+ bindingMode = mkNullableOption {
+ type = barColorSetModule;
+ default = {
+ border = "#2f343a";
+ background = "#900000";
+ text = "#ffffff";
+ };
+ description =
+ "Border, background and text color for the binding mode indicator";
+ };
+ };
+ };
+ default = { };
+ description = ''
+ Bar color settings. All color classes can be specified using submodules
+ with 'border', 'background', 'text', fields and RGB color hex-codes as values.
+ See default values for the reference.
+ Note that 'background', 'status', and 'separator' parameters take a single RGB value.
+
+ See <link xlink:href="https://i3wm.org/docs/userguide.html#_colors"/>.
+ '';
+ };
+
+ trayOutput = mkNullableOption {
+ type = types.str;
+ default = "primary";
+ description = "Where to output tray.";
+ };
+ };
+ };
+
+ barColorSetModule = types.submodule {
+ options = {
+ border = mkOption {
+ type = types.str;
+ visible = false;
+ };
+
+ background = mkOption {
+ type = types.str;
+ visible = false;
+ };
+
+ text = mkOption {
+ type = types.str;
+ visible = false;
+ };
+ };
+ };
+
+ colorSetModule = types.submodule {
+ options = {
+ border = mkOption {
+ type = types.str;
+ visible = false;
+ };
+
+ childBorder = mkOption {
+ type = types.str;
+ visible = false;
+ };
+
+ background = mkOption {
+ type = types.str;
+ visible = false;
+ };
+
+ text = mkOption {
+ type = types.str;
+ visible = false;
+ };
+
+ indicator = mkOption {
+ type = types.str;
+ visible = false;
+ };
+ };
+ };
+
+ windowCommandModule = types.submodule {
+ options = {
+ command = mkOption {
+ type = types.str;
+ description = "${capitalModuleName}wm command to execute.";
+ example = "border pixel 1";
+ };
+
+ criteria = mkOption {
+ type = criteriaModule;
+ description =
+ "Criteria of the windows on which command should be executed.";
+ example = { title = "x200: ~/work"; };
+ };
+ };
+ };
+
+ criteriaModule = types.attrsOf types.str;
+in {
+ inherit fonts;
+
+ window = mkOption {
+ type = types.submodule {
+ options = {
+ titlebar = mkOption {
+ type = types.bool;
+ default = !isGaps;
+ defaultText = if moduleName == "i3" then
+ "xsession.windowManager.i3.package != nixpkgs.i3-gaps (titlebar should be disabled for i3-gaps)"
+ else
+ "false";
+ description = "Whether to show window titlebars.";
+ };
+
+ border = mkOption {
+ type = types.int;
+ default = 2;
+ description = "Window border width.";
+ };
+
+ hideEdgeBorders = mkOption {
+ type = types.enum [ "none" "vertical" "horizontal" "both" "smart" ];
+ default = "none";
+ description = "Hide window borders adjacent to the screen edges.";
+ };
+
+ commands = mkOption {
+ type = types.listOf windowCommandModule;
+ default = [ ];
+ description = ''
+ List of commands that should be executed on specific windows.
+ See <option>for_window</option> ${moduleName}wm option documentation.
+ '';
+ example = [{
+ command = "border pixel 1";
+ criteria = { class = "XTerm"; };
+ }];
+ };
+ };
+ };
+ default = { };
+ description = "Window titlebar and border settings.";
+ };
+
+ floating = mkOption {
+ type = types.submodule {
+ options = {
+ titlebar = mkOption {
+ type = types.bool;
+ default = !isGaps;
+ defaultText = if moduleName == "i3" then
+ "xsession.windowManager.i3.package != nixpkgs.i3-gaps (titlebar should be disabled for i3-gaps)"
+ else
+ "false";
+ description = "Whether to show floating window titlebars.";
+ };
+
+ border = mkOption {
+ type = types.int;
+ default = 2;
+ description = "Floating windows border width.";
+ };
+
+ modifier = mkOption {
+ type =
+ types.enum [ "Shift" "Control" "Mod1" "Mod2" "Mod3" "Mod4" "Mod5" ];
+ default = cfg.config.modifier;
+ defaultText = "${moduleName}.config.modifier";
+ description =
+ "Modifier key that can be used to drag floating windows.";
+ example = "Mod4";
+ };
+
+ criteria = mkOption {
+ type = types.listOf criteriaModule;
+ default = [ ];
+ description =
+ "List of criteria for windows that should be opened in a floating mode.";
+ example = [
+ { "title" = "Steam - Update News"; }
+ { "class" = "Pavucontrol"; }
+ ];
+ };
+ };
+ };
+ default = { };
+ description = "Floating window settings.";
+ };
+
+ focus = mkOption {
+ type = types.submodule {
+ options = {
+ newWindow = mkOption {
+ type = types.enum [ "smart" "urgent" "focus" "none" ];
+ default = "smart";
+ description = ''
+ This option modifies focus behavior on new window activation.
+
+ See <link xlink:href="https://i3wm.org/docs/userguide.html#focus_on_window_activation"/>
+ '';
+ example = "none";
+ };
+
+ followMouse = mkOption {
+ type = if moduleName == "sway" then
+ types.either (types.enum [ "yes" "no" "always" ]) types.bool
+ else
+ types.bool;
+ default = if moduleName == "sway" then "yes" else true;
+ description = "Whether focus should follow the mouse.";
+ apply = val:
+ if (moduleName == "sway" && isBool val) then
+ (if val then "yes" else "no")
+ else
+ val;
+ };
+
+ forceWrapping = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to force focus wrapping in tabbed or stacked container.
+
+ See <link xlink:href="https://i3wm.org/docs/userguide.html#_focus_wrapping"/>
+ '';
+ };
+
+ mouseWarping = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether mouse cursor should be warped to the center of the window when switching focus
+ to a window on a different output.
+ '';
+ };
+ };
+ };
+ default = { };
+ description = "Focus related settings.";
+ };
+
+ assigns = mkOption {
+ type = types.attrsOf (types.listOf criteriaModule);
+ default = { };
+ description = ''
+ An attribute set that assigns applications to workspaces based
+ on criteria.
+ '';
+ example = literalExample ''
+ {
+ "1: web" = [{ class = "^Firefox$"; }];
+ "0: extra" = [{ class = "^Firefox$"; window_role = "About"; }];
+ }
+ '';
+ };
+
+ modifier = mkOption {
+ type = types.enum [ "Shift" "Control" "Mod1" "Mod2" "Mod3" "Mod4" "Mod5" ];
+ default = "Mod1";
+ description = "Modifier key that is used for all default keybindings.";
+ example = "Mod4";
+ };
+
+ workspaceLayout = mkOption {
+ type = types.enum [ "default" "stacked" "tabbed" ];
+ default = "default";
+ example = "tabbed";
+ description = ''
+ The mode in which new containers on workspace level will
+ start.
+ '';
+ };
+
+ workspaceAutoBackAndForth = mkOption {
+ type = types.bool;
+ default = false;
+ example = true;
+ description = ''
+ Assume you are on workspace "1: www" and switch to "2: IM" using
+ mod+2 because somebody sent you a message. You don’t need to remember
+ where you came from now, you can just press $mod+2 again to switch
+ back to "1: www".
+ '';
+ };
+
+ keycodebindings = mkOption {
+ type = types.attrsOf (types.nullOr types.str);
+ default = { };
+ description = ''
+ An attribute set that assigns keypress to an action using key code.
+ See <link xlink:href="https://i3wm.org/docs/userguide.html#keybindings"/>.
+ '';
+ example = { "214" = "exec /bin/script.sh"; };
+ };
+
+ colors = mkOption {
+ type = types.submodule {
+ options = {
+ background = mkOption {
+ type = types.str;
+ default = "#ffffff";
+ description = ''
+ Background color of the window. Only applications which do not cover
+ the whole area expose the color.
+ '';
+ };
+
+ focused = mkOption {
+ type = colorSetModule;
+ default = {
+ border = "#4c7899";
+ background = "#285577";
+ text = "#ffffff";
+ indicator = "#2e9ef4";
+ childBorder = "#285577";
+ };
+ description = "A window which currently has the focus.";
+ };
+
+ focusedInactive = mkOption {
+ type = colorSetModule;
+ default = {
+ border = "#333333";
+ background = "#5f676a";
+ text = "#ffffff";
+ indicator = "#484e50";
+ childBorder = "#5f676a";
+ };
+ description = ''
+ A window which is the focused one of its container,
+ but it does not have the focus at the moment.
+ '';
+ };
+
+ unfocused = mkOption {
+ type = colorSetModule;
+ default = {
+ border = "#333333";
+ background = "#222222";
+ text = "#888888";
+ indicator = "#292d2e";
+ childBorder = "#222222";
+ };
+ description = "A window which is not focused.";
+ };
+
+ urgent = mkOption {
+ type = colorSetModule;
+ default = {
+ border = "#2f343a";
+ background = "#900000";
+ text = "#ffffff";
+ indicator = "#900000";
+ childBorder = "#900000";
+ };
+ description = "A window which has its urgency hint activated.";
+ };
+
+ placeholder = mkOption {
+ type = colorSetModule;
+ default = {
+ border = "#000000";
+ background = "#0c0c0c";
+ text = "#ffffff";
+ indicator = "#000000";
+ childBorder = "#0c0c0c";
+ };
+ description = ''
+ Background and text color are used to draw placeholder window
+ contents (when restoring layouts). Border and indicator are ignored.
+ '';
+ };
+ };
+ };
+ default = { };
+ description = ''
+ Color settings. All color classes can be specified using submodules
+ with 'border', 'background', 'text', 'indicator' and 'childBorder' fields
+ and RGB color hex-codes as values. See default values for the reference.
+ Note that '${moduleName}.config.colors.background' parameter takes a single RGB value.
+
+ See <link xlink:href="https://i3wm.org/docs/userguide.html#_changing_colors"/>.
+ '';
+ };
+
+ bars = mkOption {
+ type = types.listOf barModule;
+ default = if versionAtLeast config.home.stateVersion "20.09" then [{
+ mode = "dock";
+ hiddenState = "hide";
+ position = "bottom";
+ workspaceButtons = true;
+ workspaceNumbers = true;
+ statusCommand = "${pkgs.i3status}/bin/i3status";
+ fonts = [ "monospace 8" ];
+ trayOutput = "primary";
+ colors = {
+ background = "#000000";
+ statusline = "#ffffff";
+ separator = "#666666";
+ focusedWorkspace = {
+ border = "#4c7899";
+ background = "#285577";
+ text = "#ffffff";
+ };
+ activeWorkspace = {
+ border = "#333333";
+ background = "#5f676a";
+ text = "#ffffff";
+ };
+ inactiveWorkspace = {
+ border = "#333333";
+ background = "#222222";
+ text = "#888888";
+ };
+ urgentWorkspace = {
+ border = "#2f343a";
+ background = "#900000";
+ text = "#ffffff";
+ };
+ bindingMode = {
+ border = "#2f343a";
+ background = "#900000";
+ text = "#ffffff";
+ };
+ };
+ }] else
+ [ { } ];
+ description = ''
+ ${capitalModuleName} bars settings blocks. Set to empty list to remove bars completely.
+ '';
+ };
+
+ startup = mkOption {
+ type = types.listOf startupModule;
+ default = [ ];
+ description = ''
+ Commands that should be executed at startup.
+
+ See <link xlink:href="https://i3wm.org/docs/userguide.html#_automatically_starting_applications_on_i3_startup"/>.
+ '';
+ example = literalExample ''
+ [
+ { command = "systemctl --user restart polybar"; always = true; notification = false; }
+ { command = "dropbox start"; notification = false; }
+ { command = "firefox"; workspace = "1: web"; }
+ ];
+ '';
+ };
+
+ gaps = mkOption {
+ type = types.nullOr (types.submodule {
+ options = {
+ inner = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "Inner gaps value.";
+ example = 12;
+ };
+
+ outer = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "Outer gaps value.";
+ example = 5;
+ };
+
+ horizontal = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "Horizontal gaps value.";
+ example = 5;
+ };
+
+ vertical = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "Vertical gaps value.";
+ example = 5;
+ };
+
+ top = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "Top gaps value.";
+ example = 5;
+ };
+
+ left = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "Left gaps value.";
+ example = 5;
+ };
+
+ bottom = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "Bottom gaps value.";
+ example = 5;
+ };
+
+ right = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "Right gaps value.";
+ example = 5;
+ };
+
+ smartGaps = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ This option controls whether to disable all gaps (outer and inner)
+ on workspace with a single container.
+ '';
+ example = true;
+ };
+
+ smartBorders = mkOption {
+ type = types.enum [ "on" "off" "no_gaps" ];
+ default = "off";
+ description = ''
+ This option controls whether to disable container borders on
+ workspace with a single container.
+ '';
+ };
+ };
+ });
+ default = null;
+ description = if moduleName == "sway" then ''
+ Gaps related settings.
+ '' else ''
+ i3Gaps related settings. The i3-gaps package must be used for these features to work.
+ '';
+ };
+
+ terminal = mkOption {
+ type = types.str;
+ default = if moduleName == "i3" then
+ "i3-sensible-terminal"
+ else
+ "${pkgs.rxvt-unicode-unwrapped}/bin/urxvt";
+ description = "Default terminal to run.";
+ example = "alacritty";
+ };
+
+ menu = mkOption {
+ type = types.str;
+ default = if moduleName == "sway" then
+ "${pkgs.dmenu}/bin/dmenu_path | ${pkgs.dmenu}/bin/dmenu | ${pkgs.findutils}/bin/xargs swaymsg exec --"
+ else
+ "${pkgs.dmenu}/bin/dmenu_run";
+ description = "Default launcher to use.";
+ example = "bemenu-run";
+ };
+}
diff --git a/home-manager/modules/services/window-managers/i3-sway/sway.nix b/home-manager/modules/services/window-managers/i3-sway/sway.nix
new file mode 100644
index 00000000000..8f0ee608104
--- /dev/null
+++ b/home-manager/modules/services/window-managers/i3-sway/sway.nix
@@ -0,0 +1,416 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.wayland.windowManager.sway;
+
+ commonOptions = import ./lib/options.nix {
+ inherit config lib cfg pkgs;
+ moduleName = "sway";
+ capitalModuleName = "Sway";
+ };
+
+ configModule = types.submodule {
+ options = {
+ inherit (commonOptions)
+ fonts window floating focus assigns workspaceLayout
+ workspaceAutoBackAndForth modifier keycodebindings colors bars startup
+ gaps menu terminal;
+
+ left = mkOption {
+ type = types.str;
+ default = "h";
+ description = "Home row direction key for moving left.";
+ };
+
+ down = mkOption {
+ type = types.str;
+ default = "j";
+ description = "Home row direction key for moving down.";
+ };
+
+ up = mkOption {
+ type = types.str;
+ default = "k";
+ description = "Home row direction key for moving up.";
+ };
+
+ right = mkOption {
+ type = types.str;
+ default = "l";
+ description = "Home row direction key for moving right.";
+ };
+
+ keybindings = mkOption {
+ type = types.attrsOf (types.nullOr types.str);
+ default = mapAttrs (n: mkOptionDefault) {
+ "${cfg.config.modifier}+Return" = "exec ${cfg.config.terminal}";
+ "${cfg.config.modifier}+Shift+q" = "kill";
+ "${cfg.config.modifier}+d" = "exec ${cfg.config.menu}";
+
+ "${cfg.config.modifier}+${cfg.config.left}" = "focus left";
+ "${cfg.config.modifier}+${cfg.config.down}" = "focus down";
+ "${cfg.config.modifier}+${cfg.config.up}" = "focus up";
+ "${cfg.config.modifier}+${cfg.config.right}" = "focus right";
+
+ "${cfg.config.modifier}+Left" = "focus left";
+ "${cfg.config.modifier}+Down" = "focus down";
+ "${cfg.config.modifier}+Up" = "focus up";
+ "${cfg.config.modifier}+Right" = "focus right";
+
+ "${cfg.config.modifier}+Shift+${cfg.config.left}" = "move left";
+ "${cfg.config.modifier}+Shift+${cfg.config.down}" = "move down";
+ "${cfg.config.modifier}+Shift+${cfg.config.up}" = "move up";
+ "${cfg.config.modifier}+Shift+${cfg.config.right}" = "move right";
+
+ "${cfg.config.modifier}+Shift+Left" = "move left";
+ "${cfg.config.modifier}+Shift+Down" = "move down";
+ "${cfg.config.modifier}+Shift+Up" = "move up";
+ "${cfg.config.modifier}+Shift+Right" = "move right";
+
+ "${cfg.config.modifier}+b" = "splith";
+ "${cfg.config.modifier}+v" = "splitv";
+ "${cfg.config.modifier}+f" = "fullscreen toggle";
+ "${cfg.config.modifier}+a" = "focus parent";
+
+ "${cfg.config.modifier}+s" = "layout stacking";
+ "${cfg.config.modifier}+w" = "layout tabbed";
+ "${cfg.config.modifier}+e" = "layout toggle split";
+
+ "${cfg.config.modifier}+Shift+space" = "floating toggle";
+ "${cfg.config.modifier}+space" = "focus mode_toggle";
+
+ "${cfg.config.modifier}+1" = "workspace number 1";
+ "${cfg.config.modifier}+2" = "workspace number 2";
+ "${cfg.config.modifier}+3" = "workspace number 3";
+ "${cfg.config.modifier}+4" = "workspace number 4";
+ "${cfg.config.modifier}+5" = "workspace number 5";
+ "${cfg.config.modifier}+6" = "workspace number 6";
+ "${cfg.config.modifier}+7" = "workspace number 7";
+ "${cfg.config.modifier}+8" = "workspace number 8";
+ "${cfg.config.modifier}+9" = "workspace number 9";
+
+ "${cfg.config.modifier}+Shift+1" =
+ "move container to workspace number 1";
+ "${cfg.config.modifier}+Shift+2" =
+ "move container to workspace number 2";
+ "${cfg.config.modifier}+Shift+3" =
+ "move container to workspace number 3";
+ "${cfg.config.modifier}+Shift+4" =
+ "move container to workspace number 4";
+ "${cfg.config.modifier}+Shift+5" =
+ "move container to workspace number 5";
+ "${cfg.config.modifier}+Shift+6" =
+ "move container to workspace number 6";
+ "${cfg.config.modifier}+Shift+7" =
+ "move container to workspace number 7";
+ "${cfg.config.modifier}+Shift+8" =
+ "move container to workspace number 8";
+ "${cfg.config.modifier}+Shift+9" =
+ "move container to workspace number 9";
+
+ "${cfg.config.modifier}+Shift+minus" = "move scratchpad";
+ "${cfg.config.modifier}+minus" = "scratchpad show";
+
+ "${cfg.config.modifier}+Shift+c" = "reload";
+ "${cfg.config.modifier}+Shift+e" =
+ "exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -b 'Yes, exit sway' 'swaymsg exit'";
+
+ "${cfg.config.modifier}+r" = "mode resize";
+ };
+ defaultText = "Default sway keybindings.";
+ description = ''
+ An attribute set that assigns a key press to an action using a key symbol.
+ See <link xlink:href="https://i3wm.org/docs/userguide.html#keybindings"/>.
+ </para><para>
+ Consider to use <code>lib.mkOptionDefault</code> function to extend or override
+ default keybindings instead of specifying all of them from scratch.
+ '';
+ example = literalExample ''
+ let
+ modifier = config.wayland.windowManager.sway.config.modifier;
+ in lib.mkOptionDefault {
+ "''${modifier}+Return" = "exec ${cfg.config.terminal}";
+ "''${modifier}+Shift+q" = "kill";
+ "''${modifier}+d" = "exec ${cfg.config.menu}";
+ }
+ '';
+ };
+
+ bindkeysToCode = mkOption {
+ type = types.bool;
+ default = false;
+ example = true;
+ description = ''
+ Whether to make use of <option>--to-code</option> in keybindings.
+ '';
+ };
+
+ input = mkOption {
+ type = types.attrsOf (types.attrsOf types.str);
+ default = { };
+ example = { "*" = { xkb_variant = "dvorak"; }; };
+ description = ''
+ An attribute set that defines input modules. See man sway_input for options.
+ '';
+ };
+
+ output = mkOption {
+ type = types.attrsOf (types.attrsOf types.str);
+ default = { };
+ example = { "HDMI-A-2" = { bg = "~/path/to/background.png fill"; }; };
+ description = ''
+ An attribute set that defines output modules. See man sway_output for options.
+ '';
+ };
+
+ modes = mkOption {
+ type = types.attrsOf (types.attrsOf types.str);
+ default = {
+ resize = {
+ "${cfg.config.left}" = "resize shrink width 10 px";
+ "${cfg.config.down}" = "resize grow height 10 px";
+ "${cfg.config.up}" = "resize shrink height 10 px";
+ "${cfg.config.right}" = "resize grow width 10 px";
+ "Left" = "resize shrink width 10 px";
+ "Down" = "resize grow height 10 px";
+ "Up" = "resize shrink height 10 px";
+ "Right" = "resize grow width 10 px";
+ "Escape" = "mode default";
+ "Return" = "mode default";
+ };
+ };
+ description = ''
+ An attribute set that defines binding modes and keybindings
+ inside them
+
+ Only basic keybinding is supported (bindsym keycomb action),
+ for more advanced setup use 'sway.extraConfig'.
+ '';
+ };
+ };
+ };
+
+ wrapperOptions = types.submodule {
+ options = let
+ mkWrapperFeature = default: description:
+ mkOption {
+ type = types.bool;
+ inherit default;
+ example = !default;
+ description = "Whether to make use of the ${description}";
+ };
+ in {
+ base = mkWrapperFeature true ''
+ base wrapper to execute extra session commands and prepend a
+ dbus-run-session to the sway command.
+ '';
+ gtk = mkWrapperFeature false ''
+ wrapGAppsHook wrapper to execute sway with required environment
+ variables for GTK applications.
+ '';
+ };
+ };
+
+ commonFunctions = import ./lib/functions.nix {
+ inherit cfg lib;
+ moduleName = "sway";
+ };
+
+ inherit (commonFunctions)
+ keybindingsStr keycodebindingsStr modeStr assignStr barStr gapsStr
+ floatingCriteriaStr windowCommandsStr colorSetStr;
+
+ startupEntryStr = { command, always, ... }: ''
+ ${if always then "exec_always" else "exec"} ${command}
+ '';
+
+ inputStr = name: attrs: ''
+ input "${name}" {
+ ${concatStringsSep "\n"
+ (mapAttrsToList (name: value: "${name} ${value}") attrs)}
+ }
+ '';
+
+ outputStr = name: attrs: ''
+ output "${name}" {
+ ${concatStringsSep "\n"
+ (mapAttrsToList (name: value: "${name} ${value}") attrs)}
+ }
+ '';
+
+ configFile = pkgs.writeText "sway.conf" ((if cfg.config != null then
+ with cfg.config; ''
+ font pango:${concatStringsSep ", " fonts}
+ floating_modifier ${floating.modifier}
+ default_border ${if window.titlebar then "normal" else "pixel"} ${
+ toString window.border
+ }
+ default_floating_border ${
+ if floating.titlebar then "normal" else "pixel"
+ } ${toString floating.border}
+ hide_edge_borders ${window.hideEdgeBorders}
+ focus_wrapping ${if focus.forceWrapping then "yes" else "no"}
+ focus_follows_mouse ${focus.followMouse}
+ focus_on_window_activation ${focus.newWindow}
+ mouse_warping ${if focus.mouseWarping then "output" else "none"}
+ workspace_layout ${workspaceLayout}
+ workspace_auto_back_and_forth ${
+ if workspaceAutoBackAndForth then "yes" else "no"
+ }
+
+ client.focused ${colorSetStr colors.focused}
+ client.focused_inactive ${colorSetStr colors.focusedInactive}
+ client.unfocused ${colorSetStr colors.unfocused}
+ client.urgent ${colorSetStr colors.urgent}
+ client.placeholder ${colorSetStr colors.placeholder}
+ client.background ${colors.background}
+
+ ${keybindingsStr {
+ inherit keybindings;
+ bindsymArgs =
+ lib.optionalString (cfg.config.bindkeysToCode) "--to-code";
+ }}
+ ${keycodebindingsStr keycodebindings}
+ ${concatStringsSep "\n" (mapAttrsToList inputStr input)}
+ ${concatStringsSep "\n" (mapAttrsToList outputStr output)}
+ ${concatStringsSep "\n" (mapAttrsToList modeStr modes)}
+ ${concatStringsSep "\n" (mapAttrsToList assignStr assigns)}
+ ${concatStringsSep "\n" (map barStr bars)}
+ ${optionalString (gaps != null) gapsStr}
+ ${concatStringsSep "\n" (map floatingCriteriaStr floating.criteria)}
+ ${concatStringsSep "\n" (map windowCommandsStr window.commands)}
+ ${concatStringsSep "\n" (map startupEntryStr startup)}
+ ''
+ else
+ "") + "\n" + (if cfg.systemdIntegration then ''
+ exec "systemctl --user import-environment; systemctl --user start sway-session.target"
+ '' else
+ "") + cfg.extraConfig);
+
+ defaultSwayPackage = pkgs.sway.override {
+ extraSessionCommands = cfg.extraSessionCommands;
+ extraOptions = cfg.extraOptions;
+ withBaseWrapper = cfg.wrapperFeatures.base;
+ withGtkWrapper = cfg.wrapperFeatures.gtk;
+ };
+
+in {
+ meta.maintainers = [ maintainers.alexarice ];
+
+ options.wayland.windowManager.sway = {
+ enable = mkEnableOption "sway wayland compositor";
+
+ package = mkOption {
+ type = with types; nullOr package;
+ default = defaultSwayPackage;
+ defaultText = literalExample "${pkgs.sway}";
+ description = ''
+ Sway package to use. Will override the options
+ 'wrapperFeatures', 'extraSessionCommands', and 'extraOptions'.
+ Set to <code>null</code> to not add any Sway package to your
+ path. This should be done if you want to use the NixOS Sway
+ module to install Sway.
+ '';
+ };
+
+ systemdIntegration = mkOption {
+ type = types.bool;
+ default = pkgs.stdenv.isLinux;
+ example = false;
+ description = ''
+ Whether to enable <filename>sway-session.target</filename> on
+ sway startup. This links to
+ <filename>graphical-session.target</filename>.
+ '';
+ };
+
+ xwayland = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Enable xwayland, which is needed for the default configuration of sway.
+ '';
+ };
+
+ wrapperFeatures = mkOption {
+ type = wrapperOptions;
+ default = { };
+ example = { gtk = true; };
+ description = ''
+ Attribute set of features to enable in the wrapper.
+ '';
+ };
+
+ extraSessionCommands = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ export SDL_VIDEODRIVER=wayland
+ # needs qt5.qtwayland in systemPackages
+ export QT_QPA_PLATFORM=wayland
+ export QT_WAYLAND_DISABLE_WINDOWDECORATION="1"
+ # Fix for some Java AWT applications (e.g. Android Studio),
+ # use this if they aren't displayed properly:
+ export _JAVA_AWT_WM_NONREPARENTING=1
+ '';
+ description = ''
+ Shell commands executed just before Sway is started.
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [
+ "--verbose"
+ "--debug"
+ "--unsupported-gpu"
+ "--my-next-gpu-wont-be-nvidia"
+ ];
+ description = ''
+ Command line arguments passed to launch Sway. Please DO NOT report
+ issues if you use an unsupported GPU (proprietary drivers).
+ '';
+ };
+
+ config = mkOption {
+ type = types.nullOr configModule;
+ default = { };
+ description = "Sway configuration options.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description =
+ "Extra configuration lines to add to ~/.config/sway/config.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = optional (cfg.package != null) cfg.package
+ ++ optional cfg.xwayland pkgs.xwayland;
+ xdg.configFile."sway/config" = {
+ source = configFile;
+ onChange = ''
+ swaySocket=''${XDG_RUNTIME_DIR:-/run/user/$UID}/sway-ipc.$UID.$(${pkgs.procps}/bin/pgrep -x sway).sock
+ if [ -S $swaySocket ]; then
+ echo "Reloading sway"
+ $DRY_RUN_CMD ${pkgs.sway}/bin/swaymsg -s $swaySocket reload
+ fi
+ '';
+ };
+ systemd.user.targets.sway-session = mkIf cfg.systemdIntegration {
+ Unit = {
+ Description = "sway compositor session";
+ Documentation = [ "man:systemd.special(7)" ];
+ BindsTo = [ "graphical-session.target" ];
+ Wants = [ "graphical-session-pre.target" ];
+ After = [ "graphical-session-pre.target" ];
+ };
+ };
+ };
+}
diff --git a/home-manager/modules/services/window-managers/i3.nix b/home-manager/modules/services/window-managers/i3.nix
deleted file mode 100644
index 7a4ec90b1cd..00000000000
--- a/home-manager/modules/services/window-managers/i3.nix
+++ /dev/null
@@ -1,856 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
- cfg = config.xsession.windowManager.i3;
-
- commonOptions = {
- fonts = mkOption {
- type = types.listOf types.str;
- default = ["monospace 8"];
- description = ''
- Font list used for window titles. Only FreeType fonts are supported.
- The order here is improtant (e.g. icons font should go before the one used for text).
- '';
- example = [ "FontAwesome 10" "Terminus 10" ];
- };
- };
-
- startupModule = types.submodule {
- options = {
- command = mkOption {
- type = types.str;
- description = "Command that will be executed on startup.";
- };
-
- always = mkOption {
- type = types.bool;
- default = false;
- description = "Whether to run command on each i3 restart.";
- };
-
- notification = mkOption {
- type = types.bool;
- default = true;
- description = ''
- Whether to enable startup-notification support for the command.
- See <option>--no-startup-id</option> option description in the i3 user guide.
- '';
- };
-
- workspace = mkOption {
- type = types.nullOr types.str;
- default = null;
- description = ''
- Launch application on a particular workspace. DEPRECATED:
- Use <varname><link linkend="opt-xsession.windowManager.i3.config.assigns">xsession.windowManager.i3.config.assigns</link></varname>
- instead. See <link xlink:href="https://github.com/rycee/home-manager/issues/265"/>.
- '';
- };
- };
- };
-
- barColorSetModule = types.submodule {
- options = {
- border = mkOption {
- type = types.str;
- visible = false;
- };
-
- background = mkOption {
- type = types.str;
- visible = false;
- };
-
- text = mkOption {
- type = types.str;
- visible = false;
- };
- };
- };
-
- colorSetModule = types.submodule {
- options = {
- border = mkOption {
- type = types.str;
- visible = false;
- };
-
- childBorder = mkOption {
- type = types.str;
- visible = false;
- };
-
- background = mkOption {
- type = types.str;
- visible = false;
- };
-
- text = mkOption {
- type = types.str;
- visible = false;
- };
-
- indicator = mkOption {
- type = types.str;
- visible = false;
- };
- };
- };
-
- barModule = types.submodule {
- options = {
- inherit (commonOptions) fonts;
-
- extraConfig = mkOption {
- type = types.lines;
- default = "";
- description = "Extra configuration lines for this bar.";
- };
-
- id = mkOption {
- type = types.nullOr types.str;
- default = null;
- description = ''
- Specifies the bar ID for the configured bar instance.
- If this option is missing, the ID is set to bar-x, where x corresponds
- to the position of the embedding bar block in the config file.
- '';
- };
-
- mode = mkOption {
- type = types.enum [ "dock" "hide" "invisible" ];
- default = "dock";
- description = "Bar visibility mode.";
- };
-
- hiddenState = mkOption {
- type = types.enum [ "hide" "show" ];
- default = "hide";
- description = "The default bar mode when 'bar.mode' == 'hide'.";
- };
-
- position = mkOption {
- type = types.enum [ "top" "bottom" ];
- default = "bottom";
- description = "The edge of the screen i3bar should show up.";
- };
-
- workspaceButtons = mkOption {
- type = types.bool;
- default = true;
- description = "Whether workspace buttons should be shown or not.";
- };
-
- workspaceNumbers = mkOption {
- type = types.bool;
- default = true;
- description = "Whether workspace numbers should be displayed within the workspace buttons.";
- };
-
- command = mkOption {
- type = types.str;
- default = "${cfg.package}/bin/i3bar";
- defaultText = "i3bar";
- description = "Command that will be used to start a bar.";
- example = "\${pkgs.i3-gaps}/bin/i3bar -t";
- };
-
- statusCommand = mkOption {
- type = types.str;
- default = "${pkgs.i3status}/bin/i3status";
- description = "Command that will be used to get status lines.";
- };
-
- colors = mkOption {
- type = types.submodule {
- options = {
- background = mkOption {
- type = types.str;
- default = "#000000";
- description = "Background color of the bar.";
- };
-
- statusline = mkOption {
- type = types.str;
- default = "#ffffff";
- description = "Text color to be used for the statusline.";
- };
-
- separator = mkOption {
- type = types.str;
- default = "#666666";
- description = "Text color to be used for the separator.";
- };
-
- focusedWorkspace = mkOption {
- type = barColorSetModule;
- default = { border = "#4c7899"; background = "#285577"; text = "#ffffff"; };
- description = ''
- Border, background and text color for a workspace button when the workspace has focus.
- '';
- };
-
- activeWorkspace = mkOption {
- type = barColorSetModule;
- default = { border = "#333333"; background = "#5f676a"; text = "#ffffff"; };
- description = ''
- Border, background and text color for a workspace button when the workspace is active.
- '';
- };
-
- inactiveWorkspace = mkOption {
- type = barColorSetModule;
- default = { border = "#333333"; background = "#222222"; text = "#888888"; };
- description = ''
- Border, background and text color for a workspace button when the workspace does not
- have focus and is not active.
- '';
- };
-
- urgentWorkspace = mkOption {
- type = barColorSetModule;
- default = { border = "#2f343a"; background = "#900000"; text = "#ffffff"; };
- description = ''
- Border, background and text color for a workspace button when the workspace contains
- a window with the urgency hint set.
- '';
- };
-
- bindingMode = mkOption {
- type = barColorSetModule;
- default = { border = "#2f343a"; background = "#900000"; text = "#ffffff"; };
- description = "Border, background and text color for the binding mode indicator";
- };
- };
- };
- default = {};
- description = ''
- Bar color settings. All color classes can be specified using submodules
- with 'border', 'background', 'text', fields and RGB color hex-codes as values.
- See default values for the reference.
- Note that 'background', 'status', and 'separator' parameters take a single RGB value.
-
- See <link xlink:href="https://i3wm.org/docs/userguide.html#_colors"/>.
- '';
- };
-
- trayOutput = mkOption {
- type = types.str;
- default = "primary";
- description = "Where to output tray.";
- };
- };
- };
-
- windowCommandModule = types.submodule {
- options = {
- command = mkOption {
- type = types.str;
- description = "i3wm command to execute.";
- example = "border pixel 1";
- };
-
- criteria = mkOption {
- type = criteriaModule;
- description = "Criteria of the windows on which command should be executed.";
- example = { title = "x200: ~/work"; };
- };
- };
- };
-
- criteriaModule = types.attrsOf types.str;
-
- configModule = types.submodule {
- options = {
- inherit (commonOptions) fonts;
-
- window = mkOption {
- type = types.submodule {
- options = {
- titlebar = mkOption {
- type = types.bool;
- default = cfg.package != pkgs.i3-gaps;
- defaultText = "xsession.windowManager.i3.package != nixpkgs.i3-gaps (titlebar should be disabled for i3-gaps)";
- description = "Whether to show window titlebars.";
- };
-
- border = mkOption {
- type = types.int;
- default = 2;
- description = "Window border width.";
- };
-
- hideEdgeBorders = mkOption {
- type = types.enum [ "none" "vertical" "horizontal" "both" "smart" ];
- default = "none";
- description = "Hide window borders adjacent to the screen edges.";
- };
-
- commands = mkOption {
- type = types.listOf windowCommandModule;
- default = [];
- description = ''
- List of commands that should be executed on specific windows.
- See <option>for_window</option> i3wm option documentation.
- '';
- example = [ { command = "border pixel 1"; criteria = { class = "XTerm"; }; } ];
- };
- };
- };
- default = {};
- description = "Window titlebar and border settings.";
- };
-
- floating = mkOption {
- type = types.submodule {
- options = {
- titlebar = mkOption {
- type = types.bool;
- default = cfg.package != pkgs.i3-gaps;
- defaultText = "xsession.windowManager.i3.package != nixpkgs.i3-gaps (titlebar should be disabled for i3-gaps)";
- description = "Whether to show floating window titlebars.";
- };
-
- border = mkOption {
- type = types.int;
- default = 2;
- description = "Floating windows border width.";
- };
-
- modifier = mkOption {
- type = types.enum [ "Shift" "Control" "Mod1" "Mod2" "Mod3" "Mod4" "Mod5" ];
- default = cfg.config.modifier;
- defaultText = "i3.config.modifier";
- description = "Modifier key that can be used to drag floating windows.";
- example = "Mod4";
- };
-
- criteria = mkOption {
- type = types.listOf criteriaModule;
- default = [];
- description = "List of criteria for windows that should be opened in a floating mode.";
- example = [ {"title" = "Steam - Update News";} {"class" = "Pavucontrol";} ];
- };
- };
- };
- default = {};
- description = "Floating window settings.";
- };
-
- focus = mkOption {
- type = types.submodule {
- options = {
- newWindow = mkOption {
- type = types.enum [ "smart" "urgent" "focus" "none" ];
- default = "smart";
- description = ''
- This option modifies focus behavior on new window activation.
-
- See <link xlink:href="https://i3wm.org/docs/userguide.html#focus_on_window_activation"/>
- '';
- example = "none";
- };
-
- followMouse = mkOption {
- type = types.bool;
- default = true;
- description = "Whether focus should follow the mouse.";
- };
-
- forceWrapping = mkOption {
- type = types.bool;
- default = false;
- description = ''
- Whether to force focus wrapping in tabbed or stacked container.
-
- See <link xlink:href="https://i3wm.org/docs/userguide.html#_focus_wrapping"/>
- '';
- };
-
- mouseWarping = mkOption {
- type = types.bool;
- default = true;
- description = ''
- Whether mouse cursor should be warped to the center of the window when switching focus
- to a window on a different output.
- '';
- };
- };
- };
- default = {};
- description = "Focus related settings.";
- };
-
- assigns = mkOption {
- type = types.attrsOf (types.listOf criteriaModule);
- default = {};
- description = ''
- An attribute set that assigns applications to workspaces based
- on criteria.
- '';
- example = literalExample ''
- {
- "1: web" = [{ class = "^Firefox$"; }];
- "0: extra" = [{ class = "^Firefox$"; window_role = "About"; }];
- }
- '';
- };
-
- modifier = mkOption {
- type = types.enum [ "Shift" "Control" "Mod1" "Mod2" "Mod3" "Mod4" "Mod5" ];
- default = "Mod1";
- description = "Modifier key that is used for all default keybindings.";
- example = "Mod4";
- };
-
- workspaceLayout = mkOption {
- type = types.enum [ "default" "stacked" "tabbed" ];
- default = "default";
- example = "tabbed";
- description = ''
- The mode in which new containers on workspace level will
- start.
- '';
- };
-
- workspaceAutoBackAndForth = mkOption {
- type = types.bool;
- default = false;
- example = true;
- description = ''
- Assume you are on workspace "1: www" and switch to "2: IM" using
- mod+2 because somebody sent you a message. You don’t need to remember
- where you came from now, you can just press $mod+2 again to switch
- back to "1: www".
- '';
- };
-
- keybindings = mkOption {
- type = types.attrsOf (types.nullOr types.str);
- default = mapAttrs (n: mkOptionDefault) {
- "${cfg.config.modifier}+Return" = "exec i3-sensible-terminal";
- "${cfg.config.modifier}+Shift+q" = "kill";
- "${cfg.config.modifier}+d" = "exec ${pkgs.dmenu}/bin/dmenu_run";
-
- "${cfg.config.modifier}+Left" = "focus left";
- "${cfg.config.modifier}+Down" = "focus down";
- "${cfg.config.modifier}+Up" = "focus up";
- "${cfg.config.modifier}+Right" = "focus right";
-
- "${cfg.config.modifier}+Shift+Left" = "move left";
- "${cfg.config.modifier}+Shift+Down" = "move down";
- "${cfg.config.modifier}+Shift+Up" = "move up";
- "${cfg.config.modifier}+Shift+Right" = "move right";
-
- "${cfg.config.modifier}+h" = "split h";
- "${cfg.config.modifier}+v" = "split v";
- "${cfg.config.modifier}+f" = "fullscreen toggle";
-
- "${cfg.config.modifier}+s" = "layout stacking";
- "${cfg.config.modifier}+w" = "layout tabbed";
- "${cfg.config.modifier}+e" = "layout toggle split";
-
- "${cfg.config.modifier}+Shift+space" = "floating toggle";
- "${cfg.config.modifier}+space" = "focus mode_toggle";
-
- "${cfg.config.modifier}+a" = "focus parent";
-
- "${cfg.config.modifier}+Shift+minus" = "move scratchpad";
- "${cfg.config.modifier}+minus" = "scratchpad show";
-
- "${cfg.config.modifier}+1" = "workspace number 1";
- "${cfg.config.modifier}+2" = "workspace number 2";
- "${cfg.config.modifier}+3" = "workspace number 3";
- "${cfg.config.modifier}+4" = "workspace number 4";
- "${cfg.config.modifier}+5" = "workspace number 5";
- "${cfg.config.modifier}+6" = "workspace number 6";
- "${cfg.config.modifier}+7" = "workspace number 7";
- "${cfg.config.modifier}+8" = "workspace number 8";
- "${cfg.config.modifier}+9" = "workspace number 9";
- "${cfg.config.modifier}+0" = "workspace number 10";
-
- "${cfg.config.modifier}+Shift+1" = "move container to workspace number 1";
- "${cfg.config.modifier}+Shift+2" = "move container to workspace number 2";
- "${cfg.config.modifier}+Shift+3" = "move container to workspace number 3";
- "${cfg.config.modifier}+Shift+4" = "move container to workspace number 4";
- "${cfg.config.modifier}+Shift+5" = "move container to workspace number 5";
- "${cfg.config.modifier}+Shift+6" = "move container to workspace number 6";
- "${cfg.config.modifier}+Shift+7" = "move container to workspace number 7";
- "${cfg.config.modifier}+Shift+8" = "move container to workspace number 8";
- "${cfg.config.modifier}+Shift+9" = "move container to workspace number 9";
- "${cfg.config.modifier}+Shift+0" = "move container to workspace number 10";
-
- "${cfg.config.modifier}+Shift+c" = "reload";
- "${cfg.config.modifier}+Shift+r" = "restart";
- "${cfg.config.modifier}+Shift+e" = "exec i3-nagbar -t warning -m 'Do you want to exit i3?' -b 'Yes' 'i3-msg exit'";
-
- "${cfg.config.modifier}+r" = "mode resize";
- };
- defaultText = "Default i3 keybindings.";
- description = ''
- An attribute set that assigns a key press to an action using a key symbol.
- See <link xlink:href="https://i3wm.org/docs/userguide.html#keybindings"/>.
- </para><para>
- Consider to use <code>lib.mkOptionDefault</code> function to extend or override
- default keybindings instead of specifying all of them from scratch.
- '';
- example = literalExample ''
- let
- modifier = xsession.windowManager.i3.config.modifier;
- in
-
- lib.mkOptionDefault {
- "''${modifier}+Return" = "exec i3-sensible-terminal";
- "''${modifier}+Shift+q" = "kill";
- "''${modifier}+d" = "exec \${pkgs.dmenu}/bin/dmenu_run";
- }
- '';
- };
-
- keycodebindings = mkOption {
- type = types.attrsOf (types.nullOr types.str);
- default = {};
- description = ''
- An attribute set that assigns keypress to an action using key code.
- See <link xlink:href="https://i3wm.org/docs/userguide.html#keybindings"/>.
- '';
- example = { "214" = "exec --no-startup-id /bin/script.sh"; };
- };
-
- colors = mkOption {
- type = types.submodule {
- options = {
- background = mkOption {
- type = types.str;
- default = "#ffffff";
- description = ''
- Background color of the window. Only applications which do not cover
- the whole area expose the color.
- '';
- };
-
- focused = mkOption {
- type = colorSetModule;
- default = {
- border = "#4c7899"; background = "#285577"; text = "#ffffff";
- indicator = "#2e9ef4"; childBorder = "#285577";
- };
- description = "A window which currently has the focus.";
- };
-
- focusedInactive = mkOption {
- type = colorSetModule;
- default = {
- border = "#333333"; background = "#5f676a"; text = "#ffffff";
- indicator = "#484e50"; childBorder = "#5f676a";
- };
- description = ''
- A window which is the focused one of its container,
- but it does not have the focus at the moment.
- '';
- };
-
- unfocused = mkOption {
- type = colorSetModule;
- default = {
- border = "#333333"; background = "#222222"; text = "#888888";
- indicator = "#292d2e"; childBorder = "#222222";
- };
- description = "A window which is not focused.";
- };
-
- urgent = mkOption {
- type = colorSetModule;
- default = {
- border = "#2f343a"; background = "#900000"; text = "#ffffff";
- indicator = "#900000"; childBorder = "#900000";
- };
- description = "A window which has its urgency hint activated.";
- };
-
- placeholder = mkOption {
- type = colorSetModule;
- default = {
- border = "#000000"; background = "#0c0c0c"; text = "#ffffff";
- indicator = "#000000"; childBorder = "#0c0c0c";
- };
- description = ''
- Background and text color are used to draw placeholder window
- contents (when restoring layouts). Border and indicator are ignored.
- '';
- };
- };
- };
- default = {};
- description = ''
- Color settings. All color classes can be specified using submodules
- with 'border', 'background', 'text', 'indicator' and 'childBorder' fields
- and RGB color hex-codes as values. See default values for the reference.
- Note that 'i3.config.colors.background' parameter takes a single RGB value.
-
- See <link xlink:href="https://i3wm.org/docs/userguide.html#_changing_colors"/>.
- '';
- };
-
- modes = mkOption {
- type = types.attrsOf (types.attrsOf types.str);
- default = {
- resize = {
- "Left" = "resize shrink width 10 px or 10 ppt";
- "Down" = "resize grow height 10 px or 10 ppt";
- "Up" = "resize shrink height 10 px or 10 ppt";
- "Right" = "resize grow width 10 px or 10 ppt";
- "Escape" = "mode default";
- "Return" = "mode default";
- };
- };
- description = ''
- An attribute set that defines binding modes and keybindings
- inside them
-
- Only basic keybinding is supported (bindsym keycomb action),
- for more advanced setup use 'i3.extraConfig'.
- '';
- };
-
- bars = mkOption {
- type = types.listOf barModule;
- default = [{}];
- description = ''
- i3 bars settings blocks. Set to empty list to remove bars completely.
- '';
- };
-
- startup = mkOption {
- type = types.listOf startupModule;
- default = [];
- description = ''
- Commands that should be executed at startup.
-
- See <link xlink:href="https://i3wm.org/docs/userguide.html#_automatically_starting_applications_on_i3_startup"/>.
- '';
- example = literalExample ''
- [
- { command = "systemctl --user restart polybar"; always = true; notification = false; }
- { command = "dropbox start"; notification = false; }
- { command = "firefox"; workspace = "1: web"; }
- ];
- '';
- };
-
- gaps = mkOption {
- type = types.nullOr (types.submodule {
- options = {
- inner = mkOption {
- type = types.nullOr types.int;
- default = null;
- description = "Inner gaps value.";
- example = 12;
- };
-
- outer = mkOption {
- type = types.nullOr types.int;
- default = null;
- description = "Outer gaps value.";
- example = 5;
- };
-
- smartGaps = mkOption {
- type = types.bool;
- default = false;
- description = ''
- This option controls whether to disable all gaps (outer and inner)
- on workspace with a single container.
- '';
- example = true;
- };
-
- smartBorders = mkOption {
- type = types.enum [ "on" "off" "no_gaps" ];
- default = "off";
- description = ''
- This option controls whether to disable container borders on
- workspace with a single container.
- '';
- };
- };
- });
- default = null;
- description = ''
- i3gaps related settings.
- Note that i3-gaps package should be set for this options to take effect.
- '';
- };
- };
- };
-
- keybindingsStr = keybindings: concatStringsSep "\n" (
- mapAttrsToList (keycomb: action: optionalString (action != null) "bindsym ${keycomb} ${action}") keybindings
- );
-
- keycodebindingsStr = keycodebindings: concatStringsSep "\n" (
- mapAttrsToList (keycomb: action: optionalString (action != null) "bindcode ${keycomb} ${action}") keycodebindings
- );
-
- colorSetStr = c: concatStringsSep " " [ c.border c.background c.text c.indicator c.childBorder ];
- barColorSetStr = c: concatStringsSep " " [ c.border c.background c.text ];
-
- criteriaStr = criteria: "[${concatStringsSep " " (mapAttrsToList (k: v: ''${k}="${v}"'') criteria)}]";
-
- modeStr = name: keybindings: ''
- mode "${name}" {
- ${keybindingsStr keybindings}
- }
- '';
-
- assignStr = workspace: criteria: concatStringsSep "\n" (
- map (c: "assign ${criteriaStr c} ${workspace}") criteria
- );
-
- barStr = {
- id, fonts, mode, hiddenState, position, workspaceButtons,
- workspaceNumbers, command, statusCommand, colors, trayOutput, extraConfig, ...
- }: ''
- bar {
- ${optionalString (id != null) "id ${id}"}
- font pango:${concatStringsSep ", " fonts}
- mode ${mode}
- hidden_state ${hiddenState}
- position ${position}
- status_command ${statusCommand}
- i3bar_command ${command}
- workspace_buttons ${if workspaceButtons then "yes" else "no"}
- strip_workspace_numbers ${if !workspaceNumbers then "yes" else "no"}
- tray_output ${trayOutput}
- colors {
- background ${colors.background}
- statusline ${colors.statusline}
- separator ${colors.separator}
- focused_workspace ${barColorSetStr colors.focusedWorkspace}
- active_workspace ${barColorSetStr colors.activeWorkspace}
- inactive_workspace ${barColorSetStr colors.inactiveWorkspace}
- urgent_workspace ${barColorSetStr colors.urgentWorkspace}
- binding_mode ${barColorSetStr colors.bindingMode}
- }
- ${extraConfig}
- }
- '';
-
- gapsStr = with cfg.config.gaps; ''
- ${optionalString (inner != null) "gaps inner ${toString inner}"}
- ${optionalString (outer != null) "gaps outer ${toString outer}"}
- ${optionalString smartGaps "smart_gaps on"}
- ${optionalString (smartBorders != "off") "smart_borders ${smartBorders}"}
- '';
-
- floatingCriteriaStr = criteria: "for_window ${criteriaStr criteria} floating enable";
- windowCommandsStr = { command, criteria, ... }: "for_window ${criteriaStr criteria} ${command}";
-
- startupEntryStr = { command, always, notification, workspace, ... }: ''
- ${if always then "exec_always" else "exec"} ${
- if (notification && workspace == null) then "" else "--no-startup-id"
- } ${
- if (workspace == null) then
- command
- else
- "i3-msg 'workspace ${workspace}; exec ${command}'"
- }
- '';
-
- configFile = pkgs.writeText "i3.conf" ((if cfg.config != null then with cfg.config; ''
- font pango:${concatStringsSep ", " fonts}
- floating_modifier ${floating.modifier}
- new_window ${if window.titlebar then "normal" else "pixel"} ${toString window.border}
- new_float ${if floating.titlebar then "normal" else "pixel"} ${toString floating.border}
- hide_edge_borders ${window.hideEdgeBorders}
- force_focus_wrapping ${if focus.forceWrapping then "yes" else "no"}
- focus_follows_mouse ${if focus.followMouse then "yes" else "no"}
- focus_on_window_activation ${focus.newWindow}
- mouse_warping ${if focus.mouseWarping then "output" else "none"}
- workspace_layout ${workspaceLayout}
- workspace_auto_back_and_forth ${if workspaceAutoBackAndForth then "yes" else "no"}
-
- client.focused ${colorSetStr colors.focused}
- client.focused_inactive ${colorSetStr colors.focusedInactive}
- client.unfocused ${colorSetStr colors.unfocused}
- client.urgent ${colorSetStr colors.urgent}
- client.placeholder ${colorSetStr colors.placeholder}
- client.background ${colors.background}
-
- ${keybindingsStr keybindings}
- ${keycodebindingsStr keycodebindings}
- ${concatStringsSep "\n" (mapAttrsToList modeStr modes)}
- ${concatStringsSep "\n" (mapAttrsToList assignStr assigns)}
- ${concatStringsSep "\n" (map barStr bars)}
- ${optionalString (gaps != null) gapsStr}
- ${concatStringsSep "\n" (map floatingCriteriaStr floating.criteria)}
- ${concatStringsSep "\n" (map windowCommandsStr window.commands)}
- ${concatStringsSep "\n" (map startupEntryStr startup)}
- '' else "") + "\n" + cfg.extraConfig);
-
-in
-
-{
- options = {
- xsession.windowManager.i3 = {
- enable = mkEnableOption "i3 window manager.";
-
- package = mkOption {
- type = types.package;
- default = pkgs.i3;
- defaultText = literalExample "pkgs.i3";
- example = literalExample "pkgs.i3-gaps";
- description = ''
- i3 package to use.
- If 'i3.config.gaps' settings are specified, 'pkgs.i3-gaps' will be set as a default package.
- '';
- };
-
- config = mkOption {
- type = types.nullOr configModule;
- default = {};
- description = "i3 configuration options.";
- };
-
- extraConfig = mkOption {
- type = types.lines;
- default = "";
- description = "Extra configuration lines to add to ~/.config/i3/config.";
- };
- };
- };
-
- config = mkIf cfg.enable (mkMerge [
- {
- home.packages = [ cfg.package ];
- xsession.windowManager.command = "${cfg.package}/bin/i3";
- xdg.configFile."i3/config" = {
- source = configFile;
- onChange = ''
- i3Socket=''${XDG_RUNTIME_DIR:-/run/user/$UID}/i3/ipc-socket.*
- if [ -S $i3Socket ]; then
- echo "Reloading i3"
- $DRY_RUN_CMD ${cfg.package}/bin/i3-msg -s $i3Socket reload 1>/dev/null
- fi
- '';
- };
- }
-
- (mkIf (cfg.config != null) {
- xsession.windowManager.i3.package = mkDefault (
- if (cfg.config.gaps != null) then pkgs.i3-gaps else pkgs.i3
- );
- })
-
- (mkIf (cfg.config != null && (any (s: s.workspace != null) cfg.config.startup)) {
- warnings = [
- ("'xsession.windowManager.i3.config.startup.*.workspace' is deprecated, "
- + "use 'xsession.windowManager.i3.config.assigns' instead."
- + "See https://github.com/rycee/home-manager/issues/265.")
- ];
- })
- ]);
-}
diff --git a/home-manager/modules/services/xscreensaver.nix b/home-manager/modules/services/xscreensaver.nix
index 73a365aa730..ac6194e70c1 100644
--- a/home-manager/modules/services/xscreensaver.nix
+++ b/home-manager/modules/services/xscreensaver.nix
@@ -40,6 +40,10 @@ in {
Description = "XScreenSaver";
After = [ "graphical-session-pre.target" ];
PartOf = [ "graphical-session.target" ];
+
+ # Make sure the service is restarted if the settings change.
+ X-Restart-Triggers =
+ [ (builtins.hashString "md5" (builtins.toJSON cfg.settings)) ];
};
Service = {