• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
8 struct curskey_key {
9 	char *keyname;
10 	int keycode;
11 };
12 
13 static struct curskey_key *keynames;
14 static unsigned int keynames_count;
15 
16 unsigned int meta_keycode_start;
17 static uint64_t invalid_meta_char_mask[2];
18 
19 // Names for non-printable/whitespace characters and aliases for existing keys
20 static 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 
curskey_key_cmp(const void * a,const void * b)43 static 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 
curskey_find(const struct curskey_key * table,unsigned int size,const char * name)48 static 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  */
curskey_keycode(const char * name)74 int 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 
free_ncurses_keynames()101 static 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  */
create_ncurses_keynames()113 int 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  */
curskey_define_meta_keys(unsigned int keycode_start)149 int 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  */
curskey_mod_key(int key,unsigned int modifiers)189 int 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  */
curskey_parse(const char * def)228 int 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.  */
curskey_init()266 int curskey_init() {
267 	keypad(stdscr, TRUE);
268 	return create_ncurses_keynames();
269 }
270 
271 /* Destroy curskey.  */
curskey_destroy()272 void curskey_destroy() {
273 	free_ncurses_keynames();
274 }
275