diff options
Diffstat (limited to '')
-rw-r--r-- | src/boxes/handlekeys.c | 445 |
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 | |||
15 | struct 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. | ||
32 | static 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 | |||
171 | static volatile sig_atomic_t sigWinch; | ||
172 | static int stillRunning; | ||
173 | |||
174 | static void handleSIGWINCH(int signalNumber) | ||
175 | { | ||
176 | sigWinch = 1; | ||
177 | } | ||
178 | |||
179 | void 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 | |||
442 | void handle_keys_quit() | ||
443 | { | ||
444 | stillRunning = 0; | ||
445 | } | ||