/*
 * BRLTTY - A background process providing access to the console screen (when in
 *          text mode) for a blind person using a refreshable braille display.
 *
 * Copyright (C) 1995-2014 by The BRLTTY Developers.
 *
 * BRLTTY comes with ABSOLUTELY NO WARRANTY.
 *
 * This is free software, placed under the terms of the
 * GNU General Public License, as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any
 * later version. Please see the file LICENSE-GPL for details.
 *
 * Web Page: http://mielke.cc/brltty/
 *
 * This software is maintained by Dave Mielke <dave@mielke.cc>.
 */

/* BrailleLite/braille.c - Braille display library
 * For Blazie Engineering's Braille Lite series
 * Author: Nikhil Nair <nn201@cus.cam.ac.uk>
 * Copyright (C) 1998 by Nikhil Nair.
 * Some additions by: Nicolas Pitre <nico@fluxnic.net>
 * Some modifications copyright 2001 by Stéphane Doyon <s.doyon@videotron.ca>.
 * Some additions by: Dave Mielke <dave@mielke.cc>
 */

#include "prologue.h"

#include <stdio.h>
#include <string.h>

#include "log.h"
#include "parse.h"
#include "timing.h"
#include "async_wait.h"
#include "message.h"

typedef enum {
  PARM_BAUDRATE,
  PARM_KBEMU
} DriverParameter;
#define BRLPARMS "baudrate", "kbemu"

#include "brl_driver.h"
#include "braille.h"
#include "io_serial.h"

#define BL_NEED_ARRAYS
#include "bindings.h"		/* for keybindings */

#define QSZ 256			/* size of internal input queue in bytes */
#define INT_CSR_SPEED 2		/* on/off time in cycles */
#define ACK_TIMEOUT 1000	/* timeout in ms for an ACK to come back */

SerialDevice *BL_serialDevice = NULL;		/* file descriptor for Braille display */

static unsigned char *prevdata = NULL;	/* previously received data */
static unsigned char *rawdata = NULL;	/* writebrl() buffer for raw Braille data */
static int blitesz;	/* set to 18 or 40 */
static int waiting_ack = 0;	/* waiting acknowledgement flag */
static int reverse_kbd = 0;	/* reverse keyboard flag */
static int intoverride = 0;	/* internal override flag -
				 * highly dubious behaviour ...
				 */
static int int_cursor = 0;	/* position of internal cursor: 0 = none */
static unsigned int kbemu = 1; /* keyboard emulation (whether you can type) */

/* The input queue is only manipulated by the qput() and qget()
 * functions.
 */
static unsigned char *qbase = NULL;	/* start of queue in memory */
static int qoff = 0;		/* offset of first byte */
static int qlen = 0;		/* number of items in the queue */


/* Data type for a Braille Lite key, including translation into command
 * codes:
 */
typedef struct
  {
    unsigned char raw;		/* raw value, after any keyboard reversal */
    int cmd;			/* command code */
    unsigned char asc;		/* ASCII translation of Braille keys */
    unsigned char spcbar;	/* 1 = on, 0 = off */
    unsigned char routing;	/* routing key number */
  }
blkey;


static int
qput (unsigned char c)
{
  if (qlen == QSZ)
    return EOF;
  qbase[(qoff + qlen++) % QSZ] = c;
  return 0;
}


static int
qget (blkey * kp)
{
  unsigned char c;
  int how;
  static const unsigned char counts[] = {1, 3, 3};
  unsigned char count;

  if (qlen == 0)
    return EOF;
  c = qbase[qoff];

  /* extended sequences start with a zero */
  how = (c == 0)? 1:
        (c == 0x80 && blitesz != 18)? 2:
        0;
  count = counts[how];
  if (qlen < count)
    return EOF;

  memset (kp, 0, sizeof (*kp));
  switch (how) {
    case 0: /* non-extended sequences (BL18) */
      /* We must deal with keyboard reversal here: */
      if (reverse_kbd)
	{
	  if (c >= 0x80)	/* advance bar */
	    c ^= 0x03;
	  else
	    c = (c & 0x40) | ((c & 0x38) >> 3) | ((c & 0x07) << 3);
	}

      /* Now we fill in all the info about the keypress: */
      if (c >= 0x80)		/* advance bar */
	{
	  kp->raw = c;
	  switch (c) {
	    case 0x83:		/* left */
	      kp->cmd = BLT_BARLT;
	      break;

	    case 0x80:		/* right */
	      kp->cmd = BLT_BARRT;
	      break;

	    default:		/* unrecognised keypress */
	      kp->cmd = 0;
              break;
	  }
	}
      else
	{
	  kp->spcbar = ((c & 0x40)? 1: 0);
	  c &= 0x3f;		/* leave only dot key info */
	  kp->raw = c;
	  kp->cmd = cmdtrans[c];
	  kp->asc = brltrans[c];
	}
      break;

    case 1: { /* extended sequences (BL40) */
      unsigned char c2 = qbase[((qoff + 1) % QSZ)];
      unsigned char c3 = qbase[((qoff + 2) % QSZ)];

      /* We must deal with keyboard reversal here: */
      if (reverse_kbd)
	{
	  if (c2 == 0)
	    {			/* advance bars or routing keys */
	      if (c3 & 0x80)	/* advance bars */
		c3 = ((c3 & 0xF0) |
		      ((c3 & 0x1) << 3) | ((c3 & 0x2) << 1) |
		      ((c3 & 0x4) >> 1) | ((c3 & 0x8) >> 3));
	      else if (c3 > 0 && c3 <= blitesz)
		c3 = blitesz - c3 + 1;
	    }
	  else
            {
              c2 = (((c2 & 0x38) >> 3) | ((c2 & 0x07) << 3) |
                    ((c2 & 0x40) << 1) | ((c2 & 0x80) >> 1));
              c3 = ((c3 & 0x40) | ((c3 & 0x38) >> 3) | ((c3 & 0x07) << 3));
            }
	}

      /* Now we fill in all the info about the keypress: */
      if (c2 == 0)		/* advance bars or routing keys */
	{
	  kp->raw = c3;
	  if (c3 & 0x80)
            kp->cmd = (*barcmds)[c3 & 0xF];
	  else if (c3 > 0 && c3 <= blitesz)
	    kp->routing = c3;
	}
      else
	{
	  kp->spcbar = ((c3 & 0x40)? 1: 0);
	  c3 &= 0x3f;		/* leave only dot key info */
	  kp->raw = (c2 & 0xC0) | c3; /* combine info for all 8 dots */
	  /* c2&0x3F and c3&0x3F are the same, i.e. dots 1-6. */
	  kp->cmd = cmdtrans[c3];
	  kp->asc = brltrans[c3];
	}
      break;
    }

    case 2: { /* extended sequences (millennium) */
      unsigned char c3 = qbase[((qoff + 2) % QSZ)];

      /* We must deal with keyboard reversal here: */
      if (reverse_kbd)
        c3 = ((c3 & 0x11) << 3) | ((c3 & 0x22) << 1) | ((c3 & 0x44) >> 1) | ((c3 & 0x88) >> 3);
      kp->raw = c3;

      if (c3 & 0x0f)
        kp->cmd = (*barcmds)[((c3 & 0x1) << 3) | ((c3 & 0x2) << 1) | ((c3 & 0x4) >> 1) | ((c3 & 0x8) >> 3)];
      else if (c3 & 0x30)
        kp->cmd = rwwcmds[(c3 >> 4) & 0x3];
      else if (c3 & 0xc0)
        kp->cmd = lwwcmds[(c3 >> 6) & 0x3];
      else
        kp->cmd = 0;
      break;
    }

    default:
      kp->cmd = 0;
      break;
  }

  /* adjust queue variables for next member */
  qoff = (qoff + count) % QSZ;
  qlen -= count;

  return 0;
}


static void
qfill (void)
{
  unsigned char c;		/* character buffer */

  while (serialReadData (BL_serialDevice, &c, 1, 0, 0) == 1)
    {
      if (waiting_ack && c == 5)	/* ^e is the acknowledgement character ... */
	waiting_ack = 0;
      else
	qput (c);
    }
}


static void
qflush (void)
{
  qfill();
  qlen = 0;
}

static int
await_ack (void) {
  TimePeriod period;
  startTimePeriod(&period, ACK_TIMEOUT);
  waiting_ack = 1;
  do {
    asyncWait(10);	/* sleep for 10 ms */
    qfill();
    if (!waiting_ack) return 1;
  } while (!afterTimePeriod(&period, NULL));
  return 0;
}

static void
write_prebrl (void) {
  static const unsigned char request[] = {0X05, 0X44};			/* code to send before Braille */
  serialWriteData(BL_serialDevice, request, sizeof(request));
}

static int
brl_construct (BrailleDisplay *brl, char **parameters, const char *device)
{
  static const unsigned int good_baudrates[] =
    {300,600,1200,2400,4800,9600,19200,38400, 0};
  unsigned int baudrate;
  /* Init string for Model detection */

  if (!*parameters[PARM_BAUDRATE] ||
      !serialValidateBaud(&baudrate, "baud rate",
		    parameters[PARM_BAUDRATE], good_baudrates))
    baudrate = BAUDRATE;

  if (*parameters[PARM_KBEMU])
    if (!validateYesNo(&kbemu, parameters[PARM_KBEMU]))
      logMessage(LOG_WARNING, "%s: %s", "invalid keyboard emulation setting", parameters[PARM_KBEMU]);
  kbemu = !!kbemu;

  if (!isSerialDevice(&device)) {
    unsupportedDevice(device);
    return 0;
  }

  logMessage(LOG_DEBUG, "Opening serial port: %s", device);
  if ((BL_serialDevice = serialOpenDevice(device))) {
    if (serialRestartDevice(BL_serialDevice, baudrate)) {
      if (serialSetFlowControl(BL_serialDevice, SERIAL_FLOW_HARDWARE)) {
        if ((qbase = malloc(QSZ))) {
          qflush();
          write_prebrl();

          if (await_ack()) {
            logMessage(LOG_DEBUG, "Got response.");

            /* Next, let's detect the BLT-Model (18, 40, M20, M40). */
            barcmds = &bar2cmds;
            {
              unsigned char cells[18];

              memset(cells, 0, sizeof(cells));
              serialWriteData(BL_serialDevice, cells, sizeof(cells));
              waiting_ack = 1;
              asyncWait(400);
              qfill();

              if (waiting_ack) {
                /* no response, so it must be BLT40 */
                blitesz = 40;
                brl->keyBindings = "40_m20_m40";
              } else {
                blitesz = sizeof(cells);
                brl->keyBindings = "18";
              }
            }

            {
              static const unsigned char request[] = {0X05, 0X57};			/* code to send before Braille */

              asyncWait(200);
              qflush();
              serialWriteData(BL_serialDevice, request, sizeof(request));
              waiting_ack = 0;
              asyncWait(200);
              qfill();

              if (qlen) {
                char response[qlen + 1];
                int length = 0;

                do {
                  unsigned char byte = qbase[qoff % QSZ];

                  qoff = (qoff + 1) % QSZ, --qlen;
                  if (!byte) break;
                  response[length++] = byte;
                } while (qlen);

                response[length] = 0;
                logMessage(LOG_INFO, "Braille Lite identity: %s", response);

                if ((response[0] == 'X') &&
                    (response[1] == ' ') &&
                    (response[2] == 'B')) {
                  blitesz = atoi(&response[3]);
                  if (blitesz <= 20) barcmds = &bar1cmds;
                }
              }
            }

            logMessage(LOG_NOTICE, "Braille Lite %d detected.", blitesz);
            brl->textColumns = blitesz;	/* initialise size of display - */
            brl->textRows = 1;		/* Braille Lites are single line displays */

            makeOutputTable(dotsTable_ISO11548_1);
            makeInputTable();

            /* Allocate space for buffers */
            if ((prevdata = malloc(brl->textColumns))) {
              memset(prevdata, 0, brl->textColumns);

              if ((rawdata = malloc(brl->textColumns))) {
                return 1;

              //free(rawdata);
              //rawdata = NULL;
              } else {
                logMallocError();
              }

              free(prevdata);
              prevdata = NULL;
            } else {
              logMallocError();
            }
          } else {
            logMessage(LOG_DEBUG, "BrailleLite not responding.");
          }

          free(qbase);
          qbase = NULL;
        } else {
          logMallocError();
        }
      }
    }

    serialCloseDevice(BL_serialDevice);
    BL_serialDevice = NULL;
  }

  return 0;
}


static void
brl_destruct (BrailleDisplay * brl)
{
  if (rawdata) {
    free(rawdata);
    rawdata = NULL;
  }

  if (prevdata) {
    free(prevdata);
    prevdata = NULL;
  }

  if (qbase) {
    free(qbase);
    qbase = NULL;
  }

  if (BL_serialDevice) {
    serialCloseDevice(BL_serialDevice);
    BL_serialDevice = NULL;
  }
}


static int
brl_writeWindow (BrailleDisplay * brl, const wchar_t *text)
{
  short i;			/* loop counter */

  /* If the intoverride flag is set, then calls to writebrl() from the main
   * module are ignored, because the display is in internal use.
   * This is highly antisocial behaviour!
   */
  if (intoverride)
    return 1;

  /* First, the internal cursor: */
  if (int_cursor)
    {
      static int timer = 0;		/* for internal cursor */
      timer = (timer + 1) % (INT_CSR_SPEED * 2);
      brl->buffer[int_cursor - 1] = (timer < INT_CSR_SPEED)?
                                      (BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT7):
                                      (BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8);
    }

  /* Next we must handle display reversal: */
  if (reverse_kbd) {
    for (i=0; i<blitesz; i+=1) {
      rawdata[i] = translateInputCell(brl->buffer[blitesz - 1 - i]);
    }
  } else {
    memcpy(rawdata, brl->buffer, blitesz);
  }

  /* Only refresh display if the data has changed: */
  if (cellsHaveChanged(prevdata, rawdata, blitesz, NULL, NULL, NULL))
    {
      /* Dot mapping from standard to BrailleLite: */
      translateOutputCells(rawdata, rawdata, blitesz);

      /* First we process any pending keystrokes, just in case any of them
       * are ^e ...
       */
      waiting_ack = 0;		/* Not really necessary, but ... */
      qfill ();

      /* Next we send the ^eD sequence, and wait for an ACK */
      waiting_ack = 1;
      /* send the ^ED... */
      write_prebrl();
      if (!await_ack()) return 1;

      /* OK, now we'll suppose we're all clear to send Braille data. */
      serialWriteData(BL_serialDevice, rawdata, blitesz);
      await_ack();
    }
  return 1;
}


static int
brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context)
{
  static enum {
    ST_NORMAL,	/* transparent */
    ST_CURSOR,	/* position internal cursor */
    ST_REPEAT,	/* set repeat count */
    ST_CONFIG	/* preferences options */
  } state = ST_NORMAL;
  static int repeat = 0;		/* repeat count for command */
  static int repeatNext = 0; /* flag to indicate  whether 0 we repeat the
				same command or 1 we get the next command
				and repeat that. */
  static int hold, shift, shiftlck, ctrl, meta;
#ifdef USE_TEXTTRANS
  static int dot8shift;
#endif /* USE_TEXTTRANS */
  static blkey key;
  static char outmsg[41];
  int temp = BRL_CMD_NOOP;

 again:
  if(repeatNext || repeat == 0) {
    /* Process any new keystrokes: */
    qfill ();
    if (qget (&key) == EOF)	/* no keys to process */
      return EOF;
    repeatNext = 0;
  }
  if(repeat>0)
    repeat--;

  /* Our overall behaviour depends on the state variable (see above). */
  switch (state)
    {
    case ST_NORMAL:			/* transparent */
      /* First we deal with external commands: */
      do
	{
	  /* if it's not an external command, go on */
	  if (!key.cmd)
	    break;

	  /* if advance bar, return with corresponding command */
	  if (key.asc == 0)
	    return key.cmd;

	  /* I thought I was smart when I suggested to remove CMD_CUT_END.
	     Well now there's this nasty exception: the command offset
	     depends on the display size! */
	  if(key.cmd == BRL_BLK_COPY_RECT || key.cmd == BRL_BLK_COPY_LINE)
	    key.cmd += blitesz-1;

	  if(key.spcbar && (key.cmd &BRL_MSK_BLK) == BRL_BLK_PASSKEY) {
          /*
	    if(!kbemu)
	      return EOF;
          */
	    if (!shiftlck)
	      shift = 0;
	    ctrl = meta = 0;
#ifdef USE_TEXTTRANS
	    dot8shift = 0;
#endif /* USE_TEXTTRANS */
	  }

	  /* always OK if chorded */
	  if (key.spcbar)
	    return key.cmd;

	  /* kbemu could be on, then go on */
	  if (kbemu && context == KTB_CTX_DEFAULT)
	    break;

	  /* if it's a dangerous command it should have been chorded */
	  if (dangcmd[(key.raw & 0x38) >> 3] & (1 << (key.raw & 0x07)))
	    break;

	  /* finally we are OK */
	  return key.cmd;
	}
      while (0);

      /* Next, internal commands: */
      if (key.spcbar)
	switch (key.asc)
	  {
	  case BLT_KBEMU:	/* set keyboard emulation */
	    kbemu ^= 1;
	    shift = shiftlck = ctrl = meta = 0;
#ifdef USE_TEXTTRANS
	    dot8shift = 0;
#endif /* USE_TEXTTRANS */
	    if(kbemu)
	      message (NULL, gettext("keyboard emu on"), MSG_SILENT);
	    else message (NULL, gettext("keyboard emu off"), MSG_SILENT);
	    return BRL_CMD_NOOP;
	  case BLT_ROTATE:	/* rotate Braille Lite by 180 degrees */
	    reverse_kbd ^= 1;
	    return BRL_CMD_NOOP;
	  case BLT_POSITN:	/* position internal cursor */
	    int_cursor = blitesz / 2;
	    state = ST_CURSOR;
	    return BRL_CMD_NOOP;
	  case BLT_REPEAT:	/* set repeat count */
	    hold = 0;
	    snprintf (outmsg, sizeof(outmsg), "%s:", gettext("repeat count"));
	    message (NULL, outmsg, MSG_SILENT | MSG_NODELAY);
	    intoverride = 1;
	    state = ST_REPEAT;
	    return BRL_CMD_NOOP;
	  case BLT_CONFIG:	/* configuration menu */
	    snprintf (outmsg, sizeof(outmsg), "%s? [m/s/r/z]", gettext("config"));
	    message (NULL, outmsg, MSG_SILENT | MSG_NODELAY);
	    intoverride = 1;
	    state = ST_CONFIG;
	    return BRL_CMD_NOOP;
	  case ' ':		/* practical exception for */
	    /* If keyboard mode off, space bar == BRL_CMD_HOME */
	    if (!kbemu || context != KTB_CTX_DEFAULT)
	      return BRL_CMD_HOME;
	  }

      /* check for routing keys */
      if (key.routing)
	return (BRL_BLK_ROUTE + key.routing - 1);

      if (!kbemu)
	return BRL_CMD_NOOP;

      /* Now kbemu is definitely on. */
      switch (key.raw & 0xC0)
	{
	case 0x40:		/* dot 7 */
	  shift = 1;
	  break;
	case 0xC0:		/* dot 78 */
	  ctrl = 1;
	  break;
	case 0x80:		/* dot 8 */
#ifdef USE_TEXTTRANS
	  dot8shift = 1;
#else /* USE_TEXTTRANS */
	  meta = 1;
#endif /* USE_TEXTTRANS */
	  break;
	}

      if (key.spcbar && key.asc != ' ')
	switch (key.asc)
	  {
	  case BLT_UPCASE:	/* upper case next */
	    if (shift)
	      shiftlck = 1;
	    else
	      shift = 1;
	    return BRL_CMD_NOOP;
	  case BLT_UPCOFF:	/* cancel upper case */
	    shift = shiftlck = 0;
	    return BRL_CMD_NOOP;
	  case BLT_CTRL:	/* control next */
	    ctrl = 1;
	    return BRL_CMD_NOOP;
#ifdef USE_TEXTTRANS
	  case BLT_DOT8SHIFT:	/* add dot 8 to next pattern */
	    dot8shift = 1;
	    return BRL_CMD_NOOP;
#endif /* USE_TEXTTRANS */
	  case BLT_META:	/* meta next */
	    meta = 1;
	    return BRL_CMD_NOOP;
	  case BLT_ABORT:	/* abort - quit keyboard emulation */
	    kbemu = 0;
	    message (NULL, gettext("keyboard emu off"), MSG_SILENT);
	    return BRL_CMD_NOOP;
	  default:		/* unrecognised command */
	    shift = shiftlck = ctrl = meta = 0;
#ifdef USE_TEXTTRANS
	    dot8shift = 0;
#endif /* USE_TEXTTRANS */
	    return BRL_CMD_NOOP;
	  }

      /* OK, it's an ordinary (non-chorded) keystroke, and kbemu is on. */
#ifndef USE_TEXTTRANS
      if (ctrl && key.asc >= 96)
	/* old code was (key.asc & 0x1f) */
	temp = BRL_BLK_PASSCHAR | key.asc | BRL_FLG_CHAR_CONTROL;
      else if (meta && key.asc >= 96)
	temp = BRL_BLK_PASSCHAR | key.asc | BRL_FLG_CHAR_META;
      else if (shift && (key.asc & 0x40))
	/* old code was (key.asc & 0xdf) */
	temp = BRL_BLK_PASSCHAR | key.asc | BRL_FLG_CHAR_SHIFT;
      else
	temp = BRL_BLK_PASSCHAR | key.asc;
#else /* USE_TEXTTRANS */
      temp = BRL_BLK_PASSDOTS |
	(keys_to_dots[key.raw &0x3F]
	 | ((meta) ? BRL_FLG_CHAR_META : 0)
	 | ((ctrl) ? (BRL_DOT7 | BRL_DOT8) : 
	    (shift) ? BRL_DOT7 : 
	    (dot8shift) ? BRL_DOT8 : 0));
#endif /* USE_TEXTTRANS */
      if (!shiftlck)
	shift = 0;
      ctrl = meta = 0;
#ifdef USE_TEXTTRANS
      dot8shift = 0;
#endif /* USE_TEXTTRANS */
      outmsg[0] = 0;
      return temp;

    case ST_CURSOR:			/* position internal cursor */
      switch (key.cmd)
	{
	case BRL_CMD_HOME:		/* go to middle */
	  int_cursor = blitesz / 2;
	  break;
	case BRL_CMD_LNBEG:	/* beginning of display */
	  int_cursor = 1;
	  break;
	case BRL_CMD_LNEND:	/* end of display */
	  int_cursor = blitesz;
	  break;
	case BRL_CMD_FWINLT:	/* quarter left */
	  int_cursor = MAX (int_cursor - blitesz / 4, 1);
	  break;
	case BRL_CMD_FWINRT:	/* quarter right */
	  int_cursor = MIN (int_cursor + blitesz / 4, blitesz);
	  break;
	case BRL_CMD_CHRLT:	/* one character left */
	  if (int_cursor > 1)
	    int_cursor--;
	  break;
	case BRL_CMD_CHRRT:	/* one character right */
	  if (int_cursor < blitesz)
	    int_cursor++;
	  break;
	case BRL_BLK_ROUTE:	/* route cursor */
	  if (key.spcbar)
	    {
	      temp = BRL_BLK_ROUTE + int_cursor - 1;
	      int_cursor = 0;
	      state = ST_NORMAL;
	    }
	  return temp;
	case BRL_BLK_CLIP_NEW:	/* begin copy */
	case BRL_BLK_CLIP_ADD:
	  if (key.spcbar)
	    {
	      temp = key.cmd + int_cursor - 1;
	      int_cursor = 0;
	      state = ST_NORMAL;
	    }
	  return temp;
	case BRL_BLK_COPY_RECT:	/* end copy */
	case BRL_BLK_COPY_LINE:
	  if (key.spcbar)
	    {
	      temp = key.cmd + int_cursor - 1;
	      int_cursor = 0;
	      state = ST_NORMAL;
	    }
	  return temp;
	case BRL_CMD_DISPMD: /* attribute info */
	  temp = BRL_BLK_DESCCHAR + int_cursor - 1;
	  int_cursor = 0;
	  state = ST_NORMAL;
	  return temp;
	default:
	  if (key.asc == BLT_ABORT) {
            /* cancel cursor positioning */
	    int_cursor = 0;
	    state = ST_NORMAL;
          }
	  break;
	}
      if (key.routing)
	int_cursor = key.routing;
      return BRL_CMD_NOOP;
    case ST_REPEAT:			/* set repeat count */
      if (key.asc >= '0' && key.asc <= '9')
	{
	  hold = (hold * 10 + key.asc - '0') % 100;
	  if (hold) {
	    snprintf (outmsg, sizeof(outmsg), "%s: %d", gettext("repeat count"), hold);
	  } else {
            snprintf (outmsg, sizeof(outmsg), "%s: ", gettext("repeat count"));
          }
	  intoverride = 0;
	  message (NULL, outmsg, MSG_SILENT | MSG_NODELAY);
	  intoverride = 1;
	}
      else if (key.routing)
	{
	  hold = key.routing +1;
	  snprintf (outmsg, sizeof(outmsg), "%s: %d", gettext("repeat count"), hold);
	  intoverride = 0;
	  message (NULL, outmsg, MSG_SILENT | MSG_NODELAY);
	  intoverride = 1;
	}
      else {
	intoverride = 0;
	outmsg[0] = 0;
	state = ST_NORMAL;
	if (hold > 0) {
	  if (key.asc == SWITCHVT_NEXT || key.asc == SWITCHVT_PREV)
	    /* That's chorded or not... */
	    return BRL_BLK_SWITCHVT + (hold-1);
	  else if (key.asc == O_SETMARK)
	    return BRL_BLK_SETMARK + (hold-1);
	  else if (key.asc == O_GOTOMARK)
	    return BRL_BLK_GOTOMARK + (hold-1);
	  else if (key.spcbar)		/* chorded */
	    switch (key.asc)
	      {
	      case BLT_ENDCMD:	/* set repeat count */
		if (hold > 1) {
		  /* repeat next command */
		  repeat = hold;
		  repeatNext = 1;
		}
		/* fall through */
	      case BLT_ABORT:	/* abort or endcmd */
		return BRL_CMD_NOOP;
	      }
	  /* if the key is any other, start repeating it. */
	  repeat = hold;
	  goto again;
	}
      }
      return BRL_CMD_NOOP;
    case ST_CONFIG:			/* preferences options */
      switch (key.asc)
	{
	case 'm':		/* preferences menu */
	  intoverride = 0;
	  state = ST_NORMAL;
	  return BRL_CMD_PREFMENU;
	case 's':		/* save preferences */
	  intoverride = 0;
	  state = ST_NORMAL;
	  return BRL_CMD_PREFSAVE;
	case 'r':		/* restore saved preferences */
	  intoverride = 0;
	  state = ST_NORMAL;
	  return BRL_CMD_PREFLOAD;
	case BLT_ABORT:	/* abort */
	  intoverride = 0;
	  state = ST_NORMAL;
	default:		/* in any case */
	  return BRL_CMD_NOOP;
	}
    }

  /* We should never reach this point ... */
  return EOF;
}
