/*
** This file is part of the Brandy Basic V Interpreter.
** Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005 David Daniels
** and Copyright (C) 2006, 2007 Colin Tuckley
**
** Brandy is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2, or (at your option)
** any later version.
**
** Brandy is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with Brandy; see the file COPYING.  If not, write to
** the Free Software Foundation, 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
**
**	This file contains the keyboard handling routines.
**
**	When running under operating systems other than RISC OS the
**	interpreter uses its own keyboard handling functions to
**	provide both line editing and a line recall feature
*/
/*
** Colin Tuckley December 2006:
**  Rewrite to use SDL library Event handling.
*/
/*
** Crispian Daniels August 20th 2002:
**	Included Mac OS X target in conditional compilation.
*/

#include <stdio.h>
#include <string.h>
#include "common.h"
#include "target.h"
#include "basicdefs.h"
#include "errors.h"
#include "keyboard.h"
#include "screen.h"

#ifdef USE_SDL
#include "SDL.h"

Uint32 waitkey_callbackfunc(Uint32 interval, void *param)
{
  SDL_Event event;
  SDL_UserEvent userevent;
  userevent.type = SDL_USEREVENT;
  userevent.code = 0;
  userevent.data1 = NULL;
  userevent.data2 = NULL;

  event.type = SDL_USEREVENT;
  event.user = userevent;

  SDL_PushEvent(&event);
  return(0);  /* cancel the timer */
}
#endif

#ifdef TARGET_RISCOS

/* ================================================================= */
/* ================= RISC OS versions of functions ================= */
/* ================================================================= */

#include "kernel.h"
#include "swis.h"

/*
** 'emulate_get' emulates the Basic function 'get'
*/
int32 emulate_get(void) {
  return _kernel_osrdch();
}

/*
** 'emulate_inkey' does the hard work for the Basic 'inkey' function
*/
int32 emulate_inkey(int32 arg) {
  _kernel_oserror *oserror;	/* Use OS_Byte 129 to obtain the info */
  _kernel_swi_regs regs;
  regs.r[0] = 129;	/* Use OS_Byte 129 for this */
  regs.r[1] = arg & BYTEMASK;
  regs.r[2] = (arg>>BYTESHIFT) & BYTEMASK;
  oserror = _kernel_swi(OS_Byte, &regs, &regs);
  if (oserror != NIL) error(ERR_CMDFAIL, oserror->errmess);
  if (arg >= 0) {		/* +ve argument = read keyboard with time limit */
    if (regs.r[2] == 0)	/* Character was read successfully */
      return (regs.r[1]);
    else if (regs.r[2] == 0xFF)	/* Timed out */
      return -1;
    else {
      error(ERR_CMDFAIL, "C library has missed an escape event");
    }
  }
  else {	/* -ve argument */
    if (regs.r[1] == 0xFF)
      return -1;
    else {
      return regs.r[1];
    }
  }
  return 0;	/* Not needed, but it keeps the Acorn C compiler happy */
}

/*
** 'emulate_readline' reads a line from the keyboard. It returns 'true'
** if the call worked successfully or 'false' if 'escape' was pressed.
** The data input is stored at 'buffer'. Up to 'length' characters can
** be read. A 'null' is added after the last character. The reason for
** using this function in preference to 'fgets' under RISC OS is that
** 'fgets' does not use 'OS_ReadLine' and therefore bypasses the command
** line history and other features that might be available via this SWI
** call.
*/
readstate emulate_readline(char buffer[], int32 length) {
  _kernel_oserror *oserror;
  _kernel_swi_regs regs;
  int32 carry;
  regs.r[0] = TOINT(&buffer[0]);
  regs.r[1] = length-1;		/* -1 to allow for a NULL to be added at the end in all cases */
  regs.r[2] = 0;		/* Allow any character to be input */
  regs.r[3] = 255;
  oserror = _kernel_swi_c(OS_ReadLine, &regs, &regs, &carry);
  if (oserror != NIL) error(ERR_CMDFAIL, oserror->errmess);
  if (carry != 0)		/* Carry is set - 'escape' was pressed */
    buffer[0] = NUL;
  else {
    buffer[regs.r[1]] = NUL;	/* Number of characters read is returned in R1 */
  }
  return carry == 0 ? READ_OK : READ_ESC;
}

/*
 * set_fn_string - Define a function key string
 */
void set_fn_string(int key, char *string, int length) {
  printf("Key = %d  String = '%s'\n", key, string);
}

boolean init_keyboard(void) {
  return TRUE;
}

void end_keyboard(void) {
}

#else

/* ==================================================================== */
/* DOS/Linux/NetBSD/FreeBSD/MacOS/OpenBSD/AmigaOS versions of functions */
/* ==================================================================== */

#include <stdlib.h>

#if defined(TARGET_LINUX) | defined(TARGET_NETBSD) | defined(TARGET_MACOSX)\
 | defined(TARGET_DJGPP) | defined(TARGET_FREEBSD) | defined(TARGET_OPENBSD)\
 | defined(TARGET_AMIGA) & defined(__GNUC__)\
 | defined(TARGET_GNUKFREEBSD)
#include <sys/time.h>
#include <sys/types.h>
#include <errno.h>
#include <unistd.h>
#include <termios.h>
#endif

#if defined(TARGET_WIN32) | defined(TARGET_BCC32)
#include <conio.h>
#endif

#ifdef TARGET_DJGPP
#include <pc.h>
#include <keys.h>
#endif

/* ASCII codes of various useful characters */

#define CTRL_A		1
#define CTRL_B		2
#define CTRL_C		3
#define CTRL_D		4
#define CTRL_E		5
#define CTRL_F		6
#define CTRL_H		8
#define CTRL_K		0x0B
#define CTRL_N		0x0E
#define CTRL_P		0x10
#define CTRL_U		0x15
#define ESCAPE		0x1B
#define DEL	    	0x7F

/*
** Following are the key codes given in the RISC OS PRMs for the various
** special keys of interest to this program. RISC OS sequences for these
** keys consists of a NUL followed by one of these values. They are used
** internally when processing the keys. The key codes returned by foreign
** operating systems are mapped on to these values where possible.
*/
#define HOME		0x1E
#define CTRL_HOME	0x1E
#define END	    	0x8B
#define CTRL_END	0xAB
#define UP	    	0x8F
#define	CTRL_UP		0xAF
#define DOWN		0x8E
#define CTRL_DOWN	0xAE
#define LEFT		0x8C
#define CTRL_LEFT	0xAC
#define RIGHT		0x8D
#define CTRL_RIGHT	0xAD
#define PGUP		0x9F
#define CTRL_PGUP	0xBF
#define PGDOWN		0x9E
#define CTRL_PGDOWN	0xBE
#define INSERT		0xCD
#define CTRL_INSERT	0xED
#define DELETE		0x7F
#define CTRL_DELETE	0x7F

/* Function key codes */

#define KEY_F1		0x81
#define SHIFT_F1	0x91
#define CTRL_F1		0xA1
#define KEY_F2		0x82
#define SHIFT_F2	0x92
#define CTRL_F2		0xA2
#define KEY_F3		0x83
#define SHIFT_F3	0x93
#define CTRL_F3		0xA3
#define KEY_F4		0x84
#define SHIFT_F4	0x94
#define CTRL_F4		0xA4
#define KEY_F5		0x85
#define SHIFT_F5	0x95
#define CTRL_F5		0xA5
#define KEY_F6		0x86
#define SHIFT_F6	0x96
#define CTRL_F6		0xA6
#define KEY_F7		0x87
#define SHIFT_F7	0x97
#define CTRL_F7		0xA7
#define KEY_F8		0x88
#define SHIFT_F8	0x98
#define CTRL_F8		0xA8
#define KEY_F9		0x89
#define SHIFT_F9	0x99
#define CTRL_F9		0xA9
#define KEY_F10		0xCA
#define SHIFT_F10	0xDA
#define CTRL_F10	0xEA
#define KEY_F11		0xCB
#define SHIFT_F11	0xDB
#define CTRL_F11	0xEB
#define KEY_F12		0xCC
#define SHIFT_F12	0xDC
#define CTRL_F12	0xEC

/*
** Operating system version number returned by 'INKEY'. These values
** are made up
*/
#if defined(TARGET_NETBSD)
#define OSVERSION 0xFE
#elif defined(TARGET_WIN32) | defined(TARGET_BCC32)
#define OSVERSION 0xFC
#elif defined(TARGET_BEOS)
#define OSVERSION 0xFB
#elif defined(TARGET_DJGPP)
#define OSVERSION 0xFA
#elif defined(TARGET_LINUX)
#define OSVERSION 0xF9
#elif defined(TARGET_MACOSX)
#define OSVERSION 0xF8
#elif defined(TARGET_FREEBSD)
#define OSVERSION 0xF7
#elif defined(TARGET_OPENBSD)
#define OSVERSION 0xF6
#elif defined(TARGET_AMIGA)
#define OSVERSION 0xF5
#elif defined(TARGET_GNUKFREEBSD)
#define OSVERSION 0xF4
#else
#error Target operating system is either not defined or not supported
#endif

#define INKEYMAX 0x7FFF		/* Maximum wait time for INKEY */
#define WAITIME 10		/* Time to wait in centiseconds when dealing with ANSI key sequences */

#define HISTSIZE 1024		/* Size of command history buffer */
#define MAXHIST 20		/* Maximum number of entries in history list */

#define FN_KEY_COUNT 15		/* Number of function keys supported (0 to 15) */

static int32
  place,			/* Offset where next character will be added to buffer */
  highplace,			/* Highest value of 'place' (= number of characters in buffer) */
  histindex,			/* Index of next entry to fill in in history list */
  highbuffer,			/* Index of first free character in 'histbuffer' */
  recalline;			/* Index of last line recalled from history display */

static boolean enable_insert;	/* TRUE if keyboard input code is in insert mode */

static char histbuffer[HISTSIZE];	/* Command history buffer */
static int32 histlength[MAXHIST];	/* Table of sizes of entries in history buffer */

/* function key strings */

static struct {int length; char *text;} fn_key[FN_KEY_COUNT];

/*
** holdcount and holdstack are used when decoding ANSI key sequences. If a
** sequence is read that does not correspond to an ANSI sequence the
** characters are stored here so that they can be returned by future calls
** to 'emulate_get'. Note that this is a *stack* not a queue
*/
static int32 holdcount;		/* Number of characters held on stack */
static int32 holdstack[8];	/* Hold stack - Characters waiting to be passed back via 'get' */

/*
 * fn_string and fn_string_count are used when expanding a function
 * key string. Effectively input switches to the string after a
 * function key with a string associated with it is pressed
 */
static char *fn_string;		/* Non-NULL if taking chars from a function key string */
static int fn_string_count;	/* Count of characters left in function key string */ 

#if defined(TARGET_LINUX) | defined(TARGET_NETBSD) | defined(TARGET_MACOSX)\
 | defined(TARGET_FREEBSD) |defined(TARGET_OPENBSD) | defined(TARGET_AMIGA) & defined(__GNUC__)\
 | defined(TARGET_GNUKFREEBSD)

static struct termios origtty;	/* Copy of original keyboard parameters */
static int32 keyboard;		/* File descriptor for keyboard */

#endif

/* The following functions are common to all operating systems */

/*
** 'push_key' adds a key to the held key stack
*/
static void push_key(int32 ch) {
  holdcount++;
  holdstack[holdcount] = ch;
}

/*
 * pop_key - Remove a key from the held key stack
 */
static int32 pop_key(void) {
  return holdstack[holdcount--];
}

/*
 * set_fn_string - Define a function key string
 */
void set_fn_string(int key, char *string, int length) {
  if (fn_key[key].text != NIL) free(fn_key[key].text);
  fn_key[key].length = length;
  fn_key[key].text = malloc(length);
  if (fn_key[key].text != NIL) memcpy(fn_key[key].text, string, length);
}

/*
 * switch_fn_string - Called to switch input to a function
 * key string. It returns the first character of the string
 */
static int32 switch_fn_string(int32 key) {
  int32 ch;
  if (fn_key[key].length == 1) return *fn_key[key].text;
  fn_string = fn_key[key].text;
  fn_string_count = fn_key[key].length - 1;
  ch = *fn_string;
  fn_string++;
  return ch;
}

/*
 * read_fn_string - Called when input is being taken from a
 * function key string, that is, fn_string is not NULL. It
 * returns the next character in the string.
 */
static int32 read_fn_string(void) {
  int32 ch;  
  ch = *fn_string;
  fn_string++;
  fn_string_count--;
  if (fn_string_count == 0) fn_string = NIL;	/* Last character read */
  return ch;
}

/*
 * is_fn_key - Returns the function key number if the RISC OS
 * key code passed to it is one for a function key. This is used
 * when checking for function key strings, so shifted and CTRL'ed
 * function keys are of no interest
 */
static int32 is_fn_key(int32 key) {
  if (key >= KEY_F1 && key <= KEY_F9) return key - KEY_F1 + 1;
  if (key >= KEY_F10 && key <= KEY_F12) return key - KEY_F10 + 10;
/* Not a function key */
  return 0;
}

#if defined(TARGET_LINUX) | defined(TARGET_NETBSD) | defined(TARGET_MACOSX)\
 | defined(TARGET_FREEBSD) | defined(TARGET_OPENBSD) | defined(TARGET_AMIGA) & defined(__GNUC__)\
 | defined(TARGET_GNUKFREEBSD)

/* ----- Linux-, *BSD- and MACOS-specific keyboard input functions ----- */

/*
** 'waitkey' is called to wait for up to 'wait' centiseconds for
** keyboard input. It returns 'true' if there is a character available
**
*/
static boolean waitkey(int wait) {
  fd_set keyset;
  struct timeval waitime;
#ifdef USE_SDL
  SDL_Event ev;
  SDL_TimerID timer_id;
/* set up timer if wait time not zero */
  if (wait != 0) timer_id = SDL_AddTimer(wait*10, waitkey_callbackfunc, 0);
  while ( 1 ) {
/*
 * First check for SDL events
*/
    while (SDL_PollEvent(&ev) > 0)
      switch(ev.type)
      {
        case SDL_USEREVENT:
          return 0;             /* timeout expired */
        case SDL_KEYDOWN:
          switch(ev.key.keysym.sym)
          {
            case SDLK_RSHIFT:   /* ignore non-character keys */
            case SDLK_LSHIFT:
            case SDLK_RCTRL:
            case SDLK_LCTRL:
            case SDLK_RALT:
            case SDLK_LALT:
              break;
            default:
              SDL_PushEvent(&ev);  /* we got a char - push the event back and say we found one */
              return 1;
              break;
          }
          break;
        case SDL_QUIT:
          exit_interpreter(EXIT_SUCCESS);
          break;
      }
/*
 * Then for stdin keypresses
*/
    FD_ZERO(&keyset);
    FD_SET(keyboard, &keyset);
    waitime.tv_sec = waitime.tv_usec = 0;
    if ( select(1, &keyset, NIL, NIL, &waitime) > 0 ) return 1;

    if (wait == 0) return 0; /* return after one check if wait time = 0 */
  }
#else
  FD_ZERO(&keyset);
  FD_SET(keyboard, &keyset);
  waitime.tv_sec = wait/100;	/* Convert wait time to seconds and microseconds */
  waitime.tv_usec = wait%100*10000;
  return select(1, &keyset, NIL, NIL, &waitime) > 0;
#endif
}

/*
** 'read_key' reads the next character from the keyboard
** or gets the next keypress from the SDL event queue
*/
int32 read_key(void) {
  int errcode;
  byte ch = 0;
#ifdef USE_SDL
  SDL_Event ev;
  fd_set keyset;
  struct timeval waitime;
  while (ch == 0) {
/*
** First check the SDL event Queue
*/
    if (SDL_PollEvent(&ev))
      switch(ev.type)
      {
        case SDL_KEYDOWN:
          switch(ev.key.keysym.sym)
          {
            case SDLK_RSHIFT:   /* ignored keys */
            case SDLK_LSHIFT:
            case SDLK_RCTRL:
            case SDLK_LCTRL:
            case SDLK_RALT:
            case SDLK_LALT:
              break;
            case SDLK_LEFT:
              push_key(LEFT);
              return NUL;
            case SDLK_RIGHT:
              push_key(RIGHT);
              return NUL;
            case SDLK_UP:
              push_key(UP);
              return NUL;
            case SDLK_DOWN:
              push_key(DOWN);
              return NUL;
            case SDLK_HOME:
              return HOME;
            case SDLK_END:
              push_key(END);
              return NUL;
            case SDLK_PAGEUP:
              push_key(PGUP);
              return NUL;
            case SDLK_PAGEDOWN:
              push_key(PGDOWN);
              return NUL;
            case SDLK_INSERT:
              push_key(INSERT);
              return NUL;
            case SDLK_ESCAPE:
              return ESCAPE;
            case SDLK_F1: case SDLK_F2: case SDLK_F3: case SDLK_F4: case SDLK_F5:
            case SDLK_F6: case SDLK_F7: case SDLK_F8: case SDLK_F9:
              ch = KEY_F1 + ev.key.keysym.sym - SDLK_F1;
              if (ev.key.keysym.mod & KMOD_SHIFT) ch += 0x10;
              if (ev.key.keysym.mod & KMOD_CTRL)  ch += 0x20;
              push_key(ch);
              return NUL;
            case SDLK_F10: case SDLK_F11: case SDLK_F12:
              ch = KEY_F10 + ev.key.keysym.sym - SDLK_F10;
              if (ev.key.keysym.mod & KMOD_SHIFT) ch += 0x10;
              if (ev.key.keysym.mod & KMOD_CTRL)  ch += 0x20;
              push_key(ch);
              return NUL;
            default:
              ch = ev.key.keysym.unicode;
              return ch;
          }
          break;
        case SDL_QUIT:
          exit_interpreter(EXIT_SUCCESS);
          break;
      }
/*
** Then check stdin
*/
    FD_ZERO(&keyset);
    FD_SET(keyboard, &keyset);
    waitime.tv_sec = waitime.tv_usec = 0; /* Zero wait time */
    if ( select(1, &keyset, NIL, NIL, &waitime) > 0 ) {
      errcode = read(keyboard, &ch, 1);
      if (errcode < 0) {		/* read() returned an error */
        if (errno == EINTR) error(ERR_ESCAPE);	/* Assume Ctrl-C was pressed */
        error(ERR_BROKEN, __LINE__, "keyboard");	/* Otherwise roll over and die */
      }
      else return ch;
    }
/*  If we reach here then nothing happened and so we should sleep */
    SDL_Delay(10);
  }
#else
  errcode = read(keyboard, &ch, 1);
  if (errcode < 0) {		/* read() returned an error */
    if (errno == EINTR) error(ERR_ESCAPE);	/* Assume Ctrl-C was pressed */
    error(ERR_BROKEN, __LINE__, "keyboard");	/* Otherwise roll over and die */
  }
#endif
  return ch;
}

/*
** 'decode_sequence' reads a possible ANSI escape sequence and attempts
** to decode it, converting it to a RISC OS key code. It returns the first
** character of the key code (a null) if the sequence is recognised or
** the first character of the sequence read if it cannot be identified.
** Note that the decoding is incomplete as the function only deals
** with keys of interest to it. Note also that it deals with both Linux
** and NetBSD key sequences, for example, the one for 'F1' under Linux
** is 1B 5B 5B 41 and for NetBSD it is 1B 4F 50. It should also be noted
** that the range of keys that can be decoded is a long way  short of the
** key combinations possible. Lastly, the same ANSI sequences are used for
** more than one key, for example, F11 and shift-F1 both return the same
** code.
**
** States:
** 1  ESC read		2  ESC 'O'		3  ESC '['
** 4  ESC '[' '1'	5  ESC '[' '2'		6  ESC '[' '3'
** 7  ESC '[' '4'	8  ESC '[' '5'		9  ESC '[' '6'
** 10 ESC '[' '['
** 11 ESC '[' '1' '1'	12 ESC '[' '1' '2'	13 ESC '[' '1' '3'
** 14 ESC '[' '1' '4'   15 ESC '[' '1' '5'	16 ESC '[' '1' '7'
** 17 ESC '[' '1' '8'	18 ESC '[' '1' '9'
** 19 ESC '[' '2' '0'	20 ESC '[' '2' '1'	21 ESC '[' '2' '3'
** 12 ESC '[' '2' '4'	23 ESC '[' '2' '5'	24 ESC '[' '2' '6'
** 25 ESC '[' '2' '8'	26 ESC '[' '2' '9'
** 27 ESC '[' '3' '1'	28 ESC '[' '3' '2'	29 ESC '[' '3' '3'
** 30 ESC '[' '3' '4'
**
** This code cannot deal with the 'alt' key sequences that KDE passes
** through. alt-home, for example, is presented as ESC ESC '[' 'H' and
** as 'ESC ESC' is not a recognised sequence the function simply passes
** on the data as supplied.
**
** Note: there seem to be some NetBSD escape sequences missing here
*/
static int32 decode_sequence(void) {
  int state, newstate;
  int32 ch;
  boolean ok;
  static int32 state2key [] = {	/* Maps states 11 to 24 to function key */
    KEY_F1, KEY_F2, KEY_F3, KEY_F4,		/* [11..[14 */
    KEY_F5, KEY_F6, KEY_F7, KEY_F8,		/* [15..[19 */
    KEY_F9, KEY_F10, KEY_F11, KEY_F12,		/* [20..[24 */
    SHIFT_F3, SHIFT_F4, SHIFT_F5, SHIFT_F6,	/* [25..[29 */
    SHIFT_F7, SHIFT_F8, SHIFT_F9, SHIFT_F10	/* [31..[34 */
  };
  static int statelbno[] = {4, 5, 6, 7, 8, 9};
/*
** The following tables give the next machine state for the character
** input when handling the ESC '[' '1', ESC '[' '2' and ESC '[' '3'
** sequences
*/
  static int state1[] = {11, 12, 13, 14, 15, 0, 16, 17, 18};	/* 1..9 */
  static int state2[] = {19, 20, 0, 21, 22, 23, 24, 0, 25, 26};	/* 0..9 */
  static int state3[] = {27, 28, 29, 30};	/* 1..4 */
  state = 1;	/* ESC read */
  ok = TRUE;
  while (ok && waitkey(WAITIME)) {
    ch = read_key();
    switch (state) {
    case 1:	/* ESC read */
      if (ch == 'O')	/* ESC 'O' */
        state = 2;
      else if (ch == '[')	/* ESC '[' */
        state = 3;
      else {	/* Bad sequence */
        ok = FALSE;
      }
      break;
    case 2:	/* ESC 'O' read */
      if (ch >= 'P' && ch <= 'S') {	/* ESC 'O' 'P'..'S' */
        push_key(ch - 'P' + KEY_F1);	/* Got NetBSD F1..F4. Map to RISC OS F1..F4 */
        return NUL;	/* RISC OS first char of key sequence */
      }
      else {	/* Not a known key sequence */
        ok = FALSE;
      }
      break;
    case 3:	/* ESC '[' read */
      switch (ch) {
      case 'A':	/* ESC '[' 'A' - cursor up */
        push_key(UP);
        return NUL;
      case 'B':	/* ESC '[' 'B' - cursor down */
        push_key(DOWN);
        return NUL;
      case 'C':	/* ESC '[' 'C' - cursor right */
        push_key(RIGHT);
        return NUL;
      case 'D':	/* ESC '[' 'D' - cursor left */
        push_key(LEFT);
        return NUL;
      case 'F':	/* ESC '[' 'F' - 'End' key */
        push_key(END);
        return NUL;
      case 'H':	/* ESC '[' 'H' - 'Home' key */
        return HOME;
      case '1':	case '2': case '3': case '4': case '5': case '6': /* ESC '[' '1'..'6' */
        state = statelbno[ch - '1'];
        break;
      case '[':	/* ESC '[' '[' */
        state = 10;
        break;
      default:
        ok = FALSE;
      }
      break;
    case 4:	/* ESC '[' '1' read */
      if (ch >= '1' && ch <= '9') {	/* ESC '[' '1' '1'..'9' */
        newstate = state1[ch - '1'];
        if (newstate == 0)	/* Bad character */
          ok = FALSE;
        else {
          state = newstate;
        }
      }
      else if (ch == '~') 	/* ESC '[' '1 '~' - 'Home' key */
        return HOME;
      else {
        ok = FALSE;
      }
      break;
    case 5:	/* ESC '[' '2' read */
      if (ch >= '0' && ch <= '9') {	/* ESC '[' '2' '0'..'9' */
        newstate = state2[ch - '0'];
        if (newstate == 0)	/* Bad character */
          ok = FALSE;
        else {
          state = newstate;
        }
      }
      else if (ch == '~') {	/* ESC '[' '2' '~' - 'Insert' key */
        push_key(INSERT);
        return NUL;
      }
      else {
        ok = FALSE;
      }
      break;
    case 6:	/* ESC '[' '3' read */
      if (ch >= '1' && ch <= '4') {	/* ESC '[' '3' '1'..'4' */
        newstate = state3[ch - '1'];
        if (newstate == 0)	/* Bad character */
          ok = FALSE;
        else {
          state = newstate;
        }
      }
      else if (ch == '~')
        return CTRL_H;	/* ESC '[' '3' '~' - 'Del' key */
      else {
        ok = FALSE;
      }
      break;
    case 7:	/* ESC '[' '4' read */
      if (ch == '~') {	/* ESC '[' '4' '~' - 'End' key */
        push_key(END);
        return NUL;
      }
      ok = FALSE;
      break;
    case 8:	/* ESC '[' '5' read */
      if (ch == '~') {	/* ESC '[' '5' '~' - 'Page up' key */
        push_key(PGUP);
        return NUL;
      }
      ok = FALSE;
      break;
    case 9:	/* ESC '[' '6' read */
      if (ch == '~') {	/* ESC '[' '6' '~' - 'Page down' key */
        push_key(PGDOWN);
        return NUL;
      }
      ok = FALSE;
      break;
    case 10:	/* ESC '[' '[' read */
      if (ch >= 'A' && ch <= 'E') {	/* ESC '[' '[' 'A'..'E' -  Linux F1..F5 */
        push_key(ch - 'A' + KEY_F1);
        return NUL;
      }
      ok = FALSE;
      break;
    case 11: case 12: case 13: case 14:	/* ESC '[' '1' '1'..'4' */
    case 15: case 16: case 17: case 18:	/* ESC '[' '1' '5'..'9' */
    case 19: case 20:				/* ESC '[' '2' '0'..'1' */
    case 21: case 22: case 23: case 24:	/* ESC '[' '2' '3'..'6' */
    case 25: case 26:				/* ESC '[' '2' '8'..'9' */
    case 27: case 28: case 29: case 30:	/* ESC '[' '3' '1'..'4' */
      if (ch == '~') {
        push_key(state2key[state - 11]);
        return NUL;
      }
      ok = FALSE;
    }
  }
/*
** Incomplete or bad sequence found. If it is bad then 'ok' will be set to
** 'false'. If incomplete, 'ok' will be 'true'. 'ch' will be undefined
** in this case.
*/
  if (!ok) push_key(ch);
  switch (state) {
  case 1:	/* ESC read */
    return ESCAPE;
  case 2:	/* ESC 'O' read */
    push_key('O');
    return ESCAPE;
  case 3:	/* ESC '[' read */
    break;
  case 4: case 5: case 6: case 7:
  case 8: case 9:	/* ESC '[' '1'..'6' read */
    push_key('1' + state - 4);
    break;
  case 10:	/* ESC '[' '[' read */
    push_key('[');
    break;
  case 11: case 12: case 13: case 14: case 15:	/* ESC '[' '1' '1'..'5' read */
    push_key(1 + state - 11);
    push_key('1');
    break;
  case 16: case 17: case 18:	/* ESC '[' '1' '7'..'9' read */
    push_key('7' + state - 16);
    push_key('1');
    break;
  case 19: case 20:			/* ESC '[' '2' '0'..'1' read */
    push_key('0' + state - 19);
    push_key('2');
    break;
  case 21: case 22: case 23: case 24:	/* ESC '[' '2' '3'..'6' read */
    push_key('3' + state - 21);
    push_key('2');
    break;
  case 25: case 26:			/* ESC '[' '2' '8'..'9' read */
    push_key('8' + state - 25);
    push_key('2');
  case 27: case 28: case 29: case 30:	/* ESC '[' '3' '1'..'4' */
    push_key('1' + state - 27);
    push_key(3);
  }
  push_key('[');
  return ESCAPE;
}

/*
** 'emulate_get' emulates the Basic function 'get'. It is also the
** input routine used when reading a line from the keyboard
*/
int32 emulate_get(void) {
  byte ch;
  int32 key, fn_keyno;
#ifndef USE_SDL
  int32 errcode;
#endif
  if (basicvars.runflags.inredir) error(ERR_UNSUPPORTED);	/* Not reading from the keyboard */
/*
 * Check if characters are being taken from a function
 * key string and if so return the next one
 */
  if (fn_string != NIL) return read_fn_string();
  if (holdcount > 0) return pop_key();	/* Return character from hold stack if one is present */
#ifdef USE_SDL
  ch = read_key();
#else
  errcode = read(keyboard, &ch, 1);
  if (errcode < 0) {
    if(errno == EINTR) error(ERR_ESCAPE);	/* Assume CTRL-C has been pressed */
    error(ERR_BROKEN, __LINE__, "keyboard");
  }
#endif
  ch = ch & BYTEMASK;
  if (ch != ESCAPE) return ch;
/*
 * Either ESC was pressed or it marks the start of an ANSI
 * escape sequence for a function key, cursor key or somesuch.
 * Try to make sense of what follows. If function key was
 * pressed, check to see if there is a function key string
 * associated with it and return the first character of
 * the string otherwise return a NUL. (The next character
 * returned will be the RISC OS key code in this case)
 */
  key = decode_sequence();
  if (key != NUL) return key;
/* NUL found. Check for function key */
  key = pop_key();
  fn_keyno = is_fn_key(key);
  if (fn_keyno == 0) {	/* Not a function key - Return NUL then key code */
    push_key(key);
    return NUL;
  }
/* Function key hit. Check if there is a function key string */
  if (fn_key[fn_keyno].text == NIL) {	/* No string is defined for this key */
    push_key(key);
    return NUL;
  }
/*
 * There is a function key string. Switch input to string
 * and return the first character
 */
  return switch_fn_string(fn_keyno);
}

/*
** 'emulate_inkey' emulates the Basic function 'inkey'. Only the 'timed wait'
** and 'OS version' flavours of the function are supported.
** Note that the behaviour of the RISC OS version of INKEY with a +ve argument
** appears to be undefined if the wait exceeds 32767 centiseconds.
*/
int32 emulate_inkey(int32 arg) {
  if (arg >= 0) {	/* Timed wait for a key to be hit */
    if (basicvars.runflags.inredir) error(ERR_UNSUPPORTED);	/* There is no keyboard to read */
    if (arg > INKEYMAX) arg = INKEYMAX;	/* Wait must be in range 0..32767 centiseconds */
    if (waitkey(arg))
      return emulate_get();	/* Fetch the key if one is available */
    else {
      return -1;	/* Otherwise return -1 to say that nothing arrived in time */
    }
  }
  else if (arg == -256)		/* Return version of operating system */
    return OSVERSION;
  else {	/* Check is a specific key is being pressed */
    error(ERR_UNSUPPORTED);	/* Check for specific key is unsupported */
  }
  return 0;
}

#endif

#if defined(TARGET_DJGPP) | defined(TARGET_WIN32) | defined(TARGET_BCC32)

/* ----- DJGPP/WIN32/DOS keyboard input functions ----- */

/*
** This table gives the mapping from DOS extended key codes to the RISC OS
** equivalents documented in the PRMs for special keys such as 'insert'
** and the function keys.
**
** Under DOS, the special keys, for example, 'Home', 'Page Up', 'F1' and so
** forth, appear as a two byte sequence where the first byte is an escape
** character and the second denotes the key. The escape character for DJGPP
** is always a null. The LCC-WIN32 version of 'getch' uses the value 0xE0
** for keys such as 'Home' but zero for the function keys. It also uses zero
** for the 'alt' versions of the keys. Only unshifted, control- or alt-
** versions of the specials keys can be detected by this code
*/
static byte dostable []  = {
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
  0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
  0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
  0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x81, 0x82, 0x83, 0x84, 0x85,
  0x86, 0x87, 0x88, 0x89, 0xCA, 0x45, 0x46, 0x1E, 0x8F, 0x9F, 0x4A, 0x8C, 0x4C, 0x8D, 0x4E, 0x8B,
  0x8E, 0x9E, 0xCD, 0x7F, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0xDA, 0xA1, 0xA2,
  0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xEA, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
  0x70, 0x71, 0x72, 0xAC, 0xAD, 0xAB, 0xBE, 0x1E, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
  0x80, 0x81, 0x82, 0x83, 0xBF, 0xCB, 0xCC, 0xDB, 0xDC, 0xEB, 0xEC, 0x8B, 0x8C, 0xAF, 0x8E, 0x8F,
  0x90, 0xAE, 0xED, 0x7F, 0x94, 0x95, 0x96, 0x97, 0x98, 0x00, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
  0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
  0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
  0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
  0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
  0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
  0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
};
#endif

#if defined(TARGET_WIN32) | defined(TARGET_BCC32)
/*
** 'emulate_get' deals with the Basic function 'get'
*/
int32 emulate_get(void) {
  int32 ch, fn_keyno;
  if (basicvars.runflags.inredir) error(ERR_UNSUPPORTED);	/* There is no keyboard to read */
/*
 * Check if characters are being taken from a function
 * key string and if so return the next one
 */
  if (fn_string != NIL) return read_fn_string();
  if (holdcount > 0) return pop_key();	/* Return held character if one is present */
  ch = getch();
  if (ch == NUL || ch == 0xE0) {	/* DOS escape characters */
    ch = dostable[getch()];
    if (ch == HOME) return HOME;	/* Special case - 'Home' is only one byte */
/* Check for RISC OS function key */
    fn_keyno = is_fn_key(ch);
    if (fn_keyno == 0) {	/* Not a function key - Return NUL then key code */
      push_key(ch);
      return NUL;
    }
/* Function key hit. Check if there is a function key string */
    if (fn_key[fn_keyno].text == NIL) {	/* No string is defined for this key */
      push_key(ch);
      return NUL;
    }
/*
 * There is a function key string. Switch input to string
 * and return the first character
 */
    return switch_fn_string(fn_keyno);
  }
  return ch & BYTEMASK;
}

/*
** 'emulate_inkey' emulates the Basic function 'inkey'. Only the 'OS
** version' flavour of the function are supported
*/
int32 emulate_inkey(int32 arg) {
  if (arg >= 0) {	/* Timed wait for a key to be hit */
    if (arg > INKEYMAX) arg = INKEYMAX;	/* Wait must be in range 0..32767 centiseconds */
    error(ERR_UNSUPPORTED);
  }
  else if (arg == -256)		/* Return version of operating system */
    return OSVERSION;
  else {	/* Check is a specific key is being pressed */
    error(ERR_UNSUPPORTED);	/* Check for specific key is unsupported */
  }
  return 0;
}

#endif

#ifdef TARGET_DJGPP

#define EXTENDCHAR 0x100	/* Extended keys have codes >= 0x100 */
/*
** 'emulate_get' deals with the Basic function 'get'
*/
int32 emulate_get(void) {
  int32 ch, fn_keyno;
  if (basicvars.runflags.inredir) error(ERR_UNSUPPORTED);	/* There is no keyboard to read */
/*
 * Check if characters are being taken from a function
 * key string and if so return the next one
 */
  if (fn_string != NIL) return read_fn_string();
/*
 * Normal keyboard input
 */
  if (holdcount > 0) return pop_key();	/* Return held character if one is present */
  ch = getxkey();
  if (ch >= EXTENDCHAR) {		/* DOS function key or other extended key */
    ch = dostable[ch & BYTEMASK];
    if (ch == HOME) return HOME;	/* Special case - 'Home' is only one byte */
/* Check for RISC OS function key */
    fn_keyno = is_fn_key(ch);
    if (fn_keyno == 0) {	/* Not a function key - Return NUL then key code */
      push_key(ch);
      return NUL;
    }
/* Function key hit. Check if there is a function key string */
    if (fn_key[fn_keyno].text == NIL) {	/* No string is defined for this key */
      push_key(ch);
      return NUL;
    }
/*
 * There is a function key string. Switch input to string
 * and return the first character
 */
    return switch_fn_string(fn_keyno);
  }
  return ch & BYTEMASK;
}

/*
** 'emulate_inkey' emulates the Basic function 'inkey'. Only the 'timed wait'
** and 'OS version' flavours of the function are supported
*/
int32 emulate_inkey(int32 arg) {
  if (arg >= 0) {	/* Timed wait for a key to be hit */
  if (basicvars.runflags.inredir) error(ERR_UNSUPPORTED);	/* There is no keyboard to read */
    if (arg > INKEYMAX) arg = INKEYMAX;	/* Wait must be in the range 0..32767 centiseconds */
    if (holdcount > 0) return pop_key();	/* There is a character waiting so return that */
    if (kbhit()) return emulate_get();	/* There is an unread key waiting */
    while (arg > 0) {	/* Wait for 'arg' centiseconds, checking the keyboard every centisecond */
      delay(10);	/* Wait 10 milliseconds */
      if (kbhit()) return emulate_get();	/* Return a key if one is now available */
      arg--;
    }
    return -1;		/* Timeout - Return -1 to indicate that nothing was read */
  }
  else if (arg == -256)		/* Return version of operating system */
    return OSVERSION;
  else {	/* Check is a specific key is being pressed */
    error(ERR_UNSUPPORTED);	/* Check for specific key is unsupported */
  }
  return 0;
}

#endif

#ifdef TARGET_BEOS

int32 emulate_get(void) {
  error(ERR_UNSUPPORTED);
  return 0;
}

int32 emulate_inkey(int32 arg) {
  error(ERR_UNSUPPORTED);
  return 0;
}

#endif

#ifdef TARGET_AMIGA

#ifndef __AMIGADATE__
#define __AMIGADATE__ "("__DATE__")"
#endif
const char *VERsion = "$VER: Brandy 1.19 "__AMIGADATE__" $";

#ifdef __SASC
long __stack = 67000;

/*
** 'read_key' reads the next character from the keyboard
*/
int32 read_key(void) {
  byte ch;
  ch = getchar();
/*    if (errno == EINTR) error(ERR_ESCAPE);	/ * Assume Ctrl-C was pressed */
  return ch;
}

int32 emulate_get(void) {
/*  error(ERR_UNSUPPORTED);*/
  return read_key();
}

int32 emulate_inkey(int32 arg) {
  error(ERR_UNSUPPORTED);
  return 0;
}

boolean init_keyboard(void) {
  rawcon(1);
  return TRUE;
}

void end_keyboard(void) {
  rawcon(0);
}
#endif

#endif

/*
** 'display' outputs the given character 'count' times
** special case for VDU_CURBACK because it doesn't
** work if echo is off
*/
static void display(int32 what, int32 count) {
  if ( what != VDU_CURBACK) echo_off();
  while (count > 0) {
    emulate_vdu(what);
    count--;
  }
  if ( what != VDU_CURBACK) echo_on();
}

/*
** 'remove_history' removes entries from the command history buffer.
** The first 'count' entries are deleted. The function also takes
** care of updating 'highbuffer' and 'histindex'
*/
static void remove_history(int count) {
  int n, freed;
  freed = 0;
  for (n = 0; n < count; n++) freed+=histlength[n];
  if (count < histindex) {	/* Not deleting everything - Move entries down */
    memmove(histbuffer, &histbuffer[freed], highbuffer-freed);
    for (n = count; n < histindex; n++) histlength[n-count] = histlength[n];
  }
  highbuffer-=freed;
  histindex-=count;
}

/*
** 'add_history' adds an entry to the command history buffer.
** The new command is added to the end of the buffer. If there is not
** enough room for it, one or more commands are removed from the front
** of the buffer to make space for it. Also, if the maximum number of
** entries has been reached, an entry is dropped off the front of the
** buffer.
** 'cmdlen' is the length of the command. It does not include the NULL
** at the end
*/
static void add_history(char command[], int32 cmdlen) {
  int32 wanted, n, freed;
  if (highbuffer+cmdlen >= HISTSIZE) {	/* There is not enough room at the end of the buffer */
    wanted = highbuffer+cmdlen-HISTSIZE+1;	/* +1 for the NULL at the end of the command */
/*
** Figure out how many commands have to be removed from the buffer to make
** room for the new one. Scan from the start of the history list adding up
** the lengths of the commands in the history buffer until the total equals
** or exceeds the number of characters required. Entries from 0 to n-1 have
** to be deleted.
*/
    freed = 0;
    n = 0;
    do {
      freed += histlength[n];
      n++;
    } while (n < histindex && freed < wanted);
    remove_history(n);
  }
  else if (histindex == MAXHIST) {	/* History list is full */
    remove_history(1);	/* Delete the first entry */
  }
  memmove(&histbuffer[highbuffer], command, cmdlen+1);
  histlength[histindex] = cmdlen+1;
  highbuffer += cmdlen+1;
  histindex += 1;
}

static void init_recall(void) {
  recalline = histindex;
}

static void recall_histline(char buffer[], int updown) {
  int n, start, count;
  if (updown < 0) {	/* Move backwards in history list */
    if (recalline == 0) return;	/* Already at start of list */
    recalline -= 1;
  }
  else {	/* Move forwards in history list */
    if (recalline == histindex) return;	/* Already at end of list */
    recalline += 1;
  }
  if (recalline == histindex)	/* Moved to last line */
    buffer[0] = NUL;
  else {
    start = 0;
    for (n = 0; n < recalline; n++) start += histlength[n];
    strcpy(buffer, &histbuffer[start]);
  }
  display(VDU_CURBACK, place);		/* Move cursor to start of old line */
  place = strlen(buffer);
  if (place > 0) emulate_vdustr(buffer, place);
  count = highplace - place;	/* Find difference in old and new line lengths */
  if (count > 0) {	/* Some of the old line is still visible */
    display(' ', count);
    display(VDU_CURBACK, count);
  }
  highplace = place;
}

/*
** 'shift_down' moves all the characters in the buffer down by one
** overwriting the character at 'offset'. It also redraws the line on
** the screen, leaving the cursor at the postion of 'offset'
*/
static void shift_down(char buffer[], int32 offset) {
  int32 count;
  count = highplace-offset;	/* Number of characters by which to move cursor */
  highplace--;
  echo_off();
  while (offset < highplace) {
    buffer[offset] = buffer[offset+1];
    emulate_vdu(buffer[offset]);
    offset++;
  }
  emulate_vdu(DEL);
  echo_on();
  display(VDU_CURBACK, count);  /* Move cursor back to correct position */
}

/*
** 'shift_up' moves the text from 'offset' along by one character to
** make room for a new character, rewriting the line on the screen as
** well. It leaves the cursor at the screen position for the new
** character. The calling routine has to check that there is room
** for another character
*/
static void shift_up(char buffer[], int32 offset) {
  int32 n;
  if (offset == highplace) return;	/* Appending char to end of line */
  n = highplace;
  while (n >= offset+1) {
    buffer[n] = buffer[n-1];
    n--;
  }
  echo_off();
  emulate_vdu(DEL);	/* Where new character goes on screen */
  n = offset+1;
  while (n <= highplace) {
    emulate_vdu(buffer[n]);
    n++;
  }
  echo_on();
  while (n > offset) {	/* Put cursor back where it should be */
    emulate_vdu(VDU_CURBACK);
    n--;
  }
  highplace++;	/* Bump up 'last character' index */
}

/*
** 'emulate_readline' reads a line from the keyboard. It returns 'true'
** if the call worked successfully or 'false' if 'escape' was pressed.
** The data input is stored at 'buffer'. Up to 'length'-1 characters
** can be read. A 'null' is added after the last character. 'buffer'
** can be prefilled with characters if required to allow existing text
** to be edited.
**
** This function uses only the most basic facilities of the underlying
** OS to carry out its task (which counts as everything under DOS) so
** much of this code is ugly.
**
** The function provides both DOS and Unix style line editing facilities,
** for example, both 'HOME' and control 'A' move the cursor to the start
** of the line. It is also possible to recall the last line entered.
**
** There is a problem with LCC-WIN32 running under Windows 98 in that the
** extended key codes for the cursor movement keys (insert, left arrow
** and so on) are not returned correctly by 'getch()'. In theory they
** should appear as a two byte sequence of 0xE0 followed by the key code.
** Only the 0xE0 is returned. This appears to be a bug in the C runtime
** library.
*/
readstate emulate_readline(char buffer[], int32 length) {
  int32 ch, lastplace;
  if (basicvars.runflags.inredir) {	/* There is no keyboard to read - Read fron file stdin */
    char *p;
    p = fgets(buffer, length, stdin);
    if (p == NIL) {	/* Call failed */
      if (ferror(stdin)) error(ERR_READFAIL);	/* I/O error occured on stdin */
      buffer[0] = NUL;		/* End of file */
      return READ_EOF;
    }
    return READ_OK;
  }
  highplace = strlen(buffer);
  if (highplace > 0) emulate_vdustr(buffer, highplace);
  place = highplace;
  lastplace = length-2;		/* Index of last position that can be used in buffer */
  init_recall();
  do {
    ch = emulate_get();
    if (basicvars.escape) return READ_ESC;	/* Check if the escape key has been pressed and bail out if it has */
    switch (ch) {	/* Normal keys */
    case CR: case LF:	/* End of line */
      emulate_vdu('\r');
      emulate_vdu('\n');
      buffer[highplace] = NUL;
      if (highplace > 0) add_history(buffer, highplace);
      break;
    case CTRL_H: case DEL:	/* Delete character to left of cursor */
      if (place > 0) {
        emulate_vdu(VDU_CURBACK);
        place--;
        shift_down(buffer, place);
      }
      break;
    case CTRL_D:	/* Delete character under the cursor */
      if (place < highplace) shift_down(buffer, place);
      break;
    case CTRL_K:	/* Delete from cursor position to end of line */
      display(DEL, highplace-place);		/* Clears characters after cursor */
      display(VDU_CURBACK, highplace-place);		/* Now move cursor back */
      highplace = place;
      break;
    case CTRL_U:	/* Delete whole input line */
      display(VDU_CURBACK, place);	/* Move cursor to start of line */
      display(DEL, highplace);	/* Overwrite text with blanks */
      display(VDU_CURBACK, highplace);	/* Move cursor back to start of line */
      highplace = place = 0;
      break;
    case CTRL_B:	/* Move cursor left */
      if (place > 0) {
        emulate_vdu(VDU_CURBACK);
        place--;
      }
      break;
    case CTRL_F:	/* Move cursor right */
      if (place < highplace) {
        emulate_vdu(buffer[place]);	/* It does the job */
        place++;
      }
      break;
    case CTRL_P:	/* Move backwards one entry in the history list */
      recall_histline(buffer, -1);
      break;
    case CTRL_N:	/* Move forwards one entry in the history list */
      recall_histline(buffer, 1);
      break;
    case CTRL_A:	/* Move cursor to start of line */
      display(VDU_CURBACK, place);
      place = 0;
      break;
    case CTRL_E:		/* Move cursor to end of line */
      echo_off();
      while (place < highplace) {
        emulate_vdu(buffer[place]);	/* It does the job */
        place++;
      }
      echo_on();
      break;
    case HOME:	/* Move cursor to start of line */
      display(VDU_CURBACK, place);
      place = 0;
      break;
    case NUL:			/* Function or special key follows */
      ch = emulate_get();	/*Fetch the key details */
      switch (ch) {
      case END:		/* Move cursor to end of line */
        echo_off();
        while (place < highplace) {
          emulate_vdu(buffer[place]);	/* It does the job */
          place++;
        }
        echo_on();
        break;
      case UP:		/* Move backwards one entry in the history list */
        recall_histline(buffer, -1);
        break;
      case DOWN:	/* Move forwards one entry in the history list */
        recall_histline(buffer, 1);
        break;
      case LEFT:	/* Move cursor left */
        if (place > 0) {
          emulate_vdu(VDU_CURBACK);
          place--;
        }
        break;
      case RIGHT:	/* Move cursor right */
        if (place < highplace) {
          emulate_vdu(buffer[place]);	/* It does the job */
          place++;
        }
        break;
      case DELETE:	/* Delete character at the cursor */
        if (place < highplace) shift_down(buffer, place);
        break;
      case INSERT:	/* Toggle between 'insert' and 'overwrite' mode */
        enable_insert = !enable_insert;
        set_cursor(enable_insert);	/* Change cursor if in graphics mode */
        break;
      default:
        emulate_vdu(VDU_BEEP);	/* Bad character - Ring the bell */
      }
      break;
    default:
      if (ch < ' ' && ch != TAB)	/* Reject any other control character except tab */
        emulate_vdu(VDU_BEEP);
      else if (highplace == lastplace)	/* No room left in buffer */
        emulate_vdu(VDU_BEEP);
      else {	/* Add character to buffer */
        if (enable_insert) shift_up(buffer, place);
        buffer[place] = ch;
        emulate_vdu(ch);
        place++;
        if (place > highplace) highplace = place;
      }
    }
  } while (ch != CR && ch != LF);
  return READ_OK;
}

/* Initialise keyboard emulation */

#if defined(TARGET_WIN32) | defined(TARGET_BCC32)

boolean init_keyboard(void) {
  int n;
  for (n = 0; n < FN_KEY_COUNT; n++) fn_key[n].text = NIL;
  fn_string_count = 0;
  fn_string = NIL;
  holdcount = 0;
  histindex = 0;
  highbuffer = 0;
  enable_insert = TRUE;
  set_cursor(enable_insert);
  return TRUE;
}

void end_keyboard(void) {
}

#elif defined(TARGET_DJGPP)

/*
** 'init_keyboard' initialises the keyboard code and checks if
** stdin is connected to the keyboard or not. If it is then the
** keyboard code uses BDOS-level keyboard function to read the
** individual keys. If stdin is not connected to the keyboard
** then standard C functions are used instead. The assumption
** here is that stdin is most likely taking input from a file.
*/
boolean init_keyboard(void) {
  struct termios tty;
  int errcode, keyboard, n;
  for (n = 0; n < FN_KEY_COUNT; n++) fn_key[n].text = NIL;
  fn_string_count = 0;
  fn_string = NIL;
  holdcount = 0;
  histindex = 0;
  highbuffer = 0;
  enable_insert = TRUE;
  set_cursor(enable_insert);
/*
** Check to see if stdin is attached to a keyboard
*/
  keyboard = fileno(stdin);
  errcode = tcgetattr(keyboard, &tty);
  if (errcode == 0) return TRUE;	/* Keyboard being used */
/*
** tcgetattr() returned an error. If the error is ENOTTY then
** stdin does not point at a keyboard and so the program does
** simple reads from stdin rather than use the custom keyboard
** code. If the error is not ENOTTY then something has gone
** wrong so we abort the program
*/
  if (errno != ENOTTY) return FALSE;	/* tcgetattr() returned an error we cannot handle */
  basicvars.runflags.inredir = TRUE;	/* tcgetattr() returned ENOTTY - Use normal C functions to read input */
  return TRUE;
}

void end_keyboard(void) {
}

#elif defined(TARGET_LINUX) | defined(TARGET_NETBSD) | defined(TARGET_MACOSX)\
 | defined(TARGET_FREEBSD) | defined(TARGET_OPENBSD) | defined(TARGET_AMIGA) & defined(__GNUC__)\
 | defined(TARGET_GNUKFREEBSD)

/*
** 'init_keyboard' initialises the keyboard code. It checks to
** see if stdin is connected to a keyboard. If it is then the
** function changes stdin to use unbuffered I/O so that the
** individual key presses can be read. This is not done if
** stdin is pointing elsewhere as it is assumed that input is
** most likely being taken from a file.
*/
boolean init_keyboard(void) {
  struct termios tty;
  int n, errcode;
  for (n = 0; n < FN_KEY_COUNT; n++) fn_key[n].text = NIL;
  fn_string_count = 0;
  fn_string = NIL;
  holdcount = 0;
  histindex = 0;
  highbuffer = 0;
  enable_insert = TRUE;
  set_cursor(enable_insert);
/*
** Set up keyboard for unbuffered I/O
*/
  keyboard = fileno(stdin);
  errcode = tcgetattr(keyboard, &tty);
  if (errcode < 0) {	/* Could not obtain keyboard parameters */
    if (errno != ENOTTY) return FALSE;	/* tcgetattr() returned an error we cannot handle */
/*
** The error returned by tcgetattr() was ENOTTY (not a typewriter).
** This says that stdin is not associated with a keyboard. The
** most probable cause is that input is being taken from a file.
** Input is therefore read using fgets() instead of a character
** at a time using read(). This means that the command line editor
** and history facilities as well as functions like GET and INKEY
** will not be available but if input truly is from a file then
** this will not be a problem.
*/
    basicvars.runflags.inredir = TRUE;
    return TRUE;
  }
  origtty = tty;		/* Preserve original settings for later */
#ifdef TARGET_LINUX
  tty.c_lflag &= ~(XCASE|ECHONL|NOFLSH);
#else
  tty.c_lflag &= ~(ECHONL|NOFLSH);
#endif
  tty.c_lflag &= ~(ICANON|ECHO);
  tty.c_iflag &= ~(ICRNL|INLCR);
  tty.c_cflag |= CREAD;
  tty.c_cc[VTIME] = 1;
  tty.c_cc[VMIN] = 1;
  errcode = tcsetattr(keyboard, TCSADRAIN, &tty);
  if (errcode < 0) return FALSE;	/* Could not set up keyboard in the way desired */
  return TRUE;
}

void end_keyboard(void) {
  (void) tcsetattr(keyboard, TCSADRAIN, &origtty);
}

#endif

#endif
