mirror of
https://github.com/ksherlock/TwoTerm.git
synced 2024-12-22 07:30:40 +00:00
bbd0af01dc
git-svn-id: svn://qnap.local/TwoTerm/trunk@3180 5590a31f-7b70-45f8-8c82-aa3a8e5f4507
598 lines
14 KiB
Plaintext
598 lines
14 KiB
Plaintext
//
|
|
// VT52.mm
|
|
// 2Term
|
|
//
|
|
// Created by Kelvin Sherlock on 7/7/2010.
|
|
// Copyright 2010 __MyCompanyName__. All rights reserved.
|
|
//
|
|
|
|
#include <sys/ttydefaults.h>
|
|
#include <cctype>
|
|
#include <cstdio>
|
|
#include <numeric>
|
|
|
|
#import "VT52.h"
|
|
#include "OutputChannel.h"
|
|
#include "Screen.h"
|
|
|
|
enum {
|
|
StateText,
|
|
StateDCAY,
|
|
StateDCAX
|
|
};
|
|
|
|
|
|
|
|
|
|
/*
|
|
* TODO -- the VT50x are 12 rows but double spaced.
|
|
*
|
|
*
|
|
*/
|
|
|
|
// VT52 is 24 x 80 upper/lowercase.
|
|
// 50/50H is 12 * 80, uppercase only. H has a keypad.
|
|
// The 50s only display/transmit uppercase characters and lack `~ {} characters on the keypad.
|
|
// VT55 is a VT52 with extra graphic display capabilites.
|
|
enum {
|
|
ModelVT50,
|
|
ModelVT50H,
|
|
ModelVT52,
|
|
ModelVT55
|
|
};
|
|
|
|
|
|
|
|
|
|
@implementation VT50
|
|
|
|
+(void)load {
|
|
[EmulatorManager registerClass: self];
|
|
}
|
|
|
|
+(NSString *)name {
|
|
return @"VT50";
|
|
}
|
|
-(NSString *)name {
|
|
return @"VT50";
|
|
}
|
|
|
|
-(const char *)termName {
|
|
return "vt50";
|
|
}
|
|
|
|
-(struct winsize)displaySize
|
|
{
|
|
struct winsize ws = { 24, 80, 0, 0 };
|
|
|
|
return ws;
|
|
}
|
|
|
|
|
|
-(id)init {
|
|
if ((self = [super init])) {
|
|
_model = ModelVT50;
|
|
[self reset];
|
|
|
|
}
|
|
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];
|
|
|
|
}
|
|
return self;
|
|
}
|
|
|
|
-(struct winsize)displaySize
|
|
{
|
|
struct winsize ws = { 24, 80, 0, 0 };
|
|
|
|
return ws;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation VT52
|
|
|
|
+(void)load {
|
|
[EmulatorManager registerClass: self];
|
|
}
|
|
|
|
|
|
+(NSString *)name {
|
|
return @"VT52";
|
|
}
|
|
|
|
-(NSString *)name {
|
|
return @"VT52";
|
|
}
|
|
|
|
-(const char *)termName {
|
|
return "vt52";
|
|
}
|
|
|
|
-(id)init {
|
|
if ((self = [super init])) {
|
|
_model = ModelVT52;
|
|
[self reset];
|
|
}
|
|
return self;
|
|
}
|
|
@end
|
|
|
|
@implementation VT55
|
|
|
|
+(void)load {
|
|
//[EmulatorManager registerClass: self];
|
|
}
|
|
|
|
+(NSString *)name {
|
|
return @"VT55";
|
|
}
|
|
|
|
-(NSString *)name {
|
|
return @"VT55";
|
|
}
|
|
|
|
-(const char *)termName {
|
|
return "vt55";
|
|
}
|
|
|
|
-(id)init {
|
|
if ((self = [super init])) {
|
|
_model = ModelVT55;
|
|
[self reset];
|
|
|
|
}
|
|
return self;
|
|
}
|
|
@end
|
|
|
|
|
|
|
|
|
|
#define ESC "\x1b"
|
|
|
|
@implementation VT5x
|
|
|
|
|
|
|
|
|
|
+(NSString *)name {
|
|
return @"";
|
|
}
|
|
|
|
-(NSString *)name {
|
|
return @"";
|
|
}
|
|
|
|
-(const char *)termName {
|
|
return "";
|
|
}
|
|
|
|
|
|
-(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)reset
|
|
{
|
|
cs = StateText;
|
|
_escape = false;
|
|
_altKeyPad = false;
|
|
_graphics = false;
|
|
_context.cursor = iPoint(0,0);
|
|
_context.window = iRect(0, 0, 80, 24);
|
|
if (_model <= ModelVT50H) _context.window = iRect(0, 0, 80, 12);
|
|
_context.flags = 0;
|
|
}
|
|
|
|
|
|
|
|
static void advance(context &ctx, Screen *screen) {
|
|
if (ctx.cursor.x < ctx.window.maxX()-1) ctx.cursor.x++;
|
|
}
|
|
|
|
-(void)processData: (uint8_t *)data length: (size_t)length screen: (Screen *)screen output: (OutputChannel *)output
|
|
{
|
|
|
|
|
|
cs = std::accumulate(data, data + length, cs, [&](unsigned state, uint8_t c) -> unsigned {
|
|
|
|
auto &cursor = _context.cursor;
|
|
auto &window = _context.window;
|
|
|
|
c &= 0x7f;
|
|
if (c == 0x7f) return state; // pad character
|
|
if (c < 32) {
|
|
// control characters are always control characters
|
|
switch(c) {
|
|
// bell
|
|
case 007: NSBeep(); break;
|
|
// backspace
|
|
case 010: if (cursor.x) cursor.x--; break;
|
|
//tab
|
|
case 011:
|
|
if (cursor.x < 72) cursor.x = (cursor.x + 8) & ~7;
|
|
else if (cursor.x < window.maxX() -1) cursor.x++;
|
|
break;
|
|
// linefeed
|
|
case 012:
|
|
if (cursor.y < window.maxY() -1) cursor.y++;
|
|
else {
|
|
if (_model <= ModelVT50H) screen->scrollUp();
|
|
screen->scrollUp();
|
|
}
|
|
break;
|
|
// cr
|
|
case 015: cursor.x = 0; break;
|
|
// vt05-compatible dca
|
|
case 016:
|
|
if (_model == ModelVT50) return StateDCAY;
|
|
break;
|
|
|
|
case 0x1b: // escape.
|
|
if (_model >= ModelVT52) _escape = true;
|
|
else _escape = !_escape;
|
|
break;
|
|
|
|
}
|
|
return state;
|
|
}
|
|
|
|
if (state == StateDCAY) {
|
|
c -= 32;
|
|
//if (_model <= ModelVT50H) c *= 2; // double it up.
|
|
|
|
if (c >= window.maxY()) {
|
|
if (_model <= ModelVT50H) cursor.y = window.maxY() -1;
|
|
else c = cursor.y;
|
|
}
|
|
cursor.y = c;
|
|
return StateDCAX;
|
|
}
|
|
if (state == StateDCAX) {
|
|
c -= 32;
|
|
if (c >= window.maxX()) c = window.maxX() -1;
|
|
cursor.x = c;
|
|
return StateText;
|
|
}
|
|
|
|
if (_escape) {
|
|
_escape = false;
|
|
switch(c) {
|
|
case 'A': if (cursor.y) cursor.y--; break;
|
|
case 'B':
|
|
if (_model >= ModelVT50H && cursor.y < window.maxY() -1)
|
|
cursor.y++;
|
|
break;
|
|
case 'C':
|
|
if (cursor.x < window.maxX() -1) cursor.x++;
|
|
break;
|
|
case 'D':
|
|
if (_model >= ModelVT50H && cursor.x) cursor.x--;
|
|
break;
|
|
|
|
// alt graphics
|
|
case 'F':
|
|
if (_model >= ModelVT52) _graphics = true;
|
|
break;
|
|
case 'G':
|
|
if (_model >= ModelVT52) _graphics = false;
|
|
break;
|
|
|
|
|
|
// cursor home
|
|
case 'H': cursor = iPoint(0,0); break;
|
|
|
|
// reverse line feed
|
|
case 'I':
|
|
// this is documented as being vt52++
|
|
// however the 2BSD termcap entry (dating to 1980)
|
|
// and every termcap since claims 50h supports it...
|
|
if (_model >= ModelVT50H) {
|
|
if (cursor.y) cursor.y--;
|
|
else {
|
|
screen->scrollDown();
|
|
if (_model <= ModelVT50H) screen->scrollDown();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'J': {
|
|
// erase to end-of screen
|
|
|
|
iRect tmp;
|
|
|
|
if (_model <= ModelVT50H) {
|
|
cursor.y *= 2;
|
|
window.size.height *= 2;
|
|
}
|
|
|
|
tmp.origin = cursor;
|
|
tmp.size = iSize( window.maxX() - cursor.x, 1);
|
|
screen->eraseRect(tmp);
|
|
|
|
tmp.origin = iPoint(0, cursor.y+1);
|
|
tmp.size = iSize(window.maxX(), window.maxY() - cursor.y - 1);
|
|
screen->eraseRect(tmp);
|
|
|
|
if (_model <= ModelVT50H) {
|
|
cursor.y /= 2;
|
|
window.size.height /= 2;
|
|
}
|
|
|
|
|
|
break;
|
|
}
|
|
|
|
// erase to eol
|
|
case 'K': {
|
|
iRect tmp;
|
|
|
|
if (_model <= ModelVT50H) {
|
|
cursor.y *= 2;
|
|
window.size.height *= 2;
|
|
}
|
|
|
|
tmp.origin = cursor;
|
|
tmp.size = iSize( window.maxX() - cursor.x, 1);
|
|
screen->eraseRect(tmp);
|
|
|
|
if (_model <= ModelVT50H) {
|
|
cursor.y /= 2;
|
|
window.size.height /= 2;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
|
|
// direct cursor addressing
|
|
case 'Y':
|
|
if (_model >= ModelVT50H) {
|
|
_escape = false;
|
|
return StateDCAY;
|
|
}
|
|
break;
|
|
|
|
case 'Z':
|
|
// identify terminal.
|
|
// NB -- these indicate no copier.
|
|
switch(_model) {
|
|
case ModelVT50:
|
|
output->write(ESC "/A");
|
|
break;
|
|
case ModelVT50H:
|
|
output->write(ESC "/H");
|
|
break;
|
|
case ModelVT52:
|
|
output->write(ESC "/K");
|
|
break;
|
|
case ModelVT55:
|
|
output->write(ESC "/C");
|
|
break;
|
|
}
|
|
break;
|
|
// alternate keypad
|
|
case '=':
|
|
if (_model >= ModelVT52) _altKeyPad = true;
|
|
break;
|
|
case '>':
|
|
if (_model >= ModelVT52) _altKeyPad = false;
|
|
break;
|
|
|
|
|
|
}
|
|
return state;
|
|
}
|
|
// normal text!
|
|
|
|
|
|
if (c >= 0140 && (_model <= ModelVT50H))
|
|
c -= 040;
|
|
|
|
if (_model <= ModelVT50H) {
|
|
auto tmp = cursor; tmp.y *= 2;
|
|
screen->putc(c, tmp, _context.flags);
|
|
} else screen->putc(c, cursor, _context.flags);
|
|
|
|
advance(_context, screen);
|
|
return state;
|
|
|
|
|
|
});
|
|
|
|
if (_model <= ModelVT50H) {
|
|
auto tmp = _context.cursor; tmp.y *= 2;
|
|
screen->setCursor(tmp);
|
|
} else screen->setCursor(_context.cursor);
|
|
|
|
}
|
|
|
|
-(BOOL)resizable
|
|
{
|
|
return NO;
|
|
|
|
switch (_model)
|
|
{
|
|
case ModelVT52:
|
|
case ModelVT55:
|
|
return YES;
|
|
default:
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
-(struct winsize)defaultSize
|
|
{
|
|
struct winsize ws = { 0, 0, 0, 0};
|
|
|
|
// TODO -- although VT50x have 12 rows, they are double spaced.
|
|
|
|
switch (_model)
|
|
{
|
|
case ModelVT52:
|
|
case ModelVT55:
|
|
ws.ws_row = 24;
|
|
ws.ws_col = 80;
|
|
break;
|
|
|
|
default:
|
|
ws.ws_row = 12;
|
|
ws.ws_col = 80;
|
|
break;
|
|
}
|
|
|
|
return ws;
|
|
|
|
}
|
|
|
|
|
|
@end
|