10f66f451Sopenharmony_ci/* interestingtimes.c - cursor control 20f66f451Sopenharmony_ci * 30f66f451Sopenharmony_ci * Copyright 2015 Rob Landley <rob@landley.net> 40f66f451Sopenharmony_ci */ 50f66f451Sopenharmony_ci 60f66f451Sopenharmony_ci#include "toys.h" 70f66f451Sopenharmony_ci 80f66f451Sopenharmony_ciint tty_fd(void) 90f66f451Sopenharmony_ci{ 100f66f451Sopenharmony_ci int i, j; 110f66f451Sopenharmony_ci 120f66f451Sopenharmony_ci for (i = 0; i<3; i++) if (isatty(j = (i+1)%3)) return j; 130f66f451Sopenharmony_ci 140f66f451Sopenharmony_ci return notstdio(open("/dev/tty", O_RDWR)); 150f66f451Sopenharmony_ci} 160f66f451Sopenharmony_ci 170f66f451Sopenharmony_ci// Quick and dirty query size of terminal, doesn't do ANSI probe fallback. 180f66f451Sopenharmony_ci// set x=80 y=25 before calling to provide defaults. Returns 0 if couldn't 190f66f451Sopenharmony_ci// determine size. 200f66f451Sopenharmony_ci 210f66f451Sopenharmony_ciint terminal_size(unsigned *xx, unsigned *yy) 220f66f451Sopenharmony_ci{ 230f66f451Sopenharmony_ci struct winsize ws; 240f66f451Sopenharmony_ci unsigned i, x = 0, y = 0; 250f66f451Sopenharmony_ci char *s; 260f66f451Sopenharmony_ci 270f66f451Sopenharmony_ci // stdin, stdout, stderr 280f66f451Sopenharmony_ci for (i=0; i<3; i++) { 290f66f451Sopenharmony_ci memset(&ws, 0, sizeof(ws)); 300f66f451Sopenharmony_ci if (isatty(i) && !ioctl(i, TIOCGWINSZ, &ws)) { 310f66f451Sopenharmony_ci if (ws.ws_col) x = ws.ws_col; 320f66f451Sopenharmony_ci if (ws.ws_row) y = ws.ws_row; 330f66f451Sopenharmony_ci 340f66f451Sopenharmony_ci break; 350f66f451Sopenharmony_ci } 360f66f451Sopenharmony_ci } 370f66f451Sopenharmony_ci s = getenv("COLUMNS"); 380f66f451Sopenharmony_ci if (s) sscanf(s, "%u", &x); 390f66f451Sopenharmony_ci s = getenv("LINES"); 400f66f451Sopenharmony_ci if (s) sscanf(s, "%u", &y); 410f66f451Sopenharmony_ci 420f66f451Sopenharmony_ci // Never return 0 for either value, leave it at default instead. 430f66f451Sopenharmony_ci if (xx && x) *xx = x; 440f66f451Sopenharmony_ci if (yy && y) *yy = y; 450f66f451Sopenharmony_ci 460f66f451Sopenharmony_ci return x || y; 470f66f451Sopenharmony_ci} 480f66f451Sopenharmony_ci 490f66f451Sopenharmony_ci// Query terminal size, sending ANSI probe if necesary. (Probe queries xterm 500f66f451Sopenharmony_ci// size through serial connection, when local TTY doesn't know but remote does.) 510f66f451Sopenharmony_ci// Returns 0 if ANSI probe sent, 1 if size determined from tty or environment 520f66f451Sopenharmony_ci 530f66f451Sopenharmony_ciint terminal_probesize(unsigned *xx, unsigned *yy) 540f66f451Sopenharmony_ci{ 550f66f451Sopenharmony_ci if (terminal_size(xx, yy) && (!xx || *xx) && (!yy || *yy)) return 1; 560f66f451Sopenharmony_ci 570f66f451Sopenharmony_ci // Send probe: bookmark cursor position, jump to bottom right, 580f66f451Sopenharmony_ci // query position, return cursor to bookmarked position. 590f66f451Sopenharmony_ci xprintf("\033[s\033[999C\033[999B\033[6n\033[u"); 600f66f451Sopenharmony_ci 610f66f451Sopenharmony_ci return 0; 620f66f451Sopenharmony_ci} 630f66f451Sopenharmony_ci 640f66f451Sopenharmony_civoid xsetspeed(struct termios *tio, int speed) 650f66f451Sopenharmony_ci{ 660f66f451Sopenharmony_ci int i, speeds[] = {50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 670f66f451Sopenharmony_ci 4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 680f66f451Sopenharmony_ci 500000, 576000, 921600, 1000000, 1152000, 1500000, 2000000, 690f66f451Sopenharmony_ci 2500000, 3000000, 3500000, 4000000}; 700f66f451Sopenharmony_ci 710f66f451Sopenharmony_ci // Find speed in table, adjust to constant 720f66f451Sopenharmony_ci for (i = 0; i < ARRAY_LEN(speeds); i++) if (speeds[i] == speed) break; 730f66f451Sopenharmony_ci if (i == ARRAY_LEN(speeds)) error_exit("unknown speed: %d", speed); 740f66f451Sopenharmony_ci cfsetspeed(tio, i+1+4081*(i>15)); 750f66f451Sopenharmony_ci} 760f66f451Sopenharmony_ci 770f66f451Sopenharmony_ci 780f66f451Sopenharmony_ci// Reset terminal to known state, saving copy of old state if old != NULL. 790f66f451Sopenharmony_ciint set_terminal(int fd, int raw, int speed, struct termios *old) 800f66f451Sopenharmony_ci{ 810f66f451Sopenharmony_ci struct termios termio; 820f66f451Sopenharmony_ci int i = tcgetattr(fd, &termio); 830f66f451Sopenharmony_ci 840f66f451Sopenharmony_ci // Fetch local copy of old terminfo, and copy struct contents to *old if set 850f66f451Sopenharmony_ci if (i) return i; 860f66f451Sopenharmony_ci if (old) *old = termio; 870f66f451Sopenharmony_ci 880f66f451Sopenharmony_ci // the following are the bits set for an xterm. Linux text mode TTYs by 890f66f451Sopenharmony_ci // default add two additional bits that only matter for serial processing 900f66f451Sopenharmony_ci // (turn serial line break into an interrupt, and XON/XOFF flow control) 910f66f451Sopenharmony_ci 920f66f451Sopenharmony_ci // Any key unblocks output, swap CR and NL on input 930f66f451Sopenharmony_ci termio.c_iflag = IXANY|ICRNL|INLCR; 940f66f451Sopenharmony_ci if (toys.which->flags & TOYFLAG_LOCALE) termio.c_iflag |= IUTF8; 950f66f451Sopenharmony_ci 960f66f451Sopenharmony_ci // Output appends CR to NL, does magic undocumented postprocessing 970f66f451Sopenharmony_ci termio.c_oflag = ONLCR|OPOST; 980f66f451Sopenharmony_ci 990f66f451Sopenharmony_ci // Leave serial port speed alone 1000f66f451Sopenharmony_ci // termio.c_cflag = C_READ|CS8|EXTB; 1010f66f451Sopenharmony_ci 1020f66f451Sopenharmony_ci // Generate signals, input entire line at once, echo output 1030f66f451Sopenharmony_ci // erase, line kill, escape control characters with ^ 1040f66f451Sopenharmony_ci // erase line char at a time 1050f66f451Sopenharmony_ci // "extended" behavior: ctrl-V quotes next char, ctrl-R reprints unread chars, 1060f66f451Sopenharmony_ci // ctrl-W erases word 1070f66f451Sopenharmony_ci termio.c_lflag = ISIG|ICANON|ECHO|ECHOE|ECHOK|ECHOCTL|ECHOKE|IEXTEN; 1080f66f451Sopenharmony_ci 1090f66f451Sopenharmony_ci if (raw) cfmakeraw(&termio); 1100f66f451Sopenharmony_ci 1110f66f451Sopenharmony_ci if (speed) { 1120f66f451Sopenharmony_ci int i, speeds[] = {50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 1130f66f451Sopenharmony_ci 4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 1140f66f451Sopenharmony_ci 500000, 576000, 921600, 1000000, 1152000, 1500000, 2000000, 1150f66f451Sopenharmony_ci 2500000, 3000000, 3500000, 4000000}; 1160f66f451Sopenharmony_ci 1170f66f451Sopenharmony_ci // Find speed in table, adjust to constant 1180f66f451Sopenharmony_ci for (i = 0; i < ARRAY_LEN(speeds); i++) if (speeds[i] == speed) break; 1190f66f451Sopenharmony_ci if (i == ARRAY_LEN(speeds)) error_exit("unknown speed: %d", speed); 1200f66f451Sopenharmony_ci cfsetspeed(&termio, i+1+4081*(i>15)); 1210f66f451Sopenharmony_ci } 1220f66f451Sopenharmony_ci 1230f66f451Sopenharmony_ci return tcsetattr(fd, TCSAFLUSH, &termio); 1240f66f451Sopenharmony_ci} 1250f66f451Sopenharmony_ci 1260f66f451Sopenharmony_civoid xset_terminal(int fd, int raw, int speed, struct termios *old) 1270f66f451Sopenharmony_ci{ 1280f66f451Sopenharmony_ci if (-1 != set_terminal(fd, raw, speed, old)) return; 1290f66f451Sopenharmony_ci 1300f66f451Sopenharmony_ci sprintf(libbuf, "/proc/self/fd/%d", fd); 1310f66f451Sopenharmony_ci libbuf[readlink0(libbuf, libbuf, sizeof(libbuf))] = 0; 1320f66f451Sopenharmony_ci perror_exit("tcsetattr %s", libbuf); 1330f66f451Sopenharmony_ci} 1340f66f451Sopenharmony_ci 1350f66f451Sopenharmony_cistruct scan_key_list { 1360f66f451Sopenharmony_ci int key; 1370f66f451Sopenharmony_ci char *seq; 1380f66f451Sopenharmony_ci} static const scan_key_list[] = { 1390f66f451Sopenharmony_ci {KEY_UP, "\033[A"}, {KEY_DOWN, "\033[B"}, 1400f66f451Sopenharmony_ci {KEY_RIGHT, "\033[C"}, {KEY_LEFT, "\033[D"}, 1410f66f451Sopenharmony_ci 1420f66f451Sopenharmony_ci {KEY_UP|KEY_SHIFT, "\033[1;2A"}, {KEY_DOWN|KEY_SHIFT, "\033[1;2B"}, 1430f66f451Sopenharmony_ci {KEY_RIGHT|KEY_SHIFT, "\033[1;2C"}, {KEY_LEFT|KEY_SHIFT, "\033[1;2D"}, 1440f66f451Sopenharmony_ci 1450f66f451Sopenharmony_ci {KEY_UP|KEY_ALT, "\033[1;3A"}, {KEY_DOWN|KEY_ALT, "\033[1;3B"}, 1460f66f451Sopenharmony_ci {KEY_RIGHT|KEY_ALT, "\033[1;3C"}, {KEY_LEFT|KEY_ALT, "\033[1;3D"}, 1470f66f451Sopenharmony_ci 1480f66f451Sopenharmony_ci {KEY_UP|KEY_CTRL, "\033[1;5A"}, {KEY_DOWN|KEY_CTRL, "\033[1;5B"}, 1490f66f451Sopenharmony_ci {KEY_RIGHT|KEY_CTRL, "\033[1;5C"}, {KEY_LEFT|KEY_CTRL, "\033[1;5D"}, 1500f66f451Sopenharmony_ci 1510f66f451Sopenharmony_ci // VT102/VT220 escapes. 1520f66f451Sopenharmony_ci {KEY_HOME, "\033[1~"}, 1530f66f451Sopenharmony_ci {KEY_INSERT, "\033[2~"}, 1540f66f451Sopenharmony_ci {KEY_DELETE, "\033[3~"}, 1550f66f451Sopenharmony_ci {KEY_END, "\033[4~"}, 1560f66f451Sopenharmony_ci {KEY_PGUP, "\033[5~"}, 1570f66f451Sopenharmony_ci {KEY_PGDN, "\033[6~"}, 1580f66f451Sopenharmony_ci // "Normal" "PC" escapes (xterm). 1590f66f451Sopenharmony_ci {KEY_HOME, "\033OH"}, 1600f66f451Sopenharmony_ci {KEY_END, "\033OF"}, 1610f66f451Sopenharmony_ci // "Application" "PC" escapes (gnome-terminal). 1620f66f451Sopenharmony_ci {KEY_HOME, "\033[H"}, 1630f66f451Sopenharmony_ci {KEY_END, "\033[F"}, 1640f66f451Sopenharmony_ci 1650f66f451Sopenharmony_ci {KEY_FN+1, "\033OP"}, {KEY_FN+2, "\033OQ"}, {KEY_FN+3, "\033OR"}, 1660f66f451Sopenharmony_ci {KEY_FN+4, "\033OS"}, {KEY_FN+5, "\033[15~"}, {KEY_FN+6, "\033[17~"}, 1670f66f451Sopenharmony_ci {KEY_FN+7, "\033[18~"}, {KEY_FN+8, "\033[19~"}, {KEY_FN+9, "\033[20~"}, 1680f66f451Sopenharmony_ci}; 1690f66f451Sopenharmony_ci 1700f66f451Sopenharmony_ci// Scan stdin for a keypress, parsing known escape sequences, including 1710f66f451Sopenharmony_ci// responses to screen size queries. 1720f66f451Sopenharmony_ci// Blocks for timeout_ms milliseconds, 0=return immediately, -1=wait forever. 1730f66f451Sopenharmony_ci// Returns 0-255=literal, -1=EOF, -2=TIMEOUT, -3=RESIZE, 256+= a KEY_ constant. 1740f66f451Sopenharmony_ci// Scratch space is necessary because last char of !seq could start new seq. 1750f66f451Sopenharmony_ci// Zero out first byte of scratch before first call to scan_key. 1760f66f451Sopenharmony_ciint scan_key_getsize(char *scratch, int timeout_ms, unsigned *xx, unsigned *yy) 1770f66f451Sopenharmony_ci{ 1780f66f451Sopenharmony_ci struct pollfd pfd; 1790f66f451Sopenharmony_ci int maybe, i, j; 1800f66f451Sopenharmony_ci char *test; 1810f66f451Sopenharmony_ci 1820f66f451Sopenharmony_ci for (;;) { 1830f66f451Sopenharmony_ci pfd.fd = 0; 1840f66f451Sopenharmony_ci pfd.events = POLLIN; 1850f66f451Sopenharmony_ci pfd.revents = 0; 1860f66f451Sopenharmony_ci 1870f66f451Sopenharmony_ci maybe = 0; 1880f66f451Sopenharmony_ci if (*scratch) { 1890f66f451Sopenharmony_ci int pos[6]; 1900f66f451Sopenharmony_ci unsigned x, y; 1910f66f451Sopenharmony_ci 1920f66f451Sopenharmony_ci // Check for return from terminal size probe 1930f66f451Sopenharmony_ci memset(pos, 0, 6*sizeof(int)); 1940f66f451Sopenharmony_ci scratch[(1+*scratch)&15] = 0; 1950f66f451Sopenharmony_ci sscanf(scratch+1, "\033%n[%n%3u%n;%n%3u%nR%n", pos, pos+1, &y, 1960f66f451Sopenharmony_ci pos+2, pos+3, &x, pos+4, pos+5); 1970f66f451Sopenharmony_ci if (pos[5]) { 1980f66f451Sopenharmony_ci // Recognized X/Y position, consume and return 1990f66f451Sopenharmony_ci *scratch = 0; 2000f66f451Sopenharmony_ci if (xx) *xx = x; 2010f66f451Sopenharmony_ci if (yy) *yy = y; 2020f66f451Sopenharmony_ci return -3; 2030f66f451Sopenharmony_ci } else for (i=0; i<6; i++) if (pos[i]==*scratch) maybe = 1; 2040f66f451Sopenharmony_ci 2050f66f451Sopenharmony_ci // Check sequences 2060f66f451Sopenharmony_ci for (i = 0; i<ARRAY_LEN(scan_key_list); i++) { 2070f66f451Sopenharmony_ci test = scan_key_list[i].seq; 2080f66f451Sopenharmony_ci for (j = 0; j<*scratch; j++) if (scratch[j+1] != test[j]) break; 2090f66f451Sopenharmony_ci if (j == *scratch) { 2100f66f451Sopenharmony_ci maybe = 1; 2110f66f451Sopenharmony_ci if (!test[j]) { 2120f66f451Sopenharmony_ci // We recognized current sequence: consume and return 2130f66f451Sopenharmony_ci *scratch = 0; 2140f66f451Sopenharmony_ci return 256+scan_key_list[i].key; 2150f66f451Sopenharmony_ci } 2160f66f451Sopenharmony_ci } 2170f66f451Sopenharmony_ci } 2180f66f451Sopenharmony_ci 2190f66f451Sopenharmony_ci // If current data can't be a known sequence, return next raw char 2200f66f451Sopenharmony_ci if (!maybe) break; 2210f66f451Sopenharmony_ci } 2220f66f451Sopenharmony_ci 2230f66f451Sopenharmony_ci // Need more data to decide 2240f66f451Sopenharmony_ci 2250f66f451Sopenharmony_ci // 30ms is about the gap between characters at 300 baud 2260f66f451Sopenharmony_ci if (maybe || timeout_ms != -1) 2270f66f451Sopenharmony_ci if (!xpoll(&pfd, 1, maybe ? 30 : timeout_ms)) break; 2280f66f451Sopenharmony_ci 2290f66f451Sopenharmony_ci // Read 1 byte so we don't overshoot sequence match. (We can deviate 2300f66f451Sopenharmony_ci // and fail to match, but match consumes entire buffer.) 2310f66f451Sopenharmony_ci if (toys.signal>0 || 1 != read(0, scratch+1+*scratch, 1)) 2320f66f451Sopenharmony_ci return (toys.signal>0) ? -3 : -1; 2330f66f451Sopenharmony_ci ++*scratch; 2340f66f451Sopenharmony_ci } 2350f66f451Sopenharmony_ci 2360f66f451Sopenharmony_ci // Was not a sequence 2370f66f451Sopenharmony_ci if (!*scratch) return -2; 2380f66f451Sopenharmony_ci i = scratch[1]; 2390f66f451Sopenharmony_ci if (--*scratch) memmove(scratch+1, scratch+2, *scratch); 2400f66f451Sopenharmony_ci 2410f66f451Sopenharmony_ci return i; 2420f66f451Sopenharmony_ci} 2430f66f451Sopenharmony_ci 2440f66f451Sopenharmony_ci// Wrapper that ignores results from ANSI probe to update screensize. 2450f66f451Sopenharmony_ci// Otherwise acts like scan_key_getsize(). 2460f66f451Sopenharmony_ciint scan_key(char *scratch, int timeout_ms) 2470f66f451Sopenharmony_ci{ 2480f66f451Sopenharmony_ci return scan_key_getsize(scratch, timeout_ms, NULL, NULL); 2490f66f451Sopenharmony_ci} 2500f66f451Sopenharmony_ci 2510f66f451Sopenharmony_civoid tty_esc(char *s) 2520f66f451Sopenharmony_ci{ 2530f66f451Sopenharmony_ci printf("\033[%s", s); 2540f66f451Sopenharmony_ci} 2550f66f451Sopenharmony_ci 2560f66f451Sopenharmony_civoid tty_jump(int x, int y) 2570f66f451Sopenharmony_ci{ 2580f66f451Sopenharmony_ci char s[32]; 2590f66f451Sopenharmony_ci 2600f66f451Sopenharmony_ci sprintf(s, "%d;%dH", y+1, x+1); 2610f66f451Sopenharmony_ci tty_esc(s); 2620f66f451Sopenharmony_ci} 2630f66f451Sopenharmony_ci 2640f66f451Sopenharmony_civoid tty_reset(void) 2650f66f451Sopenharmony_ci{ 2660f66f451Sopenharmony_ci set_terminal(0, 0, 0, 0); 2670f66f451Sopenharmony_ci tty_esc("?25h"); 2680f66f451Sopenharmony_ci tty_esc("0m"); 2690f66f451Sopenharmony_ci tty_jump(0, 999); 2700f66f451Sopenharmony_ci tty_esc("K"); 2710f66f451Sopenharmony_ci fflush(0); 2720f66f451Sopenharmony_ci} 2730f66f451Sopenharmony_ci 2740f66f451Sopenharmony_ci// If you call set_terminal(), use sigatexit(tty_sigreset); 2750f66f451Sopenharmony_civoid tty_sigreset(int i) 2760f66f451Sopenharmony_ci{ 2770f66f451Sopenharmony_ci tty_reset(); 2780f66f451Sopenharmony_ci _exit(i ? 128+i : 0); 2790f66f451Sopenharmony_ci} 2800f66f451Sopenharmony_ci 2810f66f451Sopenharmony_civoid start_redraw(unsigned *width, unsigned *height) 2820f66f451Sopenharmony_ci{ 2830f66f451Sopenharmony_ci // If never signaled, do raw mode setup. 2840f66f451Sopenharmony_ci if (!toys.signal) { 2850f66f451Sopenharmony_ci *width = 80; 2860f66f451Sopenharmony_ci *height = 25; 2870f66f451Sopenharmony_ci set_terminal(0, 1, 0, 0); 2880f66f451Sopenharmony_ci sigatexit(tty_sigreset); 2890f66f451Sopenharmony_ci xsignal(SIGWINCH, generic_signal); 2900f66f451Sopenharmony_ci } 2910f66f451Sopenharmony_ci if (toys.signal != -1) { 2920f66f451Sopenharmony_ci toys.signal = -1; 2930f66f451Sopenharmony_ci terminal_probesize(width, height); 2940f66f451Sopenharmony_ci } 2950f66f451Sopenharmony_ci xprintf("\033[H\033[J"); 2960f66f451Sopenharmony_ci} 297