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:
M | commands.c | | | 59 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | config.c | | | 15 | +++++++++++++++ |
M | hirc.h | | | 5 | +++++ |
M | struct.h | | | 7 | +++++++ |
M | ui.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;
+}