Logo Search packages:      
Sourcecode: faumachine version File versions  Download package

vt102_core.c

/* $Id: vt102_core.c,v 1.21 2009-01-28 12:59:22 potyra Exp $ 
 *
 * Copyright (C) 2007-2009 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

#include "config.h"

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

#include "glue-main.h"
#include "glue-shm.h"

#include "vt102_keydefs.h"
#include "vt102_core.h"

#define COMP "vt102_core"

/* 
 * if we should use some (not strictly vt102-compatible) extensions, e.g.
 * colors
 */
#define VT_USE_EXTENSIONS 1

/* this will be 132 when vt102 emulation is finished */
#define MAX_COLS 80

/* well, 25 is not really correct - the original vt102 had only 24 lines
 * (excluding a predefined status line) */
#define MAX_ROWS 25

/*
 * character attributes
 */
struct vt_char_flags {
      unsigned int bold      :1;
      unsigned int blink     :1;
      unsigned int underline :1;
      unsigned int reverse   :1;
#ifdef VT_USE_EXTENSIONS
      unsigned int fgcolor   :3;
      unsigned int bgcolor   :3;
#endif
};

struct cpssp {
      /* Config */

      /* Ports */
      struct sig_boolean *port_mech_power_switch;
      struct sig_serial *port_serial;
      struct sig_integer *port_keyboard;
      struct sig_video *port_video;

      /* Signals */

      /* State */
      int power;

      /* characters in our videobuffer */
      struct vb_char {
            struct vt_char_flags flags;
            unsigned int c;
      } videobuffer[MAX_ROWS][MAX_COLS];

      /* current status of the terminal */
      struct vt_status {
            unsigned int col, row; /* current cursor position */
            struct vt_char_flags flags; /* actual character attributes */
            unsigned int top, bottom; /* scrolling region */
            int DECOM; /* origin mode active? */
            int LNM; /* new line mode active? */
            int KAM; /* keyboard locked? */
            int SRM; /* local echo on/off */
            int IRM; /* insertion-replacement mode */
            int DECCKM; /* cursor key mode */
            int DECAWM; /* auto wrap mode */

            /* states of the vt102 parser */
            enum vt_state { ST_NORMAL, ST_ESCAPE, ST_CSI } state;

            /* keypad mode: numerical keypad/application keypad */
            enum vt_kpmode { DECKPNM, DECKPAM } kpmode;

            unsigned char tabstops[17]; /* bitfield for tabstops */
            /* mapping chars to our font */
            unsigned int G0_offset; /* for offsets, see font_8x16.c */
            unsigned int G1_offset;
            int active_charset; /* 0: G0, 1: G1 */
            /* unused: G2, G3 charsets */
            /* for DECSC and DECRC: */
            unsigned int save_col, save_row; /* saved cursor position */
            int save_DECOM; /* saved origin mode */
            struct vt_char_flags save_flags; /* saved character attributes */
            int save_charset; /* saved active charset */
            unsigned int save_G1, save_G0; /* saved charset offsets */
      } ts;

      /* status of the control sequence parser */
      struct esc_status {
            unsigned char inter[2]; /* intermediate chars */
            /* quoting http://vt100.net/emu/dec_ansi_parser:
             * Strictly speaking, X3.64 says that the entire string is "subject
             * to private or experimental interpretation" if the first character
             * is one of 3C-3F, which allows sequences like CSI ?::<? F, but
             * Digital's terminals only ever used one private-marker character at
             * a time. As far as I am aware, only characters 3D (=), 3E (>) and
             * 3F (?) were used by Digital. */
            unsigned char private; /* CSI private-marker */
            unsigned char max_param; /* number of parsed parameters */
            unsigned int param[16]; /* control sequence parameters */
      } escs;
};


#ifdef VT_DEBUG
#define TRACE(fmt, arg...) \
fprintf(stderr, "serial_terminal vt102: " fmt ,##arg)
#else
#define TRACE(fmt, arg...)
#endif /* VT_DEBUG */


/* reset terminal state, redisplay video buffer */
void vt_reset(struct cpssp *cpssp);
/* initialize terminal */
void vt_init(struct cpssp *cpssp);

#define TERMINAL_ID "\033[?6c" /* vt102 response to DA and DECID */
#define TERMINAL_OK "\033[0n" /* response to terminal status report: ok */
#define PRINTER_OK "\033[?13n" /* response to printer status report:
                         no printer connected */

/* offsets of our charsets in the font
 * maybe these should be in font_8x16.h instead...
 */
#define VT_FONT_OFF_US   0
#define VT_FONT_OFF_UK   256
#define VT_FONT_OFF_DRAW 384

#ifdef VT_DEBUG
static int vt_debug = 0;
#endif

#include "vt102_core_font.c"

static void
vt102_core_serial_send(struct cpssp *cpssp, uint8_t c)
{
      sig_serial_send(cpssp->port_serial, cpssp, c);
}

static void
vt102_core_serial_sendbuf(
      struct cpssp *cpssp,
      const char *buf,
      unsigned int len
)
{
      while (0 < len) {
            vt102_core_serial_send(cpssp, *buf++);
            len--;
      }
}

/*
 * cancel parsing of a control code, reset terminal state to ST_NORMAL
 */
static void
vt_cancel_escape(struct cpssp *cpssp)
{
      cpssp->escs.inter[0] = 0;
      cpssp->escs.inter[1] = 0;
      cpssp->escs.private = 0;
      cpssp->escs.max_param = 0;
      memset(cpssp->escs.param, 0, sizeof(cpssp->escs.param));
      cpssp->ts.state = ST_NORMAL;
}

/*
 * adds an intermediate character to the control sequence parser status
 */
static void
vt_add_intermediate(struct cpssp *cpssp, unsigned char ch)
{
      if (cpssp->escs.inter[0] == 0) {
            cpssp->escs.inter[0] = ch;
            return;
      }
      if (cpssp->escs.inter[1] == 0) {
            cpssp->escs.inter[1] = ch;
            return;
      }
      /* if we've not returned until now, ignore character */
}

/*
 * "adds" a char in the range '0'..'9' to current control sequence parameter
 * "adds" a parameter if ';' is encountered
 * (other characters are silently ignored)
 */
static void
vt_add_param(struct cpssp *cpssp, unsigned char ch)
{
      if (('0' <= ch) && (ch <= '9')) {
            cpssp->escs.param[cpssp->escs.max_param] *= 10;
            cpssp->escs.param[cpssp->escs.max_param] += (ch - '0');
            return;
      } 
      if ((ch == ';') && (cpssp->escs.max_param < 15)) {
            cpssp->escs.max_param++;
      } else {
            TRACE("vt_add_param: ch '%c' max %d\n", ch, cpssp->escs.max_param);
      }
}

/*
 * sets the private marker of the control sequence,
 * see struct esc_status for details
 */
static void
vt_add_private(struct cpssp *cpssp, unsigned char ch)
{
      if (cpssp->escs.private == 0) {
            cpssp->escs.private = ch;
      }
}

/*
 * empty the list of tab stops
 */
#define vt_empty_tabstops() memset(cpssp->ts.tabstops, 0, sizeof(cpssp->ts.tabstops))

/*
 * set tab stop at position x
 */
#define vt_set_tabstop(X) cpssp->ts.tabstops[ (X) / 8 ] |= (1 << ((X) % 8))

/*
 * clear tab stop at position x
 */
#define vt_clear_tabstop(X) cpssp->ts.tabstops[ (X) / 8 ] &= ~(1 << ((X) % 8))

/*
 * get next tab stop for position x
 */
static unsigned int
vt_get_next_tabstop(struct cpssp *cpssp, unsigned int x)
{
      unsigned int i;
      for (i = (x + 1); i < MAX_COLS; i++) {
            if (cpssp->ts.tabstops[i/8] & (1 << (i % 8))) break;
      }
      if (MAX_COLS <= i) {
            i = MAX_COLS-1;
      }
      TRACE("next tab for %u at  %u\n", x, i);
      return i;
}

/*
 * clear the cpssp->ts.flags structure
 */
static void
vt_clear_flags(struct cpssp *cpssp)
{
      /* memset doesn't work on bitfields... */
      cpssp->ts.flags.bold = 0;
      cpssp->ts.flags.blink = 0;
      cpssp->ts.flags.underline = 0;
      cpssp->ts.flags.reverse = 0;
#ifdef VT_USE_EXTENSIONS
      cpssp->ts.flags.fgcolor = 7; /* white */
      cpssp->ts.flags.bgcolor = 0; /* black */
#endif /* VT_USE_EXTENSIONS */
}

/*
 * initialize terminal state
 */
static void
vt_init_status(struct cpssp *cpssp)
{
      /* most of these initial settings should be configurable */
      cpssp->ts.row = 0;
      cpssp->ts.col = 0;
      cpssp->ts.state = ST_NORMAL;
      cpssp->ts.top = 0;
      cpssp->ts.bottom = MAX_ROWS-1;
      cpssp->ts.DECOM = 0;
      cpssp->ts.LNM = 0;
      cpssp->ts.KAM = 0;
      cpssp->ts.SRM = 1;
      cpssp->ts.IRM = 0;
      cpssp->ts.DECCKM = 0;
      cpssp->ts.DECAWM = 0;
      cpssp->ts.kpmode = DECKPNM;
      vt_clear_flags(cpssp);
      cpssp->ts.save_flags = cpssp->ts.flags;
      cpssp->ts.save_col = cpssp->ts.col;
      cpssp->ts.save_row = cpssp->ts.row;
      cpssp->ts.save_DECOM = cpssp->ts.DECOM;
      cpssp->ts.active_charset = 0;
      cpssp->ts.G0_offset = VT_FONT_OFF_US;
      cpssp->ts.G1_offset = VT_FONT_OFF_UK;
      cpssp->ts.save_charset = cpssp->ts.active_charset;
      cpssp->ts.save_G0 = cpssp->ts.G0_offset;
      cpssp->ts.save_G1 = cpssp->ts.G1_offset;
      vt_empty_tabstops();
      vt_cancel_escape(cpssp);
      /* some applications seem to depend on tabstops every 8 chars without
       * setting them explicitely */
      vt_set_tabstop(7);
      vt_set_tabstop(15);
      vt_set_tabstop(23);
      vt_set_tabstop(31);
      vt_set_tabstop(39);
      vt_set_tabstop(47);
      vt_set_tabstop(55);
      vt_set_tabstop(63);
      vt_set_tabstop(71);
      vt_set_tabstop(79);
}

/*
 * erase characters in a line of the videobuffer by setting them to 0 and
 * their flags to the current used character flags.
 * line: line number, from: first character (inklusive),
 * to: last character (exclusive!)
 */
static void
vt_erase_in_line(struct cpssp *cpssp, unsigned int line, unsigned int from, unsigned int to)
{
      unsigned int i;
      if ((to <= from) || (MAX_COLS <= from) 
        || (MAX_COLS < to) || (MAX_ROWS <= line)) {
            TRACE("erase_in_line: WRONG PARAMETERS! %u %u %u\n",
                        line, from, to);
            return;
      }
      for (i = from; i < to; i++) {
            cpssp->videobuffer[line][i].c = 0;
            cpssp->videobuffer[line][i].flags = cpssp->ts.flags;
      }
}

/*
 * erase a region of characters in the videobuffer (NOT a rectangle!)
 * start with character no <from> at line <start> (inclusive), erase
 * all consecutive lines *completely*, stop at character no <to> at
 * line <stop> (exclusive!)
 */
static void
vt_erase_region(
      struct cpssp *cpssp,
      unsigned int start,
      unsigned int from,
      unsigned int stop,
      unsigned int to
)
{
      unsigned int i;
      if ((stop < start) || (MAX_ROWS < start) || (MAX_ROWS < stop)
        || (MAX_COLS <= from) || (MAX_COLS < to) || (to == 0)) {
            TRACE("erase_region: WRONG PARAMETERS! %u %u %u %u\n",
                        start, from, stop, to);
            return;
      }
      if (start == stop) {
            vt_erase_in_line(cpssp, start, from, to);
            return;
      }
      vt_erase_in_line(cpssp, start, from, MAX_COLS);
      for (i = start + 1; i < stop; i++) {
            vt_erase_in_line(cpssp, i, 0, MAX_COLS);
      }
      vt_erase_in_line(cpssp, stop, 0, to);
}

/*
 * clear the screen, reset the videobuffer
 */
static void
vt_clear_screen(struct cpssp *cpssp)
{
      vt_erase_region(cpssp, 0, 0, MAX_ROWS - 1, MAX_COLS);
}

/*
 * scroll videobuffer up one line, and redraw the scrolled region
 * adheres to scrolling region defined by ts.top and ts.bottom
 */
static void
vt_scrollup(struct cpssp *cpssp)
{
      if (1 <= (cpssp->ts.bottom-cpssp->ts.top)) {
            /* scroll up */
            memmove(&cpssp->videobuffer[cpssp->ts.top][0], &cpssp->videobuffer[cpssp->ts.top+1][0],
                        (cpssp->ts.bottom - cpssp->ts.top) * MAX_COLS
                        * sizeof(struct vb_char));
            /* empty the last line */
            vt_erase_in_line(cpssp, cpssp->ts.bottom, 0, MAX_COLS);
            
      } else {
            TRACE("scrolling region too small! (top %u, bottom %u)\n",
                        cpssp->ts.top, cpssp->ts.bottom);
      }
}

/*
 * scroll videobuffer down one line, and redraw the scrolled region
 * adheres to scrolling region defined by ts.top and ts.bottom
 */
static void
vt_scrolldown(struct cpssp *cpssp)
{
      if (1 <= (cpssp->ts.bottom-cpssp->ts.top)) {
            /* scroll down */
            memmove(&cpssp->videobuffer[cpssp->ts.top+1][0], &cpssp->videobuffer[cpssp->ts.top][0],
                        (cpssp->ts.bottom - cpssp->ts.top) * MAX_COLS
                        * sizeof(struct vb_char));
            /* empty the first line */
            vt_erase_in_line(cpssp, cpssp->ts.top, 0, MAX_COLS);
            
      } else {
            TRACE("scrolling region too small! (top %u, bottom %u)\n",
                        cpssp->ts.top, cpssp->ts.bottom);
      }
}

/*
 * shows a screen to test terminal. traditionally, fills screen with uppercase
 * E's for screen focus and alignment. maybe we'll do something more nifty,
 * sometime...
 */
static void
vt_show_testscreen(struct cpssp *cpssp)
{
      int i, j;
      for (i=0; i < MAX_ROWS; i++) {
            for (j=0; j < MAX_COLS; j++) {
                  /* using ts.flags here maybe is not the best idea */
                  cpssp->videobuffer[i][j].flags = cpssp->ts.flags;
                  cpssp->videobuffer[i][j].c = 'E';
            }
      }
}

/*
 * *************************************  ANSI control functions
 */
static void
vt_ctrl_BS(struct cpssp *cpssp)
{
      if (cpssp->ts.col != 0) {
            cpssp->ts.col--;
      }
}

static void
vt_ctrl_HT(struct cpssp *cpssp)
{
      cpssp->ts.col = vt_get_next_tabstop(cpssp, cpssp->ts.col);
}

static void
vt_ctrl_LF(struct cpssp *cpssp)
{
      if (cpssp->ts.row < cpssp->ts.bottom) {
            cpssp->ts.row++;
      } else {
            vt_scrollup(cpssp);
      }
      if (cpssp->ts.LNM) {
            cpssp->ts.col = 0;
      }
}

static void
vt_ctrl_CR(struct cpssp *cpssp)
{
      cpssp->ts.col = 0;
}

static void
vt_ctrl_CSI(struct cpssp *cpssp)
{
      vt_cancel_escape(cpssp);
      cpssp->ts.state = ST_CSI;
}

static void
vt_ctrl_IND(struct cpssp *cpssp)
{
      if (cpssp->ts.row < cpssp->ts.bottom) {
            cpssp->ts.row++;
      } else {
            vt_scrollup(cpssp);
      }
}

static void
vt_ctrl_RI(struct cpssp *cpssp)
{
      if (cpssp->ts.top < cpssp->ts.row) {
            cpssp->ts.row--;
      } else {
            vt_scrolldown(cpssp);
      }
}

static void
vt_ctrl_NEL(struct cpssp *cpssp)
{
      if (cpssp->ts.row < cpssp->ts.bottom) {
            cpssp->ts.row++;
      } else {
            vt_scrollup(cpssp);
      }
      cpssp->ts.col = 0;
}

static void
vt_ctrl_DECSC(struct cpssp *cpssp)
{
      cpssp->ts.save_col = cpssp->ts.col;
      cpssp->ts.save_row = cpssp->ts.row;
      cpssp->ts.save_flags = cpssp->ts.flags;
      cpssp->ts.save_DECOM = cpssp->ts.DECOM;
      cpssp->ts.save_charset = cpssp->ts.active_charset;
      cpssp->ts.save_G0 = cpssp->ts.G0_offset;
      cpssp->ts.save_G1 = cpssp->ts.G1_offset;
}

static void
vt_ctrl_DECRC(struct cpssp *cpssp)
{
      cpssp->ts.col = cpssp->ts.save_col;
      cpssp->ts.row = cpssp->ts.save_row;
      cpssp->ts.flags = cpssp->ts.save_flags;
      cpssp->ts.DECOM = cpssp->ts.save_DECOM;
      cpssp->ts.G0_offset = cpssp->ts.save_G0;
      cpssp->ts.G1_offset = cpssp->ts.save_G1;
      cpssp->ts.active_charset = cpssp->ts.save_charset;
}

static void
vt_ctrl_DECALN(struct cpssp *cpssp)
{
      TRACE("DECALN: showing testscreen\n");
      vt_reset(cpssp);
      vt_show_testscreen(cpssp);
}

static void
vt_ctrl_DECKPAM(struct cpssp *cpssp)
{
      TRACE("DECKPAM set\n");
      cpssp->ts.kpmode = DECKPAM;
}

static void
vt_ctrl_DECKPNM(struct cpssp *cpssp)
{
      TRACE("DECKPNM set (and DECCKM reset)\n");
      cpssp->ts.DECCKM = 0;
      cpssp->ts.kpmode = DECKPNM;
}

static void
vt_ctrl_SCS(struct cpssp *cpssp, unsigned char inter, unsigned int fontoffset)
{
      switch (inter) {
      case '(':
            cpssp->ts.G0_offset = fontoffset;
            break;
      case ')':
            cpssp->ts.G1_offset = fontoffset;
            break;
      default:
            TRACE("SCS: wrong intermediate %hhu for offset %u\n",
                        cpssp->escs.inter[0], fontoffset);
            break;
      }
}

static void
vt_ctrl_DECID(struct cpssp *cpssp)
{
      TRACE("DECID: sent id.\n");
      vt102_core_serial_sendbuf(cpssp, TERMINAL_ID, strlen(TERMINAL_ID));
}

static void
vt_ctrl_DECSTBM(struct cpssp *cpssp, unsigned int top, unsigned int bottom)
{
      int i, j;

      TRACE("DECSTBM: top %u bottom %u\n", top, bottom);

      if (MAX_ROWS < top) {
            i = MAX_ROWS - 1;
      } else {
            i = top - 1;
      }
      if (i < 0) {
            i = 0;
      }
      if ((MAX_ROWS < bottom) || (bottom == 0)) {
            j = MAX_ROWS - 1;
      } else {
            j = bottom - 1;
      }
      if (i < j) {
            cpssp->ts.top = i;
            cpssp->ts.bottom = j;
            TRACE("DECSTBM: set %d %d\n", i, j);
            /* After the margins are selected, the cursor moves to
             * the home position. */
            if (cpssp->ts.DECOM) {
                  cpssp->ts.row = cpssp->ts.top;
            } else {
                  cpssp->ts.row = 0;
            }
            cpssp->ts.col = 0;
      } else {
            TRACE("DECSTBM: won't set %d %d", i, j);
      }
}

static void
vt_ctrl_CUU(struct cpssp *cpssp, unsigned int amount)
{
      int i;

      if (amount == 0) {
            amount = 1;
      }
      i = cpssp->ts.row - amount;
      TRACE("CUU: relative %u absolute %d\n", amount, i);
      if (i < (int) cpssp->ts.top) {
            i = cpssp->ts.top;
            TRACE("CUU: using top margin %d\n", i);
      }
      cpssp->ts.row = i;
}

static void
vt_ctrl_CUD(struct cpssp *cpssp, unsigned int amount)
{
      int i;

      if (amount == 0) {
            amount = 1;
      }
      i = cpssp->ts.row + amount;
      TRACE("CUD: relative %u absolute %d\n", amount, i);
      if ((int) cpssp->ts.bottom < i) {
            i = cpssp->ts.bottom;
            TRACE("CUD: using bottom margin %d\n", i);
      }
      cpssp->ts.row = i;
}

static void
vt_ctrl_CUF(struct cpssp *cpssp, unsigned int amount)
{
      int i;

      if (amount == 0) {
            amount = 1;
      }
      i = cpssp->ts.col + amount;
      TRACE("CUF: relative %u absolute %d\n", amount, i);
      if (MAX_COLS <= i) {
            i = MAX_COLS - 1;
            TRACE("CUF: using %d\n", i);
      }
      cpssp->ts.col = i;
}

static void
vt_ctrl_CUB(struct cpssp *cpssp, unsigned int amount)
{
      int i;

      if (amount == 0) {
            amount = 1;
      }
      i = cpssp->ts.col - amount;
      TRACE("CUB: relative %u absolute %d\n", amount, i);
      if (i < 0) {
            i = 0;
            TRACE("CUB: using %d\n", i);
      }
      cpssp->ts.col = i;
}

static void
vt_ctrl_HVP(struct cpssp *cpssp, unsigned int col, unsigned int row)
{
      int i;

      TRACE("CUP/HVP: %u %u (%d)\n", col, row, cpssp->ts.DECOM);

      if (cpssp->ts.DECOM) {
            /* calculate absolute row number */
            i = col - 1 + cpssp->ts.top;
            if (i < (int) cpssp->ts.top) {
                  i = cpssp->ts.top;
            }
            if ((int) cpssp->ts.bottom < i) {
                  i = cpssp->ts.bottom;
            }
      } else { /* no origin mode */
            if (col < MAX_ROWS) {
                  i = col - 1;
            } else {
                  i = MAX_ROWS - 1;
            }
            if (i < 0) {
                  i = 0;
            }
      }
      cpssp->ts.row = i;
      TRACE("CUP/HVP: new row %d\n", i);
      if (row < MAX_COLS) {
            i = row - 1;
      } else {
            i = MAX_COLS - 1;
      }
      if (i < 0) {
            i = 0;
      }
      cpssp->ts.col = i;
      TRACE("CUP/HVP: new column %d\n", i);
}

static void
vt_ctrl_SGR(struct cpssp *cpssp, unsigned int attr)
{
      switch (attr) {
      case 0: /* Turn off character attributes. */
            vt_clear_flags(cpssp);
            break;
      case 1: /* Select bold. */
            cpssp->ts.flags.bold = 1;
            break;
      case 4: /* Select underline. */
            cpssp->ts.flags.underline = 1;
            break;
      case 5: /* Select blink */
            cpssp->ts.flags.blink = 1;
            break;
      case 7: /* Select reverse video */
            cpssp->ts.flags.reverse = 1;
            break;
#ifdef VT_USE_EXTENSIONS
            /* the following are ECMA-48 SGR parameters which are
             * unsupportedd on real vt102s */
      case 22: /* set normal intensity */
            cpssp->ts.flags.bold = 0;
            break;
      case 24: /* underline off */
            cpssp->ts.flags.underline = 0;
            break;
      case 25: /* blink off */
            cpssp->ts.flags.blink = 0;
            break;
      case 27: /* reverse video off */
            cpssp->ts.flags.reverse = 0;
            break;
      case 30:  case 31:  case 32:  case 33:  case 34:
      case 35:  case 36:  case 37: /* set foreground color */
            cpssp->ts.flags.fgcolor = attr - 30;
            break;
      case 38: /* default foreground color, underline on */
            cpssp->ts.flags.fgcolor = 7;
            cpssp->ts.flags.underline = 1;
            break;
      case 39: /* default foreground color, underline off */
            cpssp->ts.flags.fgcolor = 7;
            cpssp->ts.flags.underline = 0;
            break;
      case 40:  case 41:  case 42:  case 43:  case 44:
      case 45:  case 46:  case 47: /* set background color */
            cpssp->ts.flags.bgcolor = attr - 40;
            break;
      case 49: /* default background color */
            cpssp->ts.flags.bgcolor = 0;
            break;
#endif /* VT_USE_EXTENSIONS */
      default:
            TRACE("unknown SGR parameter: %u\n", attr);
            break;
      }
}

static void
vt_ctrl_TBC(struct cpssp *cpssp, unsigned int mode)
{
      switch (mode) {
      case 0: /* Clear a horizontal tab stop at cursor position */
            vt_clear_tabstop(cpssp->ts.col);
            break;
      case 3: /* Clears all horizontal tab stops */
            vt_empty_tabstops();
            break;
      default:
            TRACE("unknown TBC parameter: 0x%x\n", cpssp->escs.param[0]);
            break;
      }
}

static void
vt_ctrl_EL(struct cpssp *cpssp, unsigned int mode)
{
      switch (mode) {
      case 0: /* Erase from cursor to end of line */
            vt_erase_in_line(cpssp, cpssp->ts.row, cpssp->ts.col, MAX_COLS);
            break;
      case 1: /* Erase from beginning of line to cursor */
            vt_erase_in_line(cpssp, cpssp->ts.row, 0, (cpssp->ts.col + 1));
            break;
      case 2: /* Erase complete line */
            vt_erase_in_line(cpssp, cpssp->ts.row, 0, MAX_COLS);
            break;
      default:
            TRACE("unknown EL parameter: 0x%x\n", cpssp->escs.param[0]);
            break;
      }
}

static void
vt_ctrl_ED(struct cpssp *cpssp, unsigned int mode)
{
      switch (mode) {
      case 0: /* Erase from cursor (incl.) to end of screen */
            vt_erase_region(cpssp, cpssp->ts.row, cpssp->ts.col, MAX_ROWS-1, MAX_COLS);
            break;
      case 1: /* Erase from beginning of screen to cursor */
            vt_erase_region(cpssp, 0, 0, cpssp->ts.row, cpssp->ts.col + 1);
            break;
      case 2: /* Erase complete display. */
            /* TODO: double-width lines */
            vt_clear_screen(cpssp);
            break;
      default:
            TRACE("unknown ED parameter: 0x%x\n", cpssp->escs.param[0]);
            break;
      }
}

static void
vt_ctrl_DCH(struct cpssp *cpssp, unsigned int num)
{
      int i;

      if (num == 0) {
            num = 1;
      }
      if (MAX_COLS < (cpssp->ts.col + num)) {
            num = MAX_COLS - cpssp->ts.col;
            TRACE("DCH: deleting only %d char(s).\n", num);
      }
      i = MAX_COLS - cpssp->ts.col - num; /* no. of remaining chars */
      if (0 < i) {
            /* move remaining chars left */
            memmove(&cpssp->videobuffer[cpssp->ts.row][cpssp->ts.col],
                        &cpssp->videobuffer[cpssp->ts.row][cpssp->ts.col + num],
                        i * sizeof(struct vb_char));
      }
      /* empty the rest */
      vt_erase_in_line(cpssp, cpssp->ts.row, cpssp->ts.col + i, MAX_COLS);
}

static void
vt_ctrl_IL(struct cpssp *cpssp, int num)
{
      int i;

      /* only works when cursor is inside scrolling region: */
      if ((cpssp->ts.row < cpssp->ts.top) || (cpssp->ts.bottom < cpssp->ts.row)) {
            TRACE("IL: curser outside scrolling region!\n");
            return;
      }
      if (num == 0) {
            num = 1;
      }
      if (cpssp->ts.bottom < (cpssp->ts.row + num)) {
            num = cpssp->ts.bottom - cpssp->ts.row;
            TRACE("IL: inserting only %d line(s).\n", num);
      }
      if (0 < num) {
            i = cpssp->ts.bottom - cpssp->ts.row - num; /* lines to move down */
            if (0 < i) {
                  /* move i lines down, start from cpssp->ts.row+1 */
                  memmove(&cpssp->videobuffer[cpssp->ts.row + 1 + num][0],
                              &cpssp->videobuffer[cpssp->ts.row + 1][0],
                              i * MAX_COLS * sizeof(struct vb_char));
            }
            /* empty the rest */
            vt_erase_region(cpssp, cpssp->ts.row + 1, 0, cpssp->ts.row + num, MAX_COLS);
      }
}

static void
vt_ctrl_DL(struct cpssp *cpssp, int num)
{
      int i;

      /* only works when cursor is inside scrolling region: */
      if ((cpssp->ts.row < cpssp->ts.top) || (cpssp->ts.bottom < cpssp->ts.row)) {
            TRACE("DL: curser outside scrolling region!\n");
            return;
      }
      if (cpssp->ts.bottom < (cpssp->ts.row + num - 1)) {
            num = cpssp->ts.bottom - cpssp->ts.row + 1;
            TRACE("DL: deleting only %d lines.\n", num);
      }
      if (0 < num) {
            i = cpssp->ts.bottom + 1 - cpssp->ts.row - num; /* lines to move up */
            if (0 < i) {
                  /* move i lines up to current line */
                  memmove(&cpssp->videobuffer[cpssp->ts.row][0],
                              &cpssp->videobuffer[cpssp->ts.row + num][0],
                              i * MAX_COLS * sizeof(struct vb_char));
            }
            /* empty the rest */
            /* FIXME: Lines added to bottom of screen should have
             * spaces with same character attributes as last line
             * moved up. */
            vt_erase_region(cpssp, cpssp->ts.row + i, 0, cpssp->ts.bottom+1, MAX_COLS);
      }
}

static void
vt_ctrl_DSR(struct cpssp *cpssp, unsigned char priv, unsigned int mode)
{
      char buf[20]; /* CPR response buffer */
      if (priv == 0) {
            switch (mode) {
            case 5: /* send terminal status report */
                  vt102_core_serial_sendbuf(cpssp,
                              TERMINAL_OK, strlen(TERMINAL_OK));
                  break;
            case 6: /* send cursor position report (CPR) */
                  if (cpssp->ts.DECOM) {
                        /* use relative line number */
                        snprintf(buf, sizeof(buf), "\033[%u;%uR",
                                    (cpssp->ts.row + 1 - cpssp->ts.top),
                                    cpssp->ts.col + 1);
                        TRACE("DSR/CPR: position is %u %u (1)\n",
                                    (cpssp->ts.row+1-cpssp->ts.top), cpssp->ts.col+1);
                  } else {
                        /* use absolute number */
                        snprintf(buf, sizeof(buf), "\033[%u;%uR",
                                    cpssp->ts.row + 1, cpssp->ts.col + 1);
                        TRACE("DSR/CPR: position is %u %u (0)\n",
                                    cpssp->ts.row + 1, cpssp->ts.col + 1);
                  }
                  vt102_core_serial_sendbuf(cpssp,
                              buf, strlen(buf));
                  break;
            default:
                  TRACE("unknown ANSI DSR: %u\n", mode);
                  break;
            }
      } else if (priv == '?') {
            if (mode == 15) {
                  /* send printer status report */
                  vt102_core_serial_sendbuf(cpssp,
                              PRINTER_OK, strlen(PRINTER_OK));
            } else {
                  TRACE("unknown DEC DSR: %u\n", mode);
            }
      } else {
            TRACE("unknown private DSR: %hhu %u\n", priv, mode);
      }
}

static void
vt_ctrl_DECTST(struct cpssp *cpssp, unsigned int p1, unsigned int p2)
{
      if (p1 == 2) {
            switch(p2) {
            case 1: /* Power-up test */
                  vt_init(cpssp);
                  break;
            case 2: /* Data loopback test */
            case 4: /* EIA loopback test */
            case 16: /* Printer loopback test */
            case 9: /* Repeat power-up test continuously */
            case 10: /* Repeat data loopback test continuously */
            case 12: /* Repeat EIA loopback test continuously */
            case 24: /* Repeat printer loopback test continuously */
                  TRACE("unsupported confidence test: %u\n", p2);
                  break;
            default:
                  TRACE("unknown confidence test: %u\n", p2);
                  break;
            }
      } else {
            TRACE("unknown DECTST parameters: %u %u\n", p1, p2);
      }
}

static void
vt_ctrl_DECLL(void)
{
      /* TODO: maybe we can use the IDE LED as a keyboard indicator? */
}

#ifdef VT_USE_EXTENSIONS /* sequences not understood by real vt102s */
static void
vt_ctrl_ICH(struct cpssp *cpssp, unsigned int num)
{
      int i;

      if (num == 0) {
            num = 1;
      }
      if (MAX_COLS < (cpssp->ts.col + num)) {
            num = MAX_COLS - cpssp->ts.col;
            TRACE("ICH: inserting only %d chars.\n", num);
      }
      i = MAX_COLS - cpssp->ts.col - num; /* no. of remaining chars */
      if (0 < i) {
            /* move remaining chars right */
            memmove(&cpssp->videobuffer[cpssp->ts.row][cpssp->ts.col + num],
                        &cpssp->videobuffer[cpssp->ts.row][cpssp->ts.col],
                        i * sizeof(struct vb_char));
      }
      /* empty the rest */
      vt_erase_in_line(cpssp, cpssp->ts.row, cpssp->ts.col, cpssp->ts.col + num);
}

static void
vt_ctrl_CNL(struct cpssp *cpssp, unsigned int num)
{
      int i;

      if (num == 0) {
            num = 1;
      }
      i = cpssp->ts.row + num;
      TRACE("CNL: relative %u absolute %d\n", num, i);
      if ((int) cpssp->ts.bottom < i) {
            i = cpssp->ts.bottom;
            TRACE("CNL: using bottom margin %d\n", i);
      }
      cpssp->ts.row = i;
      cpssp->ts.col = 0;
}

static void
vt_ctrl_CPL(struct cpssp *cpssp, unsigned int num)
{
      int i;

      if (num == 0) {
            num = 1;
      }
      i = cpssp->ts.row - num;
      TRACE("CPL: relative %u absolute %d\n", num, i);
      if (i < (int) cpssp->ts.top) {
            i = cpssp->ts.top;
            TRACE("CPL: using top margin %d\n", i);
      }
      cpssp->ts.row = i;
      cpssp->ts.col = 0;
}

static void
vt_ctrl_CHA(struct cpssp *cpssp, int col)
{
      col--;
      if (col < 0) {
            col = 0;
      }
      TRACE("HPA/CHA: absolute %d\n", col);
      if (MAX_COLS <= col) {
            col = MAX_COLS - 1;
            TRACE("HPA/CHA: using %d\n", col);
      }
      cpssp->ts.col = col;

}

static void
vt_ctrl_ECH(struct cpssp *cpssp, unsigned int num)
{
      if (num == 0) {
            num = 1;
      }
      if (MAX_COLS < (cpssp->ts.col + num)) {
            num = MAX_COLS - cpssp->ts.col;
            TRACE("ECH: erasing only %d chars.\n", num);
      }
      vt_erase_in_line(cpssp, cpssp->ts.row, cpssp->ts.col, cpssp->ts.col + num);
}

static void
vt_ctrl_VPA(struct cpssp *cpssp, unsigned int row)
{
      int i;

      if (cpssp->ts.DECOM) {
            /* calculate absolute row number */
            i = row - 1 + cpssp->ts.top;
            if (i < (int) cpssp->ts.top) {
                  i = cpssp->ts.top;
            }
            if ((int) cpssp->ts.bottom < i) {
                  i = cpssp->ts.bottom;
            }
      } else { /* no origin mode */
            if (row < MAX_ROWS) {
                  i = row - 1;
            } else {
                  i = MAX_ROWS - 1;
            }
            if (i < 0) {
                  i = 0;
            }
      }
      cpssp->ts.row = i;
      TRACE("VPA: new row %d (param %u)\n", i, row);
}
#endif /* VT_USE_EXTENSIONS */

/*
 * check if ch is a control character and react appropriate, if it is
 * return vaules:
 * 1 ch was a control character an NO further action is needed
 * 0 further processing (as displaying the character) is required
 *
 * (see table 5-2 of the vt102 user guide)
 */
static int
vt_parse_control(struct cpssp *cpssp, unsigned char ch)
{
      switch (ch) {
      case 000: /* NUL */
            /* ignore this character */
            return 1;
      case 003: /* ETX */
      case 004: /* EOT */
            return 1;
      case 005: /* ENQ */
            /* send back ACK */
            vt102_core_serial_send(cpssp, '\006');
            return 1;
      case 007: /* BEL */
            /* TODO: ring bell */
            return 1;
      case 010: /* BS */
            vt_ctrl_BS(cpssp);
            return 1;
      case 011: /* HT */
            vt_ctrl_HT(cpssp);
            return 1;
      case 012: /* LF */
      case 013: /* VT */
      case 014: /* FF */
            /* LF, VT and FF all mean the same */
            vt_ctrl_LF(cpssp);
            return 1;
      case 015: /* CR */
            vt_ctrl_CR(cpssp);
            return 1;
      case 016: /* SO */
            cpssp->ts.active_charset = 1; /* select G1 charset */
            return 1;
      case 017: /* SI */
            cpssp->ts.active_charset = 0; /* select G0 charset */
            return 1;
      case 021: /* DC1 */
            /* process as XON */
      case 023: /* DC3 */
            /* process as XOFF */
            /* TODO: implement flow control (?) */
            return 1;
      case 030: /* CAN */
      case 032: /* SUB */
            /* cancel escape sequence */
            vt_cancel_escape(cpssp);
            return 1;
      case 033: /* ESC */
            vt_cancel_escape(cpssp);
            cpssp->ts.state = ST_ESCAPE;
            return 1;
#ifdef VT_USE_EXTENSIONS
      case 0233: /* CSI (8-bit) */
            vt_ctrl_CSI(cpssp);
            return 1;
#endif
      default:
            return 0;
      }
}

/*
 * parse the characters of an escape sequence
 * (see appendix d of the vt102 user guide, as well as chapter 5)
 * Quote: --
 * The format for an escape sequence is as follows.
 *
 * ESC           I.....I         F
 * 033           040-057         060-176
 *
 * Escape        Intermediate    Final
 * sequence      characters      character
 * introducer    (0 or more      (1 character)
 *               characters)
 *                                      -- VT102 User Guide, Appendix D
 */
static void
vt_parse_esc(struct cpssp *cpssp, unsigned char ch)
{
      if ((040 <= ch) && (ch <= 057)) {
            /* intermediate character */
            vt_add_intermediate(cpssp, ch);
      } else {
            /* final character or control sequence introducer */
            switch (ch) {
            case '[': /* CSI */
                  vt_ctrl_CSI(cpssp);
                  return;
            case 'D': /* Index (IND) */
                  vt_ctrl_IND(cpssp);
                  break;
            case 'M': /* Reverse Index (RI) */
                  vt_ctrl_RI(cpssp);
                  break;
            case 'E': /* Next Line (NEL) */
                  vt_ctrl_NEL(cpssp);
                  break;
            case '7': /* Save Cursor (DECSC) */
                  vt_ctrl_DECSC(cpssp);
                  break;
            case '8': 
                  if (cpssp->escs.inter[0] == '#') {
                        /* ESC # 8: Screen Alignment Display (DECALN) */
                   /* Fills screen with uppercase E's for screen focus
                    * and alignment. This command is used by DIGITAL
                    * Manufacturing and Field Service personnel. 
                    *                  -- VT102 User Guide, Chapter 5 */
                        vt_ctrl_DECALN(cpssp);
                  } else {
                        /* ESC 8: Restore Cursor (DECRC) */
                        vt_ctrl_DECRC(cpssp);
                  }
                  break;
            case '=': /* Application Keypad Mode (DECKPAM) */
                  vt_ctrl_DECKPAM(cpssp);
                  break;
            case '>': /* Numeric Keypad Mode (DECKPNM) */
                  vt_ctrl_DECKPNM(cpssp);
                  break;
            /* --- Select Character Set (SCS) */
            case 'A': /* UK character set */
                  vt_ctrl_SCS(cpssp, cpssp->escs.inter[0], VT_FONT_OFF_UK);
                  break;
            case 'B': /* US character set */
                  vt_ctrl_SCS(cpssp, cpssp->escs.inter[0], VT_FONT_OFF_US);
                  break;
            case '0': /* line drawing character set */
                  vt_ctrl_SCS(cpssp, cpssp->escs.inter[0], VT_FONT_OFF_DRAW);
                  break;
            case '1': /* alternate ROM character set */
                  /* fall-through */
            case '2': /* alternate ROM special characters character set */
                  /* no alternate sets atm., so we use the UK set */
                  vt_ctrl_SCS(cpssp, cpssp->escs.inter[0], VT_FONT_OFF_UK);
                  break;
            /* --- */
            case 'N': /* Single Shift 2 (SS2) */
            case 'O': /* Single Shift 3 (SS3) */
                  /* SS2 and SS3 will probably never be implemented,
                     because there's no way of setting the G2 and G3
                     charsets at this point */
                  break;
            case 'H': /* Horizontal Tabulation Set (HTS) */
                  vt_set_tabstop(cpssp->ts.col);
                  break;
            case '3': /* Double-Height Line (DECDHL) [top] */
            case '4': /* Double-Height Line (DECDHL) [bottom] */
            case '5': /* Single-Width Line (DECSWL) */
            case '6': /* Double-Width Line (DECDWL) */
                  break;
            case 'Z': /* Identify Terminal (DECID) */
                  vt_ctrl_DECID(cpssp);
                  break;
            case 'c': /* Reset to Initial State (RIS) */
                  vt_init_status(cpssp);
                  /* unfortunately, the user guide doesn't tell if the
                     RIS sequence will also blank the screen or do other
                     things */
                  break;
#ifdef VT_USE_EXTENSIONS /* ESC sequences not understood by real vt102s */
            case ']': /* Operating System Command (OSC) */
                  /*
                   * the "linux" virtual terminal uses OSC to change
                   * the color palette. TODO.
                   * 
                   * from "man console_codes":
                   * ESC ] P nrrggbb: set palette, with parameter
                   * given in 7 hexadecimal digits after the final P :-(.
                   * Here n is the color (0-15), and rrggbb indicates
                   * the red/green/blue values (0-255).
                   * ESC ] R: reset palette
                   *
                   * [this will be not very nice to implement]
                   * 
                   * additionally:
                   * ESC [ 1 ; n ]   Set color n as the underline color
                   * ESC [ 2 ; n ]   Set color n as the dim color
                   * ESC [ 8 ]       Make the current color pair the
                   *                 default attributes.
                   * ESC [ 9 ; n ]   Set screen blank timeout to n
                   *                 minutes.
                   * ESC [ 10 ; n ]  Set bell frequency in Hz.
                   * ESC [ 11 ; n ]  Set bell duration in msec.
                   * ESC [ 12 ; n ]  Bring specified console to the
                   *                 front.
                   * ESC [ 13 ]      Unblank the screen.
                   * ESC [ 14 ; n ]  Set the VESA powerdown interval in
                   *                 minutes.
                   * 
                   * most of these don't make sense for us.
                   */
                  break;
#endif /* VT_USE_EXTENSIONS */
            default:
                  /* ignore character */
                  return;
            } /*switch*/
            /* reset state to normal */
            vt_cancel_escape(cpssp);
      } /*else*/
}

/*
 * changes the terminal mode (implements SM/RM control sequence)
 * mode: 1 = set, 0 = reset
 * (see table 5-5 and table 5-6 of the vt102 user guide)
 */
static void
vt_change_mode(struct cpssp *cpssp, int mode)
{
      int i;

      if (cpssp->escs.private == '?') { /* ANSI-Compatible Private Modes */
            for (i=0; i <= cpssp->escs.max_param; i++) {
                  switch (cpssp->escs.param[i]) {
                  case 1: /* Cursor Key Mode (DECCKM) */
                        if (cpssp->ts.kpmode == DECKPNM) {
                              TRACE("%s DECCKM useless (DECKPNM)!\n",
                                    mode ? "setting" : "resetting");
                              mode = 0;
                        }
                        cpssp->ts.DECCKM = mode;
                        TRACE("DECCKM %s.\n", mode ? "set" : "reset");
                        break;
                  case 2: /* VT52 Mode (DECANM) */
                  case 3: /* Column Mode (DECCOLM) */
                        break;
                  case 4: /* Scroll Mode (DECSCLM) */
                        TRACE("DECSCLM unsupported! (%d)\n", mode);
                        break;
                  case 5: /* Screen Mode (DECSCNM) */
                        break;
                  case 6: /* Origin Mode (DECOM) */
                        cpssp->ts.DECOM = mode;
                        cpssp->ts.col = 0;
                        if (mode) {
                              TRACE("DECOM set.\n");
                              cpssp->ts.row = cpssp->ts.top;
                        } else {
                              TRACE("DECOM reset.\n");
                              cpssp->ts.row = 0;
                        }
                        break;
                  case 7: /* Auto Wrap Mode (DECAWM) */
                        cpssp->ts.DECAWM = mode;
                        TRACE("DECAWM %s.\n", mode ? "set" : "reset");
                        break;
                  case 8: /* Auto Repeat Mode (DECARM) */
                        TRACE("DECARM unsupported! (%d)\n", mode);
                        break;
                  case 18: /* Printer Form Feed Mode (DECPFF) */
                        TRACE("DECPFF unsupported! (%d)\n", mode);
                        break;
                  case 19: /* Printer Extent Mode (DECPEX) */
                        TRACE("DECPEX unsupported! (%d)\n", mode);
                        break;
                  }
            }
            return;
      }
      if (cpssp->escs.private == 0) { /* ANSI-Specified Modes */
            for (i=0; i <= cpssp->escs.max_param; i++) {
                  switch (cpssp->escs.param[i]) {
                  case 2: /* Keyboard Action Mode (KAM) */
                        cpssp->ts.KAM = mode;
                        TRACE("KAM %s.\n", mode ? "set" : "reset");
                        break;
                  case 4: /* Insertion-Replacement Mode (IRM) */
                        cpssp->ts.IRM = mode;
                        TRACE("IRM %s.\n", mode ? "set" : "reset");
                        break;
                  case 12: /* Send-Receive Mode (SRM) */
                        cpssp->ts.SRM = mode;
                        TRACE("SRM %s.\n", mode ? "set" : "reset");
                        break;
                  case 20: /* Linefeed/New Line Mode (LNM) */
                        cpssp->ts.LNM = mode;
                        TRACE("LNM %s.\n", mode ? "set" : "reset");
                        break;
                  }
            }
      }     
}

/*
 * parse the characters of an control sequence
 * (see appendix d of the vt102 user guide, as well as chapter 5)
 * Quote: --
 * The format of a control sequence is as follows.
 *
 * CSI           P.....P        I.....I         F
 * 033 133       060-077        040-057         100-176
 *
 * Control       Parameter      Intermediate    Final character
 * sequence      characters     characters      (1 character)
 * introducer    (0 or more     (0 or more
 *               characters)    characters)
 *                                      -- VT102 User Guide, Appendix D
 */
static void
vt_parse_csi(struct cpssp *cpssp, unsigned char ch)
{
      int i;
      if ((040 <= ch) && (ch <= 057)) {
            /* intermediate character */
            vt_add_intermediate(cpssp, ch);
            return;
      }
      if ((060 <= ch) && (ch <= 077)) {
            /* parameter character */
            switch (ch) {
            case 072: case 074: case 075: case 076:   case 077:
                  /* ':', '>', '=', '<' and '?' are "private" markers */
                  vt_add_private(cpssp, ch);
                  break;
            default:
                  vt_add_param(cpssp, ch);
                  break;
            }
            return;
      }
      /* if this function didn't return until now, we've got a final character
         or something to ignore */
      switch (ch) {
      case 'h': /* Set Mode (SM) */
            vt_change_mode(cpssp, 1);
            break;
      case 'l': /* Reset Mode (RM) */
            vt_change_mode(cpssp, 0);
            break;
      case 'r': /* Set Top and Bottom Margins (DECSTBM) */
            vt_ctrl_DECSTBM(cpssp, cpssp->escs.param[0], cpssp->escs.param[1]);
            break;
      case 'A': /* Cursor Up (CUU) */
            vt_ctrl_CUU(cpssp, cpssp->escs.param[0]);
            break;
#ifdef VT_USE_EXTENSIONS /* VPR isn't understood by real vt102s */
      case 'e': /* Line Position Forward (VPR) */
            TRACE("(VPR)\n");
            /* fall-through - VPR and CUD behave the same */
#endif /* VT_USE_EXTENSIONS */
      case 'B': /* Cursor Down (CUD) */
            vt_ctrl_CUD(cpssp, cpssp->escs.param[0]);
            break;
#ifdef VT_USE_EXTENSIONS /* HPR isn't understood by real vt102s */
      case 'a': /* Character Position Forward (HPR) */
            TRACE("(HPR)\n");
            /* fall-through - HPR and CUF behave the same */
#endif /* VT_USE_EXTENSIONS */
      case 'C': /* Cursor Forward (CUF) */
            vt_ctrl_CUF(cpssp, cpssp->escs.param[0]);
            break;
      case 'D': /* Cursor Backward (CUB) */
            vt_ctrl_CUB(cpssp, cpssp->escs.param[0]);
            break;
      case 'H': /* Cursor Position (CUP) */
            /* fall-through - CUP and HVP behave the same */
      case 'f': /* Horizontal and Vertical Position (HVP) */
            vt_ctrl_HVP(cpssp, cpssp->escs.param[0], cpssp->escs.param[1]);
            break;
      case 'm': /* Select Graphic Rendition (SGR) */
            for (i = 0; i <= cpssp->escs.max_param; i++) {
                  vt_ctrl_SGR(cpssp, cpssp->escs.param[i]);
            }
            break;
      case 'g': /* Tabulation Clear (TBC) */
            vt_ctrl_TBC(cpssp, cpssp->escs.param[0]);
            break;
      case 'K': /* Erase In Line (EL) */
            vt_ctrl_EL(cpssp, cpssp->escs.param[0]);
            break;
      case 'J': /* Erase In Display (ED) */
            vt_ctrl_ED(cpssp, cpssp->escs.param[0]);
            break;
      case 'P': /* Delete Character (DCH) */
            vt_ctrl_DCH(cpssp, cpssp->escs.param[0]);
            break;
      case 'L': /* Insert Line (IL) */
            vt_ctrl_IL(cpssp, cpssp->escs.param[0]);
            break;
      case 'M': /* Delete Line (DL) */
            vt_ctrl_DL(cpssp, cpssp->escs.param[0]);
            break;
      case 'i': /* Media Copy (MC) */
            /* Media Copy (printing) is not supported atm. */
            break;
      case 'n': /* Device Status Report (DSR) */
            vt_ctrl_DSR(cpssp, cpssp->escs.private, cpssp->escs.param[0]);
            break;
      case 'c': /* Device Attributes (DA) */
            /* this means 'identify terminal' if Ps is null and there are
             * no private markers */
            if ((cpssp->escs.param[0] == 0) && (cpssp->escs.private == '\0')) {
                  vt_ctrl_DECID(cpssp);
            }
            break;
      case 'y': /* Invoke Confidence Test (DECTST) */
            vt_ctrl_DECTST(cpssp, cpssp->escs.param[0], cpssp->escs.param[1]);
            break;
      case 'q': /* Load LED (DECLL) */
            vt_ctrl_DECLL();
            break;
#ifdef VT_USE_EXTENSIONS /* CSI sequences not understood by real vt102s */
      case '@': /* Insert Character (ICH) */
            vt_ctrl_ICH(cpssp, cpssp->escs.param[0]);
            break;
      case 'E': /* Cursor Next Line (CNL) */
            vt_ctrl_CNL(cpssp, cpssp->escs.param[0]);
            break;
      case 'F': /* Cursor Preceding Line (CPL) */
            vt_ctrl_CPL(cpssp, cpssp->escs.param[0]);
            break; 
      case '`': /* Character Position Absolute (HPA) */
            /* fall-through - HPA and CHA do the same */
      case 'G': /* Cursor Character Absolute (CHA) */
            vt_ctrl_CHA(cpssp, cpssp->escs.param[0]);
            break;
      case 'X': /* Erase Character (ECH) */
            vt_ctrl_ECH(cpssp, cpssp->escs.param[0]);
            break;
      case 'd': /* Line Position Absolute (VPA) */
            vt_ctrl_VPA(cpssp, cpssp->escs.param[0]);
            break;
      case 's': /* ? (?) *** NOT ECMA-48 *** */
            /* console_codes(4) says "Save cursor location." */
            break;
      case 'u': /* ? (?) *** NOT ECMA-48 *** */
            /* console_codes(4) says "Restore cursor location." */
            break;
#endif /* VT_USE_EXTENSIONS */
      default:
            /*ignore character */
            return;
      } /* switch(ch) */
      /* reset parser state to normal */
      vt_cancel_escape(cpssp);
}

/*
 * receive a character from the server and parse it
 */
void
host_to_vt(struct cpssp *cpssp, unsigned char ch)
{
      unsigned int glyph;
#ifndef VT_USE_EXTENSIONS
      /* real vt102s can only handle 7-bit characters, so throw away the
       * highest bit */
      if (ch & 0x80) {
            TRACE("ignoring high bit of character <%u>\n", ch);
            ch = ch & 0x7f;
      }
#endif /* VT_USE_EXTENSIONS */
#ifdef VT_DEBUG
      if (vt_debug) {
            if ((ch <= ' ') || ('\177' <= ch)) {
                  fprintf(stderr,"<%u> ", ch);
            } else {
                  fprintf(stderr,"%c ", ch);
            }
      }
#endif /* VT_DEBUG */
      /* first, try if its an control char */
      if (! vt_parse_control(cpssp, ch)) {
            switch (cpssp->ts.state) {
            case ST_NORMAL:
                  /* map the character to a glyph in our font */
                  if (ch & 0x80) {
                        /* 8-bit chars with higehst bit set always end
                         * up in our (hopefully ISO latin 1) 8-bit
                         * charset
                         * note: if VT_USE_EXTENSIONS isn't enabled,
                         * this will never happen (see above) */
                        glyph = ch;
                  } else {
                        /* 7-bit chars get mapped to the charset
                         * selected by SCS and SI/SO */
                        if (cpssp->ts.active_charset == 0) {
                              glyph = ch + cpssp->ts.G0_offset;
                        } else {
                              glyph = ch + cpssp->ts.G1_offset;
                        }
                  }
                  /* put the character in the videobuffer */
                  if ((cpssp->ts.IRM) && (cpssp->ts.col < (MAX_COLS - 1))) {
                        /* insert mode: */
                        /* move remaining chars to the right */
                        memmove(&cpssp->videobuffer[cpssp->ts.row][cpssp->ts.col+1],
                              &cpssp->videobuffer[cpssp->ts.row][cpssp->ts.col],
                                (MAX_COLS - 1 - cpssp->ts.col)
                              * sizeof(struct vb_char));
                  }
                  cpssp->videobuffer[cpssp->ts.row][cpssp->ts.col].c = glyph;
                  cpssp->videobuffer[cpssp->ts.row][cpssp->ts.col].flags = cpssp->ts.flags;
                  /* move the cursor */
                  if (cpssp->ts.col < (MAX_COLS - 1)) {
                        cpssp->ts.col++;
                  } else {
                        if (cpssp->ts.DECAWM) {
                              /* we're at the right margin, so just
                               * act like we'd got an CR/LF */
                              vt_ctrl_CR(cpssp);
                              vt_ctrl_LF(cpssp);
                        }
                  }
                  break;
            case ST_ESCAPE:
                  /* parse simple escape sequence */
                  vt_parse_esc(cpssp, ch);
                  break;
            case ST_CSI:
                  /* parse control sequence */
                  vt_parse_csi(cpssp, ch);
                  break;
            } /*switch*/
      }
}

/*
 * receive a character from our gui and send it to the server
 */
void
vt_to_host(struct cpssp *cpssp, unsigned char key)
{
      const char* seq = "";

      if (cpssp->ts.KAM) {
            /* keyboard locked - don't process anything */
            TRACE("keyboard locked - ignoring keypress\n");
            return;
      }
      if (key < 128) {
            /* this looks like a normal key, so we just send it away */
            vt102_core_serial_send(cpssp, key);
            if (! cpssp->ts.SRM) {
                  /* "local echo" (SRM) enabled */
                  /*
                   * NOTA BENE: if SRM is SET, local echo is DISABLED
                   *            if SRM is RESET, local echo is ENABLED
                   */
                  host_to_vt(cpssp, key);
            }
            return;
      }
#ifdef VT_DEBUG
      /* top secret special debugging option */
      if (key == KEY_DEBUG) {
            if (vt_debug) {
                  TRACE("Your movements are now unencumbered."
                        "\n\n");
                  vt_debug = 0;
            } else {
                  TRACE("The voice of Anhur thunders: \"Thou "
                        "art chosen to steal souls for My "
                        "Glory!\"\n\n");
                  vt_debug = 1;
            }
            TRACE("You are granted an insight!\n\n");
            TRACE("\033[7mWeapons\033[m\n");
            TRACE("a - a %s origin mode called DECOM\n",
                  cpssp->ts.DECOM ? "blessed" : "cursed");
            TRACE("b - an uncursed +%d top margin %s\n", cpssp->ts.top,
                  cpssp->ts.top ? "(weapon in hands)" : "");
            TRACE("c - %d uncursed bottom margins\n", cpssp->ts.bottom);
            TRACE("\033[7mArmor\033[m\n");
            TRACE("d - a linefeed/newline mode called LNM %s\n",
                  cpssp->ts.LNM ? "(being worn)" : "");
            TRACE("e - a cursed -1 keyboard lock called KAM %s\n",
                  cpssp->ts.KAM ? "(being worn)" : "");
            TRACE("\033[7mComestibles\033[m\n");
            TRACE("f - a %s local echo called SRM\n",
                  cpssp->ts.SRM ? "blessed" : "cursed");
            TRACE("g - a %s insert/replacement mode called IRM\n",
                  cpssp->ts.IRM ? "blessed" : "cursed");
            TRACE("\033[7mScrolls\033[m\n");
            TRACE("h - a %s scroll labeled DECCKM\n",
                  cpssp->ts.DECCKM ? "blessed" : "cursed");
            TRACE("i - a %s scroll labeled DECAWM\n",
                  cpssp->ts.DECAWM ? "blessed" : "cursed");
            TRACE("\033[7mWands\033[m\n");
            TRACE("j - a wand of wishing (1:0)\n");
            TRACE("\033[7mTools\033[m\n");
            TRACE("k - a cursed debugger\n\n");
            return;
      }
#endif /* VT_DEBUG */
      /* keypad keys */
      switch (cpssp->ts.kpmode) {
      case DECKPNM: /* Numeric Keypad Mode */
            switch (key) {
            case KEY_ENTER:
                  key = KEY_RETURN;
                  break;
            case KEY_NUMPAD_0:
                  seq = "0";
                  break;
            case KEY_NUMPAD_1:
                  seq = "1";
                  break;
            case KEY_NUMPAD_2:
                  seq = "2";
                  break;
            case KEY_NUMPAD_3:
                  seq = "3";
                  break;
            case KEY_NUMPAD_4:
                  seq = "4";
                  break;
            case KEY_NUMPAD_5:
                  seq = "5";
                  break;
            case KEY_NUMPAD_6:
                  seq = "6";
                  break;
            case KEY_NUMPAD_7:
                  seq = "7";
                  break;
            case KEY_NUMPAD_8:
                  seq = "8";
                  break;
            case KEY_NUMPAD_9:
                  seq = "9";
                  break;
            case KEY_NUMPAD_MINUS:
                  seq = "-";
                  break;
            case KEY_NUMPAD_COMMA:
                  seq = ",";
                  break;
            case KEY_NUMPAD_PERIOD:
                  seq = ".";
                  break;
            } /* switch (key) */
            break;
      case DECKPAM: /* Application Keypad Mode */
            switch (key) {
            case KEY_ENTER:
                  seq = "\033OM";
                  break;
            case KEY_NUMPAD_0:
                  seq = "\033Op";
                  break;
            case KEY_NUMPAD_1:
                  seq = "\033Oq";
                  break;
            case KEY_NUMPAD_2:
                  seq = "\033Or";
                  break;
            case KEY_NUMPAD_3:
                  seq = "\033Os";
                  break;
            case KEY_NUMPAD_4:
                  seq = "\033Ot";
                  break;
            case KEY_NUMPAD_5:
                  seq = "\033Ou";
                  break;
            case KEY_NUMPAD_6:
                  seq = "\033Ov";
                  break;
            case KEY_NUMPAD_7:
                  seq = "\033Ow";
                  break;
            case KEY_NUMPAD_8:
                  seq = "\033Ox";
                  break;
            case KEY_NUMPAD_9:
                  seq = "\033Oy";
                  break;
            case KEY_NUMPAD_MINUS:
                  seq = "\033Om";
                  break;
            case KEY_NUMPAD_COMMA:
                  seq = "\033Ol";
                  break;
            case KEY_NUMPAD_PERIOD:
                  seq = "\033On";
                  break;
            } /* switch (key) */
            break;
      } /* switch (cpssp->ts.kpmode) */
      /* cursor keys */
      if (cpssp->ts.DECCKM) {
            /* Cursor Key Mode Set */
            switch (key) {
            case KEY_CURSOR_UP:
                  seq = "\033OA";
                  break;
            case KEY_CURSOR_DOWN:
                  seq = "\033OB";
                  break;
            case KEY_CURSOR_LEFT:
                  seq = "\033OD";
                  break;
            case KEY_CURSOR_RIGHT:
                  seq = "\033OC";
                  break;
            }
      } else {
            /* Cursor Key Mode Reset */
            switch (key) {
            case KEY_CURSOR_UP:
                  seq = "\033[A";
                  break;
            case KEY_CURSOR_DOWN:
                  seq = "\033[B";
                  break;
            case KEY_CURSOR_LEFT:
                  seq = "\033[D";
                  break;
            case KEY_CURSOR_RIGHT:
                  seq = "\033[C";
                  break;
            }
      } /* if (cpssp->ts.DECCKM) */

      /* other "special" keys which aren't subject to DECCKM or keypad mode */
      switch(key) {
      /* "function keys" */
      case KEY_PF1:
            seq = "\033OP";
            break;
      case KEY_PF2:
            seq = "\033OQ";
            break;
      case KEY_PF3:
            seq = "\033OR";
            break;
      case KEY_PF4:
            seq = "\033OS";
            break;
      case KEY_RETURN: /* "return"/"enter" key */
            if (cpssp->ts.LNM) {
                  /* New Line Mode set: send CR,LF */
                  seq = "\015\012";
            } else {
                  /* New Line Mode not set: send CR */
                  seq = "\015";
            }
            break;
      case KEY_DELETE: /* "Delete" key */
            /* as backspace sends delete, delete will send backspace... */
            seq = "\010";
            break;
      }
      if (seq[0]) {
            /* there is something to send away, so send it */
            vt102_core_serial_sendbuf(cpssp, seq, strlen(seq));
            /* if local echo is on and its not an escape sequence, send it
             * also to the terminal, see "NOTE" in ECMA-48 7.2.15 */
            if ((! cpssp->ts.SRM) && (seq[0] != 033)) {
                  do {
                        host_to_vt(cpssp, *seq);
                  } while (*seq++);
            }
      }
}

/*
 * reset terminal state, redisplay video buffer
 */
void
vt_reset(struct cpssp *cpssp)
{
      /* TODO: some status bits should NOT be re-initialized */
      vt_init_status(cpssp);
}

/*
 * initialize terminal emulation
 */
void
vt_init(struct cpssp *cpssp)
{
      /* greeting for testing purposes */
      const char* greeting =
#ifdef TEST_GREETING
            "\012\012\012\012\012"
            "\012"
            "                    [this space intentionally left blank]\015\012"
            "\012\012\012\012\012"
            "Magicians, and progeny the scientists, have always taken themselves and their\015\012"
            "subject in an orderly and sober manner, thereby disregarding an essential\015\012"
            "metaphysical balance. when magicians learn to approach philosophy as a\015\012"
            "malleable art instead of an immutable Truth, and learn to appreciate the\015\012"
            "absurdity of man's endeavours, then they will be able to pursue their art with\015\012"
            "a lighter heart, and perhaps gain a clearer understanding of it, and therefore\015\012"
            "gain more effective magic. CHAOS IS ENERGY.\015\012"
#endif /* TEST_GREETING */
            "\012"
            "  Welcome to the FAUmachine VT102 emulation.\015\012\012";
      TRACE("vt_init...\n");
      vt_init_status(cpssp);
      TRACE("  clear_screen\n");
      vt_clear_screen(cpssp);
      TRACE("  greeting\n");
      do {
            host_to_vt(cpssp, *greeting);
      } while (*(++greeting));
      TRACE("...vt_init\n");
}

/*
 * "power off" the emulation
 */
void
vt_poweroff(struct cpssp *cpssp)
{
      /* just clear the screen and hide the cursor */
      vt_clear_screen(cpssp);
      TRACE("vt102 emulator powered down.\n");
}

static void
vt102_core_switch_set(void *_cpssp, unsigned int val)
{
      struct cpssp *cpssp = (struct cpssp *) _cpssp;

      cpssp->power = val;

      if (cpssp->power) {
            vt_init(cpssp);
      } else {
            vt_poweroff(cpssp);
      }
}

static void
vt102_core_serial_recv(void *_cpssp, uint8_t c)
{
      struct cpssp *cpssp = (struct cpssp *) _cpssp;

      if (cpssp->power) {
            host_to_vt(cpssp, c);
      }
}

static void
vt102_core_keyboard_set(void *_cpssp, int key)
{
      struct cpssp *cpssp = (struct cpssp *) _cpssp;

      vt_to_host(cpssp, key);
}

static void
vt102_core_gen_line(
      struct cpssp *cpssp,
      struct sig_video *video,
      unsigned int j,
      unsigned int ch,
      struct vt_char_flags attr,
      int cursor
)
{
#ifdef VT_USE_EXTENSIONS
      static const unsigned long colormap[16] = {
            0x00000000L, /* black */
            0x00aa0000L, /* red */
            0x0000aa00L, /* green */
            0x00aa5500L, /* brown */
            0x000000aaL, /* blue */
            0x00aa00aaL, /* magenta */
            0x0000aaaaL, /* cyan */
            0x00aaaaaaL, /* light gray */
            0x00555555L, /* dark gray */
            0x00ff5555L, /* light red */
            0x0055ff55L, /* light green */
            0x00ffff55L, /* yellow */
            0x005555ffL, /* light blue */
            0x00ff55ffL, /* light magenta */
            0x0055ffffL, /* light cyan */
            0x00ffffffL  /* white */
      };
#endif
      unsigned long backcolor;
      unsigned long frontcolor;
      unsigned int bits;
      int x;

      if (cursor) {
            /* Drawing at cursor position. */
            attr.reverse = ! attr.reverse;
#ifdef VT_USE_EXTENSIONS
            /*
             * Make sure we can see a cursor even if
             * fgcolor and bgcolor of the underlying
             * character are the same.
             */
            if (attr.bgcolor == attr.fgcolor) {
                  attr.fgcolor = 7 - attr.bgcolor;
            }
#endif /* VT_USE_EXTENSIONS */
      }
#ifdef VT_USE_EXTENSIONS
      assert(attr.fgcolor < 8);
      assert(attr.bgcolor < 8);
      /* use colors according to our colormap */
      if (attr.bold) {
            frontcolor = colormap[attr.fgcolor + 8];
      } else {
            frontcolor = colormap[attr.fgcolor];
      }
      backcolor = colormap[attr.bgcolor];
#else
      /* true vt102 can only handle "black" & "white" */
      if (attr.bold) {
            frontcolor = 0xffffffffL;
      } else {
            frontcolor = 0x00aaaaaaL;
      }
      backcolor = 0L;
#endif /* VT_USE_EXTENSIONS */
      if (attr.reverse) {
            /* exchange back- and foreground */
            unsigned long tmp;

            tmp = backcolor;
            backcolor = frontcolor;
            frontcolor = tmp;
      }

      if (attr.underline && j == 14) {
            bits = -1; /* All Bits Set */
      } else {
            bits = fontdata_8x16[ch * FONT_HEIGHT + j];
      }

      for (x = FONT_WIDTH - 1; 0 <= x; x--) {
            uint8_t r, g, b;

            if (bits & (1 << x)) {
                  r = (frontcolor >> 16) & 0xff;
                  g = (frontcolor >>  8) & 0xff;
                  b = (frontcolor >>  0) & 0xff;
            } else {
                  r = (backcolor >> 16) & 0xff;
                  g = (backcolor >>  8) & 0xff;
                  b = (backcolor >>  0) & 0xff;
            }
            sig_video_out(video, cpssp, r, g, b);
      }
}

static void
vt102_core_event(void *_cpssp)
{
      struct cpssp *cpssp = (struct cpssp *) _cpssp;
      unsigned int y;
      unsigned int x;

      if (! cpssp->power) {
            sig_video_no_sync(cpssp->port_video, cpssp);

      } else {
            for (y = 0; y < MAX_ROWS * FONT_HEIGHT; y++) {
                  for (x = 0; x < MAX_COLS * FONT_WIDTH; x += FONT_WIDTH) {
                        unsigned int ch;
                        struct vt_char_flags attr;
                        int cursor;

                        ch = cpssp->videobuffer[y / FONT_HEIGHT][x / FONT_WIDTH].c;
                        attr = cpssp->videobuffer[y / FONT_HEIGHT][x / FONT_WIDTH].flags;
                        if (y / FONT_HEIGHT == cpssp->ts.row
                         && x / FONT_WIDTH == cpssp->ts.col) {
                              /* Drawing at cursor position. */
                              cursor = 1;
                        } else {
                              cursor = 0;
                        }
                        vt102_core_gen_line(cpssp, cpssp->port_video,
                                    y % FONT_HEIGHT, ch, attr, cursor);
                  }

                  sig_video_hor_retrace(cpssp->port_video, cpssp);
            }

            sig_video_vert_retrace(cpssp->port_video, cpssp);
      }

      time_call_after(TIME_HZ / 5, vt102_core_event, cpssp);
}

void
vt102_core_init(
      unsigned int nr,
      struct sig_boolean *port_mech_power_switch,
      struct sig_serial *port_serial,
      struct sig_integer *port_keyboard,
      struct sig_video *port_video
)
{
      static const struct sig_boolean_funcs switch_funcs = {
            .set = vt102_core_switch_set,
      };
      static const struct sig_serial_funcs serial_funcs = {
            .recv = vt102_core_serial_recv,
      };
      static const struct sig_integer_funcs keyboard_funcs = {
            .set = vt102_core_keyboard_set,
      };
      struct cpssp *cpssp;

      cpssp = shm_map(COMP, nr, sizeof(*cpssp), 0);

      cpssp->port_mech_power_switch = port_mech_power_switch;
      cpssp->port_serial = port_serial;
      cpssp->port_keyboard = port_keyboard;
      cpssp->port_video = port_video;

      /* Call */
      sig_serial_connect(port_serial, cpssp, &serial_funcs);

      /* Out */

      /* In */
      sig_boolean_connect_in(port_mech_power_switch, cpssp, &switch_funcs);
      sig_integer_connect_in(port_keyboard, cpssp, &keyboard_funcs);

      time_call_after(TIME_HZ / 5, vt102_core_event, cpssp);
}

void
vt102_core_create(unsigned int nr, const char *name)
{
      struct cpssp *cpssp;

      shm_create(COMP, nr, sizeof(struct cpssp));
      cpssp = shm_map(COMP, nr, sizeof(*cpssp), 0);

      shm_unmap(cpssp, sizeof(*cpssp));
}

void
vt102_core_destroy(unsigned int nr)
{
      struct cpssp *cpssp;

      cpssp = shm_map(COMP, nr, sizeof(*cpssp), 0);

      shm_unmap(cpssp, sizeof(*cpssp));
      shm_destroy(COMP, nr);
}

Generated by  Doxygen 1.6.0   Back to index