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