/* guivio.c */

/*
 * This file contains a VIO (console) user interface for OS/2.  It had
 * been derived from the "guitcap.c" file of elvis 2.0 Lee Johnson and
 * adapted to Elvis 2.2 by Herbert.
 *
 * $Log: guivio.c,v $
 * Revision 1.9  2003/10/17 17:41:23  steve
 * Renamed the BOOLEAN data type to ELVBOOL to avoid name clashes with
 *   types defined other headers.
 *
 * Revision 1.8  2003/10/12 18:36:18  steve
 * Changed elvis' ctype macro names to start with "elv"
 *
 * Revision 1.7  2001/10/31 19:13:27  steve
 * Added "mapmode" option
 * Added "mode=..." flag for :map commands.
 *
 * Revision 1.6  2001/06/13 18:21:37  steve
 * Modified :map and related commands, to support a variety of context and
 *   behavior flags.  This is an extension beyond the long-standing "visual"
 *   flag.
 * Modified :mkexrc to save abbreviations.
 *
 * Revision 1.5  2001/04/20 00:00:37  steve
 * Some bug fixes, and uglification of the source code.
 *
 * Revision 1.2  2000/06/04 10:26:52  HERBERT
 * Some formatting and CVS Logging.
 *
 */

#include "elvis.h"
#ifdef GUI_VIO
#include "guivio.h"
/*#undef NDEBUG*/
#include "debug.h"
#include <signal.h>

/* Forward declarations: */
static void movecurs (VWIN *vw);
static void chgsize (VWIN *vw, int newheight, ELVBOOL winch);
static void vio_CE (VCOLOR color);
static void vio_CM (int row, int col);

#define  MINHEIGHT  4

/* This table lists the keys which are mapped automatically */
struct
{
  char  *label;    /* keytop legend of the key */
  char  *rawin;    /* raw characters sent by key */
  char  *cooked;  /* what the key should map to (if anything) */
  MAPFLAGS flags;    /* when the map should be effective */
} keys[] =
  {
    /* Grey keys. */
    { "<Up>",    "\013e048",    "k",    MAP_ALL },
    { "<Down>",    "\013e050",    "j",    MAP_ALL },
    { "<Left>",    "\013e04b",    "h",    MAP_ALL },
    { "<Right>",  "\013e04d",    "l",    MAP_ALL },
    { "<PgUp>",    "\013e049",    "\002",   MAP_ALL },
    { "<PgDn>",    "\013e051",    "\006",   MAP_ALL },
    { "<Home>",    "\013e047",    "^",    MAP_ALL },
    { "<End>",    "\013e04f",    "$",    MAP_ALL },
    { "<Insert>",  "\013e052",    "i",    MAP_ALL },
    { "<Delete>",  "\013e053",    "x",    MAP_ALL },

    /* Numeric keypad keys (with Num Lock off.) */
    { "<kpUp>",    "\0130048",    "k",    MAP_ALL },
    { "<kpDown>",  "\0130050",    "j",    MAP_ALL },
    { "<kpLeft>",  "\013004b",    "h",    MAP_ALL },
    { "<kpRight>",  "\013004d",    "l",    MAP_ALL },
    { "<kpPgUp>",  "\0130049",    "\002",   MAP_ALL },
    { "<kpPgDn>",  "\0130051",    "\006",   MAP_ALL },
    { "<kpHome>",  "\0130047",    "^",    MAP_ALL },
    { "<kpEnd>",  "\013004f",    "$",    MAP_ALL },
    { "<kpInsert>",  "\0130052",    "i",    MAP_ALL },
    { "<kpDelete>",  "\0130053",    "x",    MAP_ALL },
    { "<kp/>",    "\0132fe0",    "/",    MAP_ALL },
    { "<kpEnter>",  "\0130de0",    "\r",    MAP_ALL },

  /* Unshifted function keys. */
    { "#1",      "\013003b",    ":help\r",  MAP_ALL },
    { "#2",      "\013003c" },
    { "#3",      "\013003d" },
    { "#4",      "\013003e" },
    { "#5",      "\013003f" },
    { "#6",      "\0130040" },
    { "#7",      "\0130041" },
    { "#8",      "\0130042" },
    { "#9",      "\0130043" },
    { "#10",    "\0130044" },
    { "#11",    "\0130085" },
    { "#12",    "\0130086" },

  /* Shift-function keys. */
    { "#1s",    "\0130054" },
    { "#2s",    "\0130055" },
    { "#3s",    "\0130056" },
    { "#4s",    "\0130057" },
    { "#5s",    "\0130058" },
    { "#6s",    "\0130059" },
    { "#7s",    "\013005a" },
    { "#8s",    "\013005b" },
    { "#9s",    "\013005c" },
    { "#10s",    "\013005d" },
    { "#11s",    "\0130087" },
    { "#12s",    "\0130088" },

  /* Ctrl-function keys. */
    { "#1c",    "\013005e" },
    { "#2c",    "\013005f" },
    { "#3c",    "\0130060" },
    { "#4c",    "\0130061" },
    { "#5c",    "\0130062" },
    { "#6c",    "\0130063" },
    { "#7c",    "\0130064" },
    { "#8c",    "\0130065" },
    { "#9c",    "\0130066" },
    { "#10c",    "\0130067" },
    { "#11c",    "\0130089" },
    { "#12c",    "\013008a" },

  /* Alt-function keys. */
    { "#1a",    "\0130068" },
    { "#2a",    "\0130069" },
    { "#3a",    "\013006a" },
    { "#4a",    "\013006b" },
    { "#5a",    "\013006c" },
    { "#6a",    "\013006d" },
    { "#7a",    "\013006e" },
    { "#8a",    "\013006f" },
    { "#9a",    "\0130070" },
    { "#10a",    "\0130071" },
    { "#11a",    "\013008b" },
    { "#12a",    "\013008c" }
  };

static char  GC_V = 0xb3;  /* vertical bar character */
static char  GC_H = 0xc4;  /* horizontal bar character */
static char  GC_1 = 0xc0;  /* lower left corner character */
static char  GC_2 = 0xc1;  /* horizontal line with up-tick character */
static char  GC_3 = 0xd9;  /* lower right corner character */
static char  GC_4 = 0xc3;  /* vertical line with right-tick character */
static char  GC_5 = 0xc5;  /* center cross character */
static char  GC_6 = 0xb4;  /* vertical line with left-tick character */
static char  GC_7 = 0xda;  /* upper left corner character */
static char  GC_8 = 0xc2;  /* horizontal line with down-tick character */
static char  GC_9 = 0xbf;  /* upper right corner character */

/* These are GUI-dependent global options */

struct ttygoptvals_s ttygoptvals;

static OPTDESC goptdesc[] =
{
  {"term", "termtype",  optsstring,  optisstring},
  {"ttyrows", "ttylines",  optnstring,  optisnumber},
  {"ttycolumns", "ttycolumns",optnstring,  optisnumber},
  {"ttyunderline", "ttyu",NULL,    NULL     },
};

#define o_term      ttygoptvals.term.value.string
#define o_ttyrows    ttygoptvals.ttyrows.value.number
#define o_ttycolumns  ttygoptvals.ttycolumns.value.number
#define o_ttyunderline  ttygoptvals.ttyunderline.value.boolean

/*----------------------------------------------------------------------------*/
/* These are mid-level terminal I/O functions.  They buffer the output, but
 * don't do much more than that.
 */
static char   ttybuf[2048];/* the output buffer */
static int    ttycount;    /* number of characters in ttybuf */
long          ttycaught;   /* bitmap of recently-received signals */
static ELVBOOL  raw_mode;  /* If FALSE, use VioWrtTTY() to flush output */
static VCOLOR  vc_current; /* Current fg/bg color attribute */
static VCOLOR  vc_italic;  /* Italic part of vc_current */
static VCOLOR  vc_underlined;/* Underline part of vc_current */
static VCOLOR  vc_bold;    /* Bold part of vc_current */
static VCOLOR  vc_color;   /* Color part of vc_current */
static VCOLOR  vc_term;    /* fg/bg color for shell commands */
static USHORT  c_col, c_row;/* physical cursor position (VIO) */

static int    afterprg;    /* expose windows (after running prg) */
static int    afterscrl;   /* number of status lines (after running prg) */
static ELVBOOL  fgcolored; /* have foreground colors been set? */
static ELVBOOL  bgcolored; /* have background colors been set? */
static int    physx, physy;/* physical cursor position */
static VWIN    *vwins;     /* list of windows */
static VWIN    *current;   /* window with keyboard focus */
static int    nwindows;    /* number of windows allocated */

/* Extract foreground color index: */
#define  VC_FG(vc)    ((vc) & 0x0f)

/* Extract background color index: */
#define  VC_BG(vc)    (((vc) & 0xf0) >> 4)

/* This function writes the contents of 'ttybuf' to the screen */
static void 
do_flush (void)
{
  if (ttycount > 0)
    {
      if (raw_mode)
        {
          VioWrtCellStr (ttybuf, ttycount, c_row, c_col, 0);
          if ((c_col += ttycount / 2) >= o_ttycolumns)
            {
              c_col -= o_ttycolumns;
              c_row++;
            }
        }
      else
        {
          VioWrtTTY (ttybuf, ttycount, 0);
        }
      ttycount = 0;
    }
}

/* This function is used internally.  It is passed to the tputs() function
 * which uses it to output individual characters.  This function saves the
 * characters in a buffer and outputs them in a bunch.
 */
static int
ttych(int ch)
{
  if (raw_mode)
    {
      switch (ch)
        {
        case '\n':
          do_flush ();
          c_col = 0;
          c_row++;
          break;

        case '\r':
          do_flush ();
          c_col = 0;
          break;

        default:
          ttybuf[ttycount++] = ch;
          ttybuf[ttycount++] = vc_current;
          if (ttycount >= QTY (ttybuf))
            {
              do_flush ();
            }
          break;
        }
    }
  else
    {
      ttybuf[ttycount++] = ch;
      if (ttycount >= QTY (ttybuf))
        {
          do_flush ();
        }
    }

  return ch;
}

#if 0
/*
 * This is OS/2s VIO color code/rgb mapping. It differs from the terminal
 * values.
 */
static struct
{
  char  *name;  /* name of the color */
  long  ansi;   /* ANSI.SYS color; if > 10, then set "bold" attribute */
  unsigned char rgb[3]; /* approximate RGB color index */
} viocolors[] =
  {
    {"black",   0, {  0,   0,   0}},
    {"blue",    1, {170,   0,   0}},
    {"green",   2, {  0, 170,   0}},
    {"cyan",    3, {170, 170,   0}},
    {"red",     4, {  0,   0, 170}},
    {"magenta", 5, {170,   0, 170}},
    {"brown",   6, {  0, 170, 170}},
    {"white",   7, {170, 170, 170}},
    {"gray",    8, {  0,   0,   0}},
    {"grey",    8, { 85,  85,  85}},
    {"yellow", 14, {255, 255,  85}},
    {(char *)0}
  };
#endif

/*
 * A map of the corresponding Termcap and VIO color codes. As the color
 * handling now is done in color.c I remap the values when setting new
 * colors. All exported functions still use the ANSI-like color codes.
 */
struct {
  long ansi;
  long vio;
} colormapping[] = {
  { 0,  0},
  { 1,  4},
  { 2,  2},
  { 3,  6},
  { 4,  1},
  { 5,  5},
  { 6,  3},
  { 7,  7},
  {10,  8},
  {13, 14},
};

/*
 * This is only needed in change() as this is the only place where we
 * set OS/2s physical color codes instead of the (logical) ANSI ones.
 */
static VCOLOR 
ansi2vio (long ansi)
{
  int i;
  VCOLOR ret;
  for (i = 0; i < sizeof colormapping && colormapping[i].ansi != ansi; i++)
    {
    }
  if (i >= sizeof colormapping)
    { 
      if (ansi > 10)
        {
          return ((ret = ansi2vio (ansi - 10)) > 0)? ret | 0x08: 0;
        }
      return (VCOLOR)0;
    }
  return (VCOLOR)colormapping[i].vio;
}

/*
 * This is needed whenever we want to make logical (ANSI) color codes from
 * physical (OS/2) ones.
 */
static long 
vio2ansi (long vio)
{
  int i;
  long ret;
  for (i = 0; i < sizeof colormapping && colormapping[i].vio != vio; i++)
    {
    }
  if (i >= sizeof colormapping)
    { 
      if (vio & 0x08)
        {
          return ((ret = vio2ansi (vio & ~0x08)) > 0)? ret + 10: 0;
        }
      return 0;
    }
  return colormapping[i].ansi;
}

/* Change attributes.  The VIO interface is ANSI-like anyway, so this
 * code contains mostly OS/2 VIO interface code.
 */
static void 
change(long fg,   /* new foreground color, fgcolored */
       long  bg,  /* new background color, bgcolored */
       int bits)  /* attribute bits */
{
  static long  currentfg;
  static long  currentbg;
  static int  currentbits;

  int resetting, setting;
  VCOLOR newfg = (VCOLOR)ansi2vio (fg);
  VCOLOR newbg = (VCOLOR)ansi2vio (bg);

DPRINTF (("change (%d, %d, %x) -> %s, %s\n", 
  fg, bg, bits, colorname (fg), colorname (bg)));

DPRINTF (("fg: %d, newfg: %d, bg: %d, newbg: %d\n", fg, newfg, bg, newbg));

  /* Termcap doesn't allow bold & italics to mix.  If attempting to mix,
   * then use plain bold. This makes sense for VIO, too as dimming
   * (italic) and brightening (bold) are a bit contradictory...
   */
  if ((bits & (COLOR_BOLD|COLOR_ITALIC)) == (COLOR_BOLD|COLOR_ITALIC))
    {
      bits &= ~COLOR_ITALIC; 
    }

  /* Okay, we are finally ready to begin outputting escape sequences.
   * This proceeds in three phases: First we turn off any attributes
   * that must be off, then we change colors, and finally we turn on
   * any attributes which should be on.  We do it this way because
   * sometimes turning off an attribute will affect colors or other
   * attributes.
   */

  /* We take care of the colors first as applying styles is being
   * simulating by color manipulations. This is why we handle real
   * color and styles separately.
   */
  if (fgcolored && newfg != currentfg)
    {
      /*DPRINTF (("fg %s to %s\n", 
        colorname (currentfg), colorname(newfg)));*/
      vc_color = (vc_color & 0xf0) | newfg;
      currentfg = newfg;
    }
  if (bgcolored && newbg != currentbg)
    {
      /*DPRINTF (("bg %s to %s\n", 
        colorname (currentbg), colorname (newbg)));*/
      vc_color = (vc_color & 0x0f) | (newbg << 4);
      currentbg = newbg;
    }

  /* Decide which attributes to turn off. */
  resetting = currentbits & ~bits;
  /* Bold is simulated as "brightening" colors. So we "unbright"
   * the Bold filter.
   */
  if (resetting & COLOR_BOLD)
    {
      vc_bold = 0x00;
      currentbits &= ~COLOR_BOLD;
    }
  /* Italic is simulated as "dimming" colors. So now we "undim"
   * the Italic filter.
   */
  if (resetting & COLOR_ITALIC)
    {
      vc_italic = 0xff;
      currentbits &= ~COLOR_ITALIC;
    }
  /* Underlining is simulated as "dimming" colors. So now we "undim"
   * the Underline filter.
   */
  if (resetting & COLOR_UNDERLINED)
    {
      vc_underlined = 0xff;
      currentbits &= ~COLOR_UNDERLINED;
    }
  /* Boxing is simulated as color swapping. This has to be done on
   * the final color, so we don't care about this now.
   */
  if (resetting & COLOR_BOXED|COLOR_LEFTBOX|COLOR_RIGHTBOX) 
    {
      currentbits &= ~COLOR_BOXED;
    }

  /* Set attributes */
  setting = bits & ~currentbits;
  /* For bold we bright the current color of, if it is already bright 
   * by choosing bright white.
   */
  if (setting & COLOR_BOLD)
    {
      vc_bold = ((vc_color & 0x08) != 0)? 0x0f: 0x08;
    }

  /* Dimming is only being done if the result will still be visible.
   * Else we use a "neutral" filter.
   */
  if (setting & COLOR_ITALIC)
    {
      vc_italic = 
        (VC_FG (vc_color & 0x77) != VC_BG (vc_color & 0x77))? 0x77: 0xff;
    }
  /* Dimming is only being done if the result will still be visible.
   * Else we use a "neutral" filter.
   */
  if (setting & COLOR_UNDERLINED)
    {
      vc_underlined = 
        (VC_FG (vc_color & 0x77) != VC_BG (vc_color & 0x77))? 0x77: 0xff;
    }
  currentbits = bits;
  if (currentbits & (COLOR_BOXED|COLOR_LEFTBOX|COLOR_RIGHTBOX))
    {
      vc_current = VC_BG (vc_color) | (VC_FG (vc_color) << 4);
    }
  else
    {
      vc_current = vc_color;
    }
  vc_current = ((vc_current & vc_italic) & vc_underlined) | vc_bold;
}

/* This function does what ttyread() does with the termcap interface 
 */
static int
vio_read (char *buf, int len, int timeout)
{
  KBDKEYINFO ki;

  /* Fast 'n' sleazy keyboard processing. */
  KbdCharIn(&ki, IO_WAIT, 0);
  if ((ki.fbStatus & 0x02) != 0)
    {
      /*
       * Convert an extended scan code to ^K, followed by four
       * hex digits.  This scheme is borrowed from the X11 GUI.
       */
      sprintf (buf, "%c%02x%02x", ELVCTRL ('K'), ki.chChar, ki.chScan);
      return strlen (buf);
    }
  else
    {
      buf[0] = ki.chChar;
      return 1;
    }
}

static VCOLOR
vio_get_default_color (void)
{
  USHORT row, col;
  CHAR cell[2];
  USHORT len = sizeof (cell);

  VioGetCurPos (&row, &col, 0);
  VioReadCellStr (cell, &len, row, col, 0);
  return cell[1];
}

static void
vio_CE (VCOLOR color)
{
  CHAR cell[2];

  if (c_col < o_ttycolumns)
    {
      cell[0] = ' ';
      cell[1] = color;
      VioWrtNCell (cell, o_ttycolumns - c_col, c_row, c_col, 0);
    }
}

static void 
vio_CM (int row, int col)
{
  USHORT o_row, o_col;

  c_row = row;
  c_col = col;

  /*
   * Don't move the cursor if it's already in the right place.  We
   * want to avoid any weird flickering that might result if moving 
   * the cursor resets its blink timer.
   */
  VioGetCurPos (&o_row, &o_col, 0);
  if (o_row != c_row || o_col != c_col)
    {
      VioSetCurPos (c_row, c_col, 0);
    }
}

/* This macro is used to reset attributes and colors to normal values. */
#define vio_revert(tw)  if ((tw) == current)\
change(colorinfo[COLOR_FONT_NORMAL].fg,\
       colorinfo[COLOR_FONT_NORMAL].bg,\
       colorinfo[COLOR_FONT_NORMAL].da.bits);\
else\
       change(colorinfo[COLOR_FONT_IDLE].fg,\
              colorinfo[COLOR_FONT_IDLE].bg,\
              colorinfo[COLOR_FONT_IDLE].da.bits)

     /* Send any required termination strings.  Turn off "raw" mode. */
     static void 
       vio_suspend (void)
{
  /* revert to the normal font */
  vio_revert (NULL);

  do_flush ();
  raw_mode = ElvFalse;
}

/* Put the terminal in RAW mode.  Send any required strings */
static void
vio_resume (ELVBOOL sendstr)    /* send strings? */
{
  /* Determine the present whereabouts of the cursor. */
  VioGetCurPos (&c_row, &c_col, 0);
  raw_mode = TRUE;

  /* reset, so we don't try any suspicious optimizations */
  vio_reset ();
}

/*
 * This function determines the screen size.
 */
static void 
vio_getsize (void)
{
  VIOMODEINFO info;
  USHORT rc;

  /* get the window size, one way or another. */
  info.cb = sizeof (info);
  rc = VioGetMode (&info, 0);

  /* did we get a realistic value? */
  if (rc == NO_ERROR && info.row >= 2 && info.col >= 30)
    {
      o_ttyrows = info.row;
      o_ttycolumns = info.col;
    }
}

/* This is an internal function which moves the physical cursor to the logical
 * position of the cursor in a given window, if it isn't there already.
 */
static void 
movecurs (VWIN *vw) /* window whose cursor is to be moved */
{
  int  y = vw->pos + vw->cursy;

  /* maybe we don't need to move at all? */
  if ((afterprg > 0 && y <= o_ttyrows - afterscrl)
      || (y == physy && vw->cursx == physx))
    {
      /* already there */
      return;
    }

  /* revert to the normal font */
  vio_revert(vw);

  /* move the cursor to its current position */
  c_row = physy = y;
  c_col = physx = vw->cursx;
}

/* clear to end of line */
static ELVBOOL 
vio_clrtoeol (GUIWIN  *gw)    /* window whose row is to be cleared */
{
  VWIN  *vw = (VWIN *)gw;

  /* after running a program, disable the :ce: string for a while. */
  if (afterprg)
    return ElvTrue;

  /* if we're on the bottom row of a window which doesn't end at the
   * bottom of the screen, then fail.  This will cause elvis to output
   * a bunch of spaces instead.  The vio_draw() function will convert 
   * those spaces to underscore characters so the window has a border.
   */
  if (vw->cursy == vw->height - 1 && vw->pos + vw->height != o_ttyrows)
    {
      return ElvFalse;
    }

  /* revert to the normal font */
  vio_revert (vw);

  /* move the physical cursor to where the window thinks it should be */
  movecurs (vw);

  /* clear to the end of the current line */
  vio_CE (vc_current);

  return ElvTrue;
}

/* insert or delete columns */
static ELVBOOL 
vio_shift (GUIWIN *gw,  /* window to be shifted */
           int qty,    /* columns to insert (may be negative to delete) */
           int rows)    /* number of rows affected (always 1 for this GUI) */
{
  CHAR cell[2];

  /* revert to the normal font */
  vio_revert ((VWIN *)gw);

  /* move the physical cursor to where this window thinks it is */
  movecurs ((VWIN *)gw);

  /* This is what we'll fill the scrolled region with: */
  cell[0] = ' ';
  cell[1] = vc_current;
  cell[0] = '#';
  cell[1] = 0xe0;

  if (qty > 0)
    {
      VioScrollRt (c_row, c_col, c_row, o_ttycolumns - 1, qty, cell, 0);
    }
  else
    {
      /* take the absolute value of qty */
      qty = -qty;

      VioScrollLf (c_row, c_col, c_row, o_ttycolumns - 1, qty, cell, 0);
    }
  return ElvTrue;
}

/* insert or delete rows.  qty is positive to insert, negative to delete */
static ELVBOOL 
vio_scroll (GUIWIN *gw,       /* window to be scrolled */
            int qty,          /* rows to insert (may be negative to delete) */
            ELVBOOL notlast)  /* if ElvTrue, then leave last row unchanged */
{
  VWIN  *vw = (VWIN *)gw;
  CHAR   cell[2];
  USHORT  bottom_row;

  /* Mentally adjust the number of rows used for messages.  This is only
   * significant immediately after running an external program, and is
   * used for hiding any premature attempts to redraw the window's text
   * but still show the window's messages.
   */
  afterscrl -= qty;

  /* If this window isn't the only window, then fail.
   * Later, this function may be smart enough to use scrolling regions,
   * or do the idlok() kind of thing, but not yet.
   */
  if (vwins->next)
    {
      return ElvFalse;
    }

  /* revert to the normal font */
  vio_revert (vw);

  /* move the physical cursor to where the window thinks it should be */
  movecurs (vw);

  /* This is what we'll fill the scrolled region with: */
  cell[0] = ' ';
  cell[1] = vc_current;

  bottom_row = notlast ? o_ttyrows - 2 : o_ttyrows - 1;
  if (qty > 0)
    {
      VioScrollDn (c_row, 0, bottom_row, o_ttycolumns - 1, qty, cell, 0);
    }
  else
    {
      VioScrollUp (c_row, 0, bottom_row, o_ttycolumns - 1, -qty, cell, 0);
    }
  return ElvTrue;
}

/* Forget where the cursor is, and which mode we're in */
static void 
vio_reset (void)
{
  physx = physy = 9999;
  vio_revert (NULL);
}

/* Flush any changes out to the display */
static void 
vio_flush (void)
{
  if (current)
    {
      movecurs (current);
    }
  do_flush ();
  vio_CM (c_row, c_col);
}

/* Set a window's cursor position. */
static void 
vio_moveto (GUIWIN  *gw,    /* window whose cursor is to be moved */
            int    column,  /* new column of cursor */
            int    row)     /* new row of cursor */
{
  VWIN *vw = (VWIN *)gw;

  do_flush ();

  vw->cursy = row;
  vw->cursx = column;
}

/* put graphic characters.  This function is called only from vio_draw() */
static void 
drawgraphic (GUIWIN  *gw,   /* window where text should be drawn */
             long    fg,    /* foreground color */
             long    bg,    /* background color */
             int   bits,    /* attribute bits */
             CHAR  *text,   /* plain chars to be mapped to graphic chars */
             int    len)    /* length of text */
{
  VWIN  *vw = (VWIN *)gw;
  int  i;
  ELVBOOL  graf;
  char  gc;

  /* Change attributes if necessary.  Note that we mask out COLOR_GRAPHIC
   * since COLOR_GRAPHIC is really a specific combination of other
   * attribute bits, and we don't want those other attributes to be set.
   */
  change(fg, bg, bits & ~COLOR_GRAPHIC);

  /* draw each character */
  for (graf = ElvFalse, i = 0; i < len; i++)
    {
      /* try to convert plain character to graphic character */
      switch (text[i])
        {
        case '-':  gc = GC_H;  break;
        case '|':  gc = GC_V;  break;
        case '1':  gc = GC_1;  break;
        case '2':  gc = GC_2;  break;
        case '3':  gc = GC_3;  break;
        case '4':  gc = GC_4;  break;
        case '5':  gc = GC_5;  break;
        case '6':  gc = GC_6;  break;
        case '7':  gc = GC_7;  break;
        case '8':  gc = GC_8;  break;
        case '9':  gc = GC_9;  break;
        default:  gc = 0;
        }
    
      /* did we get a graphic character? */
      if (gc)
        {
          /* output the graphic character in graphic mode */
          ttych (gc);
        }
      else
        {
          /* output elvis' plain character in text mode */
          if (elvdigit (text[i]))
            ttych('+');
          else
            ttych (text[i]);
        }
    }

  /* drawing the characters has the side-effect of moving the cursor */
  vw->cursx += len;
  physx += len;
}

/* put characters: first move, then set attribute, then execute char.
 */
static void 
vio_draw (GUIWIN  *gw,  /* window where text should be drawn */
          long  fg,     /* foreground color */
          long  bg,     /* background color */
          int bits,     /* other attributes */
          CHAR  *text,  /* text to draw */
          int    len)   /* length of text */
{
  VWIN  *vw = (GUIWIN *)gw;
  int  i;
#ifndef NDEBUG
  VWIN  *scan;

  for (scan = vwins; scan != vw && scan; scan = scan->next)
    {
    }
  assert (scan);
#endif

  /* if COLOR_LEFTBOX is set, but not COLOR_BOXED, then draw in two
   * phases: First draw a single character with COLOR_LEFTBOX, and then
   * draw the remainder without COLOR_LEFTBOX.
   */
  if ((bits & (COLOR_LEFTBOX|COLOR_BOXED)) == COLOR_LEFTBOX && len > 1)
  {
    vio_draw(gw, fg, bg, bits, text, 1);
    bits &= ~COLOR_LEFTBOX;
    text++;
    len--;
  }

  /* After a program, don't output any text except messages for a while.
   * This is mostly an optimization; the window is about to be redrawn
   * from scratch anyway.  But it also prevents the screen from doing
   * strange, unexpected things.
   */
  if (afterprg > 0)
    {
#if 0
      fprintf (stderr, "draw(\"%.*s\"), vw->cursy=%d, vw->height=%d, physy=%d, afterscrl=%d\n", len, tochar8 (text), vw->cursy, vw->height, physy, afterscrl);
#endif
      if (vw->cursy < vw->height - afterscrl - 1)
        return;

      /*
       * movecurs() could put us on the wrong row if the current
       * window isn't the one on the bottom; however, we still
       * need to be able to adjust the column in order to be
       * correct for all values of (guivio.movecost).
       */
      c_col = vw->cursx;
    }
  else
    {
      /* move the cursor to where this window thinks it is */
      movecurs (vw);
    }

  /* if graphic characters, then handle specially */
  if (bits & COLOR_GRAPHIC)
    {
      drawgraphic (gw, fg, bg, bits, text, len);
      return;
    }

  /* If we're on the bottom row of a window (except the last window)
   * then any normal characters should be underlined.  This will give
   * us the effect of a window border.
   */
  if (vw->cursy == vw->height - 1 && physy < o_ttyrows - 1)
    {
      bits |= COLOR_UNDERLINED;
    }

  change (fg, bg, bits);

  /* draw each character.  If this is the bottom row of any window except
   * the bottom window, then also replace any blanks with '_' characters.
   * This will provide a window border in case the terminal can't do
   * real underlining.
   */
  if (vw->cursy == vw->height - 1 && physy < o_ttyrows - 1)
    {
      for (i = 0; i < len; i++)
        {
          ttych (text[i] == ' ' ? '_' : text[i]);
        }
    }
  else /* normal row */
    {
      for (i = 0; i < len; i++)
        {
          ttych (text[i]);
        }
    }

  /* drawing the characters has the side-effect of moving the cursor */
  vw->cursx += len;
  physx += len;
}

/* return ElvTrue if VIO is available. */
static int 
vio_test (void)
{
  VIOMODEINFO info;
  USHORT rc;

#if 0
  {
    static char digits[] = "0123456789abcdef";
    char buffer[32];
    int i, j;

    for (i = 0; i < 16; i++)
      {
        for (j = 0; j < 16; j++)
          {
            buffer[j * 2] = digits[j];
            buffer[j * 2 + 1] = i * 16 + j;
            buffer[j * 2 + 1] = 0x07;
            buffer[j * 2 ] = i * 16 + j;
          }
        VioWrtCellStr (buffer, 32, i, 0, 0);
      }
    vio_read (buffer, 1, 0);
  }
#endif

  info.cb = sizeof (info);
  rc = VioGetMode (&info, 0);

  return rc == NO_ERROR;
}

/* initialize the PC BIOS interface. */
static int 
vio_init (int argc,     /* number of command-line arguments */
          char **argv)  /* values of command-line arguments */
{
  int  i;

  /* Choose font colors in the absence of user settings. */
  vc_term = vio_get_default_color ();
  
  vc_current = vc_term;
  vc_color = vc_term;
  vc_italic = 0xff;
  vc_underlined = 0xff;

  /* Dark if black or blue... */
  o_background = (VC_BG (vc_term) <= 1)? 'd': 'l';
  DPRINTF (("o_background: %x, %c\n", VC_BG (vc_term), o_background));

  /* change the terminal mode to cbreak/noecho */
  vio_resume (ElvTrue);

  /* try to get true screen size, from the operating system */
  vio_getsize ();

  /* pretend the cursor is in an impossible place, so we're guaranteed
   * to move it on the first vio_moveto() or vio_draw() call.
   */
  physx = physy = -100;

  /* map the arrow keys */
  for (i = 0; i < QTY (keys); i++)
    {
      if (keys[i].cooked != NULL && keys[i].rawin != NULL)
        {
          mapinsert (toCHAR (keys[i].rawin), (int)strlen (keys[i].rawin),
                     toCHAR (keys[i].cooked), (int)strlen (keys[i].cooked),
                     toCHAR (keys[i].label),
                     keys[i].flags, NULL);
        }
    }

  /* add the global options to the list known to :set */
  o_ttyunderline = ElvTrue;
  optinsert ("vio", QTY (goptdesc), goptdesc, &ttygoptvals.term);

  return argc;
}

/* change the shape of the cursor */
static void 
cursorshape (ELVCURSOR shape)
{
  static ELVCURSOR prev_shape;  /* current shape */
  VIOCURSORINFO ci;

  if (shape != prev_shape)
    {
      /* Default width (1 column): */
      ci.cx = 0;
      ci.attr = 0;

      switch (shape)
        {
        case CURSOR_INSERT:
          ci.yStart = -80;
          ci.cEnd = -100;
          break;

        case CURSOR_REPLACE:
          ci.yStart = -0;
          ci.cEnd = -100;
          break;

        case CURSOR_COMMAND:
          ci.yStart = -10;
          ci.cEnd = -90;
          break;

        case CURSOR_QUOTE:
          ci.yStart = -20;
          ci.cEnd = -80;
          break;

        default:
          /* Hide the cursor. */
          ci.attr = -1;
          break;
        }
      VioSetCurType (&ci, 0L);
      prev_shape = shape;
    }
}

/* Repeatedly get events (keystrokes), and call elvis' event functions */
static void 
vio_loop (void)
{
  char  buf[20];
  int    len;
  int    timeout = 0;
  MAPSTATE mst = MAP_CLEAR;
  VWIN  *scan;

  /* perform the -c command or -t tag */
  mainfirstcmd(windefault);

  while (vwins)
    {
      /* reset the ttycaught bitmap */
      ttycaught = 0;

      /* if no window is current, then make the newest current */
      if (!current)
        {
          current = vwins;
        }

      /* redraw the window(s) */
      {
        /* redraw each window; the current one last */
        for (scan = vwins; scan; scan = scan->next)
          { 
            if (scan != current)
              {
                scan->shape = eventdraw ((GUIWIN *)scan);
              }
          }
        current->shape = eventdraw ((GUIWIN *)current);
        movecurs (current);

        /* make the cursor be this window's shape */
        cursorshape (current->shape);
      }

      /* choose a timeout value */
      switch (mst)
        {
        case MAP_CLEAR:  timeout = 0;    break;
        case MAP_USER:  timeout = o_usertime;  break;
        case MAP_KEY:    timeout = o_keytime;  break;
        }

      /* read events */
      do_flush ();
      vio_CM (c_row, c_col);
      len = vio_read (buf, sizeof buf, timeout);

      /* process keystroke data */
      if (len == -2)
        {
          /* vio_read() itself did something.  We don't need to
           * do anything except the usual screen updates.
           */
        }
      else if (len == -1)
        {
          /* Maybe the screen was resized?  Get new size */
          vio_getsize ();

          /* Resize the windows to match the new screen.  The
           * easiest way to do this is to "change" the size of the
           * current window to its original size and force the
           * other windows to compensate.  If there is only one
           * window, then should be resized to the screen size.
           */
          chgsize (current, vwins->next ? 
            current->height : (int)o_ttyrows, ElvTrue);
        }
      else
        {
          mst = eventkeys ((GUIWIN *)current, toCHAR (buf), len);

          /* if first keystroke after running an external
           * program, then we need to expose every window.
           */
          if (afterprg == 1)
            {
              /* reset the flag BEFORE exposing windows,
               * or else the eventexpose() won't work right.
               */
              afterprg = 0;
              vio_resume (ElvTrue);
              for (scan = vwins; scan; scan = scan->next)
                { 
                  eventexpose  ((GUIWIN *)scan, 0, 0,
                                scan->height - 1, (int)(o_ttycolumns - 1));
                }
            }
          else if (afterprg == 2)
            {
              /* it became 2 while processing the earlier
               * keystrokes.  Set it to 1 now, so we'll
               * read one more keystroke before exposing
               * all the windows.
               */
              afterprg = 1;
            }
        }
    }
}

/* shut down the VIO interface */
static void 
vio_term (void)
{
  cursorshape (CURSOR_NONE);

  /* change the terminal mode back the way it was */
  vio_suspend ();

  do_flush ();
}

/* This draws a bunch of underscores on the physical screen on the bottom
 * row of a window, if that window doesn't end at the bottom of the screen.
 * This should be called after a window is resized or moved.
 */
static void 
drawborder (VWIN *vw)  /* window whose border needs to be redrawn */
{
  int  col;

  /* if this window ends at the bottom of the screen, then do nothing */
  if (vw->pos + vw->height == o_ttyrows)
    {
      return;
    }

  /* move the physical cursor to the bottom of the window */
  vw->cursx = 0;
  vw->cursy = vw->height - 1;
  movecurs (vw);
  vio_revert(vw);
  change(0L, 0L, COLOR_UNDERLINED);

  for (col = 0; col < o_ttycolumns; col++)
    {
      ttych ('_');
    }

  /* figure out where the physical cursor would be after that */
  physx = o_ttycolumns;
}

/* This function changes the height of a given window.  The total heights of
 * all windows must be o_ttyrows and the minimum height of each window is
 * MINHEIGHT.
 */
static void 
chgsize (VWIN  *vw,       /* window to be resized */
         int  newheight,  /* desired height of window */
         ELVBOOL  winch)  /* Did the whole screen change size? */
{
  VWIN  *scan;
  int  pos;
  int  otherheight;
  int  oldheight;
  int  toosmall;

  /* if the current window can't be as large as requested, then reduce
   * the requested size.
   */
  if ((nwindows - 1) * MINHEIGHT + newheight > o_ttyrows)
    {
      newheight = o_ttyrows - (nwindows - 1) * MINHEIGHT;
    }

  /* if window is already the requested height, we're done */
  if (vw->height == newheight && !winch)
    {
      return;
    }

  /* Set the size of the current window.  Also, adjust the sizes of other
   * windows, and maybe their positions.  If any window other than the
   * requested one is moved, expose it.  If any window other than the
   * requested one is resized, then resize it.
   */
  toosmall = 0;
  do
    {
      for (oldheight = vw->height, pos = 0, scan = vwins;
           scan;
           pos += scan->newheight, scan = scan->next)
        {
          /* the requested window? */
          if (scan == vw)
            {
              /* yes, set it */
              scan->newpos = pos;
              scan->newheight = newheight;
            }
          else
            {
              /* no, some other window */

              /* compute the size that this window should be */
              if (!scan->next)
                {
                  scan->newheight = o_ttyrows - pos;
                  toosmall = MINHEIGHT - scan->newheight;
                }
              else if (scan->next == vw && !scan->next->next)
                {
                  scan->newheight = o_ttyrows - pos - newheight;
                  toosmall = MINHEIGHT - scan->newheight;
                }
              else
                {
                  if (winch)
                    otherheight = MINHEIGHT;
                  else
                    otherheight = scan->height 
                      * (o_ttyrows - newheight) / (o_ttyrows - oldheight);
                  if (otherheight < MINHEIGHT)
                    {
                      scan->newheight = MINHEIGHT;
                    }
                  else
                    {
                      scan->newheight = otherheight - toosmall;
                      if (scan->newheight < MINHEIGHT)
                        {
                          scan->newheight = MINHEIGHT;
                        }
                      toosmall -= otherheight - scan->newheight;
                    }
                }
              scan->newpos = pos;
            }
        }
    } while (toosmall > 0);

  /* resize/redraw the windows, as necessary */
  for (scan = vwins; scan; scan = scan->next)
    {
      /* set the size & position of this window.  If its
       * size has changed then resize the window; else if
       * its position has changed resize the window.
       */
      if (scan == vw && !winch)
        {
          /* just remember new stats.  Calling function will
           * call eventredraw() or eventdraw(), as necessary.
           */
          scan->height = scan->newheight;
          scan->pos = scan->newpos;
        }
      else if (scan->newheight != scan->height || winch)
        {
          scan->height = scan->newheight;
          scan->pos = scan->newpos;
          if (scan->pos + scan->height < o_ttyrows)
            {
              drawborder (scan);
            }
          else
            {
              /* draw the border the hard way: erase last row */
              physy = o_ttyrows - 1;
              physx = 0;
              c_row = physy;
              c_col = physx;
              vio_CE (vc_current);
            }
          eventresize ((GUIWIN *)scan, scan->height, (int)o_ttycolumns);
        }
      else if (scan->newpos != scan->pos)
        {
          scan->pos = scan->newpos;
          drawborder (scan);
          eventexpose ((GUIWIN *)scan, 0, 0, scan->height - 1, 
            (int)(o_ttycolumns - 1));
        }
    }
}

/* This function creates a window */
static ELVBOOL 
vio_creategw (char *name,       /* name of new window's buffer */
              char *attributes) /* other window parameters, if any */
{
  VWIN *newp;

  /* if we don't have room for any more windows, then fail */
  if (o_ttyrows / (nwindows + 1) < MINHEIGHT)
    {
      return ElvFalse;
    }

  /* create a window */
  newp = safealloc (1, sizeof (VWIN));

  /* initialize the window */
  if (vwins)
    {
      newp->height = 0;
      newp->pos = o_ttyrows;
    }
  else
    {
      newp->height = o_ttyrows;
      newp->pos = 0;
    }
  newp->cursx = newp->cursy = 0;
  newp->shape = CURSOR_NONE;

  /* insert the new window into the list of windows */
  newp->next = vwins;
  vwins = newp;
  nwindows++;

  /* adjust the heights of the other windows to make room for this one */
  chgsize (newp, (int)(o_ttyrows / nwindows), ElvFalse);
  drawborder (newp);

  /* make elvis do its own initialization */
  if (!eventcreate ((GUIWIN *)newp, NULL, name, newp->height, 
      (int)o_ttycolumns))
    {
      /* elvis can't make it -- fail */
      safefree (newp);
      return ElvFalse;
    }

  /* make the new window be the current window */
  current = newp;

  return ElvTrue;
}

/* This function deletes a window */
static void 
vio_destroygw (GUIWIN *gw,      /* window to be destroyed */
               ELVBOOL force)   /* if ElvTrue, try harder */
{
  VWIN *scan, *lag;

  /* delete the window from the list of windows */
  for (lag = NULL, scan = vwins; scan != (VWIN *)gw; 
      lag = scan, scan = scan->next)
    {
    }
  if (lag)
    {
      lag->next = scan->next;
    }
  else
    {
      vwins = scan->next;
    }

  /* if it was the current window, it isn't now */
  if (scan == current)
    {
      current = vwins;
    }

  /* adjust the sizes of other windows (if any) */
  nwindows--;
  if (nwindows > 0)
    {
      chgsize ((VWIN *)gw, 0, ElvFalse);
    }

  /* If this is the last window, move the cursor to the last line, and
   * erase it.  If the buffer is going to be written, this is where the
   * "wrote..." message will appear.
   */
  if (nwindows == 0)
    {
      vio_revert (NULL);
      vio_CM(o_ttyrows - 1, 0);
      vio_CE(vc_term);
    }

  /* simulate a "destroy" event */
  eventdestroy (gw);

  /* free the storage */
  safefree (gw);
}

/* This function changes window focus */
static ELVBOOL
vio_focusgw (GUIWIN *gw)    /* window to be the new "current" window */
{
  current = (VWIN *)gw;
  return ElvTrue;
}

/* This function handles the visual <Tab> command */
static ELVBOOL 
vio_tabcmd (GUIWIN  *gw,  /* window that the command should affect */
            _CHAR_  key2, /* second key of <Tab> command */
            long  count)  /* argument of the <Tab> command */
{
  VWIN  *vw = (GUIWIN *)gw;
  int  newheight;
  int  oldheight;
  int  oldpos;

  /* if only one window, then we can't change its size */
  if (nwindows == 1)
    return ElvFalse;

  /* remember the old position */
  newheight = oldheight = vw->height;
  oldpos = vw->pos;

  switch (key2)
    {
    case '=':
      if (count >= MINHEIGHT)
        {
          newheight = count;
          break;
        }
      /* else fall through... */

    case '+':
      newheight += (count ? count : 1);
      break;

    case '-':
      newheight -= (count ? count : 1);
      if (newheight < MINHEIGHT)
        {
          newheight = MINHEIGHT;
        }
      break;

    case '\\':
      newheight = o_ttyrows; /* will be reduced later */
      break;

    default:  return ElvFalse;
    }

  /* try to change the heights of other windows to make this one fit */
  chgsize (vw, newheight, ElvFalse);
  newheight = vw->height;

  /* resize/expose this window */
  if (newheight != oldheight)
    {
      drawborder (vw);
      eventresize (vw, vw->height, (int)o_ttycolumns);
    }
  else if (vw->pos != oldpos)
    {
      drawborder (vw);
      eventexpose (vw, 0, 0, newheight - 1, (int)(o_ttycolumns - 1));
    }
  return ElvTrue;
}

/* This function rings the bell */
static void
vio_beep (GUIWIN *gw)  /* window that generated the beep */
{
#if 0
  if (VB)
    tputs (VB, 0, ttych);
  else
    ttych ('\007');
#endif
}

/* This function converts key labels to raw codes */
static int 
keylabel (CHAR  *given,   /* what the user typed in as the key name */
          int  givenlen,  /* length of the "given" string */
          CHAR  **label,  /* standard name for that key */
          CHAR  **rawptr) /* control code sent by that key */
{
  int  i;

  /* compare the given text to each key's strings */
  for (i = 0; i < QTY(keys); i++)
    {
      /* ignore unsupported keys */
      if (!keys[i].rawin)
        continue;

      /* does given string match key label or raw characters? */
      if ((!strncmp(keys[i].label, tochar8(given), 
          (size_t)givenlen) && !keys[i].label[givenlen])
          || (!strncmp(keys[i].rawin, tochar8(given), 
          (size_t)givenlen) && !keys[i].rawin[givenlen]))
        {

          /* Set the label and rawptr pointers, return rawlen */
          *label = toCHAR(keys[i].label);
          *rawptr = toCHAR(keys[i].rawin);
          return CHARlen(*rawptr);
        }
    }

  /* We reached the end of the keys[] array without finding a match,
   * so this given string is not a key.
   */
  return 0;
}

/* This function defines colors for fonts */
static ELVBOOL 
vio_color(int  fontcode,        /* name of font being changed */
          CHAR  *name,          /* name of new color */
          ELVBOOL  isfg,        /* ElvTrue for foreground, ElvFalse for background */
          long  *colorptr,      /* where to store the color number */
          unsigned char rgb[3]) /* color broken down into RGB components */
{

DPRINTF (("vio_color (%d, %s, %d, %p, %x %x %x)\n", 
  fontcode, name, isfg, colorptr, rgb[0], rgb[1], rgb[2]));

  /* Normal colors must be set first, so we have a way to switch back
   * from specialized colors.
   */
  if (fontcode != COLOR_FONT_NORMAL && !(isfg ? fgcolored : bgcolored))
    {
      colorset (COLOR_FONT_NORMAL, 
        colorname (vio2ansi (VC_FG (vc_term))), ElvTrue);
    }

  /* Set the colors. */
  if (!coloransi (fontcode, name, isfg, colorptr, rgb))
    return ElvFalse;

  /* Success!  Remember if we've set foreground or background */
  if (isfg)
    fgcolored = ElvTrue;
  else
    bgcolored = ElvTrue;
  return ElvTrue;
}

static ELVBOOL isfilter;

/* Suspend curses while running an external program */
static ELVBOOL
vio_prgopen (char *command,       /* the shell command to run */
             ELVBOOL  willwrite,  /* redirect stdin from elvis */
             ELVBOOL  willread)   /* redirect stdiout back to elvis */
{
  /* unless both stdin and stdout/stderr are going to be redirected,
   * move the cursor to the bottom of the screen before running program.
   */
  isfilter = (ELVBOOL)(willwrite && willread);
  if (!isfilter)
    {
      vio_CM (o_ttyrows - 1, 0);
      vio_CE (vc_term);
      vio_reset ();
      do_flush ();
  
      /* suspend curses */
      vio_suspend ();
    }

  /* try to call the regular prgopen(); if it fails, then clean up */
  if (!prgopen (command, willwrite, willread))
    {
      if (!isfilter)
        vio_resume (ElvTrue);
      return ElvFalse;
    }

  return ElvTrue;
}

/* After running a program, resume curses and redraw all screens */
static int
vio_prgclose (void)
{
  int  status;

  /* wait for the program to terminate */
  status = prgclose ();

  /* resume curses */
  if (!isfilter)
    {
      vio_resume (ElvTrue);

      /* If the program left the cursor anywhere but the first
       * column of the last line of the screen, move the cursor
       * there and scroll to the next line, for good measure.
       */
      if (c_row != o_ttyrows - 1 || c_col != 0)
        {
          vio_CM (o_ttyrows - 1, 0);
          VioWrtTTY ("\n", 1, 0);
        }
      vio_CE (vc_current);

      /* Okay, now we're in a weird sort of situation.  The screen is
       * about to be forced to display "Hit <Enter> to continue" on
       * the bottom of the window in open mode, and then wait for a
       * keystroke.  That's a Good Thing.  But there are two problems
       * we need to address:
       *    * We want that prompt to appear at the bottom of the
       *  screen, not the bottom of the window.
       *    * After the user hits a key, we want to redraw all
       *  windows.
       *
       * We'll set a flag indicating this situation.  The movecurs()
       * function will test for that flag, and merely pretend to move
       * the cursor when it is set.  The loop() function will test
       * that flag after each keystroke, and expose all windows if
       * it is set.
       */
      afterprg = 2;
      afterscrl = 0;
    }

  return status;
}

/* Check for signs of boredom from user, so we can abort a time-consuming
 * operation.  Here we check to see if SIGINT has been caught recently.
 * Returns ElvTrue to abort an operation, or ElvFalse to continue it.
 */
static ELVBOOL 
vio_poll (ELVBOOL reset)
{
  return (ELVBOOL)((ttycaught & (1 << SIGINT)) != 0);
}

/* structs of this type are used to describe each available GUI */
GUI guivio =
{
  "vio",          /* name */
  "OS/2 console interface with windows & color",
  ElvFalse,          /* exonly */
  ElvTrue,           /* newblank */
  ElvFalse,          /* minimizeclr */
  ElvFalse,          /* scrolllast */
  ElvFalse,          /* shiftrows */
  8,              /* movecost */
  0,              /* opts */
  NULL,           /* optdescs */
  vio_test,
  vio_init,
  NULL,           /* usage */
  vio_loop,
  vio_poll,
  vio_term,
  vio_creategw,
  vio_destroygw,
  vio_focusgw,
  NULL,           /* retitle */
  vio_reset,
  vio_flush,
  vio_moveto,
  vio_draw,
  vio_shift,
  vio_scroll,
  vio_clrtoeol,
  NULL,           /* textline */
  vio_beep,       /* beep */
  NULL,           /* msg */
  NULL,           /* scrollbar */
  NULL,           /* status */
  keylabel,
  NULL,           /* clipopen */
  NULL,           /* clipwrite */
  NULL,           /* clipread */
  NULL,           /* clipclose */
  vio_color,      /* color */
  NULL,           /* colorfree */
  NULL,           /* setbg */
  NULL,           /* guicmd */
  vio_tabcmd,
  NULL,           /* save */
  NULL,           /* wildcard */
  vio_prgopen,
  vio_prgclose,
  NULL            /* stop */
};
#endif

