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
signal_emit("keyboard destroyed", 1, keyboard);
g_free_not_null(keyboard->key_state);
- g_free_not_null(keyboard->key_prev_state);
g_free(keyboard);
}
/* 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)
/* 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)
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, ©)) {
+ /* 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)
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();
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();
}
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)
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,
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;
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>]]] */
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";
}
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();
(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);
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);