TwoTerm/Emulators/VT100.mm
Kelvin Sherlock 6f409db93e vt100 wip.
2018-11-24 13:26:47 -05:00

1086 lines
28 KiB
Plaintext

//
// VT100.mm
// 2Term
//
// Created by Kelvin Sherlock on 7/14/2010.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//
#import "VT100.h"
#include "Screen.h"
#include "OutputChannel.h"
#include <sys/ttydefaults.h>
@implementation VT100
#define ESC "\x1b"
enum {
StateText,
StateEsc,
StateDCAY,
StateDCAX,
StateBracket,
StateBracketQuestion,
StatePound,
StateRParen,
StateLParen
};
+(void)load
{
[EmulatorManager registerClass: self];
}
-(id)init
{
self = [super init];
[self reset];
return self;
}
+(NSString *)name
{
return @"VT100";
}
-(NSString *)name
{
return @"VT100";
}
-(const char *)termName
{
return "vt100";
}
-(BOOL)resizable
{
return YES;
}
-(struct winsize)defaultSize
{
struct winsize ws = { 24, 80, 0, 0};
return ws;
}
-(void)reset
{
_state = StateText;
_vt52Mode = NO;
_flags.DECANM = 1; // ansi/vt100 mode
_flags.DECARM = 0;
_flags.DECAWM = 0;
_flags.DECCKM = 0;
_flags.DECKPAM = 0;
_flags.DECKPNM = 1;
_flags.DECCOLM = 0;
_flags.DECSCLM = 0;
_flags.DECSCNM = 0;
_flags.DECOM = 0;
_flags.DECINLM = 0;
_flags.LNM = 0;
}
-(void)vt52ProcessCharacter:(uint8_t)c screen:(Screen *)screen output:(OutputChannel *)output
{
if (_state == StateEsc)
{
switch (c)
{
case 0x00:
case 0x7f:
break;
case 'A':
// cursor up
screen->decrementY();
_state = StateText;
break;
case 'B':
// cursor down
screen->incrementY();
_state = StateText;
break;
case 'C':
// cursor right
screen->incrementX();
_state = StateText;
break;
case 'D':
screen->decrementX();
_state = StateText;
break;
case 'F':
// graphics character set on
_state = StateText;
break;
case 'G':
// graphics character set off.
_state = StateText;
break;
case 'H':
// home
screen->setCursor(0, 0);
_state = StateText;
break;
case 'I':
// inverse line feed.
screen->reverseLineFeed();
_state = StateText;
break;
case 'J':
// erase to end of screen
screen->eraseScreen();
_state = StateText;
break;
case 'K':
//erase to end of line.
screen->eraseLine();
_state = StateText;
break;
case 'Y':
// dca
_state = StateDCAY;
break;
case 'Z':
//identify
output->write(ESC "/Z");
_state = StateText;
break;
case '=':
// alt key pad
_altKeyPad = YES;
_state = StateText;
break;
case '>':
_altKeyPad = NO;
_state = StateText;
break;
case '1':
// graphics on
_state = StateText;
break;
case '2':
// graphics off
_state = StateText;
break;
case '<':
// ansi mode
_vt52Mode = NO;
_state = StateText;
break;
default:
NSLog(@"[%s %s]: unrecognized escape character: `%c' (%02x)", object_getClassName(self), sel_getName(_cmd), c, (int)c);
_state = StateText;
}
}
else if (_state == StateDCAY)
{
if (c == 0x00) return;
_dca.y = c - 0x20;
_state = StateDCAX;
}
else if (_state == StateDCAX)
{
if (c == 0x00) return;
_dca.x = c - 0x20;
/*
* If the line # does not specify a line that exists on the screen, the VT50H
* will move the cursor to the bottom line of the screen. However, the VT52 will
* not move the cursor vertically if the vertical parameter is out of bounds.
*/
/*
* If the column number is greater than 157 and, therefore, does not specify a column
* that exists on the screen, the cursor is moved to the rightmost column on a line on all models.
*/
screen->setCursor(_dca, true, false);
_state = StateText;
}
}
-(void)processCharacter:(uint8_t)c screen:(Screen *)screen output:(OutputChannel *)output
{
if (_vt52Mode && _state != StateText)
{
[self vt52ProcessCharacter: c screen: screen output: output];
return;
}
if (_state == StateBracketQuestion)
{
// ESC [? \d+ h|l
switch(c)
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
_parms.back() = _parms.back() * 10 + (c - '0');
break;
case ';':
_parms.push_back(0);
break;
case 'h':
{
// h = set
std::vector<int>::iterator iter;
for (iter = _parms.begin(); iter != _parms.end(); ++iter)
{
switch (*iter)
{
/*
case 20:
_flags.LNM = 1;
break;
*/
case 1:
_flags.DECCKM = 1;
break;
case 3:
_flags.DECCOLM = 1;
screen->setSize(132, 24);
break;
case 4:
_flags.DECSCLM = 1;
break;
case 5:
_flags.DECSCNM = 1;
break;
case 6:
_flags.DECOM = 1;
break;
case 7:
_flags.DECAWM = 1;
break;
case 8:
_flags.DECARM = 1;
break;
case 9:
_flags.DECINLM = 1;
break;
}
}
_state = StateText;
break;
}
case 'l':
{
// l = reset
std::vector<int>::iterator iter;
for (iter = _parms.begin(); iter != _parms.end(); ++iter)
{
switch (*iter)
{
/*
case 20:
_flags.LNM = 0;
break;
*/
case 1:
_flags.DECCKM = 0;
break;
case 2:
_vt52Mode = YES;
_flags.DECANM = 0;
break;
case 3:
_flags.DECCOLM = 0;
screen->setSize(80, 24);
break;
case 4:
_flags.DECSCLM = 0;
break;
case 5:
_flags.DECSCNM = 0;
break;
case 6:
_flags.DECOM = 0;
break;
case 7:
_flags.DECAWM = 0;
break;
case 8:
_flags.DECARM = 0;
break;
case 9:
_flags.DECINLM = 0;
break;
}
}
_state = StateText;
break;
}
default:
NSLog(@"[%s %s]: unrecognized escape character: `ESC [? %d %c' (%02x)",
object_getClassName(self), sel_getName(_cmd), _parms[0], c, (int)c);
_state = StateText;
break;
}
return;
}
if (_state == StateBracket)
{
// '[' [0-9]* CODE
// '[' [0-9]+ (';' [0-9]+)* CODE
switch (c)
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
_parms.back() = _parms.back() * 10 + (c - '0');
break;
case ';':
_parms.push_back(0);
break;
case '?':
_parms.clear();
_parms.push_back(0);
_state = StateBracketQuestion;
break;
case 'A':
// cursor up. default 1.
{
int count = std::max(_parms[0], 1);
screen->setY(screen->y() - count);
_state = StateText;
break;
}
case 'B':
// cursor down. default 1.
{
int count = std::max(_parms[0], 1);
screen->setY(screen->y() + count);
_state = StateText;
break;
}
case 'C':
// cursor forward. default 1.
{
int count = std::max(_parms[0], 1);
screen->setX(screen->x() + count);
_state = StateText;
break;
}
case 'D':
// cursor back. default 1.
{
int count = std::max(_parms[0], 1);
screen->setX(screen->x() - count);
_state = StateText;
break;
}
case 'H':
case 'f':
// set cursor position.
// line numbering depends on DECOM.
//
if (_parms.size() == 2)
{
// 0 = line
// 1 = column
screen->setCursor(std::max(_parms[1], 1) - 1, std::max(_parms[0],1) - 1);
}
else screen->setCursor(0, 0);
_state = StateText;
break;
case 'J':
/*
* erase
* 0 J -> erase from cursor to end of screen
* 1 J -> erase from beginning of screen to cursor
* 2 J -> erase entire screen
*/
{
switch (_parms[0])
{
default:
screen->erase(Screen::EraseAfterCursor);
break;
case 1:
screen->erase(Screen::EraseBeforeCursor);
break;
case 2:
screen->erase(Screen::EraseAll);
break;
}
_state = StateText;
break;
}
case 'K':
/*
* erase
* 0 K -> erase from cursor to end of line
* 1 K -> erase from beginning of line to cursor
* 2 K -> erase entire line contaning the cursor.
*/
{
switch(_parms[0])
{
default:
screen->erase(Screen::EraseLineAfterCursor);
break;
case 1:
screen->erase(Screen::EraseLineBeforeCursor);
break;
case 2:
screen->erase(Screen::EraseLineAll);;
break;
}
_state = StateText;
break;
}
case 'c':
{
// who are you?
output->write(ESC "[?1;0c");
_state = StateText;
break;
}
case 'n':
// status reports.
{
char *str = NULL;
switch (_parms[0])
{
case 5:
// terminal status report
// ESC [ 0 n == terminal ok
// ESC [ 3 n == terminal not ok.
output->write(ESC "0n");
break;
case 6:
// cursor position
// ESC [ line ; column R
asprintf(&str, ESC "[%u;%uR", screen->y() + 1, screen->x() + 1);
if (str)
{
output->write(str);
free(str);
}
break;
default:
NSLog(@"[%s %s]: unrecognized escape character: `ESC [ %d %c'",
object_getClassName(self), sel_getName(_cmd), _parms[0], c);
break;
}
_state = StateText;
break;
}
case 'l':
{
// l = reset
std::vector<int>::iterator iter;
for (iter = _parms.begin(); iter != _parms.end(); ++iter)
{
switch (*iter)
{
case 20:
_flags.LNM = 0;
break;
}
}
_state = StateText;
break;
}
case 'h':
{
// h = set
std::vector<int>::iterator iter;
for (iter = _parms.begin(); iter != _parms.end(); ++iter)
{
switch (*iter)
{
case 20:
_flags.LNM = 1;
break;
}
}
_state = StateText;
break;
}
case 'm':
{
std::vector<int>::iterator iter;
unsigned flag = screen->flag();
for (iter = _parms.begin(); iter != _parms.end(); ++iter)
{
switch (*iter)
{
case 0:
// attributes off
flag = Screen::FlagNormal;
break;
case 1:
// bold/increased intensity.
flag |= Screen::FlagBold;
break;
case 4:
// underscore.
flag |= Screen::FlagInverse;
break;
case 5:
//blink
flag |= Screen::FlagBlink;
break;
case 7:
// inverse
flag |= Screen::FlagInverse;
break;
}
}
screen->setFlag(flag);
_state = StateText;
break;
}
default:
NSLog(@"[%s %s]: unrecognized escape character: `ESC [ %c' (%02x)", object_getClassName(self), sel_getName(_cmd), c, (int)c);
_state = StateText;
break;
}
if (_state == StateText) _parms.clear();
return;
}
if (_state == StateRParen || _state == StateLParen)
{
NSLog(@"[%s %s]: unrecognized escape character: `ESC %c %c' (%02x)", object_getClassName(self), sel_getName(_cmd), _state == StateRParen ? ')' : '(', c, (int)c);
_state = StateText;
return;
}
if (_state == StateEsc)
{
switch(c)
{
case 0x00:
case 0x07f:
break;
case '(':
_state = StateLParen;
_parms.clear();
_parms.push_back(0);
break;
case ')':
_state = StateLParen;
_parms.clear();
_parms.push_back(0);
break;
case '[':
_state = StateBracket;
_parms.clear();
_parms.push_back(0);
break;
case '#':
_state = StatePound;
break;
case '=':
_flags.DECKPAM = 1;
_flags.DECKPNM = 0;
_altKeyPad = YES;
_state = StateText;
break;
case '>':
_flags.DECKPAM = 0;
_flags.DECKPNM = 1;
_altKeyPad = NO;
_state = StateText;
break;
case 'D':
// Index
screen->lineFeed();
_state = StateText;
break;
case 'M':
// Reverse Index
screen->reverseLineFeed();
_state = StateText;
break;
case 'Z':
output->write(ESC "[?1;0c");
_state = StateText;
break;
case '7':
// save cursor + attributes
case '8':
//restore cursor + attributes.
_state = StateText;
break;
default:
NSLog(@"[%s %s]: unrecognized escape character: `ESC %c' (%02x)", object_getClassName(self), sel_getName(_cmd), c, (int)c);
_state = StateText;
break;
}
return;
}
if (_state == StateText)
{
switch (c)
{
case 0x00:
case 0x7f:
break;
case 0x1b:
_state = StateEsc;
break;
case CTRL('G'):
NSBeep();
break;
case 0x08:
screen->decrementX();
break;
case '\t':
[self tab: screen];
break;
case '\n':
screen->lineFeed();
if (_flags.LNM)
screen->setX(0);
break;
case '\r':
screen->setX(0);
break;
default:
if (c >= 0x20 && c < 0x7f)
{
screen->putc(c);
}
break;
}
}
}
static void vt100ModeKey(unichar uc, NSEventModifierFlags flags, Screen *screen, OutputChannel *output, struct __vt100flags vt100flags)
{
const char *str = NULL;
if (vt100flags.DECKPAM && (flags & NSFunctionKeyMask))
{
switch (uc)
{
case '0':
str = ESC "Op";
break;
case '1':
str = ESC "Oq";
break;
case '2':
str = ESC "Or";
break;
case '3':
str = ESC "Os";
break;
case '4':
str = ESC "Ot";
break;
case '5':
str = ESC "Ou";
break;
case '6':
str = ESC "Ov";
break;
case '7':
str = ESC "Ow";
break;
case '8':
str = ESC "Ox";
break;
case '9':
str = ESC "Oy";
break;
case ',':
str = ESC "Ol";
break;
case '-':
str = ESC "Om";
break;
case '.':
str = ESC "On";
break;
case NSNewlineCharacter: //?
case NSEnterCharacter:
str = ESC "?M";
break;
}
if (str)
{
output->write(str);
return;
}
}
switch(uc)
{
case NSUpArrowFunctionKey:
str = vt100flags.DECKPAM ? ESC "OA" : ESC "[A";
break;
case NSDownArrowFunctionKey:
str = vt100flags.DECKPAM ? ESC "OB" : ESC "[B";
break;
case NSRightArrowFunctionKey:
str = vt100flags.DECKPAM ? ESC "OC" : ESC "[C";
break;
case NSLeftArrowFunctionKey:
str = vt100flags.DECKPAM ? ESC "OD" : ESC "[D";
break;
case NSF1FunctionKey:
str = ESC "OP";
break;
case NSF2FunctionKey:
str = ESC "OQ";
break;
case NSF3FunctionKey:
str = ESC "OR";
break;
case NSF4FunctionKey:
str = ESC "OS";
break;
case '\n':
if (vt100flags.LNM)
output->write("\r\n");
else output->write('\n');
break;
case NSDeleteCharacter:
uc = 0x7f;
// fallthrough.
default:
if (uc <= 0x7f)
{
uint8_t c = uc;
if (flags & NSControlKeyMask)
{
c = CTRL(c);
}
output->write(c);
}
break;
}
if (str)
{
output->write(str);
}
}
static void vt52ModeKey(unichar uc, NSEventModifierFlags flags, Screen * screen, OutputChannel *output, struct __vt100flags vt100flags)
{
const char *str = NULL;
if (vt100flags.DECKPAM && (flags & NSNumericPadKeyMask))
{
switch(uc)
{
case '0':
str = ESC "?p";
break;
case '1':
str = ESC "?q";
break;
case '2':
str = ESC "?r";
break;
case '3':
str = ESC "?s";
break;
case '4':
str = ESC "?t";
break;
case '5':
str = ESC "?u";
break;
case '6':
str = ESC "?v";
break;
case '7':
str = ESC "?w";
break;
case '8':
str = ESC "?x";
break;
case '9':
str = ESC "?y";
break;
case ',':
str = ESC "?l";
break;
case '-':
str = ESC "?m";
break;
case '.':
str = ESC "?n";
break;
case NSNewlineCharacter: //?
case NSEnterCharacter:
str = ESC "?M";
break;
}
if (str)
{
output->write(str);
return;
}
}
switch(uc)
{
case NSUpArrowFunctionKey:
str = ESC "A";
break;
case NSDownArrowFunctionKey:
str = ESC "B";
break;
case NSRightArrowFunctionKey:
str = ESC "C";
break;
case NSLeftArrowFunctionKey:
str = ESC "D";
break;
case NSF1FunctionKey:
str = ESC "P";
break;
case NSF2FunctionKey:
str = ESC "Q";
break;
case NSF3FunctionKey:
str = ESC "R";
break;
case NSF4FunctionKey:
str = ESC "S";
break;
case NSDeleteCharacter:
uc = 0x7f;
// fallthrough..
default:
if (uc <= 0x7f)
{
uint8_t c = uc;
if (flags & NSControlKeyMask)
{
c = CTRL(c);
}
output->write(c);
}
break;
}
if (str)
{
output->write(str);
}
}
-(void)keyDown:(NSEvent *)event screen:(Screen *)screen output:(OutputChannel *)output
{
NSEventModifierFlags flags = [event modifierFlags];
NSString *chars = [event charactersIgnoringModifiers];
NSUInteger length = [chars length];
for (unsigned i = 0; i < length; ++i)
{
unichar uc = [chars characterAtIndex: i];
if (_vt52Mode)
vt52ModeKey(uc, flags, screen, output, _flags);
else
vt100ModeKey(uc, flags, screen, output, _flags);
}
}
-(void)tab: (Screen *)screen
{
/*
* TAB (011_8) causes the cursor to move right to the next TAB stop each time the TAB code is received.
* TAB stops are preset eight character spaces apart. TAB stop locations are at characters positions 1, 9,
* 17, 25, 33, 41, 49, 57, and 65. Once the cursor reaches character position 65, all TAB commands
* received thereafter will cause the cursor to move only one character position. Once the cursor reaches
* character position 72, receipt of the the TAB code has no effect.
*/
int x = screen->x();
if (x >= screen->width() - 8)
{
screen->tabTo(x + 1);
}
else
{
screen->tabTo((x + 8) & ~7);
}
}
@end