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