On Mon, 27 Jan 2014 11:26:16 +1000 David Seikel <onef...@gmail.com>
wrote:

> I'm finally getting back to working on my editor.  At least for the
> next week.  I broke this out of the other thread, it should have it's
> own.

<snip>

I've fixed up the bit rot, and got joe / wordstar stuff to work.  As
well as ^C and other keys usually swallowed by the terminal.

> Squeezing it down to manageable by Rob bits might be tricky due to -
> 
> On Thu, 27 Dec 2012 06:06:53 -0600 Rob Landley <r...@landley.net>
> wrote:
> > Basically what I'd like is a self-contained line reader. More or
> > less a getline variant that can handle cursoring left and right to
> > insert stuff, handle backspace past a wordwrap (which on unix
> > requires knowing the screen width and your current cursor
> > position), and one that lets up/down escape sequences be hooked for
> > less/more screen scrolling, vi line movement, shell command
> > history, and so on.  
> 
> Which is most of the editor already.

Nearing the end of the week, and I hopefully have a first bit to put
into toybox.  The generic input handler, including SIGWINCH and
terminal size probing.  Hopefully it's Rob friendly enough this time
around, and can be added to toysh.  If it's still too big to digest, I
can leave out the terminal size probe, and send that again later.  The
CSI parser to handle that is a big lump of code in the middle.

The terminal size stuff could be integrated into the existing
terminal_size(), but I wanted this first code drop to be just stand
alone files.

First is handlekeys.c and handlekeys.h, both to be put into lib.  Docs
on what it is and how to use it are in handlekeys.h.  Should be usable
for all your keyboard input and screen sizing needs.  Can be extended to
deal with mouse input to, but that's for another day.  Error handling
leaves a lot to be desired, but is more suitable for testing at the
moment.

Second is dumbsh.c, to be put into pending.  It's really just an
example for how to use handle_keys(), but it could be fleshed out into a
real toysh a little bit at a time.

Referring to Rob's comment above -

It's sorta self contained, but since it's up to the application to deal
with what to do with keystrokes and CSI commands, I made the input
handler the self contained blob.  It translates things to something
easily digested by user code, and tries to deal with the gotchas.  Using
that to make a getline variant doesn't take much, as the dumbsh example
shows.

The dumbsh.c file is a brain dead and stupid "shell" example.  It uses
Emacs style keys (trivial to change to vi or something else) to do
cursor left, right, home, end, backspace, and delete.  It reads the bash
history file and lets you up and down arrow through it.  It handles
terminal resizes, even in the middle of typing commands.

Dumbsh doesn't do multi line editing, so no backspace past a wrap,
should be doable easily enough.  It doesn't add stuff to the history,
should be trivial.  It doesn't actually execute commands, I'm still
thinking about how to deal with ^C and friends for that.

Dumbsh has a bug or three.  First time you get to the bottom of the
terminal it will swallow the next line, and resizing quickly gets crap
on the screen. Perhaps other bugs.

I'll likely work on fixing the dumbsh bugs more during the weekend.
Next week I'll be working on something else for a while.  Likely there
will be a week of effort on the editor during February, but I can deal
with any small issues in the mean time.

The editor stuff is still on https://github.com/onefang/boxes , but will
leave putting that into toybox until much later.  It still needs lots of
cleaning up.

-- 
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 <won_f...@yahoo.com.au>
 *
 * 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 (strlen(toybuf) <= TT.x)  TT.x = strlen(toybuf);
  if (TT.w < TT.x)  TT.x = TT.w;
  if (0 > TT.y)  TT.y = 0;
  if (TT.h < TT.y)
  {
    printf("\x1B[%d;0H\n", TT.y + 1);
    fflush(stdout);
    TT.y = TT.h;
  }
  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++;
  updateLine();
}

static void endOfLine()
{
  TT.x = strlen(toybuf) - 1;
  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},
  {"^J",	doCommand},
  {"^M",	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 j;

  // Search for a key sequence bound to a command.
  for (j = 0; j < (sizeof(simpleEmacsKeys) / sizeof(*simpleEmacsKeys)); j++)
  {
    if (strcmp(simpleEmacsKeys[j].key, sequence) == 0)
    {
      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.
  // So here we just check the first character, and insert it all.
  if (isprint(sequence[0]))
  {
    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();
    }
    return 1;
  }

  // Return 0 if we didn't handle it, handle_keys will just keep on
  // accumulating sequences and trying again.
  return 0;
}

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 <won_f...@yahoo.com.au>
 */

// 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,
// and the MC "Esc digit" versions.
// 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",		"Return"},	// LF  Roxterm translates Ctrl-M to this.
  {"\x0B",		"^K"},		// VT
  {"\x0C",		"^L"},		// FF
  {"\x0D",		"^M"},		// CR  Other 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 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"},
  {"\x1BOO",		"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"},

  // MC "Esc digit" specials.
  // NOTE - The MC Esc variations might not be such a good idea, other programs
  //        want the Esc key for other things.
  //          Notably seems that "Esc somekey" is used in place of "Alt somekey"
  //          AKA "Meta somekey" coz apparently some OSes swallow those.
  //            Conversely, some terminals send "Esc somekey" when you do
  //            "Alt somekey".
  //          MC Esc variants might be used on Macs for other things?
  {"\x1B\x31",		"F1"},
  {"\x1B\x32",		"F2"},
  {"\x1B\x33",		"F3"},
  {"\x1B\x34",		"F4"},
  {"\x1B\x35",		"F5"},
  {"\x1B\x36",		"F6"},
  {"\x1B\x37",		"F7"},
  {"\x1B\x38",		"F8"},
  {"\x1B\x39",		"F9"},
  {"\x1B\x30",		"F10"}
};

static volatile sig_atomic_t sigWinch;
static int stillRunning;

static void handleSIGWINCH(int signalNumber)
{
    sigWinch = 1;
}

// TODO - Unhandled complications -
//   Less and more have the "ZZ" command, but nothing else seems to have
//   multi ordinary character commands.
void handle_keys(long extra,
  int (*handle_sequence)(long extra, char *sequence),
  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;

  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.

// TODO - A bit unstable at the moment, something makes it go into
//        a horrid CPU eating edit line flicker mode sometimes.  And / or vi mode
//        can crash on exit (stack smash).
//          This might be fixed now.

    // 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 ((0 == buffer[1]) && ('\x1B' == buffer[0]))
      {
        // After a short delay to check, this is a real Escape key,
        // not part of an escape sequence, so deal with it.
        // TODO - So far the only uses of this have the escape at the start,
        //        but maybe a strcat is needed instead later?
        strcpy(sequence, "^[");
        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))
    {
      // I am assuming that we get the input atomically, each multibyte key
      // fits neatly into one read.
      // If that's not true (which is entirely likely), then we have to get
      // complicated with circular buffers and stuff, or just one byte at a time.
      j = read(0, &buffer[buffIndex], sizeof(buffer) - (buffIndex + 1));
      if (j < 0)      // An error happened.
      {
        // For now, just ignore errors.
        fprintf(stderr, "input error on %d\n", p);
        fflush(stderr);
      }
      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 if it's a CSI before we check for the known key sequences.
    if ('\x9B' == buffer[0])
      csi = 1;
    if (('\x1B' == buffer[0]) && ('[' == buffer[1]))
      csi = 2;
    if (('\xC2' == buffer[0]) && ('\x9B' == buffer[1]))
      csi = 2;
    if (2 == csi)
    {
      buffer[0] = '\x9B';
      for (j = 1; buffer[j]; j++)
        buffer[j] = buffer[j + 1];
      buffIndex--;
      csi = 1;
    }

    // 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.
      }
      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);

        // Get the final command sequence, and pass it to the callback.
        strcat(csFinal, &buffer[csIndex]);
        if (handle_CSI)
          handle_CSI(extra, csFinal, csParams, p);
      }

      csi = 0;
      // Wether or not it's a CSI we understand, it's been handled either here
      // or in the key sequence scanning above.
      buffer[0] = buffIndex = 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))
      {
        sequence[0] = 0;
        buffer[0] = buffIndex = 0;
      }
    }
  }

  sigaction(SIGWINCH, &oldSigAction, NULL);
}

void handle_keys_quit()
{
  stillRunning = 0;
}
/* handlekeys.h - Generic terminal input handler.
 *
 * Copyright 2012 David Seikel <won_f...@yahoo.com.au>
 */

/* An input loop that handles keystrokes and terminal CSI commands.
 *
 * Reads stdin, trying to convert 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.  As a bonus, Midnight Commander style
 * "Esc digit" sequences are translated to function keys.
 *
 * 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.  It should return 1 if the sequence has been dealt with,
 * otherwise it should return 0, then handle_keys will keep adding more
 * complete keystroke sequences on the end, and try again later.
 *
 * 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),
  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
Toybox@lists.landley.net
http://lists.landley.net/listinfo.cgi/toybox-landley.net

Reply via email to