mirror of
https://github.com/ksherlock/TwoTerm.git
synced 2024-12-22 07:30:40 +00:00
448 lines
9.5 KiB
Plaintext
448 lines
9.5 KiB
Plaintext
//
|
|
// VT50.m
|
|
// TwoTerm
|
|
//
|
|
// Created by Kelvin Sherlock on 3/2/2018.
|
|
//
|
|
|
|
#include <sys/ttydefaults.h>
|
|
#include <cctype>
|
|
#include <cstdio>
|
|
#include <numeric>
|
|
#include <algorithm>
|
|
|
|
#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 vt50 arg1 arg2 $dca
|
|
);
|
|
|
|
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
|