key-string.c revision 1.63
1/* $OpenBSD: key-string.c,v 1.63 2020/07/06 07:27:39 nicm Exp $ */
2
3/*
4 * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/types.h>
20
21#include <stdlib.h>
22#include <string.h>
23#include <wchar.h>
24
25#include "tmux.h"
26
27static key_code	key_string_search_table(const char *);
28static key_code	key_string_get_modifiers(const char **);
29
30static const struct {
31	const char     *string;
32	key_code	key;
33} key_string_table[] = {
34	/* Function keys. */
35	{ "F1",		KEYC_F1|KEYC_IMPLIED_META },
36	{ "F2",		KEYC_F2|KEYC_IMPLIED_META },
37	{ "F3",		KEYC_F3|KEYC_IMPLIED_META },
38	{ "F4",		KEYC_F4|KEYC_IMPLIED_META },
39	{ "F5",		KEYC_F5|KEYC_IMPLIED_META },
40	{ "F6",		KEYC_F6|KEYC_IMPLIED_META },
41	{ "F7",		KEYC_F7|KEYC_IMPLIED_META },
42	{ "F8",		KEYC_F8|KEYC_IMPLIED_META },
43	{ "F9",		KEYC_F9|KEYC_IMPLIED_META },
44	{ "F10",	KEYC_F10|KEYC_IMPLIED_META },
45	{ "F11",	KEYC_F11|KEYC_IMPLIED_META },
46	{ "F12",	KEYC_F12|KEYC_IMPLIED_META },
47	{ "IC",		KEYC_IC|KEYC_IMPLIED_META },
48	{ "Insert",     KEYC_IC|KEYC_IMPLIED_META },
49	{ "DC",		KEYC_DC|KEYC_IMPLIED_META },
50	{ "Delete",     KEYC_DC|KEYC_IMPLIED_META },
51	{ "Home",	KEYC_HOME|KEYC_IMPLIED_META },
52	{ "End",	KEYC_END|KEYC_IMPLIED_META },
53	{ "NPage",	KEYC_NPAGE|KEYC_IMPLIED_META },
54	{ "PageDown",	KEYC_NPAGE|KEYC_IMPLIED_META },
55	{ "PgDn",	KEYC_NPAGE|KEYC_IMPLIED_META },
56	{ "PPage",	KEYC_PPAGE|KEYC_IMPLIED_META },
57	{ "PageUp",	KEYC_PPAGE|KEYC_IMPLIED_META },
58	{ "PgUp",	KEYC_PPAGE|KEYC_IMPLIED_META },
59	{ "Tab",	'\011' },
60	{ "BTab",	KEYC_BTAB },
61	{ "Space",	' ' },
62	{ "BSpace",	KEYC_BSPACE },
63	{ "Enter",	'\r' },
64	{ "Escape",	'\033' },
65
66	/* Arrow keys. */
67	{ "Up",		KEYC_UP|KEYC_CURSOR|KEYC_IMPLIED_META },
68	{ "Down",	KEYC_DOWN|KEYC_CURSOR|KEYC_IMPLIED_META },
69	{ "Left",	KEYC_LEFT|KEYC_CURSOR|KEYC_IMPLIED_META },
70	{ "Right",	KEYC_RIGHT|KEYC_CURSOR|KEYC_IMPLIED_META },
71
72	/* Numeric keypad. */
73	{ "KP/", 	KEYC_KP_SLASH|KEYC_KEYPAD },
74	{ "KP*",	KEYC_KP_STAR|KEYC_KEYPAD },
75	{ "KP-",	KEYC_KP_MINUS|KEYC_KEYPAD },
76	{ "KP7",	KEYC_KP_SEVEN|KEYC_KEYPAD },
77	{ "KP8",	KEYC_KP_EIGHT|KEYC_KEYPAD },
78	{ "KP9",	KEYC_KP_NINE|KEYC_KEYPAD },
79	{ "KP+",	KEYC_KP_PLUS|KEYC_KEYPAD },
80	{ "KP4",	KEYC_KP_FOUR|KEYC_KEYPAD },
81	{ "KP5",	KEYC_KP_FIVE|KEYC_KEYPAD },
82	{ "KP6",	KEYC_KP_SIX|KEYC_KEYPAD },
83	{ "KP1",	KEYC_KP_ONE|KEYC_KEYPAD },
84	{ "KP2",	KEYC_KP_TWO|KEYC_KEYPAD },
85	{ "KP3",	KEYC_KP_THREE|KEYC_KEYPAD },
86	{ "KPEnter",	KEYC_KP_ENTER|KEYC_KEYPAD },
87	{ "KP0",	KEYC_KP_ZERO|KEYC_KEYPAD },
88	{ "KP.",	KEYC_KP_PERIOD|KEYC_KEYPAD },
89
90	/* Mouse keys. */
91	KEYC_MOUSE_STRING(MOUSEDOWN1, MouseDown1),
92	KEYC_MOUSE_STRING(MOUSEDOWN2, MouseDown2),
93	KEYC_MOUSE_STRING(MOUSEDOWN3, MouseDown3),
94	KEYC_MOUSE_STRING(MOUSEUP1, MouseUp1),
95	KEYC_MOUSE_STRING(MOUSEUP2, MouseUp2),
96	KEYC_MOUSE_STRING(MOUSEUP3, MouseUp3),
97	KEYC_MOUSE_STRING(MOUSEDRAG1, MouseDrag1),
98	KEYC_MOUSE_STRING(MOUSEDRAG2, MouseDrag2),
99	KEYC_MOUSE_STRING(MOUSEDRAG3, MouseDrag3),
100	KEYC_MOUSE_STRING(MOUSEDRAGEND1, MouseDragEnd1),
101	KEYC_MOUSE_STRING(MOUSEDRAGEND2, MouseDragEnd2),
102	KEYC_MOUSE_STRING(MOUSEDRAGEND3, MouseDragEnd3),
103	KEYC_MOUSE_STRING(WHEELUP, WheelUp),
104	KEYC_MOUSE_STRING(WHEELDOWN, WheelDown),
105	KEYC_MOUSE_STRING(SECONDCLICK1, SecondClick1),
106	KEYC_MOUSE_STRING(SECONDCLICK2, SecondClick2),
107	KEYC_MOUSE_STRING(SECONDCLICK3, SecondClick3),
108	KEYC_MOUSE_STRING(DOUBLECLICK1, DoubleClick1),
109	KEYC_MOUSE_STRING(DOUBLECLICK2, DoubleClick2),
110	KEYC_MOUSE_STRING(DOUBLECLICK3, DoubleClick3),
111	KEYC_MOUSE_STRING(TRIPLECLICK1, TripleClick1),
112	KEYC_MOUSE_STRING(TRIPLECLICK2, TripleClick2),
113	KEYC_MOUSE_STRING(TRIPLECLICK3, TripleClick3),
114};
115
116/* Find key string in table. */
117static key_code
118key_string_search_table(const char *string)
119{
120	u_int	i, user;
121
122	for (i = 0; i < nitems(key_string_table); i++) {
123		if (strcasecmp(string, key_string_table[i].string) == 0)
124			return (key_string_table[i].key);
125	}
126
127	if (sscanf(string, "User%u", &user) == 1 && user < KEYC_NUSER)
128		return (KEYC_USER + user);
129
130	return (KEYC_UNKNOWN);
131}
132
133/* Find modifiers. */
134static key_code
135key_string_get_modifiers(const char **string)
136{
137	key_code	modifiers;
138
139	modifiers = 0;
140	while (((*string)[0] != '\0') && (*string)[1] == '-') {
141		switch ((*string)[0]) {
142		case 'C':
143		case 'c':
144			modifiers |= KEYC_CTRL;
145			break;
146		case 'M':
147		case 'm':
148			modifiers |= KEYC_META;
149			break;
150		case 'S':
151		case 's':
152			modifiers |= KEYC_SHIFT;
153			break;
154		default:
155			*string = NULL;
156			return (0);
157		}
158		*string += 2;
159	}
160	return (modifiers);
161}
162
163/* Lookup a string and convert to a key value. */
164key_code
165key_string_lookup_string(const char *string)
166{
167	static const char	*other = "!#()+,-.0123456789:;<=>'\r\t";
168	key_code		 key, modifiers;
169	u_int			 u, i;
170	struct utf8_data	 ud, *udp;
171	enum utf8_state		 more;
172	utf8_char		 uc;
173	char			 m[MB_LEN_MAX + 1];
174	int			 mlen;
175
176	/* Is this no key or any key? */
177	if (strcasecmp(string, "None") == 0)
178		return (KEYC_NONE);
179	if (strcasecmp(string, "Any") == 0)
180		return (KEYC_ANY);
181
182	/* Is this a hexadecimal value? */
183	if (string[0] == '0' && string[1] == 'x') {
184	        if (sscanf(string + 2, "%x", &u) != 1)
185	                return (KEYC_UNKNOWN);
186		mlen = wctomb(m, u);
187		if (mlen <= 0 || mlen > MB_LEN_MAX)
188			return (KEYC_UNKNOWN);
189		m[mlen] = '\0';
190
191		udp = utf8_fromcstr(m);
192		if (udp == NULL ||
193		    udp[0].size == 0 ||
194		    udp[1].size != 0 ||
195		    utf8_from_data(&udp[0], &uc) != UTF8_DONE) {
196			free(udp);
197			return (KEYC_UNKNOWN);
198		}
199		free(udp);
200		return (uc);
201	}
202
203	/* Check for modifiers. */
204	modifiers = 0;
205	if (string[0] == '^' && string[1] != '\0') {
206		modifiers |= KEYC_CTRL;
207		string++;
208	}
209	modifiers |= key_string_get_modifiers(&string);
210	if (string == NULL || string[0] == '\0')
211		return (KEYC_UNKNOWN);
212
213	/* Is this a standard ASCII key? */
214	if (string[1] == '\0' && (u_char)string[0] <= 127) {
215		key = (u_char)string[0];
216		if (key < 32)
217			return (KEYC_UNKNOWN);
218	} else {
219		/* Try as a UTF-8 key. */
220		if ((more = utf8_open(&ud, (u_char)*string)) == UTF8_MORE) {
221			if (strlen(string) != ud.size)
222				return (KEYC_UNKNOWN);
223			for (i = 1; i < ud.size; i++)
224				more = utf8_append(&ud, (u_char)string[i]);
225			if (more != UTF8_DONE)
226				return (KEYC_UNKNOWN);
227			if (utf8_from_data(&ud, &uc) != UTF8_DONE)
228				return (KEYC_UNKNOWN);
229			return (uc|modifiers);
230		}
231
232		/* Otherwise look the key up in the table. */
233		key = key_string_search_table(string);
234		if (key == KEYC_UNKNOWN)
235			return (KEYC_UNKNOWN);
236		if (~modifiers & KEYC_META)
237			key &= ~KEYC_IMPLIED_META;
238	}
239
240	/* Convert the standard control keys. */
241	if (key < KEYC_BASE && (modifiers & KEYC_CTRL) && !strchr(other, key)) {
242		if (key >= 97 && key <= 122)
243			key -= 96;
244		else if (key >= 64 && key <= 95)
245			key -= 64;
246		else if (key == 32)
247			key = 0;
248		else if (key == 63)
249			key = 127;
250		else
251			return (KEYC_UNKNOWN);
252		modifiers &= ~KEYC_CTRL;
253	}
254
255	return (key|modifiers);
256}
257
258/* Convert a key code into string format, with prefix if necessary. */
259const char *
260key_string_lookup_key(key_code key, int with_flags)
261{
262	key_code		 saved = key;
263	static char		 out[64];
264	char			 tmp[8];
265	const char		*s;
266	u_int			 i;
267	struct utf8_data	 ud;
268	size_t			 off;
269
270	*out = '\0';
271
272	/* Literal keys are themselves. */
273	if (key & KEYC_LITERAL) {
274		snprintf(out, sizeof out, "%c", (int)(key & 0xff));
275		goto out;
276	}
277
278	/* Display C-@ as C-Space. */
279	if ((key & (KEYC_MASK_KEY|KEYC_MASK_MODIFIERS)) == 0)
280		key = ' '|KEYC_CTRL;
281
282	/* Fill in the modifiers. */
283	if (key & KEYC_CTRL)
284		strlcat(out, "C-", sizeof out);
285	if (key & KEYC_META)
286		strlcat(out, "M-", sizeof out);
287	if (key & KEYC_SHIFT)
288		strlcat(out, "S-", sizeof out);
289	key &= KEYC_MASK_KEY;
290
291	/* Handle no key. */
292	if (key == KEYC_NONE) {
293		s = "None";
294		goto append;
295	}
296
297	/* Handle special keys. */
298	if (key == KEYC_UNKNOWN) {
299		s = "Unknown";
300		goto append;
301	}
302	if (key == KEYC_ANY) {
303		s = "Any";
304		goto append;
305	}
306	if (key == KEYC_FOCUS_IN) {
307		s = "FocusIn";
308		goto append;
309	}
310	if (key == KEYC_FOCUS_OUT) {
311		s = "FocusOut";
312		goto append;
313	}
314	if (key == KEYC_PASTE_START) {
315		s = "PasteStart";
316		goto append;
317	}
318	if (key == KEYC_PASTE_END) {
319		s = "PasteEnd";
320		goto append;
321	}
322	if (key == KEYC_MOUSE) {
323		s = "Mouse";
324		goto append;
325	}
326	if (key == KEYC_DRAGGING) {
327		s = "Dragging";
328		goto append;
329	}
330	if (key == KEYC_MOUSEMOVE_PANE) {
331		s = "MouseMovePane";
332		goto append;
333	}
334	if (key == KEYC_MOUSEMOVE_STATUS) {
335		s = "MouseMoveStatus";
336		goto append;
337	}
338	if (key == KEYC_MOUSEMOVE_STATUS_LEFT) {
339		s = "MouseMoveStatusLeft";
340		goto append;
341	}
342	if (key == KEYC_MOUSEMOVE_STATUS_RIGHT) {
343		s = "MouseMoveStatusRight";
344		goto append;
345	}
346	if (key == KEYC_MOUSEMOVE_BORDER) {
347		s = "MouseMoveBorder";
348		goto append;
349	}
350	if (key >= KEYC_USER && key < KEYC_USER + KEYC_NUSER) {
351		snprintf(tmp, sizeof tmp, "User%u", (u_int)(key - KEYC_USER));
352		strlcat(out, tmp, sizeof out);
353		goto out;
354	}
355
356	/* Try the key against the string table. */
357	for (i = 0; i < nitems(key_string_table); i++) {
358		if (key == (key_string_table[i].key & KEYC_MASK_KEY))
359			break;
360	}
361	if (i != nitems(key_string_table)) {
362		strlcat(out, key_string_table[i].string, sizeof out);
363		goto out;
364	}
365
366	/* Is this a UTF-8 key? */
367	if (key > 127 && key < KEYC_BASE) {
368		utf8_to_data(key, &ud);
369		off = strlen(out);
370		memcpy(out + off, ud.data, ud.size);
371		out[off + ud.size] = '\0';
372		goto out;
373	}
374
375	/* Invalid keys are errors. */
376	if (key > 255) {
377		snprintf(out, sizeof out, "Invalid#%llx", saved);
378		goto out;
379	}
380
381	/* Check for standard or control key. */
382	if (key <= 32) {
383		if (key == 0 || key > 26)
384			xsnprintf(tmp, sizeof tmp, "C-%c", (int)(64 + key));
385		else
386			xsnprintf(tmp, sizeof tmp, "C-%c", (int)(96 + key));
387	} else if (key >= 32 && key <= 126) {
388		tmp[0] = key;
389		tmp[1] = '\0';
390	} else if (key == 127)
391		xsnprintf(tmp, sizeof tmp, "C-?");
392	else if (key >= 128)
393		xsnprintf(tmp, sizeof tmp, "\\%llo", key);
394
395	strlcat(out, tmp, sizeof out);
396	goto out;
397
398append:
399	strlcat(out, s, sizeof out);
400
401out:
402	if (with_flags && (saved & KEYC_MASK_FLAGS) != 0) {
403		strlcat(out, "[", sizeof out);
404		if (saved & KEYC_LITERAL)
405			strlcat(out, "L", sizeof out);
406		if (saved & KEYC_KEYPAD)
407			strlcat(out, "K", sizeof out);
408		if (saved & KEYC_CURSOR)
409			strlcat(out, "C", sizeof out);
410		if (saved & KEYC_IMPLIED_META)
411			strlcat(out, "I", sizeof out);
412		if (saved & KEYC_BUILD_MODIFIERS)
413			strlcat(out, "B", sizeof out);
414		strlcat(out, "]", sizeof out);
415	}
416	return (out);
417}
418