diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 1cce9834..a12d9d95 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -92,6 +92,9 @@
/modules/programs/go.nix @rvolosatovs
+/modules/programs/hexchat.nix @superherointj @thiagokokada
+/tests/modules/programs/hexchat @thiagokokada
+
/modules/programs/himalaya.nix @ambroisie
/tests/modules/programs/himalaya @ambroisie
diff --git a/modules/misc/news.nix b/modules/misc/news.nix
index e19f9604..5bdb31f8 100644
--- a/modules/misc/news.nix
+++ b/modules/misc/news.nix
@@ -2225,6 +2225,14 @@ in
you may need to do some changes.
'';
}
+
+ {
+ time = "2021-10-23T17:12:22+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'programs.hexchat'.
+ '';
+ }
];
};
}
diff --git a/modules/modules.nix b/modules/modules.nix
index 337951ab..1bb3481a 100644
--- a/modules/modules.nix
+++ b/modules/modules.nix
@@ -72,6 +72,7 @@ let
./programs/gnome-terminal.nix
./programs/go.nix
./programs/gpg.nix
+ ./programs/hexchat.nix
./programs/himalaya.nix
./programs/home-manager.nix
./programs/htop.nix
diff --git a/modules/programs/hexchat.nix b/modules/programs/hexchat.nix
new file mode 100644
index 00000000..1b15406d
--- /dev/null
+++ b/modules/programs/hexchat.nix
@@ -0,0 +1,368 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.hexchat;
+
+ channelOptions = with types;
+ submodule {
+ options = {
+ autoconnect = mkOption {
+ type = nullOr bool;
+ default = false;
+ description = "Autoconnect to network.";
+ };
+
+ connectToSelectedServerOnly = mkOption {
+ type = nullOr bool;
+ default = true;
+ description = "Connect to selected server only.";
+ };
+
+ bypassProxy = mkOption {
+ type = nullOr bool;
+ default = true;
+ description = "Bypass proxy.";
+ };
+
+ forceSSL = mkOption {
+ type = nullOr bool;
+ default = false;
+ description = "Use SSL for all servers.";
+ };
+
+ acceptInvalidSSLCertificates = mkOption {
+ type = nullOr bool;
+ default = false;
+ description = "Accept invalid SSL certificates.";
+ };
+
+ useGlobalUserInformation = mkOption {
+ type = nullOr bool;
+ default = false;
+ description = "Use global user information.";
+ };
+ };
+ };
+
+ modChannelOption = with types;
+ submodule {
+ options = {
+ autojoin = mkOption {
+ type = listOf str;
+ default = [ ];
+ example = [ "#home-manager" "#linux" "#nix" ];
+ description = "Channels list to autojoin on connecting to server.";
+ };
+
+ charset = mkOption {
+ type = nullOr str;
+ default = null;
+ example = "UTF-8 (Unicode)";
+ description = "Character set.";
+ };
+
+ commands = mkOption {
+ type = listOf str;
+ default = [ ];
+ example = literalExample ''[ "ECHO Greetings fellow Nixer! ]'';
+ description = "Commands to be executed on connecting to server.";
+ };
+
+ loginMethod = mkOption {
+ type = nullOr (enum (attrNames loginMethodMap));
+ default = null;
+ description = ''
+ The login method. The allowed options are:
+
+
+ null
+ Default
+
+
+ "nickServMsg"
+ NickServ (/MSG NickServ + password)
+
+
+ "nickServ"
+ NickServ (/NICKSERV + password)
+
+
+ "challengeAuth"
+ Challenge Auth (username + password)
+
+
+ "sasl"
+ SASL (username + password)
+
+
+ "serverPassword"
+ Server password (/PASS password)
+
+
+ "saslExternal"
+ SASL EXTERNAL (cert)
+
+
+ "customCommands"
+
+ Use "commands" field for auth. For example
+
+ commands = [ "/msg NickServ IDENTIFY my_password" ]
+
+
+
+
+
+ '';
+ };
+
+ nickname = mkOption {
+ type = nullOr str;
+ default = null;
+ description = "Primary nickname.";
+ };
+
+ nickname2 = mkOption {
+ type = nullOr str;
+ default = null;
+ description = "Secondary nickname.";
+ };
+
+ options = mkOption {
+ type = nullOr channelOptions;
+ default = null;
+ example = {
+ autoconnect = true;
+ useGlobalUserInformation = true;
+ };
+ description = "Channel options.";
+ };
+
+ password = mkOption {
+ type = nullOr str;
+ default = null;
+ description = ''
+ Password to use. Note this password will be readable by all user's
+ in the Nix store.
+ '';
+ };
+
+ realName = mkOption {
+ type = nullOr str;
+ default = null;
+ description = ''
+ Real name. Is used to populate the real name field that appears when
+ someone uses the WHOIS command on your nick.
+ '';
+ };
+
+ userName = mkOption {
+ type = nullOr str;
+ default = null;
+ description = ''
+ User name. Part of your user@host hostmask that
+ appears to other on IRC.
+ '';
+ };
+
+ servers = mkOption {
+ type = listOf str;
+ default = [ ];
+ example = [ "chat.freenode.net" "irc.freenode.net" ];
+ description = "IRC Server Address List.";
+ };
+ };
+ };
+
+ transformField = k: v: if (v != null) then "${k}=${v}" else null;
+
+ listChar = c: l:
+ if l != [ ] then concatMapStringsSep "\n" (transformField c) l else null;
+
+ computeFieldsValue = channel:
+ let
+ ifTrue = p: n: if p then n else 0;
+ result = with channel.options;
+ foldl' (a: b: a + b) 0 [
+ (ifTrue (!connectToSelectedServerOnly) 1)
+ (ifTrue useGlobalUserInformation 2)
+ (ifTrue forceSSL 4)
+ (ifTrue autoconnect 8)
+ (ifTrue (!bypassProxy) 16)
+ (ifTrue acceptInvalidSSLCertificates 32)
+ ];
+ in toString (if channel.options == null then 0 else result);
+
+ loginMethodMap = {
+ nickServMsg = 1;
+ nickServ = 2;
+ challengeAuth = 4;
+ sasl = 6;
+ serverPassword = 7;
+ customCommands = 9;
+ saslExternal = 10;
+ };
+
+ loginMethod = channel:
+ transformField "L" (optionalString (channel.loginMethod != null)
+ (toString loginMethodMap.${channel.loginMethod}));
+
+ # Note: Missing option `D=`.
+ transformChannel = channelName:
+ let channel = cfg.channels.${channelName};
+ in concatStringsSep "\n" (filter (v: v != null) [
+ "" # leave a space between one server and another
+ (transformField "N" channelName)
+ (loginMethod channel)
+ (transformField "E" channel.charset)
+ (transformField "F" (computeFieldsValue channel))
+ (transformField "I" channel.nickname)
+ (transformField "i" channel.nickname2)
+ (transformField "R" channel.realName)
+ (transformField "U" channel.userName)
+ (transformField "P" channel.password)
+ (listChar "S" channel.servers)
+ (listChar "J" channel.autojoin)
+ (listChar "C" channel.commands)
+ ]);
+
+in {
+ meta.maintainers = with maintainers; [ superherointj thiagokokada ];
+
+ options.programs.hexchat = with types; {
+ enable = mkEnableOption "HexChat, a graphical IRC client";
+
+ channels = mkOption {
+ type = attrsOf modChannelOption;
+ default = { };
+ example = literalExample ''
+ {
+ freenode = {
+ autojoin = [
+ "#home-manager"
+ "#linux"
+ "#nixos"
+ ];
+ charset = "UTF-8 (Unicode)";
+ commands = [
+ "ECHO Buzz Lightyear sent you a message: 'To Infinity... and Beyond!'"
+ ];
+ loginMethod = sasl;
+ nickname = "my_nickname";
+ nickname2 = "my_secondchoice";
+ options = {
+ acceptInvalidSSLCertificates = false;
+ autoconnect = true;
+ bypassProxy = true;
+ connectToSelectedServerOnly = true;
+ useGlobalUserInformation = false;
+ forceSSL = false;
+ };
+ password = "my_password";
+ realName = "my_realname";
+ servers = [
+ "chat.freenode.net"
+ "irc.freenode.net"
+ ];
+ userName = "my_username";
+ };
+ }'';
+ description = ''
+ Configures ~/.config/hexchat/servlist.conf.
+ '';
+ };
+
+ settings = mkOption {
+ default = null;
+ type = nullOr (attrsOf str);
+ example = literalExample ''
+ {
+ irc_nick1 = "mynick";
+ irc_username = "bob";
+ irc_realname = "Bart Simpson";
+ text_font = "Monospace 14";
+ };
+ '';
+ description = ''
+ Configuration for ~/.config/hexchat/hexchat.conf, see
+
+ for supported values.
+ '';
+ };
+
+ overwriteConfigFiles = mkOption {
+ type = nullOr bool;
+ default = false;
+ description = ''
+ Enables overwriting HexChat configuration files
+ (hexchat.conf, servlist.conf).
+ Any existing HexChat configuration will be lost. Certify to back-up any
+ previous configuration before enabling this.
+
+ Enabling this setting is recommended, because everytime HexChat
+ application is closed it overwrites Nix/Home Manager provided
+ configuration files, causing:
+
+
+ Nix/Home Manager provided configuration to be out of sync with
+ actual active HexChat configuration.
+
+
+ Blocking Nix/Home Manager updates until configuration files are
+ manually removed.
+
+
+ '';
+ };
+
+ theme = mkOption {
+ type = nullOr package;
+ default = null;
+ example = literalExample ''
+ stdenv.mkDerivation rec {
+ name = "hexchat-theme-MatriY";
+ buildInputs = [ pkgs.unzip ];
+ src = fetchurl {
+ url = "https://dl.hexchat.net/themes/MatriY.hct";
+ sha256 = "sha256-ffkFJvySfl0Hwja3y7XCiNJceUrGvlEoEm97eYNMTZc=";
+ };
+ unpackPhase = "unzip ''${src}";
+ installPhase = "cp -r . $out";
+ };
+ '';
+ description = ''
+ Theme package for HexChat. Expects a derivation containing decompressed
+ theme files. .hct file format requires unzip
+ decompression, as seen in example.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [
+ (hm.assertions.assertPlatform "programs.hexchat" pkgs platforms.linux)
+ ];
+
+ home.packages = [ pkgs.hexchat ];
+
+ xdg.configFile."hexchat" = mkIf (cfg.theme != null) {
+ source = cfg.theme;
+ recursive = true;
+ };
+
+ xdg.configFile."hexchat/hexchat.conf" = mkIf (cfg.settings != null) {
+ force = cfg.overwriteConfigFiles;
+ text = concatMapStringsSep "\n" (x: x + " = " + cfg.settings.${x})
+ (attrNames cfg.settings);
+ };
+
+ xdg.configFile."hexchat/servlist.conf" = mkIf (cfg.channels != { }) {
+ force = cfg.overwriteConfigFiles;
+ # Final line breaks is required to avoid cropping last field value.
+ text = concatMapStringsSep "\n" transformChannel (attrNames cfg.channels)
+ + "\n\n";
+ };
+ };
+}
diff --git a/tests/default.nix b/tests/default.nix
index 9694bc99..4a37cf89 100644
--- a/tests/default.nix
+++ b/tests/default.nix
@@ -110,6 +110,7 @@ import nmt {
./modules/programs/foot
./modules/programs/getmail
./modules/programs/gnome-terminal
+ ./modules/programs/hexchat
./modules/programs/i3status-rust
./modules/programs/mangohud
./modules/programs/ncmpcpp-linux
diff --git a/tests/modules/programs/hexchat/basic-configuration-expected-main-config b/tests/modules/programs/hexchat/basic-configuration-expected-main-config
new file mode 100644
index 00000000..e3617ca8
--- /dev/null
+++ b/tests/modules/programs/hexchat/basic-configuration-expected-main-config
@@ -0,0 +1,10 @@
+dcc_dir = /home/user/Downloads
+gui_quit_dialog = 0
+gui_slist_skip = 1
+irc_nick1 = user
+irc_nick2 = user_
+irc_nick3 = user__
+irc_real_name = real user
+irc_user_name = user
+text_font = Monospace 14
+text_font_main = Monospace 14
\ No newline at end of file
diff --git a/tests/modules/programs/hexchat/basic-configuration-expected-serverlist-config b/tests/modules/programs/hexchat/basic-configuration-expected-serverlist-config
new file mode 100644
index 00000000..29d23284
--- /dev/null
+++ b/tests/modules/programs/hexchat/basic-configuration-expected-serverlist-config
@@ -0,0 +1,24 @@
+
+N=efnet
+L=
+F=4
+S=irc.choopa.net
+S=irc.colosolutions.net
+S=irc.mzima.net
+S=irc.prison.net
+J=#computers
+
+N=freenode
+L=6
+E=UTF-8 (Unicode)
+F=12
+I=user
+i=user_
+R=real_user
+U=user
+P=password
+S=chat.freenode.net
+S=irc.freenode.net
+J=#home-manager
+J=#nixos
+
diff --git a/tests/modules/programs/hexchat/basic-configuration.nix b/tests/modules/programs/hexchat/basic-configuration.nix
new file mode 100644
index 00000000..1aa39bd4
--- /dev/null
+++ b/tests/modules/programs/hexchat/basic-configuration.nix
@@ -0,0 +1,61 @@
+{ config, lib, pkgs, ... }:
+
+{
+ config = {
+ programs.hexchat = {
+ enable = true;
+ overwriteConfigFiles = true;
+ channels = {
+ freenode = {
+ charset = "UTF-8 (Unicode)";
+ userName = "user";
+ password = "password";
+ loginMethod = "sasl";
+ nickname = "user";
+ nickname2 = "user_";
+ realName = "real_user";
+ options = {
+ autoconnect = true;
+ forceSSL = true;
+ };
+ servers = [ "chat.freenode.net" "irc.freenode.net" ];
+ autojoin = [ "#home-manager" "#nixos" ];
+ };
+ efnet = {
+ options = { forceSSL = true; };
+ servers = [
+ "irc.choopa.net"
+ "irc.colosolutions.net"
+ "irc.mzima.net"
+ "irc.prison.net"
+ ];
+ autojoin = [ "#computers" ];
+ };
+ };
+ settings = {
+ dcc_dir = "/home/user/Downloads";
+ irc_nick1 = "user";
+ irc_nick2 = "user_";
+ irc_nick3 = "user__";
+ irc_user_name = "user";
+ irc_real_name = "real user";
+ text_font = "Monospace 14";
+ text_font_main = "Monospace 14";
+ gui_slist_skip = "1"; # Skip network list on start-up
+ gui_quit_dialog = "0";
+ };
+ };
+
+ test.stubs.hexchat = { };
+
+ nmt.script = ''
+ assertFileContent \
+ home-files/.config/hexchat/hexchat.conf \
+ ${./basic-configuration-expected-main-config}
+ assertFileContent \
+ home-files/.config/hexchat/servlist.conf \
+ ${./basic-configuration-expected-serverlist-config}
+ '';
+ };
+
+}
diff --git a/tests/modules/programs/hexchat/default.nix b/tests/modules/programs/hexchat/default.nix
new file mode 100644
index 00000000..e70c4610
--- /dev/null
+++ b/tests/modules/programs/hexchat/default.nix
@@ -0,0 +1 @@
+{ hexchat-basic-configuration = ./basic-configuration.nix; }