hirc

IRC client
Log | Files | Refs

commit 86c120c1673cf68e48917744145f2a69c31b5eda
parent 63c7b1f584e1ebb5979e48a923708b362ac8d43d
Author: hhvn <dev@hhvn.uk>
Date:   Mon,  6 Dec 2021 16:48:33 +0000

commands.c ui.c config.c struct.h hirc.h: keybindings

Diffstat:
Mcommands.c | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mconfig.c | 15+++++++++++++++
Mhirc.h | 5+++++
Mstruct.h | 7+++++++
Mui.c | 138+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 224 insertions(+), 0 deletions(-)

diff --git a/commands.c b/commands.c @@ -21,6 +21,7 @@ static void command_format(struct Server *server, char *str); static void command_server(struct Server *server, char *str); static void command_names(struct Server *server, char *str); static void command_topic(struct Server *server, char *str); +static void command_bind(struct Server *server, char *str); static void command_help(struct Server *server, char *str); static char *command_optarg; @@ -78,6 +79,13 @@ struct Command commands[] = { "usage: /topic [-clear] [channel] [topic]", "Sets, clears, or checks topic in channel.", "Provide only channel name to check.", NULL}}, + {"bind", command_bind, { + "usage: /bind [<keybind> ['/']<cmd>]", + " /bind -delete <keybind>", + "Bind command to key.", + "Accepts caret formatted control characters (eg, ^C).", + "Accepts multiple characters (alt-c = '^[c'), though", + "these must be inputted faster than wgetch can read.", NULL}}, {"help", command_help, { "usage: /help [command or variable]", "Print help information.", @@ -504,6 +512,57 @@ command_topic(struct Server *server, char *str) { } static void +command_bind(struct Server *server, char *str) { + struct Keybind *p; + char *binding = NULL, *cmd = NULL; + int delete = 0, ret; + enum { opt_delete, }; + struct CommandOpts opts[] = { + {"delete", CMD_NARG, opt_delete}, + {NULL, 0, 0}, + }; + + while ((ret = command_getopt(&str, opts)) != opt_done) { + switch (ret) { + case opt_error: + return; + case opt_delete: + delete = 1; + break; + } + } + + if (str) + binding = strtok_r(str, " ", &cmd); + + if (delete) { + if (ui_unbind(binding) == -1) + ui_error("no such keybind: '%s'", binding); + return; + } + + if (!binding) { + hist_format(selected.history, Activity_none, HIST_SHOW|HIST_TMP|HIST_MAIN, "SELF_KEYBIND_START :Keybindings:"); + for (p = keybinds; p; p = p->next) + hist_format(selected.history, Activity_none, HIST_SHOW|HIST_TMP|HIST_MAIN, "SELF_KEYBIND_LIST %s :%s", ui_unctrl(p->binding), p->cmd); + hist_format(selected.history, Activity_none, HIST_SHOW|HIST_TMP|HIST_MAIN, "SELF_KEYBIND_END :End of keybindings"); + } else if (!cmd) { + for (p = keybinds; p; p = p->next) { + if (strcmp(p->binding, binding) == 0) { + hist_format(selected.history, Activity_none, HIST_SHOW|HIST_TMP|HIST_MAIN, "SELF_KEYBIND_START :Keybindings:"); + hist_format(selected.history, Activity_none, HIST_SHOW|HIST_TMP|HIST_MAIN, "SELF_KEYBIND_LIST %s :%s", ui_unctrl(p->binding), p->cmd); + hist_format(selected.history, Activity_none, HIST_SHOW|HIST_TMP|HIST_MAIN, "SELF_KEYBIND_END :End of keybindings"); + return; + } + } + + ui_error("no such keybind: '%s'", binding); + } else { + ui_bind(binding, cmd); + } +} + +static void command_help(struct Server *server, char *str) { int cmdonly = 0; int i, j; diff --git a/config.c b/config.c @@ -222,6 +222,21 @@ struct Config config[] = { .description = { "Format of SELF_TLSNOTCOMPILED messages", NULL}}, #endif /* TLS */ + {"format.ui.keybind", 1, Val_string, + .str = " ${1}: ${2}", + .strhandle = config_redraws, + .description = { + "Format of /bind output", NULL}}, + {"format.ui.keybind.start", 1, Val_string, + .str = "Keybindings:", + .strhandle = config_redraws, + .description = { + "Format of header of /bind output", NULL}}, + {"format.ui.keybind.end", 1, Val_string, + .str = "", + .strhandle = config_redraws, + .description = { + "Format of footer of /bind output", NULL}}, {"format.privmsg", 1, Val_string, .str = "%{nick:${nick}}${nick}%{o}%{=}${2}", .strhandle = config_redraws, diff --git a/hirc.h b/hirc.h @@ -117,6 +117,10 @@ void ui_select(struct Server *server, struct Channel *channel); void ui_filltoeol(struct Window *window, char c); void ui_wclear(struct Window *window); char * ui_format(char *format, struct History *hist); +char * ui_rectrl(char *str); +char * ui_unctrl(char *str); +int ui_bind(char *binding, char *cmd); +int ui_unbind(char *binding); void ui_error_(char *file, int line, char *format, ...); #define ui_error(format, ...) ui_error_(__FILE__, __LINE__, format, __VA_ARGS__); void ui_perror_(char *file, int line, char *str); @@ -150,6 +154,7 @@ extern struct HistInfo *main_buf; /* ui.c */ extern struct Selected selected; +extern struct Keybind *keybinds; extern struct Window windows[Win_last]; extern int uineedredraw; diff --git a/struct.h b/struct.h @@ -210,4 +210,11 @@ struct Selected { char *name; }; +struct Keybind { + struct Keybind *prev; + char *binding; + char *cmd; + struct Keybind *next; +}; + #endif /* H_STRUCT */ diff --git a/ui.c b/ui.c @@ -57,6 +57,9 @@ struct { #ifndef TLS {"SELF_TLSNOTCOMPILED", "format.ui.tlsnotcompiled"}, #endif /* TLS */ + {"SELF_KEYBIND_START", "format.ui.keybind.start"}, + {"SELF_KEYBIND_LIST", "format.ui.keybind"}, + {"SELF_KEYBIND_END", "format.ui.keybind.end"}, /* Real commands/numerics from server */ {"PRIVMSG", "format.privmsg"}, {"JOIN", "format.join"}, @@ -164,6 +167,7 @@ struct { } input; struct Selected selected; +struct Keybind *keybinds = NULL; void ui_error_(char *file, int line, char *format, ...) { @@ -269,7 +273,11 @@ ui_placewindow(struct Window *window) { void ui_read(void) { + struct Keybind *kp; int key; + int savecounter; + + savecounter = input.counter; /* Loop over input, return only if ERR is received. * Normally wgetch exits fast enough that unless something @@ -278,6 +286,24 @@ ui_read(void) { for (;;) { switch (key = wgetch(windows[Win_input].window)) { case ERR: /* no input received */ + /* Match keybinds here - this allows multikey + * bindings such as those with alt, but since + * there is no delay with wgetch() it's unlikely + * that the user pressing multiple keys will + * trigger one. */ + if (input.counter != savecounter) { + for (kp = keybinds; kp; kp = kp->next) { + if (strncmp(kp->binding, &input.string[savecounter], (input.counter - savecounter)) == 0) { + command_eval(kp->cmd); + memmove(&input.string[savecounter], + &input.string[input.counter], + strlen(&input.string[input.counter]) + 1); + input.counter = savecounter; + return; + } + } + } + /* Only redraw the input window if there * hasn't been any input received - this * is to avoid forcing a redraw for each @@ -1217,3 +1243,115 @@ ui_format(char *format, struct History *hist) { return ret; } + +char * +ui_rectrl(char *str) { + static char ret[8192]; + static char *rp = NULL; + int caret, rc; + char c; + + if (rp) { + free(rp); + rp = NULL; + } + + for (rc = 0; str && *str; str++) { + if (caret) { + c = toupper(*str) - 64; + if (c <= 31 && c >= 0) { + ret[rc++] = c; + } else { + ret[rc++] = '^'; + ret[rc++] = *str; + } + caret = 0; + } else if (*str == '^') { + caret = 1; + } else { + ret[rc++] = *str; + } + } + + ret[rc] = '\0'; + rp = strdup(ret); + + return rp; +} + +char * +ui_unctrl(char *str) { + static char ret[8192]; + static char *rp = NULL;; + int rc; + + if (rp) { + free(rp); + rp = NULL; + } + + for (rc = 0; str && *str; str++) { + if (*str <= 31 && *str >= 0) { + ret[rc++] = '^'; + ret[rc++] = (*str) + 64; + } else { + ret[rc++] = *str; + } + } + + ret[rc] = '\0'; + rp = strdup(ret); + + return rp; +} + +int +ui_bind(char *binding, char *cmd) { + struct Keybind *p; + char *tmp; + + if (!binding || !cmd) + return -1; + + p = malloc(sizeof(struct Keybind)); + p->binding = strdup(ui_rectrl(binding)); + if (*cmd != '/') { + tmp = malloc(strlen(cmd) + 2); + snprintf(tmp, sizeof(tmp) + 2, "/%s", cmd); + p->cmd = tmp; + } else { + p->cmd = strdup(cmd); + } + p->prev = NULL; + p->next = keybinds; + if (keybinds) + keybinds->prev = p; + keybinds = p; + + return 0; +} + +int +ui_unbind(char *binding) { + struct Keybind *p; + + if (!binding) + return -1; + + for (p=keybinds; p; p = p->next) { + if (strcmp(p->binding, binding) == 0) { + if (p->prev) + p->prev->next = p->next; + else + keybinds = p->next; + + if (p->next) + p->next->prev = p->prev; + + free(p); + return 0; + } + } + + return -1; +}