1#include <stdlib.h> 2#include <string.h> 3#include <stdint.h> 4#include "curskey.h" 5#include "utils.h" 6#include "mem.h" 7 8struct curskey_key { 9 char *keyname; 10 int keycode; 11}; 12 13static struct curskey_key *keynames; 14static unsigned int keynames_count; 15 16unsigned int meta_keycode_start; 17static uint64_t invalid_meta_char_mask[2]; 18 19// Names for non-printable/whitespace characters and aliases for existing keys 20static const struct curskey_key keyname_aliases[] = { 21 // Sorted by `keyname` 22 { "DEL", KEY_DEL }, 23 { "DELETE", KEY_DC }, 24 { "ENTER", '\n' }, 25 { "ENTER", '\r' }, 26 { "ESCAPE", KEY_ESCAPE }, 27 { "INSERT", KEY_IC }, 28 { "PAGEDOWN", KEY_NPAGE }, 29 { "PAGEUP", KEY_PPAGE }, 30 { "SPACE", KEY_SPACE }, 31 { "TAB", KEY_TAB } 32}; 33 34#define STARTSWITH_KEY(S) \ 35 ((name[0] == 'K' || name[0] == 'k') && \ 36 (name[1] == 'E' || name[1] == 'e') && \ 37 (name[2] == 'Y' || name[2] == 'y') && \ 38 (name[3] == '_')) 39 40#define IS_META(S) \ 41 ((S[0] == 'M' || S[0] == 'm' || S[0] == 'A' || S[0] == 'a') && S[1] == '-') 42 43static int curskey_key_cmp(const void *a, const void *b) { 44 return strcmp(((struct curskey_key*) a)->keyname, 45 ((struct curskey_key*) b)->keyname); 46} 47 48static int curskey_find(const struct curskey_key *table, unsigned int size, const char *name) { 49 unsigned int start = 0; 50 unsigned int end = size; 51 unsigned int i; 52 int cmp; 53 54 while (1) { 55 i = (start+end) / 2; 56 cmp = strcasecmp(name, table[i].keyname); 57 58 if (cmp == 0) 59 return table[i].keycode; 60 else if (end == start + 1) 61 return ERR; 62 else if (cmp > 0) 63 start = i; 64 else 65 end = i; 66 } 67} 68 69/* Translate the name of a ncurses KEY_ constant to its value. 70 * "KEY_DOWN" -> 258 71 * 72 * Return ERR on failure. 73 */ 74int curskey_keycode(const char *name) 75{ 76 int i; 77 78 if (! name) 79 return ERR; 80 81 if (STARTSWITH_KEY(name)) 82 name += 4; 83 84 if (name[0] == 'F' || name[0] == 'f') { 85 i = (name[1] == '(' ? 2 : 1); 86 87 if (name[i] >= '0' && name[i] <= '9') { 88 i = atoi(name + i); 89 if (i >= 1 && i <= 63) 90 return KEY_F(i); 91 } 92 } 93 94 i = curskey_find(keyname_aliases, ARRAY_SIZE(keyname_aliases), name); 95 if (i != ERR) 96 return i; 97 98 return curskey_find(keynames, keynames_count, name); 99} 100 101static void free_ncurses_keynames() { 102 if (keynames) { 103 while (keynames_count) 104 free(keynames[--keynames_count].keyname); 105 free(keynames); 106 keynames = NULL; 107 } 108} 109 110/* Create the list of ncurses KEY_ constants and their names. 111 * Returns OK on success, ERR on failure. 112 */ 113int create_ncurses_keynames() { 114 int key; 115 char *name; 116 117 free_ncurses_keynames(); 118 keynames = ccalloc(sizeof(struct curskey_key), (KEY_MAX - KEY_MIN)); 119 120 for (key = KEY_MIN; key != KEY_MAX; ++key) { 121 name = (char*) keyname(key); 122 123 if (!name || !STARTSWITH_KEY(name)) 124 continue; 125 126 name += 4; 127 if (name[0] == 'F' && name[1] == '(') 128 continue; // ignore KEY_F(1),... 129 130 keynames[keynames_count].keycode = key; 131 keynames[keynames_count].keyname = cstrdup(name); 132 ++keynames_count; 133 } 134 135 keynames = crealloc(keynames, keynames_count * sizeof(struct curskey_key)); 136 qsort(keynames, keynames_count, sizeof(struct curskey_key), curskey_key_cmp); 137 138 return OK; 139} 140 141/* Defines meta escape sequences in ncurses. 142 * 143 * Some combinations with meta/alt may not be available since they collide 144 * with the prefix of a pre-defined key. 145 * For example, keys F1 - F4 begin with "\eO", so ALT-O cannot be defined. 146 * 147 * Returns OK if meta keys are available, ERR otherwise. 148 */ 149int curskey_define_meta_keys(unsigned int keycode_start) { 150#ifdef NCURSES_VERSION 151 int ch; 152 int keycode; 153 int new_keycode = keycode_start; 154 char key_sequence[3] = "\e "; 155 156 invalid_meta_char_mask[0] = 0; 157 invalid_meta_char_mask[1] = 0; 158 159 for (ch = 0; ch <= CURSKEY_MAX_META_CHAR; ++ch) { 160 key_sequence[1] = ch; 161 keycode = key_defined(key_sequence); 162 if (! keycode) { 163 define_key(key_sequence, new_keycode); 164 } 165 else if (keycode == new_keycode) 166 ; 167 else 168 invalid_meta_char_mask[ch/65] |= (1UL << (ch % 64)); 169 170 ++new_keycode; 171 } 172 173 meta_keycode_start = keycode_start; 174 return OK; 175#endif 176 return ERR; 177} 178 179/* Return the keycode for a key with modifiers applied. 180 * 181 * Available modifiers are: 182 * - CURSKEY_MOD_META / CURSKEY_MOD_ALT 183 * - CURSKEY_MOD_CNTRL 184 * 185 * See also the macros curskey_meta_key(), curskey_cntrl_key(). 186 * 187 * Returns ERR if the modifiers cannot be applied to this key. 188 */ 189int curskey_mod_key(int key, unsigned int modifiers) { 190 if (modifiers & CURSKEY_MOD_CNTRL) { 191 if ((key >= 'A' && key <= '_') || (key >= 'a' && key <= 'z') || key == ' ') 192 key = key % 32; 193 else 194 return ERR; 195 } 196 197 if (modifiers & CURSKEY_MOD_META) { 198 if (meta_keycode_start && 199 (key >= 0 && key <= CURSKEY_MAX_META_CHAR) && 200 ! (invalid_meta_char_mask[key/65] & (1UL << (key % 64)))) { 201 key = meta_keycode_start + key; 202 } 203 else 204 return ERR; 205 } 206 207 return key; 208} 209 210/* Return the ncurses keycode for a key definition. 211 * 212 * Key definition may be: 213 * - Single character (a, z, ...) 214 * - Character with control-modifier (^x, C-x, c-x, ...) 215 * - Character with meta/alt-modifier (M-x, m-x, A-x, a-x, ...) 216 * - Character with both modifiers (C-M-x, M-C-x, M-^x, ...) 217 * - Curses keyname, no modifiers allowed (KEY_HOME, HOME, F1, F(1), ...) 218 * 219 * Returns ERR if either 220 * - The key definition is NULL or empty 221 * - The key could not be found ("KEY_FOO") 222 * - The key combination is invalid in general ("C-TAB", "C-RETURN") 223 * - The key is invalid because of compile time options (the 224 * `define_key()` function was not available.) 225 * - The key is invalid because it could not be defined by 226 * curskey_define_meta_keys() 227 */ 228int curskey_parse(const char *def) { 229 int c; 230 unsigned int mod = 0; 231 232 if (! def) 233 return ERR; 234 235 for (;;) { 236 if (def[0] == '^' && def[1] != '\0') { 237 ++def; 238 mod |= CURSKEY_MOD_CNTRL; 239 } 240 else if ((def[0] == 'C' || def[0] == 'c') && def[1] == '-') { 241 def += 2; 242 mod |= CURSKEY_MOD_CNTRL; 243 } 244 else if (IS_META(def)) { 245 if (! meta_keycode_start) 246 return ERR; 247 def += 2; 248 mod |= CURSKEY_MOD_ALT; 249 } 250 else 251 break; 252 } 253 254 if (*def == '\0') 255 return ERR; 256 else if (*(def+1) == '\0') 257 c = *def; 258 else 259 c = curskey_keycode(def); 260 261 return curskey_mod_key(c, mod); 262} 263 264/* Initialize curskey. 265 * Returns OK on success, ERR on failure. */ 266int curskey_init() { 267 keypad(stdscr, TRUE); 268 return create_ncurses_keynames(); 269} 270 271/* Destroy curskey. */ 272void curskey_destroy() { 273 free_ncurses_keynames(); 274} 275