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; }