aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/binds.c284
-rw-r--r--src/binds.h40
2 files changed, 324 insertions, 0 deletions
diff --git a/src/binds.c b/src/binds.c
new file mode 100644
index 0000000..23ee43f
--- /dev/null
+++ b/src/binds.c
@@ -0,0 +1,284 @@
+#include "binds.h"
+#include "list.h"
+
+#include <stdbool.h>
+
+struct bind_node {
+ char *key; /* input key to reach this node */
+ char *command; /* command to run for this node, or NULL if not leaf node */
+ struct list *suffixes; /* list of bind_node* suffixes, or NULL if leaf node */
+};
+
+struct imv_binds {
+ struct bind_node bind_tree;
+ struct list *keys;
+};
+
+static int compare_node_key(const void* item, const void *key)
+{
+ const struct bind_node *node = item;
+ const char *keystr = key;
+ return strcmp(node->key, keystr);
+}
+
+static void init_bind_node(struct bind_node *bn)
+{
+ bn->key = NULL;
+ bn->command = NULL;
+ bn->suffixes = list_create();
+}
+
+static void destroy_bind_node(struct bind_node *bn)
+{
+ for(size_t i = 0; i < bn->suffixes->len; ++i) {
+ destroy_bind_node(bn->suffixes->items[i]);
+ }
+ free(bn->key);
+ free(bn->command);
+ list_deep_free(bn->suffixes);
+}
+
+struct imv_binds *imv_binds_create(void)
+{
+ struct imv_binds *binds = malloc(sizeof(struct imv_binds));
+ init_bind_node(&binds->bind_tree);
+ binds->keys = list_create();
+ return binds;
+}
+
+void imv_binds_free(struct imv_binds *binds)
+{
+ destroy_bind_node(&binds->bind_tree);
+ free(binds);
+}
+
+enum bind_result imv_binds_add(struct imv_binds *binds, const struct list *keys, const char *command)
+{
+ if(!command) {
+ return BIND_INVALID_COMMAND;
+ }
+
+ if(!keys) {
+ return BIND_INVALID_KEYS;
+ }
+
+ /* Prepare our return code */
+ int result = BIND_SUCCESS;
+
+ /* Traverse the trie */
+ struct bind_node *node = &binds->bind_tree;
+ for(size_t i = 0; i < keys->len; ++i) {
+ /* If we've reached a node that already has a command, there's a conflict */
+ if(node->command) {
+ result = BIND_INVALID_COMMAND;
+ break;
+ }
+
+ /* Find / create a child with the current key */
+ struct bind_node *next_node = NULL;
+ int child_index = list_find(node->suffixes, &compare_node_key, keys->items[i]);
+ if(child_index == -1) {
+ /* Create our new node */
+ next_node = malloc(sizeof(struct bind_node));
+ init_bind_node(next_node);
+ next_node->key = strdup(keys->items[i]);
+ list_append(node->suffixes, next_node);
+ } else {
+ next_node = node->suffixes->items[child_index];
+ }
+
+ /* We've now found the correct node for this key */
+
+ /* Check if the node has a command */
+ if(next_node->command) {
+ if(i + 1 < keys->len) {
+ /* If we're not at the end, it's a conflict */
+ result = BIND_CONFLICTS;
+ break;
+ } else {
+ /* Otherwise we just need to overwrite the existing bind. */
+ free(next_node->command);
+ next_node->command = strdup(command);
+ result = BIND_SUCCESS;
+ break;
+ }
+ }
+
+ if(i + 1 == keys->len) {
+ /* this is the last key part, try to insert command */
+ /* but first, make sure there's no children */
+ if(next_node->suffixes->len > 0) {
+ result = BIND_CONFLICTS;
+ } else {
+ next_node->command = strdup(command);
+ }
+ } else {
+ /* Otherwise, move down the trie */
+ node = next_node;
+ }
+ }
+
+ return result;
+}
+
+enum lookup_result {
+ LOOKUP_PARTIAL,
+ LOOKUP_INVALID,
+ LOOKUP_MATCH,
+};
+
+static enum lookup_result bind_lookup(struct bind_node *node, struct list *keys, const char **out_str)
+{
+ for(size_t part = 0; part < keys->len; ++part) {
+ const char* cur_key = keys->items[part];
+ int found = 0;
+ for(size_t i = 0; i < node->suffixes->len; ++i) {
+ struct bind_node* cur_node = node->suffixes->items[i];
+ if(strcmp(cur_node->key, cur_key) == 0) {
+ node = node->suffixes->items[i];
+ found = 1;
+ break;
+ }
+ }
+ if(!found) {
+ return LOOKUP_INVALID;
+ }
+ }
+
+ if(node->command) {
+ *out_str = node->command;
+ return LOOKUP_MATCH;
+ }
+ return LOOKUP_PARTIAL;
+}
+
+static int print_event(char *buf, size_t len, const SDL_Event *event)
+{
+ /* only accept keydown events */
+ if(event->type != SDL_KEYDOWN) {
+ buf[0] = 0;
+ return 0;
+ }
+
+ const SDL_KeyboardEvent *kevent = &event->key;
+
+ /* filter out modifier keys */
+ switch(kevent->keysym.sym) {
+ case SDLK_LCTRL:
+ case SDLK_RCTRL:
+ case SDLK_LALT:
+ case SDLK_RALT:
+ case SDLK_LSHIFT:
+ case SDLK_RSHIFT:
+ return 0;
+ }
+
+ /* Build prefix first: */
+ char prefix[16] = {0};
+ snprintf(prefix, sizeof prefix, "%s%s%s",
+ SDL_GetModState() & KMOD_CTRL ? "Ctrl+" : "",
+ SDL_GetModState() & KMOD_ALT ? "Meta+" : "",
+ SDL_GetModState() & KMOD_SHIFT ? "Shift+" : "");
+
+ /* Try plain old character input */
+ const char *keyname = SDL_GetKeyName(kevent->keysym.sym);
+
+ /* Because '<' and '>' have special meaning in our syntax, and '=' is
+ * restricted within ini files, we rename these.
+ */
+ if(!strcmp(keyname, "<")) {
+ keyname = "Less";
+ } else if(!strcmp(keyname, ">")) {
+ keyname = "Greater";
+ } else if(!strcmp(keyname, "=")) {
+ keyname = "Equals";
+ }
+
+ return snprintf(buf, len, "%s%s", prefix, keyname);
+}
+
+void imv_bind_clear_input(struct imv_binds *binds)
+{
+ list_deep_free(binds->keys);
+ binds->keys = list_create();
+}
+
+const char *imv_bind_handle_event(struct imv_binds *binds, const SDL_Event *event)
+{
+ if(event->key.keysym.sym == SDLK_ESCAPE) {
+ imv_bind_clear_input(binds);
+ return NULL;
+ }
+
+ char buffer[128];
+ print_event(buffer, sizeof(buffer), event);
+ list_append(binds->keys, strdup(buffer));
+
+ const char *command = NULL;
+ enum lookup_result result = bind_lookup(&binds->bind_tree, binds->keys, &command);
+ if(result == LOOKUP_PARTIAL) {
+ return NULL;
+ } else if(result == LOOKUP_MATCH) {
+ imv_bind_clear_input(binds);
+ return command;
+ } else if(result == LOOKUP_INVALID) {
+ imv_bind_clear_input(binds);
+ return NULL;
+ }
+
+ /* Should not happen */
+ imv_bind_clear_input(binds);
+ return NULL;
+}
+
+struct list *imv_bind_parse_keys(const char *keys)
+{
+ struct list *list = list_create();
+
+ /* Iterate through the string, breaking it into its parts */
+ while(*keys) {
+
+ if(*keys == '<') {
+ /* Keyname block, need to extract the name, and convert it */
+ const char *end = keys;
+ while(*end && *end != '>') {
+ ++end;
+ }
+ if(*end == '>') {
+ /* We've got a <stuff> block. Check if it's a valid special key name. */
+ const size_t key_len = end - keys;
+ char *key = malloc(key_len);
+ memcpy(key, keys + 1 /* skip the '<' */, key_len - 1);
+ key[key_len - 1] = 0;
+ list_append(list, key);
+ keys = end + 1;
+ } else {
+ /* A <key> block that didn't have a closing '<'. Abort. */
+ list_deep_free(list);
+ return NULL;
+ }
+ } else {
+ /* Just a regular character */
+ char *item = malloc(2);
+ item[0] = *keys;
+ item[1] = 0;
+ list_append(list, item);
+ ++keys;
+ }
+ }
+
+ return list;
+}
+
+size_t imv_bind_print_keylist(const struct list *keys, char *buf, size_t len)
+{
+ size_t printed = 0;
+
+ /* iterate through all the keys, wrapping them in '<' and '>' if needed */
+ for(size_t i = 0; i < keys->len; ++i) {
+ const char *key = keys->items[i];
+ const char *format = strlen(key) > 1 ? "<%s>" : "%s";
+ printed += snprintf(buf, len - printed, format, key);
+ }
+ return printed;
+}
diff --git a/src/binds.h b/src/binds.h
new file mode 100644
index 0000000..cf41c74
--- /dev/null
+++ b/src/binds.h
@@ -0,0 +1,40 @@
+#ifndef IMV_BINDS_H
+#define IMV_BINDS_H
+
+#include <SDL2/SDL.h>
+
+struct imv_binds;
+struct list;
+
+enum bind_result {
+ BIND_SUCCESS,
+ BIND_INVALID_KEYS,
+ BIND_INVALID_COMMAND,
+ BIND_CONFLICTS,
+};
+
+/* Create an imv_binds instance */
+struct imv_binds *imv_binds_create(void);
+
+/* Clean up an imv_binds instance */
+void imv_binds_free(struct imv_binds *binds);
+
+/* Create a key binding */
+enum bind_result imv_binds_add(struct imv_binds *binds, const struct list *keys, const char *cmd);
+
+/* Fetch the list of keys pressed so far */
+const struct list *imv_bind_input_buffer(struct imv_binds *binds);
+
+/* Abort the current input key sequence */
+void imv_bind_clear_input(struct imv_binds *binds);
+
+/* Handle an input event, if a bind is triggered, return its command */
+const char *imv_bind_handle_event(struct imv_binds *binds, const SDL_Event *event);
+
+/* Convert a string (such as from a config) to a key list */
+struct list *imv_bind_parse_keys(const char *keys);
+
+/* Convert a key list to a string */
+size_t imv_bind_print_keylist(const struct list *keys, char *buf, size_t len);
+
+#endif