TwoTerm/VT52View.mm

676 lines
15 KiB
Plaintext

//
// VT52View.mm
// 2Term
//
// Created by Kelvin Sherlock on 7/2/2010.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//
#include <cctype>
#include <algorithm>
#import "VT52View.h"
const char esc = 0x1b;
#define ESC "\x1b"
enum {
StateText,
StateEscape,
StateEscapeY1,
StateEscapeY2
};
@interface VT52View (Cursor)
-(void)cursorLeft;
-(void)cursorRight;
-(void)cursorUp;
-(void)cursorDown;
-(void)cursorHome;
-(void)_setCursor: (CursorPosition)cursor;
-(void)lineFeed;
-(void)reverseLineFeed;
-(void)carriageReturn;
-(void)tab;
@end
@interface VT52View (Private)
-(void)invalidate;
-(void)processCharacter: (uint8_t)c;
-(void)appendChar: (uint8_t)c;
-(void)eraseLine;
-(void)eraseScreen;
@end
@implementation VT52View
-(void)dataAvailable
{
//NB -- this is not the main thread.
// actually read the data
for(;;)
{
ssize_t i;
uint8_t buffer[512];
ssize_t size = read(_fd, buffer, sizeof(buffer));
NSAutoreleasePool *pool;
if (size == 0) break;
if (size < 0)
{
if (errno == EAGAIN) break;
perror("[VT52View dataAvailable] : read: ");
break;
}
[_lock lock];
pool = [NSAutoreleasePool new];
for (i = 0 ; i < size; ++i)
{
//if (buffer[i] < ' ') std::fprintf(stderr, "%02x\n", (int)buffer[i]);
[self processCharacter: buffer[i]];
}
[self invalidate];
[_lock unlock];
[pool release];
}
}
-(void)keyDown: (NSEvent *)theEvent
{
unsigned flags = [theEvent modifierFlags];
NSString *chars = [theEvent charactersIgnoringModifiers];
unsigned length = [chars length];
unsigned i;
if (flags & NSCommandKeyMask) return;
// length should always be 1...
for (i = 0 ; i < length; ++i)
{
unichar uc = [chars characterAtIndex: i];
char c;
if (flags & NSNumericPadKeyMask)
{
const char *str = NULL;
if (_altKeyPad)
{
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;
}
}
switch (uc)
{
case NSEnterCharacter:
uc = '\r';
break;
case NSUpArrowFunctionKey:
str = ESC "A";
break;
case NSDownArrowFunctionKey:
str = ESC "B";
break;
case NSRightArrowFunctionKey:
str = ESC "C";
break;
case NSLeftArrowFunctionKey:
str = ESC "D";
break;
}
if (str)
{
ssize_t size = write(_fd, str, strlen(str));
if (size < 0)
{
perror("keyDown: write");
}
continue;
}
}
if (uc > 0x7f) continue;
c = uc;
if (flags & NSControlKeyMask)
{
// 040, 0100, and 0140 are all equivalent
c &= 0x1f;
}
/*
else
{
if (c == NSEnterCharacter) c = '\r';
}
*/
write(_fd, &c, 1);
}
}
@end
@implementation VT52View (Cursor)
// these are not thread safe...
#pragma mark -
#pragma mark Cursor Control
-(void)setCursor: (CursorPosition)cursor
{
[_lock lock];
[self _setCursor: cursor];
//[self invalidate];
[_lock unlock];
}
-(void)_setCursor: (CursorPosition)cursor
{
if (cursor.x < 0) cursor.x = 0;
if (cursor.x >= _width) cursor.x = _width - 1;
if (cursor.y < 0) cursor.y = 0;
if (cursor.y >= _height) cursor.y = _height - 1;
if (cursor == _cursor) return;
// TODO -- cursor should be a child view, to handle blinking, cursor style, etc.
//_updates.push_back(_cursor);
//_updates.push_back(cursor);
_cursor = cursor;
}
-(void)cursorLeft
{
if (_cursor.x == 0) return;
[self _setCursor: CursorPosition(_cursor.x - 1, _cursor.y)];
}
-(void)cursorRight
{
if (_cursor.x == (_width - 1)) return;
[self _setCursor: CursorPosition(_cursor.x + 1, _cursor.y)];
}
-(void)cursorUp
{
if (_cursor.y == 0) return;
[self _setCursor: CursorPosition(_cursor.x, _cursor.y - 1)];
}
-(void)cursorDown
{
if (_cursor.y == (_height - 1)) return;
[self _setCursor: CursorPosition(_cursor.x, _cursor.y + 1)];
}
-(void)lineFeed
{
// increment line, column doesn't change. If already on the bottom line, a 1-line scroll occurs.
if (_cursor.y == _height - 1)
{
CharInfo tmp = {0,0};
// scroll all the lines....
for (unsigned y = 1; y < _height; ++y)
{
for (unsigned x = 0; x < _width; ++x)
{
_screen[y-1][x] = _screen[y][x];
}
}
for (unsigned x = 0; x < _width; ++x)
{
_screen[_height -1][x] = tmp;
}
[self setNeedsDisplay: YES];
}
else
{
[self _setCursor: CursorPosition(_cursor.x, _cursor.y + 1)];
}
}
-(void)reverseLineFeed
{
// decrement line, column doesn't change. If already on the bottom line, a 1-line scroll occurs.
if (_cursor.y == 0)
{
CharInfo tmp = {0,0};
// scroll all the lines....
for (unsigned y = 0; y < _height - 1; ++y)
{
for (unsigned x = 0; x < _width; ++x)
{
_screen[y+1][x] = _screen[y][x];
}
}
for (unsigned x = 0; x < _width; ++x)
{
_screen[0][x] = tmp;
}
[self setNeedsDisplay: YES];
}
else
{
[self _setCursor: CursorPosition(_cursor.x, _cursor.y - 1)];
}
}
-(void)carriageReturn
{
// move x to 0.
if (_cursor.x == 0) return;
[self _setCursor: CursorPosition(0, _cursor.y)];
}
-(void)cursorHome
{
[self _setCursor: CursorPosition(0,0)];
}
-(void)tab
{
// TODO -- does this insert spaces?
// move right 1 (or more) positions.
// stops (1-based): 9, 17, 25, 33, 41, 49, 57, 65, 73,
// if x >= 73, move right 1. If x == _width
int x = _cursor.x;
if (x == _width -1) return;
//x += 1;
// doesn't handle end case...
x = (x + 8) & ~0x07;
[self _setCursor: CursorPosition(x, _cursor.y)];
}
@end
@implementation VT52View (Private)
-(void)invalidate
{
// caller must lock prior to calling.
// resets the _updates list.
std::vector<struct CursorPosition>::iterator iter;
int minX = _width - 1;
int maxX = 0;
int minY = _height - 1;
int maxY = 0;
if (_updates.empty()) return;
for (iter = _updates.begin(); iter != _updates.end(); ++iter)
{
minX = std::min(minX, iter->x);
maxX = std::max(maxX, iter->x);
minY = std::min(minY, iter->y);
maxY = std::max(maxY, iter->y);
}
// TODO -- character height/width sizes.
[self setNeedsDisplayInRect: NSMakeRect(minX * _charWidth, minY * _charHeight, (maxX - minX + 1) * _charWidth, (maxY - minY + 1) * _charHeight)];
_updates.clear();
}
// state machine.
-(void)processCharacter: (uint8_t)c
{
switch (_state)
{
case StateText:
{
switch (c)
{
case 0x00:
case 0x7f:
// padding;
break;
case 0x1b:
_state = StateEscape;
break;
case 0x07:
// bell...
NSBeep();
break;
case 0x08:
[self cursorLeft];
// backspace
break;
case 0x09:
// tab
[self tab];
break;
case 0x0a:
case 0x0b:
case 0x0c:
// lf
[self lineFeed];
break;
case 0x0d:
// cr
[self carriageReturn];
break;
case 0x0e:
case 0x0f:
// g0/g1 char set
break;
default:
if (c >= 0x20 && c <= 0x7f)
[self appendChar: c];
break;
}
break;
}
case StateEscape:
{
switch (c)
{
case 0x00:
case 0x7f:
break;
case 0x1b:
// on vt52 is ignored, on vt50 cancels escape sequence.
if (_vt50) _state = StateText;
break;
case 'A':
_state = StateText;
[self cursorUp];
break;
case 'B':
_state = StateText;
[self cursorDown];
break;
case 'C':
_state = StateText;
[self cursorRight];
break;
case 'D':
_state = StateText;
[self cursorLeft];
break;
case 'I':
_state = StateText;
if (!_vt50)
[self reverseLineFeed];
break;
case 'H':
_state = StateText;
[self _setCursor: CursorPosition(0,0)];
break;
case 'J':
_state = StateText;
[self eraseScreen];
break;
case 'K':
_state = StateText;
[self eraseLine];
break;
case 'Y':
_state = StateEscapeY1;
break;
case '=':
_state = StateText;
_altKeyPad = YES;
break;
case '>':
_state = StateText;
_altKeyPad = NO;
break;
case 'Z':
//identity
_state = StateText;
write(_fd, _vt50 ? ESC "/A" : ESC "/K", 3);
break;
case '1':
case '2':
// alt graphic modes (unsupported)
_state = StateText;
break;
case 'F':
case 'G':
_state = StateText;
// graphic/ascii char set (unsupported)
break;
/*
case '<':
//ANSI mode (vt100)
_state = StateText;
break;
*/
default:
_state = StateText;
std::fprintf(stderr, "Unrecognized escape character: %c\n", c);
break;
}
break;
}
/*
* ESC Y line# column#
* line# 040--067
*
* vt50H moved to bottom line if out of bounds.
* vt52 does not adjust if out of bounds.
*
* column# 040--0157
* if out of bounds, moves to the rightmost column.
*/
case StateEscapeY1:
{
_state = StateEscapeY2;
if (c >= 0x20) c -= 0x20;
else c = -1;
_yTemp[0] = c;
break;
}
case StateEscapeY2:
{
CursorPosition cp = _cursor;
_state = StateText;
if (c >= 0x20) c -= 0x20;
else c = -1;
_yTemp[1] = c;
// vt52 style.
if (_yTemp[0] < _height) cp.y = _yTemp[0];
if (_yTemp[1] < _width) cp.x = _yTemp[1];
[self _setCursor: cp];
break;
}
}
}
-(void)appendChar: (uint8_t)c
{
CharInfo ci = { c, 0 };
int x = _cursor.x;
int y = _cursor.y;
if (y == _width) return; // eol.
_screen[y][x] = ci;
_updates.push_back(_cursor);
[self _setCursor: CursorPosition(x + 1, y)];
}
-(void)eraseLine
{
CharInfo clear = {0, 0};
for (unsigned x = _cursor.x; x < _width; ++x)
{
_screen[_cursor.y][x] = clear;
}
// everything in between will be redrawn...
_updates.push_back(_cursor);
_updates.push_back(CursorPosition(_width - 1, _cursor.y));
}
-(void)eraseScreen
{
CharInfo clear = {0, 0};
// erase line and all lines below.
[self eraseLine];
for (unsigned y = _cursor.y + 1; y < _height; ++y)
{
for (unsigned x = 0; x < _width; ++x)
{
_screen[y][x] = clear;
}
}
_updates.push_back(CursorPosition(0, _cursor.y + 1));
_updates.push_back(CursorPosition(_width - 1, _height - 1));
}
@end