aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src/boxes/handlekeys.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/boxes/handlekeys.c445
1 files changed, 445 insertions, 0 deletions
diff --git a/src/boxes/handlekeys.c b/src/boxes/handlekeys.c
new file mode 100644
index 0000000..8bae529
--- /dev/null
+++ b/src/boxes/handlekeys.c
@@ -0,0 +1,445 @@
1/* handlekeys.c - Generic terminal input handler.
2 *
3 * Copyright 2012 David Seikel <won_fang@yahoo.com.au>
4 */
5
6// I use camelCaseNames internally, instead of underscore_names as is preferred
7// in the rest of toybox. A small limit of 80 characters per source line infers
8// shorter names should be used. CamelCaseNames are shorter. Externally visible
9// stuff is underscore_names as usual. Plus, I'm used to camelCaseNames, my
10// fingers twitch that way.
11
12#include "toys.h"
13#include "handlekeys.h"
14
15struct key
16{
17 char *code;
18 char *name;
19};
20
21// This table includes some variations I have found on some terminals.
22// http://rtfm.etla.org/xterm/ctlseq.html has a useful guide.
23// TODO - Don't think I got all the linux console or xterm variations.
24// TODO - Add more shift variations, plus Ctrl & Alt variations when needed.
25// TODO - tmux messes with the shift function keys somehow.
26// TODO - Add other miscelany that does not use an escape sequence.
27
28// This is sorted by type, though there is some overlap.
29// Human typing speeds wont need fast searching speeds on this small table.
30// So simple wins out over speed, and sorting by terminal type wins
31// the simple test.
32static struct key keys[] =
33{
34 // Control characters.
35 // Commented out coz it's the C string terminator, and may confuse things.
36 //{"\x00", "^@"}, // NUL
37 {"\x01", "^A"}, // SOH Apparently sometimes sent as Home.
38 {"\x02", "^B"}, // STX
39 {"\x03", "^C"}, // ETX SIGINT Emacs and vi.
40 {"\x04", "^D"}, // EOT EOF Emacs, joe, and nano.
41 {"\x05", "^E"}, // ENQ Apparently sometimes sent as End
42 {"\x06", "^F"}, // ACK
43 {"\x07", "^G"}, // BEL
44 {"\x08", "Del"}, // BS Delete key, usually.
45 {"\x09", "Tab"}, // HT
46 {"\x0A", "Enter"}, // LF Roxterm translates Ctrl-M to this.
47 {"\x0B", "^K"}, // VT
48 {"\x0C", "^L"}, // FF
49 {"\x0D", "Return"}, // CR Other Enter/Return key, usually.
50 {"\x0E", "^N"}, // SO
51 {"\x0F", "^O"}, // SI DISCARD
52 {"\x10", "^P"}, // DLE
53 {"\x11", "^Q"}, // DC1 SIGCONT Vi.
54 {"\x12", "^R"}, // DC2
55 {"\x13", "^S"}, // DC3 SIGSTOP can't be caught. Emacs and vi.
56 {"\x14", "^T"}, // DC4 SIGINFO STATUS
57 {"\x15", "^U"}, // NAK KILL character
58 {"\x16", "^V"}, // SYN LNEXT
59 {"\x17", "^W"}, // ETB WERASE
60 {"\x18", "^X"}, // CAN KILL character
61 {"\x19", "^Y"}, // EM DSUSP SIGTSTP
62 {"\x1A", "^Z"}, // SUB SIGTSTP
63 // Commented out coz it's the ANSI start byte in the below multibyte keys.
64 // Handled in the code with a timeout.
65 //{"\x1B", "Esc"}, // ESC Esc key.
66 {"\x1C", "^\\"}, // FS SIGQUIT
67 {"\x1D", "^]"}, // GS
68 {"\x1E", "^^"}, // RS
69 {"\x1F", "^_"}, // US
70 {"\x7F", "BS"}, // Backspace key, usually. Ctrl-? perhaps?
71 // Commented out for the same reason Esc is.
72 //{"\x9B", "CSI"}, // CSI The eight bit encoding of "Esc [".
73
74 // "Usual" xterm CSI sequences, with ";1" omitted for no modifiers.
75 // Even though we have a proper CSI parser,
76 // these should still be in this table. Coz we would need a table anyway
77 // in the CSI parser, so might as well keep them with the others.
78 // Also, less code, no need to have a separate scanner for that other table.
79 {"\x9B\x31~", "Home"}, // Duplicate, think I've seen this somewhere.
80 {"\x9B\x32~", "Ins"},
81 {"\x9B\x33~", "Del"},
82 {"\x9B\x34~", "End"}, // Duplicate, think I've seen this somewhere.
83 {"\x9B\x35~", "PgUp"},
84 {"\x9B\x36~", "PgDn"},
85 {"\x9B\x37~", "Home"},
86 {"\x9B\x38~", "End"},
87 {"\x9B\x31\x31~", "F1"},
88 {"\x9B\x31\x32~", "F2"},
89 {"\x9B\x31\x33~", "F3"},
90 {"\x9B\x31\x34~", "F4"},
91 {"\x9B\x31\x35~", "F5"},
92 {"\x9B\x31\x37~", "F6"},
93 {"\x9B\x31\x38~", "F7"},
94 {"\x9B\x31\x39~", "F8"},
95 {"\x9B\x32\x30~", "F9"},
96 {"\x9B\x32\x31~", "F10"},
97 {"\x9B\x32\x33~", "F11"},
98 {"\x9B\x32\x34~", "F12"},
99
100 // As above, ";2" means shift modifier.
101 {"\x9B\x31;2~", "Shift Home"},
102 {"\x9B\x32;2~", "Shift Ins"},
103 {"\x9B\x33;2~", "Shift Del"},
104 {"\x9B\x34;2~", "Shift End"},
105 {"\x9B\x35;2~", "Shift PgUp"},
106 {"\x9B\x36;2~", "Shift PgDn"},
107 {"\x9B\x37;2~", "Shift Home"},
108 {"\x9B\x38;2~", "Shift End"},
109 {"\x9B\x31\x31;2~", "Shift F1"},
110 {"\x9B\x31\x32;2~", "Shift F2"},
111 {"\x9B\x31\x33;2~", "Shift F3"},
112 {"\x9B\x31\x34;2~", "Shift F4"},
113 {"\x9B\x31\x35;2~", "Shift F5"},
114 {"\x9B\x31\x37;2~", "Shift F6"},
115 {"\x9B\x31\x38;2~", "Shift F7"},
116 {"\x9B\x31\x39;2~", "Shift F8"},
117 {"\x9B\x32\x30;2~", "Shift F9"},
118 {"\x9B\x32\x31;2~", "Shift F10"},
119 {"\x9B\x32\x33;2~", "Shift F11"},
120 {"\x9B\x32\x34;2~", "Shift F12"},
121
122 // "Normal" Some terminals are special, and it seems they only have
123 // four function keys.
124 {"\x9B\x41", "Up"},
125 {"\x9B\x42", "Down"},
126 {"\x9B\x43", "Right"},
127 {"\x9B\x44", "Left"},
128 {"\x9B\x46", "End"},
129 {"\x9BH", "Home"},
130 {"\x9BP", "F1"},
131 {"\x9BQ", "F2"},
132 {"\x9BR", "F3"},
133 {"\x9BS", "F4"},
134 {"\x9B\x31;2P", "Shift F1"},
135 {"\x9B\x31;2Q", "Shift F2"},
136 {"\x9B\x31;2R", "Shift F3"},
137 {"\x9B\x31;2S", "Shift F4"},
138
139 // "Application" Esc O is known as SS3
140 {"\x1BOA", "Up"},
141 {"\x1BOB", "Down"},
142 {"\x1BOC", "Right"},
143 {"\x1BOD", "Left"},
144 {"\x1BOF", "End"},
145 {"\x1BOH", "Home"},
146 {"\x1BOn", "Del"},
147 {"\x1BOp", "Ins"},
148 {"\x1BOq", "End"},
149 {"\x1BOw", "Home"},
150 {"\x1BOP", "F1"},
151 {"\x1BOQ", "F2"},
152 {"\x1BOR", "F3"},
153 {"\x1BOS", "F4"},
154 {"\x1BOT", "F5"},
155 // These two conflict with the above four function key variations.
156 {"\x9BR", "F6"},
157 {"\x9BS", "F7"},
158 {"\x9BT", "F8"},
159 {"\x9BU", "F9"},
160 {"\x9BV", "F10"},
161 {"\x9BW", "F11"},
162 {"\x9BX", "F12"},
163
164 // Can't remember, but saw them somewhere.
165 {"\x1BO1;2P", "Shift F1"},
166 {"\x1BO1;2Q", "Shift F2"},
167 {"\x1BO1;2R", "Shift F3"},
168 {"\x1BO1;2S", "Shift F4"},
169};
170
171static volatile sig_atomic_t sigWinch;
172static int stillRunning;
173
174static void handleSIGWINCH(int signalNumber)
175{
176 sigWinch = 1;
177}
178
179void handle_keys(long extra, int (*handle_event)(long extra, struct keyevent *event))
180{
181 struct keyevent event;
182 fd_set selectFds;
183 struct timespec timeOut;
184 struct sigaction sigAction, oldSigAction;
185 sigset_t signalMask;
186 char buffer[20], sequence[20];
187 int buffIndex = 0, pendingEsc = 0;
188
189 buffer[0] = 0;
190 sequence[0] = 0;
191
192 // Terminals send the SIGWINCH signal when they resize.
193 memset(&sigAction, 0, sizeof(sigAction));
194 sigAction.sa_handler = handleSIGWINCH;
195 sigAction.sa_flags = SA_RESTART; // Useless if we are using poll.
196 if (sigaction(SIGWINCH, &sigAction, &oldSigAction))
197 perror_exit("can't set signal handler for SIGWINCH");
198 sigemptyset(&signalMask);
199 sigaddset(&signalMask, SIGWINCH);
200
201 // TODO - OS buffered keys might be a problem, but we can't do the
202 // usual timestamp filter for now.
203
204 stillRunning = 1;
205 while (stillRunning)
206 {
207 int j, p, csi = 0;
208
209 // Apparently it's more portable to reset these each time.
210 FD_ZERO(&selectFds);
211 FD_SET(0, &selectFds);
212 timeOut.tv_sec = 0; timeOut.tv_nsec = 100000000; // One tenth of a second.
213
214 // We got a "terminal size changed" signal, ask the terminal
215 // how big it is now.
216 if (sigWinch)
217 {
218 // Send - save cursor position, down 999, right 999,
219 // request cursor position, restore cursor position.
220 fputs("\x1B[s\x1B[999C\x1B[999B\x1B[6n\x1B[u", stdout);
221 fflush(stdout);
222 sigWinch = 0;
223 }
224
225 // TODO - Should only ask for a time out after we get an Escape, or
226 // the user requested time ticks.
227 // I wanted to use poll, but that would mean using ppoll, which is
228 // Linux only, and involves defining swear words to get it.
229 p = pselect(0 + 1, &selectFds, NULL, NULL, &timeOut, &signalMask);
230 if (0 > p)
231 {
232 if (EINTR == errno)
233 continue;
234 perror_exit("poll");
235 }
236 else if (0 == p) // A timeout, trigger a time event.
237 {
238 if (pendingEsc)
239 {
240 // After a short delay to check, this is a real Escape key,
241 // not part of an escape sequence, so deal with it.
242 strcat(sequence, "Esc");
243 buffer[0] = buffIndex = 0;
244 }
245 // TODO - Call some sort of timer tick callback. This wont be
246 // a precise timed event, but don't think we need one.
247 }
248 else if ((0 < p) && FD_ISSET(0, &selectFds))
249 {
250 j = xread(0, &buffer[buffIndex], sizeof(buffer) - (buffIndex + 1));
251 if (j == 0) // End of file.
252 stillRunning = 0;
253 else
254 {
255 buffIndex += j;
256 buffer[buffIndex] = 0;
257
258 // Send raw keystrokes, mostly for things like showkey.
259 event.type = HK_RAW;
260 event.sequence = buffer;
261 event.isTranslated = 0;
262 handle_event(extra, &event);
263
264 if (sizeof(buffer) < (buffIndex + 1)) // Ran out of buffer.
265 {
266 buffer[buffIndex] = 0;
267 fprintf(stderr, "Full buffer - %s -> %s\n", buffer, sequence);
268 for (j = 0; buffer[j]; j++)
269 fprintf(stderr, "(%x) %c, ", (int) buffer[j], buffer[j]);
270 fflush(stderr);
271 buffer[0] = buffIndex = 0;
272 }
273 }
274 }
275
276 // Check for lone Esc first, wait a bit longer if it is.
277 pendingEsc = ((0 == buffer[1]) && ('\x1B' == buffer[0]));
278 if (pendingEsc) continue;
279
280 // Check if it's a CSI before we check for the known key sequences.
281 // C29B is the UTF8 encoding of CSI.
282 // In all cases we reduce CSI to 9B to keep the keys table shorter.
283 if ((('\x1B' == buffer[0]) && ('[' == buffer[1]))
284 || (('\xC2' == buffer[0]) && ('\x9B' == buffer[1])))
285 {
286 buffer[0] = '\x9B';
287 for (j = 1; buffer[j]; j++)
288 buffer[j] = buffer[j + 1];
289 buffIndex--;
290 }
291 csi = ('\x9B' == buffer[0]);
292
293 // Check for known key sequences.
294 // For a real timeout checked Esc, buffer is now empty, so this for loop
295 // wont find it anyway. While it's true we could avoid it by checking,
296 // the user already had to wait for a time out, and this loop wont take THAT long.
297 for (j = 0; j < ARRAY_LEN(keys); j++)
298 {
299 if (strcmp(keys[j].code, buffer) == 0)
300 {
301 strcat(sequence, keys[j].name);
302 buffer[0] = buffIndex = 0;
303 csi = 0;
304 break;
305 }
306 }
307
308 // Find out if it's a CSI sequence that's not in the known key sequences.
309 if (csi)
310 {
311 /* ECMA-048 section 5.2 defines this, and is unreadable.
312 * So I'll include some notes here that tries to simplify that.
313 *
314 * The CSI format is - CSI [private] n1 ; n2 [extra] final
315 * Each of those parts, except for the initial CSI bytes, is an ordinary
316 * ASCII character.
317 *
318 * The optional [private] part is one of these characters "<=>?".
319 * If the first byte is one of these, then this is a private command, if
320 * it's one of the other n1 ones, it's not private.
321 *
322 * Next is a semi colon separated list of parameters (n1, n2, etc), which
323 * can be any characters from this set "01234567890:;<=>?". What the non
324 * digit ones mean is up to the command. Parameters can be left out, but
325 * their defaults are command dependant.
326 *
327 * Next is an optional [extra] part from this set of characters
328 * "!#$%&'()*+,-./", which includes double quotes. Can be many of these,
329 * likely isn't.
330 *
331 * Finally is the "final" from this set of characters "@[\]^_`{|}~", plus
332 * upper and lower case letters. It's private if it's one of these
333 * "pqrstuvwxyz{|}~". Though the "private" ~ is used for key codes.
334 *
335 * A full CSI command is the private, extra, and final parts.
336 *
337 * Any C0 controls, DEL (0x7f), or higher characters are undefined.
338 * TODO - So abort the current CSI and start from scratch on one of those.
339 */
340
341 if ('M' == buffer[1])
342 {
343 // We have a mouse report, which is CSI M ..., where the rest is
344 // binary encoded, more or less. Not fitting into the CSI format.
345 // To make things worse, can't tell how long this will be.
346 // So leave it up to the caller to tell us if they used it.
347 event.type = HK_MOUSE;
348 event.sequence = buffer;
349 event.isTranslated = 0;
350 if (handle_event(extra, &event))
351 {
352 buffer[0] = buffIndex = 0;
353 sequence[0] = 0;
354 }
355 }
356 else
357 {
358 char *t, csFinal[8];
359 int csIndex = 1, csParams[8];
360
361 csFinal[0] = 0;
362 p = 0;
363
364 // Unspecified params default to a value that is command dependant.
365 // However, they will never be negative, so we can use -1 to flag
366 // a default value.
367 for (j = 0; j < ARRAY_LEN(csParams); j++)
368 csParams[j] = -1;
369
370 // Check for the private bit.
371 if (index("<=>?", buffer[1]))
372 {
373 csFinal[0] = buffer[1];
374 csFinal[1] = 0;
375 csIndex++;
376 }
377
378 // Decode parameters.
379 j = csIndex;
380 do
381 {
382 // So we know when we get to the end of parameter space.
383 t = index("01234567890:;<=>?", buffer[j + 1]);
384 // See if we passed a paremeter.
385 if ((';' == buffer[j]) || (!t))
386 {
387 // Only stomp on the ; if it's really the ;.
388 if (t)
389 buffer[j] = 0;
390 // Empty parameters are default parameters, so only deal with
391 // non defaults.
392 if (';' != buffer[csIndex] || (!t))
393 {
394 // TODO - Might be ":" in the number somewhere, but we are not
395 // expecting any in anything we do.
396 csParams[p] = atoi(&buffer[csIndex]);
397 }
398 p++;
399 csIndex = j + 1;
400 }
401 j++;
402 }
403 while (t);
404
405 // Check if we got the final byte, and send it to the callback.
406 strcat(csFinal, &buffer[csIndex]);
407 t = csFinal + strlen(csFinal) - 1;
408 if (('\x40' <= (*t)) && ((*t) <= '\x7e'))
409 {
410 event.type = HK_CSI;
411 event.sequence = csFinal;
412 event.isTranslated = 1;
413 event.count = p;
414 event.params = csParams;
415 handle_event(extra, &event);
416 buffer[0] = buffIndex = 0;
417 sequence[0] = 0;
418 }
419 }
420 }
421
422 // Pass the result to the callback.
423 if (sequence[0] || buffer[0])
424 {
425 char b[strlen(sequence) + strlen(buffer) + 1];
426
427 sprintf(b, "%s%s", sequence, buffer);
428 event.type = HK_KEYS;
429 event.sequence = b;
430 event.isTranslated = (0 != sequence[0]);
431 if (handle_event(extra, &event))
432 {
433 buffer[0] = buffIndex = 0;
434 sequence[0] = 0;
435 }
436 }
437 }
438
439 sigaction(SIGWINCH, &oldSigAction, NULL);
440}
441
442void handle_keys_quit()
443{
444 stillRunning = 0;
445}