Added SILC Thread Queue API
[crypto.git] / apps / irssi / src / fe-common / core / keyboard.c
index 0b7ac714c0572563ca79cb7f9558c515561b6476..717bed1f6248ab6602559dc4b0dbc0f9d4c84d9e 100644 (file)
@@ -38,44 +38,15 @@ static GHashTable *keys, *default_keys;
    If the key isn't used, used_keys[key] is zero. */
 static char used_keys[256];
 
-/* contains list of all key bindings of which command is "key" -
-   this can be used to check fast if some command queue exists or not.
-   Format is _always_ in key1-key2-key3 format (like ^W-^N,
-   not ^W^N) */
+/* Contains a list of all possible executable key bindings (not "key" keys).
+   Format is _always_ in key1-key2-key3 format and fully extracted, like
+   ^[-[-A, not meta-A */
 static GTree *key_states;
-/* List of all key combo names */
-static GSList *key_combos;
 static int key_config_frozen;
 
-struct KEYBOARD_REC {
-       /* example:
-          /BIND ^[ key meta
-          /BIND meta-O key meta2
-          /BIND meta-[ key meta2
-
-          /BIND meta2-C key right
-          /BIND ^W-meta-right /echo ^W Meta-right key pressed
-
-          When ^W Meta-Right is pressed, the full char combination
-          is "^W^[^[[C".
-
-          We'll get there with key states:
-            ^W - key_prev_state = NULL, key_state = NULL -> ^W
-            ^[ - key_prev_state = NULL, key_state = ^W -> meta
-            ^[ - key_prev_state = ^W, key_state = meta -> meta
-            [ - key_prev_state = ^W-meta, key_state = meta -> meta2
-            C - key_prev_state = ^W-meta, key_state = meta2 -> right
-            key_prev_state = ^W-meta, key_state = right -> ^W-meta-right
-
-          key_state is moved to key_prev_state if there's nothing else in
-          /BINDs matching for key_state-newkey.
-
-          ^X^Y equals to ^X-^Y, ABC equals to A-B-C unless there's ABC
-          named key. ^ can be used with ^^ and - with -- */
-       char *key_state, *key_prev_state;
-
-        /* GUI specific data sent in "key pressed" signal */
-        void *gui_data;
+struct _KEYBOARD_REC {
+       char *key_state; /* the ongoing key combo */
+        void *gui_data; /* GUI specific data sent in "key pressed" signal */
 };
 
 /* Creates a new "keyboard" - this is used only for keeping track of
@@ -98,7 +69,6 @@ void keyboard_destroy(KEYBOARD_REC *keyboard)
        signal_emit("keyboard destroyed", 1, keyboard);
 
         g_free_not_null(keyboard->key_state);
-        g_free_not_null(keyboard->key_prev_state);
         g_free(keyboard);
 }
 
@@ -144,7 +114,8 @@ static CONFIG_NODE *key_config_find(const char *key)
        /* remove old keyboard settings */
        node = iconfig_node_traverse("(keyboard", TRUE);
 
-       for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+       tmp = config_node_first(node->value);
+       for (; tmp != NULL; tmp = config_node_next(tmp)) {
                node = tmp->data;
 
                if (strcmp(config_node_get_str(node, "key", ""), key) == 0)
@@ -180,8 +151,10 @@ static void keyconfig_clear(const char *key)
 
        /* remove old keyboard settings */
        node = key_config_find(key);
-        if (node != NULL)
-               iconfig_node_clear(node);
+       if (node != NULL) {
+               iconfig_node_remove(iconfig_node_traverse("(keyboard", FALSE),
+                                   node);
+       }
 }
 
 KEYINFO_REC *key_info_find(const char *id)
@@ -198,69 +171,176 @@ KEYINFO_REC *key_info_find(const char *id)
        return NULL;
 }
 
-static KEY_REC *key_combo_find(const char *key)
+static int expand_key(const char *key, GSList **out);
+
+#define expand_out_char(out, c) \
+       { \
+         GSList *tmp; \
+         for (tmp = out; tmp != NULL; tmp = tmp->next) \
+            g_string_append_c(tmp->data, c); \
+       }
+
+#define expand_out_free(out) \
+       { \
+         GSList *tmp; \
+         for (tmp = out; tmp != NULL; tmp = tmp->next) \
+            g_string_free(tmp->data, TRUE); \
+         g_slist_free(out); out = NULL; \
+       }
+
+static int expand_combo(const char *start, const char *end, GSList **out)
 {
+        KEY_REC *rec;
        KEYINFO_REC *info;
-        GSList *tmp;
+        GSList *tmp, *tmp2, *list, *copy, *newout;
+       char *str, *p;
+
+       if (start == end) {
+               /* single key */
+               expand_out_char(*out, *start);
+                return TRUE;
+       }
 
        info = key_info_find("key");
        if (info == NULL)
-               return NULL;
+               return FALSE;
 
+        /* get list of all key combos that generate the named combo.. */
+        list = NULL;
+       str = g_strndup(start, (int) (end-start)+1);
        for (tmp = info->keys; tmp != NULL; tmp = tmp->next) {
                KEY_REC *rec = tmp->data;
 
-               if (strcmp(rec->data, key) == 0)
-                        return rec;
+               if (strcmp(rec->data, str) == 0)
+                        list = g_slist_append(list, rec);
        }
 
-        return NULL;
-}
+       if (list == NULL) {
+               /* unknown keycombo - add it as-is, maybe the GUI will
+                  feed it to us as such */
+               for (p = str; *p != '\0'; p++)
+                       expand_out_char(*out, *p);
+               g_free(str);
+               return TRUE;
+       }
+       g_free(str);
 
-static void key_states_scan_key(const char *key, KEY_REC *rec, GString *temp)
-{
-       char **keys, **tmp, *p;
+       if (list->next == NULL) {
+                /* only one way to generate the combo, good */
+                rec = list->data;
+               return expand_key(rec->key, out);
+       }
 
-       g_string_truncate(temp, 0);
+       /* multiple ways to generate the combo -
+          we'll need to include all of them in output */
+        newout = NULL;
+       for (tmp = list->next; tmp != NULL; tmp = tmp->next) {
+               KEY_REC *rec = tmp->data;
 
-       /* meta-^W^Gfoo -> meta-^W-^G-f-o-o */
-       keys = g_strsplit(key, "-", -1);
-       for (tmp = keys; *tmp != NULL; tmp++) {
-               if (key_combo_find(*tmp)) {
-                        /* key combo */
-                       g_string_append(temp, *tmp);
-                        g_string_append_c(temp, '-');
-                        continue;
+               copy = NULL;
+               for (tmp2 = *out; tmp2 != NULL; tmp2 = tmp2->next) {
+                       GString *str = tmp2->data;
+                        copy = g_slist_append(copy, g_string_new(str->str));
                }
 
-               if (**tmp == '\0') {
-                        /* '-' */
-                       g_string_append(temp, "--");
-                        continue;
+               if (!expand_key(rec->key, &copy)) {
+                       /* illegal key combo, remove from list */
+                        expand_out_free(copy);
+               } else {
+                        newout = g_slist_concat(newout, copy);
                }
+       }
+
+        rec = list->data;
+       if (!expand_key(rec->key, out)) {
+               /* illegal key combo, remove from list */
+               expand_out_free(*out);
+       }
 
-               for (p = *tmp; *p != '\0'; p++) {
-                       g_string_append_c(temp, *p);
+       *out = g_slist_concat(*out, newout);
+        return *out != NULL;
+}
 
-                       if (*p == '^') {
-                                /* ctrl-code */
-                               if (p[1] != '\0')
-                                       p++;
-                               g_string_append_c(temp, *p);
+/* Expand key code - returns TRUE if successful. */
+static int expand_key(const char *key, GSList **out)
+{
+       GSList *tmp;
+       const char *start;
+       int last_hyphen;
+
+       /* meta-^W^Gf -> ^[-^W-^G-f */
+        start = NULL; last_hyphen = TRUE;
+       for (; *key != '\0'; key++) {
+               if (start != NULL) {
+                       if (i_isalnum(*key) || *key == '_') {
+                                /* key combo continues */
+                               continue;
                        }
 
-                       g_string_append_c(temp, '-');
+                       if (!expand_combo(start, key-1, out))
+                                return FALSE;
+                       expand_out_char(*out, '-');
+                        start = NULL;
+               }
+
+               if (*key == '-') {
+                       if (last_hyphen) {
+                                expand_out_char(*out, '-');
+                                expand_out_char(*out, '-');
+                       }
+                       last_hyphen = !last_hyphen;
+               } else if (*key == '^') {
+                        /* ctrl-code */
+                       if (key[1] != '\0')
+                               key++;
+
+                       expand_out_char(*out, '^');
+                       expand_out_char(*out, *key);
+                       expand_out_char(*out, '-');
+                        last_hyphen = FALSE; /* optional */
+               } else if (last_hyphen && i_isalnum(*key) && !i_isdigit(*key)) {
+                        /* possibly beginning of keycombo */
+                       start = key;
+                        last_hyphen = FALSE;
+               } else {
+                       expand_out_char(*out, *key);
+                       expand_out_char(*out, '-');
+                        last_hyphen = FALSE; /* optional */
                }
        }
-       g_strfreev(keys);
 
-       if (temp->len > 0) {
-               g_string_truncate(temp, temp->len-1);
+       if (start != NULL)
+               return expand_combo(start, key-1, out);
+
+       for (tmp = *out; tmp != NULL; tmp = tmp->next) {
+               GString *str = tmp->data;
+
+               g_string_truncate(str, str->len-1);
+       }
+
+        return TRUE;
+}
 
-               if (temp->str[1] == '-' || temp->str[1] == '\0')
-                        used_keys[(int) (unsigned char) temp->str[0]] = 1;
-               g_tree_insert(key_states, g_strdup(temp->str), rec);
+static void key_states_scan_key(const char *key, KEY_REC *rec)
+{
+       GSList *tmp, *out;
+
+       if (strcmp(rec->info->id, "key") == 0)
+               return;
+
+        out = g_slist_append(NULL, g_string_new(NULL));
+       if (expand_key(key, &out)) {
+               for (tmp = out; tmp != NULL; tmp = tmp->next) {
+                       GString *str = tmp->data;
+
+                       if (str->str[1] == '-' || str->str[1] == '\0')
+                               used_keys[(int)(unsigned char)str->str[0]] = 1;
+
+                       g_tree_insert(key_states, g_strdup(str->str), rec);
+               }
        }
+
+       expand_out_free(out);
 }
 
 static int key_state_destroy(char *key)
@@ -308,6 +388,8 @@ static void key_configure_destroy(KEY_REC *rec)
        rec->info->keys = g_slist_remove(rec->info->keys, rec);
        g_hash_table_remove(keys, rec->key);
 
+       signal_emit("key destroyed", 1, rec);
+
        if (!key_config_frozen)
                 key_states_rescan();
 
@@ -341,6 +423,8 @@ static void key_configure_create(const char *id, const char *key,
        info->keys = g_slist_append(info->keys, rec);
        g_hash_table_insert(keys, rec->key, rec);
 
+       signal_emit("key created", 1, rec);
+
        if (!key_config_frozen)
                 key_states_rescan();
 }
@@ -455,7 +539,8 @@ static int key_emit_signal(KEYBOARD_REC *keyboard, KEY_REC *key)
         return consumed;
 }
 
-int key_states_search(const char *combo, const char *search)
+static int key_states_search(const unsigned char *combo,
+                            const unsigned char *search)
 {
        while (*search != '\0') {
                if (*combo != *search)
@@ -463,104 +548,54 @@ int key_states_search(const char *combo, const char *search)
                 search++; combo++;
        }
 
-       return *combo == '\0' || *combo == '-' ? 0 : -1;
+        return 0;
 }
 
-/* Returns TRUE if key press was consumed. Control characters should be sent
-   as "^@" .. "^_" instead of #0..#31 chars, #127 should be sent as ^? */
 int key_pressed(KEYBOARD_REC *keyboard, const char *key)
 {
        KEY_REC *rec;
-       char *str;
-        int consumed;
+        char *combo;
+        int first_key, consumed;
 
        g_return_val_if_fail(keyboard != NULL, FALSE);
        g_return_val_if_fail(key != NULL && *key != '\0', FALSE);
 
-       if (keyboard->key_state == NULL) {
-               if (key[1] == '\0' &&
-                   !used_keys[(int) (unsigned char) key[0]]) {
-                        /* fast check - key not used */
-                       return FALSE;
-               }
-
-               rec = g_tree_search(key_states,
-                                   (GSearchFunc) key_states_search,
-                                   (void *) key);
-               if (rec == NULL ||
-                   (g_tree_lookup(key_states, (void *) key) != NULL &&
-                    strcmp(rec->info->id, "key") != 0)) {
-                       /* a single non-combo key was pressed */
-                       rec = g_hash_table_lookup(keys, key);
-                       if (rec == NULL)
-                               return FALSE;
-                       consumed = key_emit_signal(keyboard, rec);
-
-                       /* never consume non-control characters */
-                       return consumed && key[1] != '\0';
-               }
-       }
-
-       if (keyboard->key_state == NULL) {
-                /* first key in combo */
-               rec = g_tree_lookup(key_states, (void *) key);
-       } else {
-               /* continuing key combination */
-               str = g_strconcat(keyboard->key_state, "-", key, NULL);
-               rec = g_tree_lookup(key_states, str);
-               g_free(str);
+       if (keyboard->key_state == NULL && key[1] == '\0' &&
+           !used_keys[(int) (unsigned char) key[0]]) {
+               /* fast check - key not used */
+               return -1;
        }
 
-       if (rec != NULL && strcmp(rec->info->id, "key") == 0) {
-               /* combo has a specified name, use it */
-               g_free_not_null(keyboard->key_state);
-               keyboard->key_state = g_strdup(rec->data);
-       } else {
-               /* some unnamed key - move key_state after key_prev_state
-                  and replace key_state with this new key */
-               if (keyboard->key_prev_state == NULL)
-                       keyboard->key_prev_state = keyboard->key_state;
-               else {
-                       str = g_strconcat(keyboard->key_prev_state, "-",
-                                         keyboard->key_state, NULL);
-                       g_free(keyboard->key_prev_state);
-                       g_free(keyboard->key_state);
-                       keyboard->key_prev_state = str;
-               }
-
-               keyboard->key_state = g_strdup(key);
+        first_key = keyboard->key_state == NULL;
+       combo = keyboard->key_state == NULL ? g_strdup(key) :
+                g_strconcat(keyboard->key_state, "-", key, NULL);
+       g_free_and_null(keyboard->key_state);
+
+#if GLIB_MAJOR_VERSION == 2
+#  define GSearchFunc GCompareFunc
+#endif
+       rec = g_tree_search(key_states,
+                           (GSearchFunc) key_states_search,
+                           combo);
+       if (rec == NULL) {
+               /* unknown key combo, eat the invalid key
+                  unless it was the first key pressed */
+                g_free(combo);
+               return first_key ? -1 : 1;
        }
 
-        /* what to do with the key combo? */
-       str = keyboard->key_prev_state == NULL ?
-               g_strdup(keyboard->key_state) :
-               g_strconcat(keyboard->key_prev_state, "-",
-                           keyboard->key_state, NULL);
-
-       rec = g_tree_lookup(key_states, str);
-       if (rec != NULL) {
-               if (strcmp(rec->info->id, "key") == 0)
-                       rec = g_tree_lookup(key_states, rec->data);
-
-               if (rec != NULL) {
-                       /* full key combo */
-                       key_emit_signal(keyboard, rec);
-                       rec = NULL;
-               }
-       } else {
-                /* check that combo is possible */
-               rec = g_tree_search(key_states,
-                                   (GSearchFunc) key_states_search, str);
+       if (g_tree_lookup(key_states, combo) != rec) {
+               /* key combo continues.. */
+               keyboard->key_state = combo;
+                return 0;
        }
 
-       if (rec == NULL) {
-               /* a) key combo finished, b) unknown key combo, abort */
-               g_free_and_null(keyboard->key_prev_state);
-               g_free_and_null(keyboard->key_state);
-       }
+        /* finished key combo, execute */
+        g_free(combo);
+       consumed = key_emit_signal(keyboard, rec);
 
-       g_free(str);
-        return TRUE;
+       /* never consume non-control characters */
+       return consumed ? 1 : -1;
 }
 
 void keyboard_entry_redirect(SIGNAL_FUNC func, const char *entry,
@@ -609,11 +644,17 @@ static void sig_multi(const char *data, void *gui_data)
         g_strfreev(list);
 }
 
+static void sig_nothing(const char *data)
+{
+}
+
 static void cmd_show_keys(const char *searchkey, int full)
 {
        GSList *info, *key;
         int len;
 
+       printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_BIND_HEADER);
+
        len = searchkey == NULL ? 0 : strlen(searchkey);
        for (info = keyinfos; info != NULL; info = info->next) {
                KEYINFO_REC *rec = info->data;
@@ -623,11 +664,13 @@ static void cmd_show_keys(const char *searchkey, int full)
 
                        if ((len == 0 || g_strncasecmp(rec->key, searchkey, len) == 0) &&
                            (!full || rec->key[len] == '\0')) {
-                               printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_BIND_KEY,
+                               printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_BIND_LIST,
                                            rec->key, rec->info->id, rec->data == NULL ? "" : rec->data);
                        }
                }
        }
+
+       printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_BIND_FOOTER);
 }
 
 /* SYNTAX: BIND [-delete] [<key> [<command> [<data>]]] */
@@ -659,7 +702,7 @@ static void cmd_bind(const char *data)
        command_id = strchr(settings_get_str("cmdchars"), *id) != NULL;
        if (command_id) {
                /* using shortcut to command id */
-               keydata = g_strconcat(id, " ", keydata, NULL);
+               keydata = g_strconcat(id+1, " ", keydata, NULL);
                id = "command";
        }
 
@@ -771,7 +814,8 @@ static void read_keyboard_config(void)
                return;
        }
 
-       for (tmp = node->value; tmp != NULL; tmp = tmp->next)
+       tmp = config_node_first(node->value);
+       for (; tmp != NULL; tmp = config_node_next(tmp))
                key_config_read(tmp->data);
 
         key_configure_thaw();
@@ -785,13 +829,13 @@ void keyboard_init(void)
                                        (GCompareFunc) g_str_equal);
        keyinfos = NULL;
        key_states = g_tree_new((GCompareFunc) strcmp);
-       key_combos = NULL;
         key_config_frozen = 0;
        memset(used_keys, 0, sizeof(used_keys));
 
        key_bind("command", "Run any IRC command", NULL, NULL, (SIGNAL_FUNC) sig_command);
        key_bind("key", "Specify name for key binding", NULL, NULL, (SIGNAL_FUNC) sig_key);
        key_bind("multi", "Run multiple commands", NULL, NULL, (SIGNAL_FUNC) sig_multi);
+       key_bind("nothing", "Do nothing", NULL, NULL, (SIGNAL_FUNC) sig_nothing);
 
        /* read the keyboard config when all key binds are known */
        signal_add("irssi init read settings", (SIGNAL_FUNC) read_keyboard_config);
@@ -804,6 +848,11 @@ void keyboard_init(void)
 
 void keyboard_deinit(void)
 {
+       key_unbind("command", (SIGNAL_FUNC) sig_command);
+       key_unbind("key", (SIGNAL_FUNC) sig_key);
+       key_unbind("multi", (SIGNAL_FUNC) sig_multi);
+       key_unbind("nothing", (SIGNAL_FUNC) sig_nothing);
+
        while (keyinfos != NULL)
                keyinfo_remove(keyinfos->data);
        g_hash_table_destroy(keys);