mirror of
https://github.com/reactos/reactos.git
synced 2024-12-29 10:35:28 +00:00
1125 lines
31 KiB
C
1125 lines
31 KiB
C
|
/* vt100.c
|
||
|
*
|
||
|
* AUTHOR: John L. Miller, johnmil@cs.cmu.edu / johnmil@jprc.com
|
||
|
* DATE: 8/4/96
|
||
|
*
|
||
|
* Copyright (c) 1996 John L. Miller
|
||
|
*
|
||
|
* Full permission is granted to use, modify and distribute
|
||
|
* this code, provided:
|
||
|
* 1) This comment field is included in its entirity
|
||
|
* 2) No money is charged for any work including or based on
|
||
|
* portions of this code.
|
||
|
*
|
||
|
* If you're a nice person and find this useful, I'd appreciate a
|
||
|
* note letting me know about it. e-mail is usually what spurs me
|
||
|
* on to improve and support software I've written.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
/* This is the main code body for my generic vt-100 emulator. This code
|
||
|
* body provides parsing for most of the vt-100 escape sequences, but it
|
||
|
* doesn't actually do anything with some of them. The idea behind this
|
||
|
* parser is to provide a generic front-end that you can initialize, then
|
||
|
* send all of your output to. The output is parsed by the routines in this
|
||
|
* program, then spit out to a back-end.
|
||
|
*
|
||
|
* What back-end you say? Well, the one you have to supply yourself. There's a
|
||
|
* dozen or so routines you have to provide for character-based I/O, cursor
|
||
|
* movement, erasing and deleting text, and setting text and terminal attributes.
|
||
|
*
|
||
|
* For a list of the routines your back end must supply, read vt100.h closely.
|
||
|
*
|
||
|
* In case it's not obvious, these routines were written for a system running win32.
|
||
|
* for vt100.c and vt100.h, most of the code should be easily portable to other
|
||
|
* operating systems. Yeah, like they NEED a vt-100 emulator :p
|
||
|
*/
|
||
|
|
||
|
#include <windows.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdarg.h>
|
||
|
#include "vt100.h"
|
||
|
|
||
|
|
||
|
/* NOTE - many of the functions look like they should
|
||
|
* take X-Y pairs. Bear in mind this is a text display,
|
||
|
* so for this we're talking ROWS and COLUMNS. So parm
|
||
|
* lists go (row, column), which is the opposite of (x, y).
|
||
|
*/
|
||
|
|
||
|
char cBuffer[MAXVTBUFLEN+1]; /* terminal output buffer for unprocessed characters */
|
||
|
int BufLen; /* Number of characters in cBuffer waiting for output */
|
||
|
|
||
|
/* List of all device-independant colors. These colors will be transmitted to a
|
||
|
* back-end routine responsible for setting the foreground and background text
|
||
|
* colors appropriately. Note that the color combinations are ordered to correspond
|
||
|
* with combinations of red (0x1), green (0x2) and blue (0x4) bit flags.
|
||
|
*/
|
||
|
|
||
|
int ScColorTrans[8]= { 0,
|
||
|
SC_RED,
|
||
|
SC_GREEN,
|
||
|
SC_RED|SC_GREEN,
|
||
|
SC_BLUE,
|
||
|
SC_RED|SC_BLUE,
|
||
|
SC_RED|SC_GREEN,
|
||
|
SC_RED|SC_GREEN|SC_BLUE
|
||
|
};
|
||
|
|
||
|
|
||
|
/* List of terminal attributes which we track (used by <esc>[?#h and <esc>[?#l) */
|
||
|
|
||
|
int termAttrMode[NUM_TERM_ATTR_MODES] = { 0,
|
||
|
CURSORAPPL_MODE,
|
||
|
ANSI_MODE,
|
||
|
COL132_MODE,
|
||
|
SMOOTHSCROLL_MODE,
|
||
|
REVSCREEN_MODE,
|
||
|
ORIGINREL_MODE,
|
||
|
WRAP_MODE,
|
||
|
REPEAT_MODE,
|
||
|
};
|
||
|
|
||
|
/* FORWARD FUNCTION DECLARATIONS -
|
||
|
* these functions are intended for use only in this module */
|
||
|
|
||
|
static int ProcessBracket(int Start);
|
||
|
static int ProcessEscape(int Start);
|
||
|
static int ProcessControl(int Start);
|
||
|
static int ProcessBuffer(void);
|
||
|
|
||
|
/* END FORWARD FUNCTION DECLARATIONS */
|
||
|
|
||
|
|
||
|
|
||
|
/* vtputs() -
|
||
|
*
|
||
|
* front-end 'puts()' substitute. Call this routine instead
|
||
|
* of 'puts()', and it'll pass the output through the vt100 emulator.
|
||
|
*/
|
||
|
|
||
|
vtputs(char *f)
|
||
|
{
|
||
|
char cbuf[1024];
|
||
|
|
||
|
strcpy(cbuf,f);
|
||
|
strcat(cbuf,"\n");
|
||
|
vtProcessedTextOut(cbuf, strlen(cbuf));
|
||
|
|
||
|
return(0);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* vtprintf -
|
||
|
*
|
||
|
* This routine is a substitute for printf(). Call this routine with the
|
||
|
* same parameters you would use for printf, and output will be channelled
|
||
|
* through the vt-100 emulator.
|
||
|
*/
|
||
|
|
||
|
vtprintf(char *format, ...)
|
||
|
{
|
||
|
char cbuf[1024];
|
||
|
va_list va;
|
||
|
|
||
|
va_start(va, format);
|
||
|
vsprintf(cbuf,format, va);
|
||
|
va_end(va);
|
||
|
|
||
|
vtProcessedTextOut(cbuf, strlen(cbuf));
|
||
|
|
||
|
return(0);
|
||
|
}
|
||
|
|
||
|
/* vtInitVT100 -
|
||
|
*
|
||
|
* Set the initial state of the VT-100 emulator, and call the initialization
|
||
|
* routine for the back end. This routine MUST be invoked before any other, or
|
||
|
* the VT-100 emulator will most likely roll over and die.
|
||
|
*/
|
||
|
|
||
|
vtInitVT100(void)
|
||
|
{
|
||
|
int i=0;
|
||
|
|
||
|
cBuffer[0]='\0';
|
||
|
BufLen=0;
|
||
|
|
||
|
beInitVT100Terminal(); /* call the back-end initialization. */
|
||
|
|
||
|
return(0);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* ProcessBracket -
|
||
|
*
|
||
|
* Helper routine for processing escape sequences. By the time this
|
||
|
* routine is invoked, '<esc>[' has already been read in the input
|
||
|
* stream. 'Start' is an index in cBuffer to the '<esc>'.
|
||
|
*
|
||
|
* If an escape sequence is successfully parsed, return the index of the
|
||
|
* first character AFTER the escape sequence. Otherwise, return 'Start'.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
static int ProcessBracket(int Start)
|
||
|
{
|
||
|
int End; /* Current character being examined in cBuffer */
|
||
|
int args[10], numargs=0; /* numerical args after <esc>[ */
|
||
|
int iMode;
|
||
|
int i;
|
||
|
int itmp=0;
|
||
|
int left;
|
||
|
int iForeground, iBackground;
|
||
|
|
||
|
/* If there's no valid escape sequence, return as we were called. */
|
||
|
|
||
|
if ((cBuffer[Start+1] != '[')||(Start+2 >= BufLen))
|
||
|
return(Start);
|
||
|
|
||
|
End = Start + 2;
|
||
|
|
||
|
/* Loop through the buffer, hacking out all numeric
|
||
|
* arguments (consecutive string of digits and
|
||
|
* semicolons
|
||
|
*/
|
||
|
|
||
|
do {
|
||
|
itmp = 0; /* itmp will hold the current arguments integer value */
|
||
|
|
||
|
/* the semicolon is a delimiter */
|
||
|
if (cBuffer[End] == ';')
|
||
|
{
|
||
|
End++;
|
||
|
|
||
|
if (End >= BufLen)
|
||
|
return(Start);
|
||
|
}
|
||
|
|
||
|
/* Parse this argument into a number. */
|
||
|
|
||
|
while (isdigit(cBuffer[End]))
|
||
|
{
|
||
|
itmp = itmp*10 + (cBuffer[End]-'0');
|
||
|
End++;
|
||
|
if (End >= BufLen)
|
||
|
return(Start);
|
||
|
}
|
||
|
|
||
|
/* Save the numeric argument if we actually
|
||
|
* parsed a number.
|
||
|
*/
|
||
|
|
||
|
if (End != Start + 2)
|
||
|
args[numargs++] = itmp;
|
||
|
|
||
|
} while (cBuffer[End] == ';');
|
||
|
|
||
|
/* At this point, we've come across a character that isn't a number
|
||
|
* and isn't a semicolon. This means it is the command specifier.
|
||
|
*/
|
||
|
|
||
|
/* Number of characters left in the input stream. I don't use
|
||
|
* this as rigorously as I should here.
|
||
|
*/
|
||
|
|
||
|
left = BufLen - End;
|
||
|
|
||
|
/* Return if there's definitely not enough characters for a
|
||
|
* full escape sequence.
|
||
|
*/
|
||
|
|
||
|
if (left <= 0)
|
||
|
return(Start);
|
||
|
|
||
|
/* Giant switch statement, parsing the command specifier that followed
|
||
|
* up <esc>[arg;arg;...arg
|
||
|
*/
|
||
|
|
||
|
switch (cBuffer[End])
|
||
|
{
|
||
|
/* Cursor Up: Esc [ Pn A */
|
||
|
case 'A':
|
||
|
beOffsetCursor(numargs ? -args[0] : -1, 0);
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Cursor Down: Esc [ Pn B */
|
||
|
case 'B':
|
||
|
beOffsetCursor(numargs ? args[0] : 1, 0);
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Cursor Right: Esc [ Pn C */
|
||
|
case 'C':
|
||
|
beOffsetCursor(0, numargs ? args[0] : 1);
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Cursor Left: Esc [ Pn D */
|
||
|
case 'D':
|
||
|
beOffsetCursor(0, numargs ? -args[0] : -1);
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Direct Addressing : Esc [ Pn(row);Pn(col)H or
|
||
|
* Esc [ Pn(row);Pn(col)f
|
||
|
*/
|
||
|
case 'H':
|
||
|
case 'f':
|
||
|
if (numargs == 0)
|
||
|
beAbsoluteCursor(1,1);
|
||
|
else if (numargs == 1)
|
||
|
beAbsoluteCursor(args[0] > 0 ? args[0] : 1,1);
|
||
|
else if (numargs == 2)
|
||
|
beAbsoluteCursor(args[0] > 0 ? args[0] : 1, args[1] > 0 ? args[1] : 1);
|
||
|
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Erase from Cursor to end of screen Esc [ J
|
||
|
* Erase from Beginning of screen to cursor Esc [ 1 J
|
||
|
* Erase Entire screen Esc [ 2 J
|
||
|
*/
|
||
|
case 'J':
|
||
|
if (numargs == 0)
|
||
|
beEraseText(CUR_ROW, CUR_COL, BOTTOM_EDGE, RIGHT_EDGE);
|
||
|
else if (args[0] == 1)
|
||
|
beEraseText(TOP_EDGE, LEFT_EDGE, CUR_ROW, CUR_COL);
|
||
|
else
|
||
|
beEraseText(TOP_EDGE, LEFT_EDGE, BOTTOM_EDGE, RIGHT_EDGE);
|
||
|
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Erase from Cursor to end of line Esc [ K
|
||
|
* Erase from Beginning of line to cursor Esc [ 1 K
|
||
|
* Erase Entire line Esc [ 2 K
|
||
|
*/
|
||
|
case 'K':
|
||
|
if (numargs == 0)
|
||
|
beEraseText(CUR_ROW, CUR_COL, CUR_ROW, RIGHT_EDGE);
|
||
|
else if (args[0] == 1)
|
||
|
beEraseText(CUR_ROW, LEFT_EDGE, CUR_ROW, CUR_COL);
|
||
|
else
|
||
|
beEraseText(CUR_ROW, LEFT_EDGE, CUR_ROW, RIGHT_EDGE);
|
||
|
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
|
||
|
/* Set Graphics Rendition:
|
||
|
* ESC[#;#;....;#m
|
||
|
* The graphics rendition is basically foreground and background
|
||
|
* color and intensity.
|
||
|
*/
|
||
|
case 'm':
|
||
|
beGetTextAttributes(&iForeground, &iBackground);
|
||
|
|
||
|
if (numargs < 1)
|
||
|
{
|
||
|
/* If we just get ESC[m, treat it as though
|
||
|
* we should shut off all extra text
|
||
|
* attributes
|
||
|
*/
|
||
|
|
||
|
iForeground &= ~(SC_BOLD|SC_UL|SC_BL|SC_RV|SC_GRAPHICS|SC_G0|SC_G1);
|
||
|
iForeground |= SC_ASCII;
|
||
|
|
||
|
beSetTextAttributes(iForeground, iBackground);
|
||
|
End += 1;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* Loop through all the color specs we got, and combine them
|
||
|
* together. Note that things like 'reverse video', 'bold',
|
||
|
* and 'blink' are only applied to the foreground. The back end
|
||
|
* is responsible for applying these properties to all text.
|
||
|
*/
|
||
|
for (i=0; i < numargs; i++)
|
||
|
{
|
||
|
switch(args[i])
|
||
|
{
|
||
|
/* 0 for normal display */
|
||
|
case 0:
|
||
|
iForeground &= ~SC_BOLD;
|
||
|
break;
|
||
|
|
||
|
/* 1 for bold on */
|
||
|
case 1:
|
||
|
iForeground |= SC_BOLD;
|
||
|
break;
|
||
|
|
||
|
/* 4 underline (mono only) */
|
||
|
case 4:
|
||
|
iForeground |= SC_UL;
|
||
|
break;
|
||
|
|
||
|
/* 5 blink on */
|
||
|
case 5:
|
||
|
iForeground |= SC_BL;
|
||
|
break;
|
||
|
|
||
|
/* 7 reverse video on */
|
||
|
case 7:
|
||
|
iForeground |= SC_RV;
|
||
|
break;
|
||
|
|
||
|
/* 8 nondisplayed (invisible) BUGBUG - not doing this. */
|
||
|
|
||
|
|
||
|
/* 30-37 is bit combination of 30+ red(1) green(2) blue(4)
|
||
|
* 30 black foreground
|
||
|
*/
|
||
|
case 30:
|
||
|
case 31:
|
||
|
case 32:
|
||
|
case 33:
|
||
|
case 34:
|
||
|
case 35:
|
||
|
case 36:
|
||
|
case 37:
|
||
|
iForeground &= ~(SC_RED|SC_GREEN|SC_BLUE);
|
||
|
iForeground |= ScColorTrans[args[i]-30];
|
||
|
break;
|
||
|
|
||
|
/* 40-47 is bit combo similar to 30-37, but for background. */
|
||
|
case 40:
|
||
|
case 41:
|
||
|
case 42:
|
||
|
case 43:
|
||
|
case 44:
|
||
|
case 45:
|
||
|
case 46:
|
||
|
case 47:
|
||
|
iBackground &= ~(SC_RED|SC_GREEN|SC_BLUE);
|
||
|
iBackground |= ScColorTrans[args[i]-30];
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
beSetTextAttributes(iForeground, iBackground);
|
||
|
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Set with Esc [ Ps h
|
||
|
* Reset with Esc [ Ps l
|
||
|
* Mode name Ps Set Reset
|
||
|
* -------------------------------------------------------------------
|
||
|
* Keyboard action 2 Locked Unlocked
|
||
|
* Insertion 4 Insert Overwrite
|
||
|
* Send - Receive 12 Full Echo
|
||
|
* Line feed/New line 20 New line Line feed
|
||
|
*/
|
||
|
case 'h':
|
||
|
case 'l':
|
||
|
/* BUGBUG - many of the terminal modes are set with '?' as the
|
||
|
* first character rather than a number sign. These are dealt with
|
||
|
* later in this switch statement because they must be. Most other
|
||
|
* settings are just ignored, however.
|
||
|
*/
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Insert line Esc [ Pn L */
|
||
|
case 'L':
|
||
|
beInsertRow(CUR_ROW);
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Delete line Esc [ Pn M */
|
||
|
case 'M':
|
||
|
do {
|
||
|
beDeleteText(CUR_ROW,LEFT_EDGE, CUR_ROW, RIGHT_EDGE);
|
||
|
} while (--args[0] > 0);
|
||
|
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Delete character Esc [ Pn P */
|
||
|
case 'P':
|
||
|
do {
|
||
|
beDeleteText(CUR_ROW, CUR_COL, CUR_ROW, CUR_COL);
|
||
|
} while (--args[0] > 0);
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Set the Scrolling region Esc [ Pn(top);Pn(bot) r */
|
||
|
case 'r':
|
||
|
if (numargs == 0)
|
||
|
beSetScrollingRows(TOP_EDGE,BOTTOM_EDGE);
|
||
|
else if (numargs == 2)
|
||
|
beSetScrollingRows(args[0],args[1]);
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Print screen or region Esc [ i
|
||
|
* Print cursor line Esc [ ? 1 i
|
||
|
* Enter auto print Esc [ ? 5 i
|
||
|
* Exit auto print Esc [ ? 4 i
|
||
|
* Enter print controller Esc [ 5 i
|
||
|
* Exit print controller Esc [ 4 i
|
||
|
*/
|
||
|
case 'i':
|
||
|
/* BUGBUG - print commands are not acted upon. */
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
|
||
|
/* Clear tab at current column Esc [ g
|
||
|
* Clear all tabs Esc [ 3 g
|
||
|
*/
|
||
|
case 'g':
|
||
|
if (numargs == 0)
|
||
|
beClearTab(CUR_COL);
|
||
|
else
|
||
|
if ((numargs == 1) && (args[0] == 3))
|
||
|
beClearTab(ALL_TABS);
|
||
|
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* BUGBUG - queries which require responses are ignored. */
|
||
|
|
||
|
/* Esc [ c DA:Device Attributes
|
||
|
* or
|
||
|
*
|
||
|
* Esc [ <sol> x DECREQTPARM: Request Terminal Parameters
|
||
|
* * <sol> values other than 1 are ignored. Upon
|
||
|
* receipt of a <sol> value of 1, the following
|
||
|
* response is sent:
|
||
|
* Esc[3;<par>;<nbits>;<xspeed>;<rspeed>;1;0x
|
||
|
*
|
||
|
* * Where <par>, <nbits>, <xspeed>, and <rspeed>
|
||
|
* are as for VT100s with the following
|
||
|
* exceptions:
|
||
|
* <nbits> Values of 5 and 6 bits per
|
||
|
* character are sent as 7 bits.
|
||
|
* <xspeed>,<rspeed>
|
||
|
* These two numbers will always
|
||
|
* be the same. 9600 baud is
|
||
|
* sent for 7200 baud.
|
||
|
*
|
||
|
* Esc [ Ps n DSR: Device Status Report
|
||
|
* * Parameter values other than 5, 6, are ignored.
|
||
|
* If the parameter value is 5, the sequence
|
||
|
* Esc [ O n is returned. If the parameter value is
|
||
|
* 6, the CPR: Cursor Position Report sequence
|
||
|
* Esc [ Pn ; Pn R is returned with the Pn set to
|
||
|
* cursor row and column numbers.
|
||
|
*
|
||
|
* Cursor Controls:
|
||
|
* ESC[#;#R Reports current cursor line & column
|
||
|
*/
|
||
|
|
||
|
/* spec <esc>[<spec>h <esc>[<spec>l
|
||
|
* Cursor key ?1 Application Cursor
|
||
|
* ANSI/VT52 ?2 ANSI VT52
|
||
|
* Column ?3 132 80
|
||
|
* Scrolling ?4 Smooth Jump
|
||
|
* Screen ?5 Reverse Normal
|
||
|
* Origin ?6 Relative Absolute
|
||
|
* Wraparound ?7 Wrap Truncate
|
||
|
* Auto key repeat ?8 Repeating No repeat
|
||
|
*/
|
||
|
case '?':
|
||
|
/* We didn't catch the numeric argument because the '?' stopped
|
||
|
* it before. Parse it now.
|
||
|
*/
|
||
|
args[0]=0;
|
||
|
while (isdigit(cBuffer[++End]))
|
||
|
{
|
||
|
if (End >= BufLen)
|
||
|
return(Start);
|
||
|
args[0] = 10*args[0] + (cBuffer[End]-'0');
|
||
|
}
|
||
|
|
||
|
/* If we don't handle this particular '?' command (and
|
||
|
* there are plenty we don't) then just ignore it.
|
||
|
*/
|
||
|
if ( (args[0] > 8)
|
||
|
||( (cBuffer[End] != 'l') && (cBuffer[End] != 'h'))
|
||
|
)
|
||
|
{
|
||
|
End++;
|
||
|
if (End >= BufLen)
|
||
|
return(Start);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* This command sets terminal status. Get the current status,
|
||
|
* determine what needs to be changed, and send it back.
|
||
|
*/
|
||
|
|
||
|
iMode = beGetTermMode();
|
||
|
|
||
|
/* If we need a given mode and it's not already set, set it. */
|
||
|
|
||
|
if ((cBuffer[End] == 'h') && (!(iMode & termAttrMode[args[0]])))
|
||
|
{
|
||
|
beSetTermMode(iMode | termAttrMode[args[0]]);
|
||
|
}
|
||
|
|
||
|
/* likewise, clear it as appropriate */
|
||
|
if ((cBuffer[End] == 'l') && (iMode & termAttrMode[args[0]]))
|
||
|
{
|
||
|
beSetTermMode(iMode & ~termAttrMode[args[0]]);
|
||
|
}
|
||
|
|
||
|
End++;
|
||
|
break;
|
||
|
|
||
|
/* If it's an escape sequence we don't treat, pretend as though we never saw
|
||
|
* it.
|
||
|
*/
|
||
|
default:
|
||
|
End += 1;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return(End);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
/* ProcessEscape -
|
||
|
*
|
||
|
* At this point, <esc> has been seen. Start points to the escape
|
||
|
* itself, and then this routine is responsible for parsing the
|
||
|
* rest of the escape sequence, and either pawning off further parsing,
|
||
|
* or acting upon it as appropriate.
|
||
|
*
|
||
|
* Note that the escape sequences being parsed are still contained in cBuffer.
|
||
|
*/
|
||
|
|
||
|
static int ProcessEscape(int Start)
|
||
|
{
|
||
|
int End;
|
||
|
int left;
|
||
|
int fore, back;
|
||
|
int i;
|
||
|
|
||
|
/* if it's definitely not a full escape sequence, return that we haven't
|
||
|
* seen it.
|
||
|
*/
|
||
|
if ((cBuffer[Start] != 27)||(Start+1 >= BufLen))
|
||
|
return(Start);
|
||
|
|
||
|
End = Start + 1;
|
||
|
|
||
|
/* At this point, if the sequence is <esc> x, 'End' points at
|
||
|
* x
|
||
|
*/
|
||
|
|
||
|
/* left = number of characters left unparsed in the buffer. */
|
||
|
left = BufLen - End -1;
|
||
|
|
||
|
/* Main switch statement - parse the escape sequence according to the
|
||
|
* next character we see.
|
||
|
*/
|
||
|
|
||
|
switch (cBuffer[End])
|
||
|
{
|
||
|
/* Hard Reset Esc c BUGBUG - not imp'd. */
|
||
|
case 'c':
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Cursor up Esc A */
|
||
|
case 'A':
|
||
|
beOffsetCursor(-1,0);
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Cursor down Esc B */
|
||
|
case 'B':
|
||
|
beOffsetCursor(1,0);
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Cursor right Esc C */
|
||
|
case 'C':
|
||
|
beOffsetCursor(0,1);
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Cursor left Esc D */
|
||
|
case 'D':
|
||
|
beOffsetCursor(0,-1);
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Newline command: Esc E */
|
||
|
case 'E':
|
||
|
beRawTextOut("\n",strlen("\n"));
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Invoke the Graphics character set Esc F */
|
||
|
case 'F':
|
||
|
beGetTextAttributes(&fore, &back);
|
||
|
if (! (fore & SC_GRAPHICS))
|
||
|
{
|
||
|
fore &= ~(SC_ASCII|SC_G0|SC_G1);
|
||
|
fore |= SC_GRAPHICS;
|
||
|
beSetTextAttributes(fore, back);
|
||
|
}
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Invoke the ASCII character set Esc G */
|
||
|
case 'G':
|
||
|
beGetTextAttributes(&fore, &back);
|
||
|
if (! (fore & SC_ASCII))
|
||
|
{
|
||
|
fore &= ~(SC_G0|SC_G1|SC_GRAPHICS);
|
||
|
fore |= SC_ASCII;
|
||
|
beSetTextAttributes(fore, back);
|
||
|
}
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Move the cursor to (1,1): Home cursor Esc H */
|
||
|
case 'H':
|
||
|
beAbsoluteCursor(TOP_EDGE,LEFT_EDGE);
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Reverse line feed Esc I */
|
||
|
case 'I':
|
||
|
beOffsetCursor(-1,0);
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Erase to end of screen Esc J */
|
||
|
case 'J':
|
||
|
beEraseText(CUR_ROW, CUR_COL, BOTTOM_EDGE, RIGHT_EDGE);
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Erase to end of line Esc K */
|
||
|
case 'K':
|
||
|
beEraseText(CUR_ROW, CUR_COL, CUR_ROW, RIGHT_EDGE);
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Reverse Line: Esc M */
|
||
|
case 'M':
|
||
|
beAbsoluteCursor(CUR_ROW, LEFT_EDGE);
|
||
|
beOffsetCursor(-1,0);
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Switch to G1 graphics character set. Esc N */
|
||
|
case 'N':
|
||
|
beGetTextAttributes(&fore, &back);
|
||
|
if (! (fore & SC_G1))
|
||
|
{
|
||
|
fore &= ~(SC_G0|SC_ASCII|SC_GRAPHICS);
|
||
|
fore |= SC_G1;
|
||
|
beSetTextAttributes(fore, back);
|
||
|
}
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Switch to G0 graphics character set Esc O */
|
||
|
case 'O':
|
||
|
beGetTextAttributes(&fore, &back);
|
||
|
if (! (fore & SC_G0))
|
||
|
{
|
||
|
fore &= ~(SC_G1|SC_ASCII|SC_GRAPHICS);
|
||
|
fore |= SC_G0;
|
||
|
beSetTextAttributes(fore, back);
|
||
|
}
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Print cursor line Esc V BUGBUG - unimp'd */
|
||
|
case 'V':
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Enter print controller Esc W BUGBUG - unimp'd */
|
||
|
case 'W':
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Exit print controller Esc X BUGBUG - unimp'd */
|
||
|
case 'X':
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Cursor address Esc Y row col BUGBUG - unimp'd and needed */
|
||
|
case 'Y':
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Identify terminal type Esc Z */
|
||
|
case 'Z':
|
||
|
beTransmitText(ANSWERBACK_MESSAGE,strlen(ANSWERBACK_MESSAGE));
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* One of dozens of escape sequences starting <esc>[. Parse further */
|
||
|
case '[':
|
||
|
/* pass in the escape as the starting point */
|
||
|
End = ProcessBracket(End-1);
|
||
|
break;
|
||
|
|
||
|
/* Print screen Esc ] BUGBUG - unimp'd */
|
||
|
case ']':
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Enter auto print Esc ^ BUGBUG - unimpd' */
|
||
|
case '^':
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Exit auto print Esc - BUGBUG - unimpd' */
|
||
|
case '-':
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Alternate keypad Esc = BUGBUG - unimpd' */
|
||
|
case '=':
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Numeric keypad Esc > BUGBUG - unimpd' */
|
||
|
case '>':
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Enter ANSI mode Esc < BUGBUG - unimpd' */
|
||
|
case '<':
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Save cursor position & Attributes: Esc 7 */
|
||
|
case '7':
|
||
|
beSaveCursor();
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Restore cursor position & attributes: Esc 8 */
|
||
|
case '8':
|
||
|
beRestoreCursor();
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Set character size - BUGBUG - unimp'd.
|
||
|
* # 1 Double ht, single width top half chars
|
||
|
* # 2 Double ht, single width lower half chars
|
||
|
* # 3 Double ht, double width top half chars
|
||
|
* # 4 Double ht, double width lower half chars
|
||
|
* # 5 Single ht, single width chars
|
||
|
* # 6 Single ht, double width chars
|
||
|
*/
|
||
|
case '#':
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Select character set
|
||
|
* ESC ( A British
|
||
|
* ESC ( C Finnish
|
||
|
* ESC ( E Danish or Norwegian
|
||
|
* ESC ( H Swedish
|
||
|
* ESC ( K German
|
||
|
* ESC ( Q French Canadian
|
||
|
* ESC ( R Flemish or French/Belgian
|
||
|
* ESC ( Y Italian
|
||
|
* ESC ( Z Spanish
|
||
|
* ESC ( 1 Alternative Character
|
||
|
* ESC ( 4 Dutch
|
||
|
* ESC ( 5 Finnish
|
||
|
* ESC ( 6 Danish or Norwegian
|
||
|
* ESC ( 7 Swedish
|
||
|
* ESC ( = Swiss (French or German)
|
||
|
*/
|
||
|
case '(':
|
||
|
case ')':
|
||
|
/* BUGBUG - most character sets aren't implemented. */
|
||
|
beGetTextAttributes(&fore, &back);
|
||
|
switch (cBuffer[++End])
|
||
|
{
|
||
|
case 'B': /* ESC ( B North American ASCII set */
|
||
|
i=SC_ASCII;
|
||
|
break;
|
||
|
|
||
|
case '0': /* ESC ( 0 Line Drawing */
|
||
|
i=SC_G1;
|
||
|
break;
|
||
|
|
||
|
case '2': /* ESC ( 2 Alternative Line drawing */
|
||
|
i=SC_G0;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
/* Make sure the screen mode isn't set. */
|
||
|
i = 0xFFFFFFFF;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (! (fore & i))
|
||
|
{
|
||
|
fore &= ~(SC_ASCII|SC_G0|SC_G1|SC_GRAPHICS);
|
||
|
fore |= i;
|
||
|
beSetTextAttributes(fore, back);
|
||
|
}
|
||
|
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* Unknown escape sequence */
|
||
|
default:
|
||
|
{
|
||
|
char cbuf[80];
|
||
|
sprintf(cbuf,"<esc>%c", cBuffer[End]);
|
||
|
beRawTextOut(cbuf+Start,6);
|
||
|
End += 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return(End);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* ProcessControl -
|
||
|
*
|
||
|
* Process a probable escape or control sequence
|
||
|
* starting at the supplied index. Return the index
|
||
|
* of the first character *after* the escape sequence we
|
||
|
* process.
|
||
|
* In the case of an incomplete sequence, ie one
|
||
|
* that isn't completed by the end of the buffer, return
|
||
|
* 'Start'.
|
||
|
* In the case of an invalid sequence,
|
||
|
* note the invalid escape sequence, and return Start+1
|
||
|
*/
|
||
|
|
||
|
static int ProcessControl(int Start)
|
||
|
{
|
||
|
int fore, back;
|
||
|
int End = Start;
|
||
|
|
||
|
/* Check to make sure we at least have enough characters
|
||
|
* for a control character
|
||
|
*/
|
||
|
|
||
|
if (Start >= BufLen)
|
||
|
return(Start);
|
||
|
|
||
|
switch (cBuffer[Start])
|
||
|
{
|
||
|
/* NUL 0 Fill character; ignored on input.
|
||
|
* DEL 127 Fill character; ignored on input.
|
||
|
*/
|
||
|
case 0:
|
||
|
case 127:
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* ENQ 5 Transmit answerback message. */
|
||
|
case 5:
|
||
|
beTransmitText(ANSWERBACK_MESSAGE,strlen(ANSWERBACK_MESSAGE));
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* BEL 7 Ring the bell. */
|
||
|
case 7:
|
||
|
beRingBell();
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* BS 8 Move cursor left. */
|
||
|
case 8:
|
||
|
beOffsetCursor(0,-1);
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* HT 9 Move cursor to next tab stop. */
|
||
|
case 9:
|
||
|
beAdvanceToTab();
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* LF 10 Line feed; causes print if in autoprint. */
|
||
|
case 10:
|
||
|
beOffsetCursor(1,0);
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* VT 11 Same as LF.
|
||
|
* FF 12 Same as LF.
|
||
|
*/
|
||
|
case 11:
|
||
|
case 12:
|
||
|
beOffsetCursor(1,0);
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* CR 13 Move cursor to left margin or newline. */
|
||
|
case 13:
|
||
|
beOffsetCursor(1,0);
|
||
|
beAbsoluteCursor(CUR_ROW,LEFT_EDGE);
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* SO 14 Invoke G1 character set. */
|
||
|
case 14:
|
||
|
beGetTextAttributes(&fore, &back);
|
||
|
if (! (fore & SC_G1))
|
||
|
{
|
||
|
fore &= ~(SC_ASCII|SC_G0|SC_G1|SC_GRAPHICS);
|
||
|
fore |= SC_G1;
|
||
|
beSetTextAttributes(fore, back);
|
||
|
}
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* SI 15 Invoke G0 character set. */
|
||
|
case 15:
|
||
|
beGetTextAttributes(&fore, &back);
|
||
|
if (! (fore & SC_G0))
|
||
|
{
|
||
|
fore &= ~(SC_ASCII|SC_G0|SC_G1|SC_GRAPHICS);
|
||
|
fore |= SC_G0;
|
||
|
beSetTextAttributes(fore, back);
|
||
|
}
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* CAN 24 Cancel escape sequence and display checkerboard. BUGBUG - not imp'd.
|
||
|
* SUB 26 Same as CAN.
|
||
|
*/
|
||
|
case 24:
|
||
|
End += 1;
|
||
|
break;
|
||
|
|
||
|
/* ESC 27 Introduce a control sequence. */
|
||
|
case 27:
|
||
|
End = ProcessEscape(Start);
|
||
|
break;
|
||
|
|
||
|
/* Print any other control character received. */
|
||
|
default:
|
||
|
{
|
||
|
char buf[4];
|
||
|
sprintf(buf,"^%c",'A' + cBuffer[Start] - 1);
|
||
|
beRawTextOut(buf, 2);
|
||
|
End += 1;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return(End);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* ProcessBuffer -
|
||
|
*
|
||
|
* Process the current contents of the terminal character buffer.
|
||
|
*/
|
||
|
static int ProcessBuffer(void)
|
||
|
{
|
||
|
int Start=0,End=0;
|
||
|
|
||
|
/* Null-terminate the buffer. Why? Heck, why not? */
|
||
|
|
||
|
cBuffer[BufLen] = '\0';
|
||
|
|
||
|
/* Loop through the entire buffer. Start will be incremented
|
||
|
* to point at the start of unprocessed text in the buffer as
|
||
|
* we go.
|
||
|
*/
|
||
|
while (Start < BufLen)
|
||
|
{
|
||
|
End = Start;
|
||
|
|
||
|
/* Since we null-terminated, null < 27 and we have a termination condition */
|
||
|
while ((cBuffer[End] > 27)||(cBuffer[End] == 10)||(cBuffer[End] == 13))
|
||
|
End++;
|
||
|
|
||
|
/* At this point, if Start < End, we have a string of characters which
|
||
|
* doesn't have any control sequences, and should be printed raw.
|
||
|
*/
|
||
|
|
||
|
if (End > Start)
|
||
|
beRawTextOut(cBuffer+Start, End-Start);
|
||
|
|
||
|
if (End >= BufLen)
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* At this point, 'End' points to the beginning of an escape
|
||
|
* sequence. We'll reset 'start' to be AFTER parsing the
|
||
|
* escape sequence. Note that if the escape sequence
|
||
|
* is incomplete, ProcessControl should return the
|
||
|
* same value passed in. Otherwise, it'll return the
|
||
|
* index of the first character after the valid
|
||
|
* escape sequence.
|
||
|
*/
|
||
|
|
||
|
Start = ProcessControl(End);
|
||
|
|
||
|
if (Start == End)
|
||
|
{
|
||
|
/* The escape sequence was incomplete.
|
||
|
* Move the unprocessed portion of the input buffer
|
||
|
* to start at the beginning of the buffer, then
|
||
|
* return.
|
||
|
*/
|
||
|
|
||
|
while (End < BufLen)
|
||
|
{
|
||
|
cBuffer[End-Start] = cBuffer[End];
|
||
|
End += 1;
|
||
|
}
|
||
|
|
||
|
BufLen = End-Start;
|
||
|
return(0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* If we made it this far, Start == Buflen, and so we've finished
|
||
|
* processing the buffer completely.
|
||
|
*/
|
||
|
BufLen = 0;
|
||
|
cBuffer[BufLen] = '\0';
|
||
|
|
||
|
return(0);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* vtProcessedTextOut -
|
||
|
*
|
||
|
* Output characters to terminal, passing them through the vt100 emulator.
|
||
|
* Return -1 on error
|
||
|
*/
|
||
|
int
|
||
|
vtProcessedTextOut(char *cbuf, int count)
|
||
|
{
|
||
|
/* If we have a buffer overflow, error out if we haven't already crashed. */
|
||
|
|
||
|
if ((count + BufLen) > MAXVTBUFLEN)
|
||
|
{
|
||
|
beRawTextOut("ERROR: VT-100 internal buffer overflow!",39);
|
||
|
return(-1);
|
||
|
}
|
||
|
|
||
|
/* Otherwise, add our requested information to the
|
||
|
* output buffer, and attempt to parse it.
|
||
|
*/
|
||
|
|
||
|
memcpy(cBuffer + BufLen, cbuf, count);
|
||
|
BufLen += count;
|
||
|
|
||
|
return(ProcessBuffer());
|
||
|
}
|
||
|
|