hirc

IRC client
Log | Files | Refs

commit f754545c7dde051898a0231933a42e93de53af74
parent 356a686a20feb3be4a8fbf9ba345e52751707f26
Author: hhvn <dev@hhvn.uk>
Date:   Sun, 20 Feb 2022 19:41:55 +0000

Autocommand handling

Diffstat:
Msrc/commands.c | 124++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Msrc/config.c | 30+++++++++++++++++++++---------
Msrc/handle.c | 10++++++++--
Msrc/hirc.h | 8++++++--
Msrc/serv.c | 67++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/struct.h | 1+
Msrc/ui.c | 8++++++--
7 files changed, 209 insertions(+), 39 deletions(-)

diff --git a/src/commands.c b/src/commands.c @@ -155,8 +155,15 @@ struct Command commands[] = { "Set a formatting variable.", "This is equivalent to /set format.<format> string...", NULL}}, {"server", command_server, 0, { - "usage: /server <server> cmd....", - "Run (non-raw) command with server as target.", NULL}}, + "usage: /server [-auto] <server> [cmd....]", + " /server [-clear] <server>", + "Evaluate a cooked command with server as target.", + " -auto if supplied with a command, run that command", + " automatically when the server connects.", + " Otherwise, list autocmds that have been set.", + " -clear clear autocmds from server", + "To send a raw command to a server, use:", + " /server <server> /quote ...", NULL}}, {"names", command_names, 1, { "usage: /names <channel>", "List nicks in channel (pretty useless with nicklist.", NULL}}, @@ -205,6 +212,7 @@ struct Command commands[] = { "usage: /dump [-all] [-aliases] [-bindings] [-formats] [-config]", " [-default] [-servers] [-channels] [-queries] <file>", "Dumps configuration details into a file.", + " -autocmds dump commands specified with /server -auto", " -aliases dump /alias commands", " -bindings dump /bind commands", " -formats dump /format commands beginning with filter.", @@ -214,7 +222,9 @@ struct Command commands[] = { " -queries dump /query commands for respective servers", " -default dump default settings (dump non-default otherwise)", "If none (excluding -default) of the above are selected, it is", - "treated as though all are selected.", NULL}}, + "treated as though all are selected.", + "If -autocmds and -channels are used together, and there exists", + "an autocmd to join a channel, then only the autocmd will be dumped.", NULL}}, {"close", command_close, 0, { "usage: /close [id]", "Forget about selected buffer, or a buffer by id.", NULL}}, @@ -378,7 +388,7 @@ command_query(struct Server *server, char *str) { if ((priv = chan_get(&server->privs, str, -1)) == NULL) priv = chan_add(server, &server->privs, str, 1); - if (!readingconf) + if (!nouich) ui_select(server, priv); } @@ -694,7 +704,7 @@ command_connect(struct Server *server, char *str) { tserver = serv_add(&servers, network, host, port, nick, username, realname, tls, tls_verify); serv_connect(tserver); - if (!readingconf) + if (!nouich) ui_select(tserver, NULL); } @@ -834,12 +844,35 @@ static void command_server(struct Server *server, char *str) { struct Server *nserver; char *tserver, *cmd, *arg; - int i; + char **acmds; + int i, ret, mode; + enum { opt_norm, opt_auto, opt_clear }; + struct CommandOpts opts[] = { + {"auto", CMD_NARG, opt_auto}, + {"clear", CMD_NARG, opt_clear}, + {NULL, 0, 0}, + }; + + mode = opt_norm; + while ((ret = command_getopt(&str, opts)) != opt_done) { + switch (ret) { + case opt_error: + return; + case opt_auto: + case opt_clear: + if (mode != opt_norm) { + ui_error("conflicting flags", NULL); + return; + } + mode = ret; + } + } tserver = strtok_r(str, " ", &arg); - cmd = strtok_r(NULL, " ", &arg); + if (mode == opt_norm) + cmd = strtok_r(NULL, " ", &arg); - if (!tserver || !cmd) { + if (!tserver) { command_toofew("server"); return; } @@ -849,17 +882,53 @@ command_server(struct Server *server, char *str) { return; } - if (*cmd == '/') - cmd++; - - for (i=0; commands[i].name && commands[i].func; i++) { - if (strcmp(commands[i].name, cmd) == 0) { - commands[i].func(nserver, arg); + switch (mode) { + case opt_norm: + if (!cmd || !*cmd) { + command_toofew("server"); return; } - } - ui_error("no such commands: '%s'", cmd); + if (*cmd == '/') + cmd++; + + for (i=0; commands[i].name && commands[i].func; i++) { + if (strcmp(commands[i].name, cmd) == 0) { + commands[i].func(nserver, arg); + return; + } + } + ui_error("no such commands: '%s'", cmd); + break; + case opt_auto: + if (!arg || !*arg) { + hist_format(selected.history, Activity_none, HIST_SHOW|HIST_TMP|HIST_MAIN, "SELF_AUTOCMDS_START %s :Autocmds for %s:", + nserver->name, nserver->name); + for (acmds = nserver->autocmds; acmds && *acmds; acmds++) + hist_format(selected.history, Activity_none, HIST_SHOW|HIST_TMP|HIST_MAIN, "SELF_AUTOCMDS_LIST %s :%s", + nserver->name, *acmds); + hist_format(selected.history, Activity_none, HIST_SHOW|HIST_TMP|HIST_MAIN, "SELF_AUTOCMDS_END %s :End of autocmds for %s", + nserver->name, nserver->name); + } else { + if (*arg == '/') { + cmd = arg; + } else { + cmd = emalloc(strlen(arg) + 2); + snprintf(cmd, strlen(arg) + 2, "/%s", arg); + } + + serv_auto_add(nserver, cmd); + } + break; + case opt_clear: + if (*arg) { + command_toomany("server"); + break; + } + + serv_auto_free(nserver); + break; + } } static void @@ -1259,6 +1328,7 @@ command_dump(struct Server *server, char *str) { int selected = 0; int def = 0, ret; int i; + char **aup; struct Server *sp; struct Channel *chp; struct Alias *ap; @@ -1271,7 +1341,8 @@ command_dump(struct Server *server, char *str) { opt_servers = 16, opt_channels = 32, opt_queries = 64, - opt_default = 128, + opt_autocmds = 128, + opt_default = 256, }; static struct CommandOpts opts[] = { {"aliases", CMD_NARG, opt_aliases}, @@ -1279,6 +1350,7 @@ command_dump(struct Server *server, char *str) { {"formats", CMD_NARG, opt_formats}, {"config", CMD_NARG, opt_config}, {"servers", CMD_NARG, opt_servers}, + {"autocmds", CMD_NARG, opt_autocmds}, {"channels", CMD_NARG, opt_channels}, {"queries", CMD_NARG, opt_queries}, {"default", CMD_NARG, opt_default}, @@ -1295,6 +1367,7 @@ command_dump(struct Server *server, char *str) { case opt_config: case opt_servers: case opt_channels: + case opt_autocmds: selected |= ret; break; case opt_default: @@ -1304,7 +1377,7 @@ command_dump(struct Server *server, char *str) { } if (!selected) - selected = 127; + selected = opt_default - 1; if (!str || !*str) { command_toofew("dump"); @@ -1316,7 +1389,7 @@ command_dump(struct Server *server, char *str) { return; } - if (selected & opt_servers || selected & opt_channels || selected & opt_queries) { + if (selected & (opt_servers|opt_channels|opt_queries|opt_autocmds)) { if (selected & opt_servers) fprintf(file, "Network connections\n"); @@ -1335,9 +1408,14 @@ command_dump(struct Server *server, char *str) { sp->host, sp->port); } + if (selected & opt_autocmds) { + for (aup = sp->autocmds; *aup; aup++) + fprintf(file, "/server -auto %s %s\n", sp->name, *aup); + } if (selected & opt_channels) { for (chp = sp->channels; chp; chp = chp->next) - fprintf(file, "/server %s /join %s\n", sp->name, chp->name); + if (!(selected & opt_autocmds) || !serv_auto_haschannel(sp, chp->name)) + fprintf(file, "/server %s /join %s\n", sp->name, chp->name); } if (selected & opt_queries) { for (chp = sp->privs; chp; chp = chp->next) @@ -1546,7 +1624,7 @@ alias_eval(char *cmd) { } void -command_eval(char *str) { +command_eval(struct Server *server, char *str) { struct Command *cmdp; char msg[512]; char *cmd; @@ -1582,10 +1660,10 @@ command_eval(char *str) { for (cmdp = commands; cmdp->name && cmdp->func; cmdp++) { if (strcmp(cmdp->name, cmd) == 0) { - if (cmdp->needserver && !selected.server) { + if (cmdp->needserver && !server) { ui_error("/%s requires a server to be selected or provided by /server", cmdp->name); } else { - cmdp->func(selected.server, s); + cmdp->func(server, s); } return; } diff --git a/src/config.c b/src/config.c @@ -25,8 +25,6 @@ #include <limits.h> #include "hirc.h" -int readingconf = 0; - static int config_nicklist_location(long num); static int config_nicklist_width(long num); static int config_buflist_location(long num); @@ -277,6 +275,21 @@ struct Config config[] = { .strhandle = config_redraws, .description = { "Format of footer of /bind output", NULL}}, + {"format.ui.autocmds", 1, Val_string, + .str = " ${2}", + .strhandle = config_redraws, + .description = { + "Format of /server -auto output", NULL}}, + {"format.ui.autocmds.start", 1, Val_string, + .str = "Autocmds for ${1}:", + .strhandle = config_redraws, + .description = { + "Format of header of /server -auto output", NULL}}, + {"format.ui.autocmds.end", 1, Val_string, + .str = "", + .strhandle = config_redraws, + .description = { + "Format of footer of /server -auto output", NULL}}, {"format.ui.grep.start", 1, Val_string, .str = "%{b}%{c:94}Results of ${1}:", .strhandle = config_redraws, @@ -1358,8 +1371,7 @@ config_read(char *filename) { if (!bt) bt = emalloc((sizeof(char *)) * (btoffset + 1)); else - bt = realloc(bt, (sizeof(char *)) * (btoffset + 1)); - assert(bt != NULL); + bt = erealloc(bt, (sizeof(char *)) * (btoffset + 1)); *(bt + btoffset) = path; btoffset++; @@ -1370,13 +1382,13 @@ config_read(char *filename) { goto shrink; } - save = readingconf; - readingconf = 1; + save = nouich; + nouich = 1; while (read_line(fileno(file), buf, sizeof(buf))) if (*buf == '/') - command_eval(buf); + command_eval(NULL, buf); fclose(file); - readingconf = save; + nouich = save; shrink: /* Remove path from bt and shrink */ @@ -1386,7 +1398,7 @@ shrink: free(bt); bt = NULL; } else { - bt = realloc(bt, (sizeof(char *)) * btoffset); + bt = erealloc(bt, (sizeof(char *)) * btoffset); assert(bt != NULL); } } diff --git a/src/handle.c b/src/handle.c @@ -554,14 +554,20 @@ handle_RPL_TOPICWHOTIME(struct Server *server, struct History *msg) { static void handle_RPL_WELCOME(struct Server *server, struct History *msg) { - server->status = ConnStatus_connected; + if (server->status != ConnStatus_connected) { + server->status = ConnStatus_connected; + serv_auto_send(server); + } hist_addp(server->history, msg, Activity_status, HIST_DFL); } static void handle_RPL_ENDOFMOTD(struct Server *server, struct History *msg) { /* If server doesn't support RPL_WELCOME, use RPL_ENDOFMOTD to set status */ - server->status = ConnStatus_connected; + if (server->status != ConnStatus_connected) { + server->status = ConnStatus_connected; + serv_auto_send(server); + } hist_addp(server->history, msg, Activity_status, HIST_DFL); } diff --git a/src/hirc.h b/src/hirc.h @@ -109,6 +109,10 @@ int serv_remove(struct Server **head, char *name); int serv_selected(struct Server *server); void serv_disconnect(struct Server *server, int reconnect, char *msg); int serv_ischannel(struct Server *server, char *str); +void serv_auto_add(struct Server *server, char *cmd); +void serv_auto_free(struct Server *server); +void serv_auto_send(struct Server *server); +int serv_auto_haschannel(struct Server *server, char *chan); char * support_get(struct Server *server, char *key); void support_set(struct Server *server, char *key, char *value); void schedule_push(struct Server *server, char *tmsg, char *msg); @@ -156,7 +160,7 @@ void ui_tls_error_(char *file, int line, const char *func, struct tls *ctx, cha #endif /* TLS */ /* commands.c */ -void command_eval(char *str); +void command_eval(struct Server *server, char *str); int command_getopt(char **str, struct CommandOpts *opts); int alias_add(char *binding, char *cmd); int alias_remove(char *binding); @@ -182,10 +186,10 @@ extern struct Selected selected; extern struct Keybind *keybinds; extern struct Window windows[Win_last]; extern int uineedredraw; +extern int nouich; /* config.c */ extern struct Config config[]; -extern int readingconf; /* commands.c */ extern struct Command commands[]; diff --git a/src/serv.c b/src/serv.c @@ -102,6 +102,7 @@ serv_create(char *name, char *host, char *port, char *nick, server->reconnect = 0; for (i=0; i < Expect_last; i++) server->expect[i] = NULL; + server->autocmds = NULL; server->connectfail = 0; server->lastconnected = server->lastrecv = server->pingsent = 0; @@ -451,6 +452,70 @@ serv_ischannel(struct Server *server, char *str) { } void +serv_auto_add(struct Server *server, char *cmd) { + char **p; + size_t len; + + if (!server || !cmd) + return; + + if (!server->autocmds) { + len = 1; + server->autocmds = emalloc(sizeof(char *) * (len + 1)); + } else { + for (p = server->autocmds, len = 1; *p; p++) + len++; + server->autocmds = erealloc(server->autocmds, sizeof(char *) * (len + 1)); + } + + *(server->autocmds + len - 1) = estrdup(cmd); + *(server->autocmds + len) = NULL; +} + +void +serv_auto_free(struct Server *server) { + char **p; + + if (!server || !server->autocmds) + return; + + for (p = server->autocmds; *p; p++) + free(*p); + free(server->autocmds); + server->autocmds = NULL; +} + +void +serv_auto_send(struct Server *server) { + char **p; + int save; + + if (!server || !server->autocmds) + return; + + save = nouich; + nouich = 1; + for (p = server->autocmds; *p; p++) + command_eval(server, *p); + nouich = save; +} + +/* check if autocmds has '/join <chan>' */ +int +serv_auto_haschannel(struct Server *server, char *chan) { + char **p; + + if (!server || !server->autocmds) + return 0; + + for (p = server->autocmds; *p; p++) + if (strncmp(*p, "/join ", strlen("/join ")) == 0 && + strcmp((*p) + strlen("/join "), chan) == 0) + return 1; + return 0; +} + +void schedule_push(struct Server *server, char *tmsg, char *msg) { struct Schedule *p; @@ -513,7 +578,7 @@ schedule_pull(struct Server *server, char *tmsg) { void expect_set(struct Server *server, enum Expect cmd, char *about) { - if (cmd >= Expect_last || cmd < 0 || readingconf) + if (cmd >= Expect_last || cmd < 0 || nouich) return; free(server->expect[cmd]); diff --git a/src/struct.h b/src/struct.h @@ -150,6 +150,7 @@ struct Server { struct Schedule *schedule; int reconnect; char *expect[Expect_last]; + char **autocmds; int connectfail; /* number of failed connections */ time_t lastconnected; /* last time a connection was lost */ time_t lastrecv; /* last time a message was received from server */ diff --git a/src/ui.c b/src/ui.c @@ -30,6 +30,7 @@ #include "hirc.h" int uineedredraw = 0; +int nouich = 0; #define HIRC_COLOURS 100 static unsigned short colourmap[HIRC_COLOURS] = { @@ -89,6 +90,9 @@ struct { {"SELF_HELP_START", "format.ui.help.start"}, {"SELF_HELP", "format.ui.help"}, {"SELF_HELP_END", "format.ui.help.end"}, + {"SELF_AUTOCMDS_START", "format.ui.autocmds.start"}, + {"SELF_AUTOCMDS_LIST", "format.ui.autocmds"}, + {"SELF_AUTOCMDS_END", "format.ui.autocmds.end"}, /* Real commands/numerics from server */ {"PRIVMSG", "format.privmsg"}, {"NOTICE", "format.notice"}, @@ -394,7 +398,7 @@ ui_read(void) { for (kp = keybinds; kp; kp = kp->next) { if ((input.counter - savecounter) == strlen(kp->binding) && strncmp(kp->binding, &input.string[savecounter], (input.counter - savecounter)) == 0) { - command_eval(kp->cmd); + command_eval(selected.server, kp->cmd); memmove(&input.string[savecounter], &input.string[input.counter], strlen(&input.string[input.counter]) + 1); @@ -457,7 +461,7 @@ ui_read(void) { break; case KEY_ENTER: case '\r': - command_eval(input.string); + command_eval(selected.server, input.string); /* free checks for null */ free(input.history[INPUT_HIST_MAX - 1]); memmove(input.history + 1, input.history, (sizeof(input.history) / INPUT_HIST_MAX) * (INPUT_HIST_MAX - 1));