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