On Sat, 1 Feb 2014 12:21:29 +1000 David Seikel <[email protected]> wrote:
> Oops, found a small design fault. I'm fixing that now, and will send > an update later today. This time for sure. Fixed up the bottom of terminal bug in dumbsh, the resizing, and made the keystrokes / ordinary characters determination more sane. Did a lot of debugging and hopefully solved all know bugs except for one in the editor that I'll get to some other time. -- A big old stinking pile of genius that no one wants coz there are too many silver coated monkeys in the world.
/* dumbsh.c - A really dumb shell, to demonstrate handle_keys usage. * * Copyright 2014 David Seikel <[email protected]> * * Not a real shell, so doesn't follow any standards, * coz it wont implement them anyway. USE_DUMBSH(NEWTOY(dumbsh, "", TOYFLAG_USR|TOYFLAG_BIN)) config DUMBSH bool "dumbsh" default n help usage: dumbsh A really dumb shell. */ #include "toys.h" #include "lib/handlekeys.h" typedef void (*eventHandler) (void); struct keyCommand { char *key; eventHandler handler; }; GLOBALS( unsigned h, w; int x, y; struct double_list *history; ) #define TT this.dumbsh // Sanity check cursor location and update the current line. static void updateLine() { if (0 > TT.x) TT.x = 0; if (0 > TT.y) TT.y = 0; if (TT.w < TT.x) TT.x = TT.w; if (TT.h < TT.y) TT.y = TT.h; if (strlen(toybuf) < TT.x) TT.x = strlen(toybuf); printf("\x1B[%d;0H%-*s\x1B[%d;%dH", TT.y + 1, TT.w, toybuf, TT.y + 1, TT.x + 1); fflush(stdout); } // Callback for incoming CSI commands from the terminal. static void handleCSI(long extra, char *command, int *params, int count) { // Is it a cursor location report? if (strcmp("R", command) == 0) { // Parameters are cursor line and column. // NOTE - This may be sent at other times, not just during terminal resize. // We are assuming here that it's a resize. // The defaults are 1, which get ignored by the heuristic below. int r = params[0], c = params[1]; // Check it's not an F3 key variation, coz some of them use // the same CSI function command. // This is a heuristic, we are checking against an unusable terminal size. if ((2 == count) && (8 < r) && (8 < c)) { TT.h = r; TT.w = c; updateLine(); } } // NOTE - The CSI differs from the sequence callback // in not having to return anything. CSI sequences include a // definite terminating byte, so no need for this callback // to tell handle_keys to keep accumulating. } // The various commands. static void deleteChar() { int j; for (j = TT.x; toybuf[j]; j++) toybuf[j] = toybuf[j + 1]; updateLine(); } static void backSpaceChar() { if (TT.x) { TT.x--; deleteChar(); } } // This is where we would actually deal with // what ever command the user had typed in. // For now we just move on to the next line. // TODO - We would want to redirect I/O, capture some keys (^C), // but pass the rest on. // Dunno yet how to deal with that. // We still want handle_keys to be doing it's thing, // so maybe handing it another fd, and a callback. // A function to add and remove fd and callback pairs for // handle_keys to check? static void doCommand() { toybuf[0] = 0; TT.x = 0; TT.y++; printf("\n"); fflush(stdout); updateLine(); } static void endOfLine() { TT.x = strlen(toybuf); updateLine(); } static void leftChar() { TT.x--; updateLine(); } static void nextHistory() { TT.history = TT.history->next; strcpy(toybuf, TT.history->data); TT.x = strlen(toybuf); updateLine(); } static void prevHistory() { TT.history = TT.history->prev; strcpy(toybuf, TT.history->data); TT.x = strlen(toybuf); updateLine(); } static void quit() { handle_keys_quit(); } static void rightChar() { TT.x++; updateLine(); } static void startOfLine() { TT.x = 0; updateLine(); } // The key to command mappings, Emacs style. static struct keyCommand simpleEmacsKeys[] = { {"BS", backSpaceChar}, {"Del", deleteChar}, {"^D", deleteChar}, {"Return", doCommand}, {"Enter", doCommand}, {"Down", nextHistory}, {"^N", nextHistory}, {"End", endOfLine}, {"^E", endOfLine}, {"Left", leftChar}, {"^B", leftChar}, {"^X^C", quit}, {"^C", quit}, {"Right", rightChar}, {"^F", rightChar}, {"Home", startOfLine}, {"^A", startOfLine}, {"Up", prevHistory}, {"^P", prevHistory} }; // Callback for incoming key sequences from the user. static int handleKeySequence(long extra, char *sequence, int isTranslated) { int j, l = strlen(sequence); // Search for a key sequence bound to a command. for (j = 0; j < (sizeof(simpleEmacsKeys) / sizeof(*simpleEmacsKeys)); j++) { if (strncmp(simpleEmacsKeys[j].key, sequence, l) == 0) { // If it's a partial match, keep accumulating them. if (strlen(simpleEmacsKeys[j].key) != l) return 0; else { if (simpleEmacsKeys[j].handler) simpleEmacsKeys[j].handler(); return 1; } } } // See if it's ordinary keys. // NOTE - with vi style ordinary keys can be commands, // but they would be found by the command check above first. if (!isTranslated) { if (TT.x < sizeof(toybuf)) { int j, l = strlen(sequence); for (j = strlen(toybuf); j >= TT.x; j--) toybuf[j + l] = toybuf[j]; for (j = 0; j < l; j++) toybuf[TT.x + j] = sequence[j]; TT.x += l; updateLine(); } } // Tell handle_keys to drop it, coz we dealt with it, or it's not one of ours. return 1; } void dumbsh_main(void) { struct termios termIo, oldTermIo; char *t = getenv("HOME"); int fd; // Load bash history. t = xmsprintf("%s/%s", t ? t : "", ".bash_history"); if (-1 != (fd = open(t, O_RDONLY))) { while ((t = get_line(fd))) TT.history = dlist_add(&TT.history, t); close(fd); } if (!TT.history) TT.history = dlist_add(&TT.history, ""); // Grab the old terminal settings and save it. tcgetattr(0, &oldTermIo); tcflush(0, TCIFLUSH); termIo = oldTermIo; // Mould the terminal to our will. // In this example we are turning off all the terminal smarts, but real code // might not want that. termIo.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IUCLC | IXON | IXOFF | IXANY); termIo.c_oflag &= ~OPOST; termIo.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | TOSTOP | ICANON | ISIG | IEXTEN); termIo.c_cflag &= ~(CSIZE | PARENB); termIo.c_cflag |= CS8; termIo.c_cc[VTIME]=0; // deciseconds. termIo.c_cc[VMIN]=1; tcsetattr(0, TCSANOW, &termIo); // Let the mouldy old terminal mold us. TT.w = 80; TT.h = 24; terminal_size(&TT.w, &TT.h); // Let's rock! updateLine(); handle_keys(0, handleKeySequence, handleCSI); // Clean up. tcsetattr(0, TCSANOW, &oldTermIo); puts(""); fflush(stdout); }
/* handlekeys.c - Generic terminal input handler. * * Copyright 2012 David Seikel <[email protected]> */ // I use camelCaseNames internally, instead of underscore_names as is preferred // in the rest of toybox. A small limit of 80 characters per source line infers // shorter names should be used. CamelCaseNames are shorter. Externally visible // stuff is underscore_names as usual. Plus, I'm used to camelCaseNames, my // fingers twitch that way. #include "toys.h" #include "handlekeys.h" struct key { char *code; char *name; }; // This table includes some variations I have found on some terminals. // http://rtfm.etla.org/xterm/ctlseq.html has a useful guide. // TODO - Don't think I got all the linux console variations. // TODO - Add more shift variations, plus Ctrl & Alt variations when needed. // TODO - tmux messes with the shift function keys somehow. // TODO - Add other miscelany that does not use an escape sequence. // This is sorted by type, though there is some overlap. // Human typing speeds wont need fast searching speeds on this small table. // So simple wins out over speed, and sorting by terminal type wins // the simple test. static struct key keys[] = { // Control characters. // Commented out coz it's the C string terminator, and may confuse things. //{"\x00", "^@"}, // NUL {"\x01", "^A"}, // SOH Apparently sometimes sent as Home. {"\x02", "^B"}, // STX {"\x03", "^C"}, // ETX SIGINT Emacs and vi. {"\x04", "^D"}, // EOT EOF Emacs, joe, and nano. {"\x05", "^E"}, // ENQ Apparently sometimes sent as End {"\x06", "^F"}, // ACK {"\x07", "^G"}, // BEL {"\x08", "Del"}, // BS Delete key, usually. {"\x09", "Tab"}, // HT {"\x0A", "Enter"}, // LF Roxterm translates Ctrl-M to this. {"\x0B", "^K"}, // VT {"\x0C", "^L"}, // FF {"\x0D", "Return"}, // CR Other Enter/Return key, usually. {"\x0E", "^N"}, // SO {"\x0F", "^O"}, // SI DISCARD {"\x10", "^P"}, // DLE {"\x11", "^Q"}, // DC1 SIGCONT Vi. {"\x12", "^R"}, // DC2 {"\x13", "^S"}, // DC3 SIGSTOP can't be caught. Emacs and vi. {"\x14", "^T"}, // DC4 SIGINFO STATUS {"\x15", "^U"}, // NAK KILL character {"\x16", "^V"}, // SYN LNEXT {"\x17", "^W"}, // ETB WERASE {"\x18", "^X"}, // CAN KILL character {"\x19", "^Y"}, // EM DSUSP SIGTSTP {"\x1A", "^Z"}, // SUB SIGTSTP // Commented out coz it's the ANSI start byte in the below multibyte keys. // Handled in the code with a timeout. //{"\x1B", "Esc"}, // ESC Esc key. {"\x1C", "^\\"}, // FS SIGQUIT {"\x1D", "^]"}, // GS {"\x1E", "^^"}, // RS {"\x1F", "^_"}, // US {"\x7F", "BS"}, // Backspace key, usually. Ctrl-? perhaps? // Commented out for the same reason Esc is. //{"\x9B", "CSI"}, // CSI The eight bit encoding of "Esc [". // "Usual" xterm CSI sequences, with ";1" omitted for no modifiers. // Even though we have a proper CSI parser, // these should still be in this table. Coz we would need a table anyway // in the CSI parser, so might as well keep them with the others. // Also, less code, no need to have a separate scanner for that other table. {"\x9B\x31~", "Home"}, // Duplicate, think I've seen this somewhere. {"\x9B\x32~", "Ins"}, {"\x9B\x33~", "Del"}, {"\x9B\x34~", "End"}, // Duplicate, think I've seen this somewhere. {"\x9B\x35~", "PgUp"}, {"\x9B\x36~", "PgDn"}, {"\x9B\x37~", "Home"}, {"\x9B\x38~", "End"}, {"\x9B\x31\x31~", "F1"}, {"\x9B\x31\x32~", "F2"}, {"\x9B\x31\x33~", "F3"}, {"\x9B\x31\x34~", "F4"}, {"\x9B\x31\x35~", "F5"}, {"\x9B\x31\x37~", "F6"}, {"\x9B\x31\x38~", "F7"}, {"\x9B\x31\x39~", "F8"}, {"\x9B\x32\x30~", "F9"}, {"\x9B\x32\x31~", "F10"}, {"\x9B\x32\x33~", "F11"}, {"\x9B\x32\x34~", "F12"}, // As above, ";2" means shift modifier. {"\x9B\x31;2~", "Shift Home"}, {"\x9B\x32;2~", "Shift Ins"}, {"\x9B\x33;2~", "Shift Del"}, {"\x9B\x34;2~", "Shift End"}, {"\x9B\x35;2~", "Shift PgUp"}, {"\x9B\x36;2~", "Shift PgDn"}, {"\x9B\x37;2~", "Shift Home"}, {"\x9B\x38;2~", "Shift End"}, {"\x9B\x31\x31;2~", "Shift F1"}, {"\x9B\x31\x32;2~", "Shift F2"}, {"\x9B\x31\x33;2~", "Shift F3"}, {"\x9B\x31\x34;2~", "Shift F4"}, {"\x9B\x31\x35;2~", "Shift F5"}, {"\x9B\x31\x37;2~", "Shift F6"}, {"\x9B\x31\x38;2~", "Shift F7"}, {"\x9B\x31\x39;2~", "Shift F8"}, {"\x9B\x32\x30;2~", "Shift F9"}, {"\x9B\x32\x31;2~", "Shift F10"}, {"\x9B\x32\x33;2~", "Shift F11"}, {"\x9B\x32\x34;2~", "Shift F12"}, // "Normal" Some terminals are special, and it seems they only have // four function keys. {"\x9B\x41", "Up"}, {"\x9B\x42", "Down"}, {"\x9B\x43", "Right"}, {"\x9B\x44", "Left"}, {"\x9B\x46", "End"}, {"\x9BH", "Home"}, {"\x9BP", "F1"}, {"\x9BQ", "F2"}, {"\x9BR", "F3"}, {"\x9BS", "F4"}, {"\x9B\x31;2P", "Shift F1"}, {"\x9B\x31;2Q", "Shift F2"}, {"\x9B\x31;2R", "Shift F3"}, {"\x9B\x31;2S", "Shift F4"}, // "Application" Esc O is known as SS3 {"\x1BOA", "Up"}, {"\x1BOB", "Down"}, {"\x1BOC", "Right"}, {"\x1BOD", "Left"}, {"\x1BOF", "End"}, {"\x1BOH", "Home"}, {"\x1BOn", "Del"}, {"\x1BOp", "Ins"}, {"\x1BOq", "End"}, {"\x1BOw", "Home"}, {"\x1BOP", "F1"}, {"\x1BOQ", "F2"}, {"\x1BOR", "F3"}, {"\x1BOS", "F4"}, {"\x1BOT", "F5"}, // These two conflict with the above four function key variations. {"\x9BR", "F6"}, {"\x9BS", "F7"}, {"\x9BT", "F8"}, {"\x9BU", "F9"}, {"\x9BV", "F10"}, {"\x9BW", "F11"}, {"\x9BX", "F12"}, // Can't remember, but saw them somewhere. {"\x1BO1;2P", "Shift F1"}, {"\x1BO1;2Q", "Shift F2"}, {"\x1BO1;2R", "Shift F3"}, {"\x1BO1;2S", "Shift F4"}, }; static volatile sig_atomic_t sigWinch; static int stillRunning; static void handleSIGWINCH(int signalNumber) { sigWinch = 1; } void handle_keys(long extra, int (*handle_sequence)(long extra, char *sequence, int isTranslated), void (*handle_CSI)(long extra, char *command, int *params, int count)) { fd_set selectFds; struct timespec timeOut; struct sigaction sigAction, oldSigAction; sigset_t signalMask; char buffer[20], sequence[20]; int buffIndex = 0, pendingEsc = 0; buffer[0] = 0; sequence[0] = 0; // Terminals send the SIGWINCH signal when they resize. memset(&sigAction, 0, sizeof(sigAction)); sigAction.sa_handler = handleSIGWINCH; sigAction.sa_flags = SA_RESTART; // Useless if we are using poll. if (sigaction(SIGWINCH, &sigAction, &oldSigAction)) perror_exit("can't set signal handler for SIGWINCH"); sigemptyset(&signalMask); sigaddset(&signalMask, SIGWINCH); // TODO - OS buffered keys might be a problem, but we can't do the // usual timestamp filter for now. stillRunning = 1; while (stillRunning) { int j, p, csi = 0; // Apparently it's more portable to reset these each time. FD_ZERO(&selectFds); FD_SET(0, &selectFds); timeOut.tv_sec = 0; timeOut.tv_nsec = 100000000; // One tenth of a second. // We got a "terminal size changed" signal, ask the terminal // how big it is now. if (sigWinch) { // Send - save cursor position, down 999, right 999, // request cursor position, restore cursor position. fputs("\x1B[s\x1B[999C\x1B[999B\x1B[6n\x1B[u", stdout); fflush(stdout); sigWinch = 0; } // TODO - Should only ask for a time out after we get an Escape, or // the user requested time ticks. // I wanted to use poll, but that would mean using ppoll, which is // Linux only, and involves defining swear words to get it. p = pselect(0 + 1, &selectFds, NULL, NULL, &timeOut, &signalMask); if (0 > p) { if (EINTR == errno) continue; perror_exit("poll"); } else if (0 == p) // A timeout, trigger a time event. { if (pendingEsc) { // After a short delay to check, this is a real Escape key, // not part of an escape sequence, so deal with it. strcat(sequence, "Esc"); buffer[0] = buffIndex = 0; } // TODO - Call some sort of timer tick callback. This wont be // a precise timed event, but don't think we need one. } else if ((0 < p) && FD_ISSET(0, &selectFds)) { j = read(0, &buffer[buffIndex], sizeof(buffer) - (buffIndex + 1)); if (j < 0) perror_exit("input error"); else if (j == 0) // End of file. { stillRunning = 0; fprintf(stderr, "EOF\n"); for (j = 0; buffer[j + 1]; j++) fprintf(stderr, "(%x), ", (int) buffer[j]); fflush(stderr); } else { buffIndex += j; if (sizeof(buffer) < (buffIndex + 1)) // Ran out of buffer. { fprintf(stderr, "Full buffer - %s -> %s\n", buffer, sequence); for (j = 0; buffer[j + 1]; j++) fprintf(stderr, "(%x) %c, ", (int) buffer[j], buffer[j]); fflush(stderr); buffIndex = 0; } buffer[buffIndex] = 0; } } // Check for lone Esc first, wait a bit longer if it is pendingEsc = ((0 == buffer[1]) && ('\x1B' == buffer[0])); if (pendingEsc) continue; // Check if it's a CSI before we check for the known key sequences. if ((('\x1B' == buffer[0]) && ('[' == buffer[1])) || (('\xC2' == buffer[0]) && ('\x9B' == buffer[1]))) { buffer[0] = '\x9B'; for (j = 1; buffer[j]; j++) buffer[j] = buffer[j + 1]; buffIndex--; } csi = ('\x9B' == buffer[0]); // Check for known key sequences. // For a real timeout checked Esc, buffer is now empty, so this for loop // wont find it anyway. 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. for (j = 0; j < (sizeof(keys) / sizeof(*keys)); j++) { if (strcmp(keys[j].code, buffer) == 0) { strcat(sequence, keys[j].name); buffer[0] = buffIndex = 0; csi = 0; break; } } // Find out if it's a CSI sequence that's not in the known key sequences. if (csi) { /* ECMA-048 section 5.2 defines this, and is unreadable. * General CSI format - CSI [private] n1 ; n2 [extra] final * 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. * n1 0x30 to 0x3f "01234567890:;<=>?" * ASCII digits forming a "number" * 0x3a ":" Used for floats, not expecting any. * Could also be used as some other sort of * inter digit separator. * 0x3b [;] Separates the parameters. * extra 0x20 to 0x2f [ !"#$%&'()*+,-./] * Can be multiple, likely isn't. * final 0x40 to 0x7e "@A .. Z[\]^_`a .. z{|}~" * It's private if 0x70 to 0x7e "p .. z{|}~" * Though the "private" ~ is used for key codes. * We also have SS3 "\x1BO" for other keys, * but that's not a CSI. * C0 controls, DEL (0x7f), or high characters are undefined. * TODO - So abort the current CSI and start from scratch on one of those. */ if ('M' == buffer[1]) { // 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. // To make things worse, can't tell how long this will be. } else { char *t, csFinal[8]; int csIndex = 1, csParams[8]; csFinal[0] = 0; p = 0; // Unspecified params default to a value that is command dependant. // However, they will never be negative, so we can use -1 to flag // a default value. for (j = 0; j < (sizeof(csParams) / sizeof(*csParams)); j++) csParams[j] = -1; // Check for the private bit. if (index("<=>?", buffer[1])) { csFinal[0] = buffer[1]; csFinal[1] = 0; csIndex++; } // Decode parameters. j = csIndex; do { // So we know when we get to the end of parameter space. t = index("01234567890:;<=>?", buffer[j + 1]); // See if we passed a paremeter. if ((';' == buffer[j]) || (!t)) { // Only stomp on the ; if it's really the ;. if (t) buffer[j] = 0; // Empty parameters are default parameters, so only deal with // non defaults. if (';' != buffer[csIndex] || (!t)) { // TODO - Might be ":" in the number somewhere, but we are not // expecting any in anything we do. csParams[p] = atoi(&buffer[csIndex]); } p++; csIndex = j + 1; } j++; } while (t); // Check if we got the final byte, and send it to the callback. strcat(csFinal, &buffer[csIndex]); t = csFinal + strlen(csFinal) - 1; if (('\x40' <= (*t)) && ((*t) <= '\x7e')) { if (handle_CSI) handle_CSI(extra, csFinal, csParams, p); buffer[0] = buffIndex = 0; sequence[0] = 0; } } } // Pass the result to the callback. if ((handle_sequence) && (sequence[0] || buffer[0])) { char b[strlen(sequence) + strlen(buffer) + 1]; sprintf(b, "%s%s", sequence, buffer); if (handle_sequence(extra, b, (0 != sequence[0]))) { buffer[0] = buffIndex = 0; sequence[0] = 0; } } } sigaction(SIGWINCH, &oldSigAction, NULL); } void handle_keys_quit() { stillRunning = 0; }
/* handlekeys.h - Generic terminal input handler. * * Copyright 2012 David Seikel <[email protected]> */ /* An input loop that handles keystrokes and terminal CSI commands. * * Reads stdin, trying to translate raw keystrokes into something more readable. * See the keys[] array at the top of handlekeys.c for what byte sequences get * translated into what key names. See dumbsh.c for an example of usage. * A 0.1 second delay is used to detect the Esc key being pressed, and not Esc * being part of a raw keystroke. * * handle_keys also tries to decode CSI commands that terminals can send. * Some keystrokes are CSI commands, but those are translated as key sequences * instead of CSI commands. * * handle_keys also sets up a SIGWINCH handler to catch terminal resizes, * and sends a request to the terminal to report it's current size when it gets * a SIGWINCH. This is the main reason for handle_CSI, as those reports are * sent as CSI. It's still up to the user code to recognise and deal with the * terminal resize response, but at least it's nicely decoded for you. * * Arguments - * extra - arbitrary data that gets passed back to the callbacks. * handle_sequence - a callback to handle keystroke sequences. * handle_CSI - a callback to handle terminal CSI commands. * * handle_sequence is called when a complete keystroke sequence has been * accumulated. The sequence argument holds the accumulated keystrokes. * The translated argument flags if any have been translated, otherwise you * can assume it's all ordinary characters. * * handle_keys should return 1 if the sequence has been dealt with, or ignored. * It should return 0, if handle_keys should keep adding more * translated keystroke sequences on the end, and try again later. * 0 should really only be used if it's a partial match, and we need more * keys in the sequence to make a full match. * * handle_CSI is called when a complete terminal CSI command has been * detected. The command argument is the full CSI command code, including * private and intermediate characters. The params argument is the decoded * parameters from the command. The count argument is the number of decoded * parameters. Empty parameters are set to -1, coz -1 parameters are not legal, * and empty ones should default to something that is command dependant. * * NOTE - handle_CSI differs from handle_sequence in not having to * return anything. CSI sequences include a definite terminating byte, * so no need for this callback to tell handle_keys to keep accumulating. * Some applications use a series of keystrokes for things, so they * get accumulated until fully recognised by the user code. */ void handle_keys(long extra, int (*handle_sequence)(long extra, char *sequence, int isTranslated), void (*handle_CSI)(long extra, char *command, int *params, int count)); /* Call this when you want handle_keys to return. */ void handle_keys_quit();
signature.asc
Description: PGP signature
_______________________________________________ Toybox mailing list [email protected] http://lists.landley.net/listinfo.cgi/toybox-landley.net
