3 * AUTHOR: John L. Miller, johnmil@cs.cmu.edu / johnmil@jprc.com
6 * Copyright (c) 1996 John L. Miller
8 * Full permission is granted to use, modify and distribute
10 * 1) This comment field is included in its entirity
11 * 2) No money is charged for any work including or based on
12 * portions of this code.
14 * If you're a nice person and find this useful, I'd appreciate a
15 * note letting me know about it. e-mail is usually what spurs me
16 * on to improve and support software I've written.
20 /* This is the main code body for my generic vt-100 emulator. This code
21 * body provides parsing for most of the vt-100 escape sequences, but it
22 * doesn't actually do anything with some of them. The idea behind this
23 * parser is to provide a generic front-end that you can initialize, then
24 * send all of your output to. The output is parsed by the routines in this
25 * program, then spit out to a back-end.
27 * What back-end you say? Well, the one you have to supply yourself. There's a
28 * dozen or so routines you have to provide for character-based I/O, cursor
29 * movement, erasing and deleting text, and setting text and terminal attributes.
31 * For a list of the routines your back end must supply, read vt100.h closely.
33 * In case it's not obvious, these routines were written for a system running win32.
34 * for vt100.c and vt100.h, most of the code should be easily portable to other
35 * operating systems. Yeah, like they NEED a vt-100 emulator :p
44 /* NOTE - many of the functions look like they should
45 * take X-Y pairs. Bear in mind this is a text display,
46 * so for this we're talking ROWS and COLUMNS. So parm
47 * lists go (row, column), which is the opposite of (x, y).
50 char cBuffer
[MAXVTBUFLEN
+1]; /* terminal output buffer for unprocessed characters */
51 int BufLen
; /* Number of characters in cBuffer waiting for output */
53 /* List of all device-independant colors. These colors will be transmitted to a
54 * back-end routine responsible for setting the foreground and background text
55 * colors appropriately. Note that the color combinations are ordered to correspond
56 * with combinations of red (0x1), green (0x2) and blue (0x4) bit flags.
59 int ScColorTrans
[8]= { 0,
66 SC_RED
|SC_GREEN
|SC_BLUE
70 /* List of terminal attributes which we track (used by <esc>[?#h and <esc>[?#l) */
72 int termAttrMode
[NUM_TERM_ATTR_MODES
] = { 0,
83 /* FORWARD FUNCTION DECLARATIONS -
84 * these functions are intended for use only in this module */
86 static int ProcessBracket(int Start
);
87 static int ProcessEscape(int Start
);
88 static int ProcessControl(int Start
);
89 static int ProcessBuffer(void);
91 /* END FORWARD FUNCTION DECLARATIONS */
97 * front-end 'puts()' substitute. Call this routine instead
98 * of 'puts()', and it'll pass the output through the vt100 emulator.
107 vtProcessedTextOut(cbuf
, strlen(cbuf
));
115 * This routine is a substitute for printf(). Call this routine with the
116 * same parameters you would use for printf, and output will be channelled
117 * through the vt-100 emulator.
120 vtprintf(char *format
, ...)
125 va_start(va
, format
);
126 vsprintf(cbuf
,format
, va
);
129 vtProcessedTextOut(cbuf
, strlen(cbuf
));
136 * Set the initial state of the VT-100 emulator, and call the initialization
137 * routine for the back end. This routine MUST be invoked before any other, or
138 * the VT-100 emulator will most likely roll over and die.
148 beInitVT100Terminal(); /* call the back-end initialization. */
156 * Helper routine for processing escape sequences. By the time this
157 * routine is invoked, '<esc>[' has already been read in the input
158 * stream. 'Start' is an index in cBuffer to the '<esc>'.
160 * If an escape sequence is successfully parsed, return the index of the
161 * first character AFTER the escape sequence. Otherwise, return 'Start'.
165 static int ProcessBracket(int Start
)
167 int End
; /* Current character being examined in cBuffer */
168 int args
[10], numargs
=0; /* numerical args after <esc>[ */
173 int iForeground
, iBackground
;
175 /* If there's no valid escape sequence, return as we were called. */
177 if ((cBuffer
[Start
+1] != '[')||(Start
+2 >= BufLen
))
182 /* Loop through the buffer, hacking out all numeric
183 * arguments (consecutive string of digits and
188 itmp
= 0; /* itmp will hold the current arguments integer value */
190 /* the semicolon is a delimiter */
191 if (cBuffer
[End
] == ';')
199 /* Parse this argument into a number. */
201 while (isdigit(cBuffer
[End
]))
203 itmp
= itmp
*10 + (cBuffer
[End
]-'0');
209 /* Save the numeric argument if we actually
213 if (End
!= Start
+ 2)
214 args
[numargs
++] = itmp
;
216 } while (cBuffer
[End
] == ';');
218 /* At this point, we've come across a character that isn't a number
219 * and isn't a semicolon. This means it is the command specifier.
222 /* Number of characters left in the input stream. I don't use
223 * this as rigorously as I should here.
228 /* Return if there's definitely not enough characters for a
229 * full escape sequence.
235 /* Giant switch statement, parsing the command specifier that followed
236 * up <esc>[arg;arg;...arg
239 switch (cBuffer
[End
])
241 /* Cursor Up: Esc [ Pn A */
243 beOffsetCursor(numargs
? -args
[0] : -1, 0);
247 /* Cursor Down: Esc [ Pn B */
249 beOffsetCursor(numargs
? args
[0] : 1, 0);
253 /* Cursor Right: Esc [ Pn C */
255 beOffsetCursor(0, numargs
? args
[0] : 1);
259 /* Cursor Left: Esc [ Pn D */
261 beOffsetCursor(0, numargs
? -args
[0] : -1);
265 /* Direct Addressing : Esc [ Pn(row);Pn(col)H or
266 * Esc [ Pn(row);Pn(col)f
271 beAbsoluteCursor(1,1);
272 else if (numargs
== 1)
273 beAbsoluteCursor(args
[0] > 0 ? args
[0] : 1,1);
274 else if (numargs
== 2)
275 beAbsoluteCursor(args
[0] > 0 ? args
[0] : 1, args
[1] > 0 ? args
[1] : 1);
280 /* Erase from Cursor to end of screen Esc [ J
281 * Erase from Beginning of screen to cursor Esc [ 1 J
282 * Erase Entire screen Esc [ 2 J
286 beEraseText(CUR_ROW
, CUR_COL
, BOTTOM_EDGE
, RIGHT_EDGE
);
287 else if (args
[0] == 1)
288 beEraseText(TOP_EDGE
, LEFT_EDGE
, CUR_ROW
, CUR_COL
);
290 beEraseText(TOP_EDGE
, LEFT_EDGE
, BOTTOM_EDGE
, RIGHT_EDGE
);
295 /* Erase from Cursor to end of line Esc [ K
296 * Erase from Beginning of line to cursor Esc [ 1 K
297 * Erase Entire line Esc [ 2 K
301 beEraseText(CUR_ROW
, CUR_COL
, CUR_ROW
, RIGHT_EDGE
);
302 else if (args
[0] == 1)
303 beEraseText(CUR_ROW
, LEFT_EDGE
, CUR_ROW
, CUR_COL
);
305 beEraseText(CUR_ROW
, LEFT_EDGE
, CUR_ROW
, RIGHT_EDGE
);
311 /* Set Graphics Rendition:
313 * The graphics rendition is basically foreground and background
314 * color and intensity.
317 beGetTextAttributes(&iForeground
, &iBackground
);
321 /* If we just get ESC[m, treat it as though
322 * we should shut off all extra text
326 iForeground
&= ~(SC_BOLD
|SC_UL
|SC_BL
|SC_RV
|SC_GRAPHICS
|SC_G0
|SC_G1
);
327 iForeground
|= SC_ASCII
;
329 beSetTextAttributes(iForeground
, iBackground
);
334 /* Loop through all the color specs we got, and combine them
335 * together. Note that things like 'reverse video', 'bold',
336 * and 'blink' are only applied to the foreground. The back end
337 * is responsible for applying these properties to all text.
339 for (i
=0; i
< numargs
; i
++)
343 /* 0 for normal display */
345 iForeground
&= ~SC_BOLD
;
350 iForeground
|= SC_BOLD
;
353 /* 4 underline (mono only) */
355 iForeground
|= SC_UL
;
360 iForeground
|= SC_BL
;
363 /* 7 reverse video on */
365 iForeground
|= SC_RV
;
368 /* 8 nondisplayed (invisible) BUGBUG - not doing this. */
371 /* 30-37 is bit combination of 30+ red(1) green(2) blue(4)
372 * 30 black foreground
382 iForeground
&= ~(SC_RED
|SC_GREEN
|SC_BLUE
);
383 iForeground
|= ScColorTrans
[args
[i
]-30];
386 /* 40-47 is bit combo similar to 30-37, but for background. */
395 iBackground
&= ~(SC_RED
|SC_GREEN
|SC_BLUE
);
396 iBackground
|= ScColorTrans
[args
[i
]-30];
401 beSetTextAttributes(iForeground
, iBackground
);
408 * Set with Esc [ Ps h
409 * Reset with Esc [ Ps l
410 * Mode name Ps Set Reset
411 * -------------------------------------------------------------------
412 * Keyboard action 2 Locked Unlocked
413 * Insertion 4 Insert Overwrite
414 * Send - Receive 12 Full Echo
415 * Line feed/New line 20 New line Line feed
419 /* BUGBUG - many of the terminal modes are set with '?' as the
420 * first character rather than a number sign. These are dealt with
421 * later in this switch statement because they must be. Most other
422 * settings are just ignored, however.
427 /* Insert line Esc [ Pn L */
429 beInsertRow(CUR_ROW
);
433 /* Delete line Esc [ Pn M */
436 beDeleteText(CUR_ROW
,LEFT_EDGE
, CUR_ROW
, RIGHT_EDGE
);
437 } while (--args
[0] > 0);
442 /* Delete character Esc [ Pn P */
445 beDeleteText(CUR_ROW
, CUR_COL
, CUR_ROW
, CUR_COL
);
446 } while (--args
[0] > 0);
450 /* Set the Scrolling region Esc [ Pn(top);Pn(bot) r */
453 beSetScrollingRows(TOP_EDGE
,BOTTOM_EDGE
);
454 else if (numargs
== 2)
455 beSetScrollingRows(args
[0],args
[1]);
459 /* Print screen or region Esc [ i
460 * Print cursor line Esc [ ? 1 i
461 * Enter auto print Esc [ ? 5 i
462 * Exit auto print Esc [ ? 4 i
463 * Enter print controller Esc [ 5 i
464 * Exit print controller Esc [ 4 i
467 /* BUGBUG - print commands are not acted upon. */
472 /* Clear tab at current column Esc [ g
473 * Clear all tabs Esc [ 3 g
479 if ((numargs
== 1) && (args
[0] == 3))
480 beClearTab(ALL_TABS
);
485 /* BUGBUG - queries which require responses are ignored. */
487 /* Esc [ c DA:Device Attributes
490 * Esc [ <sol> x DECREQTPARM: Request Terminal Parameters
491 * * <sol> values other than 1 are ignored. Upon
492 * receipt of a <sol> value of 1, the following
494 * Esc[3;<par>;<nbits>;<xspeed>;<rspeed>;1;0x
496 * * Where <par>, <nbits>, <xspeed>, and <rspeed>
497 * are as for VT100s with the following
499 * <nbits> Values of 5 and 6 bits per
500 * character are sent as 7 bits.
502 * These two numbers will always
503 * be the same. 9600 baud is
504 * sent for 7200 baud.
506 * Esc [ Ps n DSR: Device Status Report
507 * * Parameter values other than 5, 6, are ignored.
508 * If the parameter value is 5, the sequence
509 * Esc [ O n is returned. If the parameter value is
510 * 6, the CPR: Cursor Position Report sequence
511 * Esc [ Pn ; Pn R is returned with the Pn set to
512 * cursor row and column numbers.
515 * ESC[#;#R Reports current cursor line & column
518 /* spec <esc>[<spec>h <esc>[<spec>l
519 * Cursor key ?1 Application Cursor
520 * ANSI/VT52 ?2 ANSI VT52
522 * Scrolling ?4 Smooth Jump
523 * Screen ?5 Reverse Normal
524 * Origin ?6 Relative Absolute
525 * Wraparound ?7 Wrap Truncate
526 * Auto key repeat ?8 Repeating No repeat
529 /* We didn't catch the numeric argument because the '?' stopped
530 * it before. Parse it now.
533 while (isdigit(cBuffer
[++End
]))
537 args
[0] = 10*args
[0] + (cBuffer
[End
]-'0');
540 /* If we don't handle this particular '?' command (and
541 * there are plenty we don't) then just ignore it.
544 ||( (cBuffer
[End
] != 'l') && (cBuffer
[End
] != 'h'))
553 /* This command sets terminal status. Get the current status,
554 * determine what needs to be changed, and send it back.
557 iMode
= beGetTermMode();
559 /* If we need a given mode and it's not already set, set it. */
561 if ((cBuffer
[End
] == 'h') && (!(iMode
& termAttrMode
[args
[0]])))
563 beSetTermMode(iMode
| termAttrMode
[args
[0]]);
566 /* likewise, clear it as appropriate */
567 if ((cBuffer
[End
] == 'l') && (iMode
& termAttrMode
[args
[0]]))
569 beSetTermMode(iMode
& ~termAttrMode
[args
[0]]);
575 /* If it's an escape sequence we don't treat, pretend as though we never saw
590 * At this point, <esc> has been seen. Start points to the escape
591 * itself, and then this routine is responsible for parsing the
592 * rest of the escape sequence, and either pawning off further parsing,
593 * or acting upon it as appropriate.
595 * Note that the escape sequences being parsed are still contained in cBuffer.
598 static int ProcessEscape(int Start
)
605 /* if it's definitely not a full escape sequence, return that we haven't
608 if ((cBuffer
[Start
] != 27)||(Start
+1 >= BufLen
))
613 /* At this point, if the sequence is <esc> x, 'End' points at
617 /* left = number of characters left unparsed in the buffer. */
618 left
= BufLen
- End
-1;
620 /* Main switch statement - parse the escape sequence according to the
621 * next character we see.
624 switch (cBuffer
[End
])
626 /* Hard Reset Esc c BUGBUG - not imp'd. */
631 /* Cursor up Esc A */
633 beOffsetCursor(-1,0);
637 /* Cursor down Esc B */
643 /* Cursor right Esc C */
649 /* Cursor left Esc D */
651 beOffsetCursor(0,-1);
655 /* Newline command: Esc E */
657 beRawTextOut("\n",strlen("\n"));
661 /* Invoke the Graphics character set Esc F */
663 beGetTextAttributes(&fore
, &back
);
664 if (! (fore
& SC_GRAPHICS
))
666 fore
&= ~(SC_ASCII
|SC_G0
|SC_G1
);
668 beSetTextAttributes(fore
, back
);
673 /* Invoke the ASCII character set Esc G */
675 beGetTextAttributes(&fore
, &back
);
676 if (! (fore
& SC_ASCII
))
678 fore
&= ~(SC_G0
|SC_G1
|SC_GRAPHICS
);
680 beSetTextAttributes(fore
, back
);
685 /* Move the cursor to (1,1): Home cursor Esc H */
687 beAbsoluteCursor(TOP_EDGE
,LEFT_EDGE
);
691 /* Reverse line feed Esc I */
693 beOffsetCursor(-1,0);
697 /* Erase to end of screen Esc J */
699 beEraseText(CUR_ROW
, CUR_COL
, BOTTOM_EDGE
, RIGHT_EDGE
);
703 /* Erase to end of line Esc K */
705 beEraseText(CUR_ROW
, CUR_COL
, CUR_ROW
, RIGHT_EDGE
);
709 /* Reverse Line: Esc M */
711 beAbsoluteCursor(CUR_ROW
, LEFT_EDGE
);
712 beOffsetCursor(-1,0);
716 /* Switch to G1 graphics character set. Esc N */
718 beGetTextAttributes(&fore
, &back
);
719 if (! (fore
& SC_G1
))
721 fore
&= ~(SC_G0
|SC_ASCII
|SC_GRAPHICS
);
723 beSetTextAttributes(fore
, back
);
728 /* Switch to G0 graphics character set Esc O */
730 beGetTextAttributes(&fore
, &back
);
731 if (! (fore
& SC_G0
))
733 fore
&= ~(SC_G1
|SC_ASCII
|SC_GRAPHICS
);
735 beSetTextAttributes(fore
, back
);
740 /* Print cursor line Esc V BUGBUG - unimp'd */
745 /* Enter print controller Esc W BUGBUG - unimp'd */
750 /* Exit print controller Esc X BUGBUG - unimp'd */
755 /* Cursor address Esc Y row col BUGBUG - unimp'd and needed */
760 /* Identify terminal type Esc Z */
762 beTransmitText(ANSWERBACK_MESSAGE
,strlen(ANSWERBACK_MESSAGE
));
766 /* One of dozens of escape sequences starting <esc>[. Parse further */
768 /* pass in the escape as the starting point */
769 End
= ProcessBracket(End
-1);
772 /* Print screen Esc ] BUGBUG - unimp'd */
777 /* Enter auto print Esc ^ BUGBUG - unimpd' */
782 /* Exit auto print Esc - BUGBUG - unimpd' */
787 /* Alternate keypad Esc = BUGBUG - unimpd' */
792 /* Numeric keypad Esc > BUGBUG - unimpd' */
797 /* Enter ANSI mode Esc < BUGBUG - unimpd' */
802 /* Save cursor position & Attributes: Esc 7 */
808 /* Restore cursor position & attributes: Esc 8 */
814 /* Set character size - BUGBUG - unimp'd.
815 * # 1 Double ht, single width top half chars
816 * # 2 Double ht, single width lower half chars
817 * # 3 Double ht, double width top half chars
818 * # 4 Double ht, double width lower half chars
819 * # 5 Single ht, single width chars
820 * # 6 Single ht, double width chars
826 /* Select character set
829 * ESC ( E Danish or Norwegian
832 * ESC ( Q French Canadian
833 * ESC ( R Flemish or French/Belgian
836 * ESC ( 1 Alternative Character
839 * ESC ( 6 Danish or Norwegian
841 * ESC ( = Swiss (French or German)
845 /* BUGBUG - most character sets aren't implemented. */
846 beGetTextAttributes(&fore
, &back
);
847 switch (cBuffer
[++End
])
849 case 'B': /* ESC ( B North American ASCII set */
853 case '0': /* ESC ( 0 Line Drawing */
857 case '2': /* ESC ( 2 Alternative Line drawing */
862 /* Make sure the screen mode isn't set. */
869 fore
&= ~(SC_ASCII
|SC_G0
|SC_G1
|SC_GRAPHICS
);
871 beSetTextAttributes(fore
, back
);
877 /* Unknown escape sequence */
881 sprintf(cbuf
,"<esc>%c", cBuffer
[End
]);
882 beRawTextOut(cbuf
+Start
,6);
893 * Process a probable escape or control sequence
894 * starting at the supplied index. Return the index
895 * of the first character *after* the escape sequence we
897 * In the case of an incomplete sequence, ie one
898 * that isn't completed by the end of the buffer, return
900 * In the case of an invalid sequence,
901 * note the invalid escape sequence, and return Start+1
904 static int ProcessControl(int Start
)
909 /* Check to make sure we at least have enough characters
910 * for a control character
916 switch (cBuffer
[Start
])
918 /* NUL 0 Fill character; ignored on input.
919 * DEL 127 Fill character; ignored on input.
926 /* ENQ 5 Transmit answerback message. */
928 beTransmitText(ANSWERBACK_MESSAGE
,strlen(ANSWERBACK_MESSAGE
));
932 /* BEL 7 Ring the bell. */
938 /* BS 8 Move cursor left. */
940 beOffsetCursor(0,-1);
944 /* HT 9 Move cursor to next tab stop. */
950 /* LF 10 Line feed; causes print if in autoprint. */
965 /* CR 13 Move cursor to left margin or newline. */
968 beAbsoluteCursor(CUR_ROW
,LEFT_EDGE
);
972 /* SO 14 Invoke G1 character set. */
974 beGetTextAttributes(&fore
, &back
);
975 if (! (fore
& SC_G1
))
977 fore
&= ~(SC_ASCII
|SC_G0
|SC_G1
|SC_GRAPHICS
);
979 beSetTextAttributes(fore
, back
);
984 /* SI 15 Invoke G0 character set. */
986 beGetTextAttributes(&fore
, &back
);
987 if (! (fore
& SC_G0
))
989 fore
&= ~(SC_ASCII
|SC_G0
|SC_G1
|SC_GRAPHICS
);
991 beSetTextAttributes(fore
, back
);
996 /* CAN 24 Cancel escape sequence and display checkerboard. BUGBUG - not imp'd.
997 * SUB 26 Same as CAN.
1003 /* ESC 27 Introduce a control sequence. */
1005 End
= ProcessEscape(Start
);
1008 /* Print any other control character received. */
1012 sprintf(buf
,"^%c",'A' + cBuffer
[Start
] - 1);
1013 beRawTextOut(buf
, 2);
1025 * Process the current contents of the terminal character buffer.
1027 static int ProcessBuffer(void)
1031 /* Null-terminate the buffer. Why? Heck, why not? */
1033 cBuffer
[BufLen
] = '\0';
1035 /* Loop through the entire buffer. Start will be incremented
1036 * to point at the start of unprocessed text in the buffer as
1039 while (Start
< BufLen
)
1043 /* Since we null-terminated, null < 27 and we have a termination condition */
1044 while ((cBuffer
[End
] > 27)||(cBuffer
[End
] == 10)||(cBuffer
[End
] == 13))
1047 /* At this point, if Start < End, we have a string of characters which
1048 * doesn't have any control sequences, and should be printed raw.
1052 beRawTextOut(cBuffer
+Start
, End
-Start
);
1059 /* At this point, 'End' points to the beginning of an escape
1060 * sequence. We'll reset 'start' to be AFTER parsing the
1061 * escape sequence. Note that if the escape sequence
1062 * is incomplete, ProcessControl should return the
1063 * same value passed in. Otherwise, it'll return the
1064 * index of the first character after the valid
1068 Start
= ProcessControl(End
);
1072 /* The escape sequence was incomplete.
1073 * Move the unprocessed portion of the input buffer
1074 * to start at the beginning of the buffer, then
1078 while (End
< BufLen
)
1080 cBuffer
[End
-Start
] = cBuffer
[End
];
1089 /* If we made it this far, Start == Buflen, and so we've finished
1090 * processing the buffer completely.
1093 cBuffer
[BufLen
] = '\0';
1099 /* vtProcessedTextOut -
1101 * Output characters to terminal, passing them through the vt100 emulator.
1102 * Return -1 on error
1105 vtProcessedTextOut(char *cbuf
, int count
)
1107 /* If we have a buffer overflow, error out if we haven't already crashed. */
1109 if ((count
+ BufLen
) > MAXVTBUFLEN
)
1111 beRawTextOut("ERROR: VT-100 internal buffer overflow!",39);
1115 /* Otherwise, add our requested information to the
1116 * output buffer, and attempt to parse it.
1119 memcpy(cBuffer
+ BufLen
, cbuf
, count
);
1122 return(ProcessBuffer());