diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index c4b70cea6..98d2957ab 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -48,6 +48,8 @@
/modules/programs/home-manager.nix @rycee
+/modules/programs/i3status.nix @JustinLovinger
+
/modules/programs/keychain.nix @marsam
/modules/programs/lesspipe.nix @rycee
diff --git a/modules/lib/maintainers.nix b/modules/lib/maintainers.nix
index 6c125315a..93d9f1fa8 100644
--- a/modules/lib/maintainers.nix
+++ b/modules/lib/maintainers.nix
@@ -7,6 +7,12 @@
# [1] https://github.com/NixOS/nixpkgs/blob/fca0d6e093c82b31103dc0dacc48da2a9b06e24b/maintainers/maintainer-list.nix#LC1
{
+ justinlovinger = {
+ name = "Justin Lovinger";
+ email = "git@justinlovinger.com";
+ github = "JustinLovinger";
+ githubId = 7183441;
+ };
owm111 = {
email = "7798336+owm111@users.noreply.github.com";
name = "Owen McGrath";
diff --git a/modules/modules.nix b/modules/modules.nix
index e44c1097f..f01975e91 100644
--- a/modules/modules.nix
+++ b/modules/modules.nix
@@ -68,6 +68,7 @@ let
(loadModule ./programs/gpg.nix { })
(loadModule ./programs/home-manager.nix { })
(loadModule ./programs/htop.nix { })
+ (loadModule ./programs/i3status.nix { })
(loadModule ./programs/info.nix { })
(loadModule ./programs/irssi.nix { })
(loadModule ./programs/lieer.nix { })
diff --git a/modules/programs/i3status.nix b/modules/programs/i3status.nix
new file mode 100644
index 000000000..c1e12fe71
--- /dev/null
+++ b/modules/programs/i3status.nix
@@ -0,0 +1,208 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.i3status;
+
+ enabledModules = filterAttrs (n: v: v.enable) cfg.modules;
+
+ formatOrder = n: ''order += "${n}"'';
+
+ formatModule = n: v:
+ let
+ formatLine = n: v:
+ let
+ formatValue = v:
+ if isBool v then
+ (if v then "true" else "false")
+ else if isString v then
+ ''"${v}"''
+ else
+ toString v;
+ in "${n} = ${formatValue v}";
+ in ''
+ ${n} {
+ ${concatStringsSep "\n " (mapAttrsToList formatLine v)}
+ }
+ '';
+
+ settingsType = with types; attrsOf (oneOf [ bool int str ]);
+
+ sortAttrNamesByPosition = comparator: set:
+ let pos = n: set."${n}".position;
+ in sort (a: b: comparator (pos a) (pos b)) (attrNames set);
+in {
+ meta.maintainers = [ hm.maintainers.justinlovinger ];
+
+ options.programs.i3status = {
+ enable = mkEnableOption "i3status";
+
+ enableDefault = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether or not to enable
+ the default configuration.
+ '';
+ };
+
+ general = mkOption {
+ type = settingsType;
+ default = { };
+ description = ''
+ Configuration to add to i3status config
+ general
section.
+ See
+
+ i3status
+ 1
+
+ for options.
+ '';
+ example = literalExample ''
+ {
+ colors = true;
+ color_good = "#e0e0e0";
+ color_degraded = "#d7ae00";
+ color_bad = "#f69d6a";
+ interval = 1;
+ }
+ '';
+ };
+
+ modules = mkOption {
+ type = types.attrsOf (types.submodule {
+ options = {
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether or not to enable this module.
+ '';
+ };
+ position = mkOption {
+ type = with types; either int float;
+ description = ''
+ Position of this module in i3status order
.
+ '';
+ };
+ settings = mkOption {
+ type = settingsType;
+ default = { };
+ description = ''
+ Configuration to add to this i3status module.
+ See
+
+ i3status
+ 1
+
+ for options.
+ '';
+ example = literalExample ''
+ {
+ format = "♪ %volume";
+ format_muted = "♪ muted (%volume)";
+ device = "pulse:1";
+ }
+ '';
+ };
+ };
+ });
+ default = { };
+ description = ''
+ Modules to add to i3status config file.
+ See
+
+ i3status
+ 1
+
+ for options.
+ '';
+ example = literalExample ''
+ {
+ "volume master" = {
+ position = 1;
+ settings = {
+ format = "♪ %volume";
+ format_muted = "♪ muted (%volume)";
+ device = "pulse:1";
+ };
+ };
+ "disk /" = {
+ position = 2;
+ settings = {
+ format = "/ %avail";
+ };
+ };
+ }
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ programs.i3status = mkIf cfg.enableDefault {
+ general = {
+ colors = mkDefault true;
+ interval = mkDefault 5;
+ };
+
+ modules = {
+ ipv6 = { position = mkDefault 1; };
+
+ "wireless _first_" = {
+ position = mkDefault 2;
+ settings = {
+ format_up = mkDefault "W: (%quality at %essid) %ip";
+ format_down = mkDefault "W: down";
+ };
+ };
+
+ "ethernet _first_" = {
+ position = mkDefault 3;
+ settings = {
+ format_up = mkDefault "E: %ip (%speed)";
+ format_down = mkDefault "E: down";
+ };
+ };
+
+ "battery all" = {
+ position = mkDefault 4;
+ settings = { format = mkDefault "%status %percentage %remaining"; };
+ };
+
+ "disk /" = {
+ position = mkDefault 5;
+ settings = { format = mkDefault "%avail"; };
+ };
+
+ load = {
+ position = mkDefault 6;
+ settings = { format = mkDefault "%1min"; };
+ };
+
+ memory = {
+ position = mkDefault 7;
+ settings = {
+ format = mkDefault "%used | %available";
+ threshold_degraded = mkDefault "1G";
+ format_degraded = mkDefault "MEMORY < %available";
+ };
+ };
+
+ "tztime local" = {
+ position = mkDefault 8;
+ settings = { format = mkDefault "%Y-%m-%d %H:%M:%S"; };
+ };
+ };
+ };
+
+ home.packages = [ pkgs.i3status ];
+
+ xdg.configFile."i3status/config".text = concatStringsSep "\n" ([ ]
+ ++ optional (cfg.general != { }) (formatModule "general" cfg.general)
+ ++ map formatOrder (sortAttrNamesByPosition lessThan enabledModules)
+ ++ mapAttrsToList formatModule
+ (mapAttrs (n: v: v.settings) enabledModules));
+ };
+}
diff --git a/tests/default.nix b/tests/default.nix
index 940126827..c6dcecbea 100644
--- a/tests/default.nix
+++ b/tests/default.nix
@@ -41,6 +41,7 @@ import nmt {
./modules/programs/fish
./modules/programs/git
./modules/programs/gpg
+ ./modules/programs/i3status
./modules/programs/kakoune
./modules/programs/lf
./modules/programs/lieer
diff --git a/tests/modules/programs/i3status/default.nix b/tests/modules/programs/i3status/default.nix
new file mode 100644
index 000000000..c8d557558
--- /dev/null
+++ b/tests/modules/programs/i3status/default.nix
@@ -0,0 +1,4 @@
+{
+ i3status-with-custom = ./with-custom.nix;
+ i3status-with-default = ./with-default.nix;
+}
diff --git a/tests/modules/programs/i3status/with-custom.nix b/tests/modules/programs/i3status/with-custom.nix
new file mode 100644
index 000000000..4aa01773e
--- /dev/null
+++ b/tests/modules/programs/i3status/with-custom.nix
@@ -0,0 +1,67 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.i3status = {
+ enable = true;
+ enableDefault = false;
+
+ general = {
+ colors = true;
+ color_good = "#e0e0e0";
+ color_degraded = "#d7ae00";
+ color_bad = "#f69d6a";
+ interval = 1;
+ };
+
+ modules = {
+ "volume master" = {
+ position = 1;
+ settings = {
+ format = "♪ %volume";
+ format_muted = "♪ muted (%volume)";
+ device = "pulse:1";
+ };
+ };
+ "disk /" = {
+ position = 2;
+ settings = { format = "/ %avail"; };
+ };
+ };
+ };
+
+ nixpkgs.overlays = [
+ (self: super: { i3status = pkgs.writeScriptBin "dummy-i3status" ""; })
+ ];
+
+ nmt.script = ''
+ assertFileContent \
+ home-files/.config/i3status/config \
+ ${
+ pkgs.writeText "i3status-expected-config" ''
+ general {
+ color_bad = "#f69d6a"
+ color_degraded = "#d7ae00"
+ color_good = "#e0e0e0"
+ colors = true
+ interval = 1
+ }
+
+ order += "volume master"
+ order += "disk /"
+ disk / {
+ format = "/ %avail"
+ }
+
+ volume master {
+ device = "pulse:1"
+ format = "♪ %volume"
+ format_muted = "♪ muted (%volume)"
+ }
+ ''
+ }
+ '';
+ };
+}
diff --git a/tests/modules/programs/i3status/with-default.nix b/tests/modules/programs/i3status/with-default.nix
new file mode 100644
index 000000000..0b7e4ee2f
--- /dev/null
+++ b/tests/modules/programs/i3status/with-default.nix
@@ -0,0 +1,73 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.i3status = {
+ enable = true;
+ enableDefault = true;
+ };
+
+ nixpkgs.overlays = [
+ (self: super: { i3status = pkgs.writeScriptBin "dummy-i3status" ""; })
+ ];
+
+ nmt.script = ''
+ assertFileContent \
+ home-files/.config/i3status/config \
+ ${
+ pkgs.writeText "i3status-expected-config" ''
+ general {
+ colors = true
+ interval = 5
+ }
+
+ order += "ipv6"
+ order += "wireless _first_"
+ order += "ethernet _first_"
+ order += "battery all"
+ order += "disk /"
+ order += "load"
+ order += "memory"
+ order += "tztime local"
+ battery all {
+ format = "%status %percentage %remaining"
+ }
+
+ disk / {
+ format = "%avail"
+ }
+
+ ethernet _first_ {
+ format_down = "E: down"
+ format_up = "E: %ip (%speed)"
+ }
+
+ ipv6 {
+
+ }
+
+ load {
+ format = "%1min"
+ }
+
+ memory {
+ format = "%used | %available"
+ format_degraded = "MEMORY < %available"
+ threshold_degraded = "1G"
+ }
+
+ tztime local {
+ format = "%Y-%m-%d %H:%M:%S"
+ }
+
+ wireless _first_ {
+ format_down = "W: down"
+ format_up = "W: (%quality at %essid) %ip"
+ }
+ ''
+ }
+ '';
+ };
+}