// // VT50.m // TwoTerm // // Created by Kelvin Sherlock on 3/2/2018. // #include #include #include #include #include #import "VT50.h" #include "OutputChannel.h" #include "Screen.h" #define ESC "\x1b" enum { ModelVT50, ModelVT50H, ModelVT52, ModelVT55 }; namespace { void normalize(iRect &r){ r.origin.y <<= 1; r.size.height <<= 1; } void normalize(iPoint &p) { p.y <<= 1; } } %%{ machine vt50; alphtype unsigned int; esc = 0x1b; action vt50h { _model == ModelVT50H } action vt50 { _model == ModelVT50 } action tab { if (cursor.x < 72) cursor.x = (cursor.x + 8) & ~7; else if (cursor.x < window.maxX() -1) cursor.x++; } action linefeed { if (cursor.y < window.maxY() - 1) cursor.y++; else { screen->scrollUp(); screen->scrollUp(); } } action rlinefeed { // this is documented as being vt52++ // however the 2BSD termcap entry (dating to 1980) // and every termcap since claims 50h supports it... if (cursor.y) cursor.y--; else { screen->scrollDown(); screen->scrollDown(); } } action erase_eos { iRect tmp; tmp.origin = cursor; tmp.size = iSize( window.maxX() - cursor.x, 1); normalize(tmp); screen->eraseRect(tmp); tmp.origin = iPoint(0, cursor.y+1); tmp.size = iSize(window.maxX(), window.maxY() - cursor.y - 1); normalize(tmp); screen->eraseRect(tmp); } action erase_eol { iRect tmp; tmp.origin = cursor; tmp.size = iSize( window.maxX() - cursor.x, 1); normalize(tmp); screen->eraseRect(tmp); } action identify { // NB -- these indicate no copier. switch(_model) { case ModelVT50: output->write(ESC "/A"); break; case ModelVT50H: output->write(ESC "/H"); break; } } action dca { unsigned y = _scratch[0]; if (y >= window.maxY()) y = window.maxY() -1; cursor.y = y; unsigned x = _scratch[1]; if (x >= window.maxX()) x = window.maxX() -1; cursor.x = x; } action forward { if (cursor.x > window.maxX()-1) { cursor.x = window.minX(); if (cursor.y >= window.maxY()-1) { screen->scrollUp(); screen->scrollUp(); } else { cursor.y++; } } } arg1 = any ${ _scratch[0] = ((fc & 0x7f) - 32); }; arg2 = any ${ _scratch[1] = ((fc & 0x7f) - 32); }; control_codes = ( 0x07 ${ NSBeep(); } | 0x08 ${ if (cursor.x) cursor.x--; } | 0x09 $tab | 0x0a $linefeed | 0x0d ${ cursor.x = 0; } | 0x0e when vt50h arg1 arg2 $dca | cntrl - esc ); escape_codes = control_codes* <: ( esc | 'A' ${ if (cursor.y) cursor.y--; } | 'B' when vt50h ${ if (cursor.y < window.maxY() -1) cursor.y++; } | 'C' ${ if (cursor.x < window.maxX() -1) cursor.x++; } | 'D' when vt50h ${ if (cursor.x) cursor.x--; } #| 'F' when vt52_or_better ${ _graphics = true; } #| 'G' when vt52_or_better ${ _graphics = false; } | 'H' ${ cursor = iPoint(0, 0); } | 'I' when vt50h $rlinefeed | 'J' $erase_eos | 'K' $erase_eol | 'Y' when vt50h arg1 arg2 $dca | 'Z' $identify #| '=' when vt52_or_better ${ _altKeyPad = true; } #| '>' when vt52_or_better ${ _altKeyPad = false; } | any ); main := ( control_codes | esc escape_codes | 0x20 .. 0x7e $forward ${ uint8_t c = fc; if (c & 0x40) c &= ~0x20; screen->putc(c, iPoint(cursor.x, cursor.y << 1), 0); cursor.x++; } | any )** $err { fgoto main; }; write data; }%% @implementation VT50x -(BOOL)resizable { return NO; } -(struct winsize)defaultSize { struct winsize ws = { 0, 0, 0, 0}; // VT50x have 12 rows. They are double spaced. ws.ws_row = 12; ws.ws_col = 80; return ws; } -(struct winsize)displaySize { struct winsize ws = { 0, 0, 0, 0}; // VT50x have 12 rows. They are double spaced. ws.ws_row = 24; ws.ws_col = 80; return ws; } +(NSString *)name { return @""; } -(NSString *)name { return @""; } -(const char *)termName { return ""; } -(void)reset: (BOOL)hard { %% write init; _altKeyPad = false; _graphics = false; if (hard) { _context.cursor = iPoint(0,0); _context.window = iRect(0, 0, 80, 12); } _context.flags = 0; } -(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]; uint8_t c; if (flags & NSNumericPadKeyMask) { if (_altKeyPad) { const char *str = NULL; 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 "?n"; break; case NSNewlineCharacter: //? case NSEnterCharacter: str = ESC "?M"; break; } if (str) { output->write(str); break; } } } switch (uc) { case NSEnterCharacter: output->write('\r'); break; case NSDeleteCharacter: output->write(0x7f); break; case NSUpArrowFunctionKey: output->write(ESC "A"); break; case NSDownArrowFunctionKey: output->write(ESC "B"); break; case NSRightArrowFunctionKey: output->write(ESC "C"); break; case NSLeftArrowFunctionKey: output->write(ESC "D"); break; // 3 function keys. (VT50H / VT52) case NSF1FunctionKey: output->write(ESC "P"); break; case NSF2FunctionKey: output->write(ESC "Q"); break; case NSF3FunctionKey: output->write(ESC "R"); break; default: if (uc > 0x7f) break; c = uc; if (flags & (NSShiftKeyMask | NSAlphaShiftKeyMask)) { c = toupper(c); } if (flags & NSControlKeyMask) { c = CTRL(c); } output->write(c); break; } } } -(void)processData: (uint8_t *)data length: (size_t)length screen:(Screen *)screen output:(OutputChannel *)output { std::transform(data, data + length, data, [](uint8_t c){ return c & 0x7f; }); const uint8_t *eof = nullptr; const uint8_t *p = data; const uint8_t *pe = std::copy_if(data, data + length, data, [](uint8_t c){ if (c == 0 || c == 0x7f) return false; return true; }); iPoint &cursor = _context.cursor; const iRect &window = _context.window; %%write exec; auto cc = cursor; cc.y <<= 1; if (cc.x == 80) cc.x = 79; screen->setCursor(cc); } @end @implementation VT50 +(void)load { [EmulatorManager registerClass: self]; } +(NSString *)name { return @"VT50"; } -(NSString *)name { return @"VT50"; } -(const char *)termName { return "vt50"; } -(id)init { if ((self = [super init])) { _model = ModelVT50; [self reset: YES]; } return self; } @end @implementation VT50H +(void)load { [EmulatorManager registerClass: self]; } +(NSString *)name { return @"VT50H"; } -(NSString *)name { return @"VT50H"; } -(const char *)termName { return "vt50h"; } -(id)init { if ((self = [super init])) { _model = ModelVT50H; [self reset: YES]; } return self; } @end