1/* vi: set sw=4 ts=4: */ 2/* 3 * Utility routines. 4 * 5 * Copyright (C) 2008 Rob Landley <rob@landley.net> 6 * Copyright (C) 2008 Denys Vlasenko <vda.linux@googlemail.com> 7 * 8 * Licensed under GPL version 2, see file LICENSE in this tarball for details. 9 */ 10#include "libbb.h" 11 12int64_t FAST_FUNC read_key(int fd, char *buffer, int timeout) 13{ 14 struct pollfd pfd; 15 const char *seq; 16 int n; 17 18 /* Known escape sequences for cursor and function keys */ 19 static const char esccmds[] ALIGN1 = { 20 'O','A' |0x80,KEYCODE_UP , 21 'O','B' |0x80,KEYCODE_DOWN , 22 'O','C' |0x80,KEYCODE_RIGHT , 23 'O','D' |0x80,KEYCODE_LEFT , 24 'O','H' |0x80,KEYCODE_HOME , 25 'O','F' |0x80,KEYCODE_END , 26#if 0 27 'O','P' |0x80,KEYCODE_FUN1 , 28 /* [ESC] ESC O [2] P - [Alt-][Shift-]F1 */ 29 /* ESC [ O 1 ; 2 P - Shift-F1 */ 30 /* ESC [ O 1 ; 3 P - Alt-F1 */ 31 /* ESC [ O 1 ; 4 P - Alt-Shift-F1 */ 32 /* ESC [ O 1 ; 5 P - Ctrl-F1 */ 33 /* ESC [ O 1 ; 6 P - Ctrl-Shift-F1 */ 34 'O','Q' |0x80,KEYCODE_FUN2 , 35 'O','R' |0x80,KEYCODE_FUN3 , 36 'O','S' |0x80,KEYCODE_FUN4 , 37#endif 38 '[','A' |0x80,KEYCODE_UP , 39 '[','B' |0x80,KEYCODE_DOWN , 40 '[','C' |0x80,KEYCODE_RIGHT , 41 '[','D' |0x80,KEYCODE_LEFT , 42 /* ESC [ 1 ; 2 x, where x = A/B/C/D: Shift-<arrow> */ 43 /* ESC [ 1 ; 3 x, where x = A/B/C/D: Alt-<arrow> */ 44 /* ESC [ 1 ; 4 x, where x = A/B/C/D: Alt-Shift-<arrow> */ 45 /* ESC [ 1 ; 5 x, where x = A/B/C/D: Ctrl-<arrow> - implemented below */ 46 /* ESC [ 1 ; 6 x, where x = A/B/C/D: Ctrl-Shift-<arrow> */ 47 '[','H' |0x80,KEYCODE_HOME , /* xterm */ 48 /* [ESC] ESC [ [2] H - [Alt-][Shift-]Home */ 49 '[','F' |0x80,KEYCODE_END , /* xterm */ 50 '[','1','~' |0x80,KEYCODE_HOME , /* vt100? linux vt? or what? */ 51 '[','2','~' |0x80,KEYCODE_INSERT , 52 /* ESC [ 2 ; 3 ~ - Alt-Insert */ 53 '[','3','~' |0x80,KEYCODE_DELETE , 54 /* [ESC] ESC [ 3 [;2] ~ - [Alt-][Shift-]Delete */ 55 /* ESC [ 3 ; 3 ~ - Alt-Delete */ 56 /* ESC [ 3 ; 5 ~ - Ctrl-Delete */ 57 '[','4','~' |0x80,KEYCODE_END , /* vt100? linux vt? or what? */ 58 '[','5','~' |0x80,KEYCODE_PAGEUP , 59 /* ESC [ 5 ; 3 ~ - Alt-PgUp */ 60 /* ESC [ 5 ; 5 ~ - Ctrl-PgUp */ 61 /* ESC [ 5 ; 7 ~ - Ctrl-Alt-PgUp */ 62 '[','6','~' |0x80,KEYCODE_PAGEDOWN, 63 '[','7','~' |0x80,KEYCODE_HOME , /* vt100? linux vt? or what? */ 64 '[','8','~' |0x80,KEYCODE_END , /* vt100? linux vt? or what? */ 65#if 0 66 '[','1','1','~'|0x80,KEYCODE_FUN1 , 67 '[','1','2','~'|0x80,KEYCODE_FUN2 , 68 '[','1','3','~'|0x80,KEYCODE_FUN3 , 69 '[','1','4','~'|0x80,KEYCODE_FUN4 , 70 '[','1','5','~'|0x80,KEYCODE_FUN5 , 71 /* [ESC] ESC [ 1 5 [;2] ~ - [Alt-][Shift-]F5 */ 72 '[','1','7','~'|0x80,KEYCODE_FUN6 , 73 '[','1','8','~'|0x80,KEYCODE_FUN7 , 74 '[','1','9','~'|0x80,KEYCODE_FUN8 , 75 '[','2','0','~'|0x80,KEYCODE_FUN9 , 76 '[','2','1','~'|0x80,KEYCODE_FUN10 , 77 '[','2','3','~'|0x80,KEYCODE_FUN11 , 78 '[','2','4','~'|0x80,KEYCODE_FUN12 , 79 /* ESC [ 2 4 ; 2 ~ - Shift-F12 */ 80 /* ESC [ 2 4 ; 3 ~ - Alt-F12 */ 81 /* ESC [ 2 4 ; 4 ~ - Alt-Shift-F12 */ 82 /* ESC [ 2 4 ; 5 ~ - Ctrl-F12 */ 83 /* ESC [ 2 4 ; 6 ~ - Ctrl-Shift-F12 */ 84#endif 85 /* '[','1',';','5','A' |0x80,KEYCODE_CTRL_UP , - unused */ 86 /* '[','1',';','5','B' |0x80,KEYCODE_CTRL_DOWN , - unused */ 87 '[','1',';','5','C' |0x80,KEYCODE_CTRL_RIGHT, 88 '[','1',';','5','D' |0x80,KEYCODE_CTRL_LEFT , 89 0 90 /* ESC [ Z - Shift-Tab */ 91 }; 92 93 pfd.fd = fd; 94 pfd.events = POLLIN; 95 96 buffer++; /* saved chars counter is in buffer[-1] now */ 97 98 start_over: 99 errno = 0; 100 n = (unsigned char)buffer[-1]; 101 if (n == 0) { 102 /* If no data, wait for input. 103 * If requested, wait TIMEOUT ms. TIMEOUT = -1 is useful 104 * if fd can be in non-blocking mode. 105 */ 106 if (timeout >= -1) { 107 if (safe_poll(&pfd, 1, timeout) == 0) { 108 /* Timed out */ 109 errno = EAGAIN; 110 return -1; 111 } 112 } 113 /* It is tempting to read more than one byte here, 114 * but it breaks pasting. Example: at shell prompt, 115 * user presses "c","a","t" and then pastes "\nline\n". 116 * When we were reading 3 bytes here, we were eating 117 * "li" too, and cat was getting wrong input. 118 */ 119 n = safe_read(fd, buffer, 1); 120 if (n <= 0) 121 return -1; 122 } 123 124 { 125 unsigned char c = buffer[0]; 126 n--; 127 if (n) 128 memmove(buffer, buffer + 1, n); 129 /* Only ESC starts ESC sequences */ 130 if (c != 27) { 131 buffer[-1] = n; 132 return c; 133 } 134 } 135 136 /* Loop through known ESC sequences */ 137 seq = esccmds; 138 while (*seq != '\0') { 139 /* n - position in sequence we did not read yet */ 140 int i = 0; /* position in sequence to compare */ 141 142 /* Loop through chars in this sequence */ 143 while (1) { 144 /* So far escape sequence matched up to [i-1] */ 145 if (n <= i) { 146 /* Need more chars, read another one if it wouldn't block. 147 * Note that escape sequences come in as a unit, 148 * so if we block for long it's not really an escape sequence. 149 * Timeout is needed to reconnect escape sequences 150 * split up by transmission over a serial console. */ 151 if (safe_poll(&pfd, 1, 50) == 0) { 152 /* No more data! 153 * Array is sorted from shortest to longest, 154 * we can't match anything later in array - 155 * anything later is longer than this seq. 156 * Break out of both loops. */ 157 goto got_all; 158 } 159 errno = 0; 160 if (safe_read(fd, buffer + n, 1) <= 0) { 161 /* If EAGAIN, then fd is O_NONBLOCK and poll lied: 162 * in fact, there is no data. */ 163 if (errno != EAGAIN) { 164 /* otherwise: it's EOF/error */ 165 buffer[-1] = 0; 166 return -1; 167 } 168 goto got_all; 169 } 170 n++; 171 } 172 if (buffer[i] != (seq[i] & 0x7f)) { 173 /* This seq doesn't match, go to next */ 174 seq += i; 175 /* Forward to last char */ 176 while (!(*seq & 0x80)) 177 seq++; 178 /* Skip it and the keycode which follows */ 179 seq += 2; 180 break; 181 } 182 if (seq[i] & 0x80) { 183 /* Entire seq matched */ 184 n = 0; 185 /* n -= i; memmove(...); 186 * would be more correct, 187 * but we never read ahead that much, 188 * and n == i here. */ 189 buffer[-1] = 0; 190 return (signed char)seq[i+1]; 191 } 192 i++; 193 } 194 } 195 /* We did not find matching sequence. 196 * We possibly read and stored more input in buffer[] by now. 197 * n = bytes read. Try to read more until we time out. 198 */ 199 while (n < KEYCODE_BUFFER_SIZE-1) { /* 1 for count byte at buffer[-1] */ 200 if (safe_poll(&pfd, 1, 50) == 0) { 201 /* No more data! */ 202 break; 203 } 204 errno = 0; 205 if (safe_read(fd, buffer + n, 1) <= 0) { 206 /* If EAGAIN, then fd is O_NONBLOCK and poll lied: 207 * in fact, there is no data. */ 208 if (errno != EAGAIN) { 209 /* otherwise: it's EOF/error */ 210 buffer[-1] = 0; 211 return -1; 212 } 213 break; 214 } 215 n++; 216 /* Try to decipher "ESC [ NNN ; NNN R" sequence */ 217 if ((ENABLE_FEATURE_EDITING_ASK_TERMINAL || ENABLE_FEATURE_VI_ASK_TERMINAL) 218 && n >= 5 219 && buffer[0] == '[' 220 && buffer[n-1] == 'R' 221 && isdigit(buffer[1]) 222 ) { 223 char *end; 224 unsigned long row, col; 225 226 row = strtoul(buffer + 1, &end, 10); 227 if (*end != ';' || !isdigit(end[1])) 228 continue; 229 col = strtoul(end + 1, &end, 10); 230 if (*end != 'R') 231 continue; 232 if (row < 1 || col < 1 || (row | col) > 0x7fff) 233 continue; 234 235 buffer[-1] = 0; 236 /* Pack into "1 <row15bits> <col16bits>" 32-bit sequence */ 237 col |= (((-1 << 15) | row) << 16); 238 /* Return it in high-order word */ 239 return ((int64_t) col << 32) | (uint32_t)KEYCODE_CURSOR_POS; 240 } 241 } 242 got_all: 243 244 if (n <= 1) { 245 /* Alt-x is usually returned as ESC x. 246 * Report ESC, x is remembered for the next call. 247 */ 248 buffer[-1] = n; 249 return 27; 250 } 251 252 /* We were doing "buffer[-1] = n; return c;" here, but this results 253 * in unknown key sequences being interpreted as ESC + garbage. 254 * This was not useful. Pretend there was no key pressed, 255 * go and wait for a new keypress: 256 */ 257 buffer[-1] = 0; 258 goto start_over; 259} 260 261void FAST_FUNC read_key_ungets(char *buffer, const char *str, unsigned len) 262{ 263 unsigned cur_len = (unsigned char)buffer[0]; 264 if (len > KEYCODE_BUFFER_SIZE-1 - cur_len) 265 len = KEYCODE_BUFFER_SIZE-1 - cur_len; 266 memcpy(buffer + 1 + cur_len, str, len); 267 buffer[0] += len; 268} 269