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