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();

Attachment: signature.asc
Description: PGP signature

_______________________________________________
Toybox mailing list
[email protected]
http://lists.landley.net/listinfo.cgi/toybox-landley.net

Reply via email to