mirror of
https://github.com/ksherlock/TwoTerm.git
synced 2024-12-22 07:30:40 +00:00
609 lines
19 KiB
Plaintext
609 lines
19 KiB
Plaintext
//
|
|
// GSOSConsole.m
|
|
// 2Term
|
|
//
|
|
// Created by Kelvin Sherlock on 7/9/2016.
|
|
//
|
|
//
|
|
|
|
#import "GSOSConsole.h"
|
|
|
|
#include "OutputChannel.h"
|
|
#include "Screen.h"
|
|
|
|
#include "algorithm.h"
|
|
|
|
// currently no way to set consFill :-)
|
|
static char_info remap_fill(const gsos_context &ctx) { return char_info(' ', 0); }
|
|
|
|
static void advance(Screen *screen, gsos_context &ctx) {
|
|
|
|
if (ctx.consAdvance) {
|
|
if (ctx.cursor.x < ctx.window.maxX() - 1) ctx.cursor.x++;
|
|
else if (ctx.consWrap) {
|
|
ctx.cursor.x = ctx.window.minX();
|
|
if (ctx.cursor.y < ctx.window.maxY() - 1) ctx.cursor.y++;
|
|
else if (ctx.consScroll) screen->scrollUp(ctx.window);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
%%{
|
|
machine console;
|
|
alphtype unsigned int;
|
|
|
|
action nop {}
|
|
|
|
arg1 = any ${ _scratch[0] = (fc - 32); };
|
|
arg2 = any ${ _scratch[1] = (fc - 32); };
|
|
arg3 = any ${ _scratch[2] = (fc - 32); };
|
|
arg4 = any ${ _scratch[3] = (fc - 32); };
|
|
|
|
|
|
action advance_if {
|
|
if (_context.consAdvance) advance(screen, _context);
|
|
}
|
|
|
|
main := (
|
|
0x00 $nop
|
|
| 0x01 ${
|
|
/*
|
|
Save Current Text Port and Reset Default Text Port
|
|
|
|
Saves the current text port and resets to the default text port. If the system is out of memory, no error is returned, and the text port is simply reset.
|
|
*/
|
|
|
|
_context_stack.push_back(_context);
|
|
_context = gsos_context();
|
|
_context.cursor = iPoint(0,0);
|
|
_context.window = iRect(0, 0, 80, 24);
|
|
}
|
|
|
|
| 0x02 arg1 arg2 arg3 arg4 ${
|
|
/*
|
|
Set Text Port Size
|
|
|
|
Accepts the next four bytes as absolute screen coordinates + 32. Sets the current text port to the new parameters. The parameters are in the following order: windLeft, windTop, windRight, windBottom. Any parameter outside the screen boundaries is clipped to a legal value. The cursor is set to the upper-left corner of the new text port.
|
|
*/
|
|
|
|
_scratch[0] = clamp(_scratch[0], 0, 80-1);
|
|
_scratch[1] = clamp(_scratch[1], 0, 24-1);
|
|
|
|
_scratch[2] = clamp(_scratch[2], 0, 80-1)+1;
|
|
_scratch[3] = clamp(_scratch[3], 0, 24-1)+1;
|
|
|
|
|
|
window = iRect(_scratch[0],
|
|
_scratch[1],
|
|
_scratch[2] - _scratch[0],
|
|
_scratch[3] - _scratch[1]
|
|
);
|
|
cursor = window.origin;
|
|
}
|
|
|
|
| 0x03 ${
|
|
/*
|
|
Clear from Beginning of Line
|
|
|
|
Clears all characters from the left edge to and including the cursor. Sets them to the current consFill character.
|
|
*/
|
|
|
|
char_info ci = remap_fill(_context);
|
|
iRect tmp(
|
|
iPoint(window.minX(), cursor.y),
|
|
iPoint(cursor.x+1, cursor.y+1)
|
|
);
|
|
screen->fillRect(tmp, ci);
|
|
|
|
}
|
|
|
|
| 0x04 ${
|
|
/*
|
|
Pop Text Port
|
|
|
|
Restores the text port to the most recently saved value (see code $01). If no saved ports exist, resets the text port to the default values. If an 80-column text port is pushed and subsequently restored in 40-column mode, the text port may be altered to fit in the 40-column screen (see code $11, Set 40-Column Mode).
|
|
*/
|
|
|
|
if (!_context_stack.empty()) {
|
|
_context = _context_stack.back();
|
|
_context_stack.pop_back();
|
|
} else {
|
|
_context = gsos_context();
|
|
}
|
|
|
|
}
|
|
|
|
| 0x05 any ${
|
|
/*
|
|
Horizontal Scroll
|
|
|
|
Interprets the next byte as an 8-bit signed integer depicting the number (N) of
|
|
columns to shift. N equal to zero is a null operation. If N is less than zero, the text
|
|
port is shifted to the left; A greater than zero shifts to the right. If the shift magnitude is equal to or greater than windWidth, the text port is cleared.
|
|
The shifted characters are moved directly to their destination location. The space vacated by the shifted characters is set to the current consFill character (see the description of consFill earlier in this chapter). Characters shifted out of the text port are removed from the screen and are not recoverable.
|
|
*/
|
|
int8_t n = fc;
|
|
if (n < 0) {
|
|
screen->scrollLeft(window, n);
|
|
}
|
|
if (n > 0) {
|
|
screen->scrollRight(window, n);
|
|
}
|
|
// .... TODO
|
|
}
|
|
|
|
| 0x06 any ${
|
|
/*
|
|
Set Vertical Position
|
|
|
|
Interprets the next byte as a text port-relative vertical position + 32. If the destination is outside the current text port, the cursor is moved to the nearest edge.
|
|
*/
|
|
unsigned n = clamp(fc - 32, 0, window.height()-1);
|
|
cursor.y = window.minY() + n;
|
|
}
|
|
|
|
| 0x07 ${
|
|
/*
|
|
Ring Bell
|
|
|
|
Causes the System Beep to be played. It has no effect on the screen.
|
|
*/
|
|
NSBeep();
|
|
}
|
|
|
|
| 0x08 ${
|
|
/*
|
|
Backspace
|
|
|
|
Moves the cursor one position to the left. If the cursor was on the left edge of the
|
|
text port and consWrap is TRUE, the cursor is placed one row higher and at the right edge. If the cursor was also on the topmost row and consScroll is TRUE, thetext port will scroll backward one line.
|
|
*/
|
|
if (cursor.x > window.minX()) cursor.x--;
|
|
else if (_context.consWrap) {
|
|
cursor.x = window.maxX() - 1;
|
|
if (cursor.y > window.minY()) cursor.y--;
|
|
else if (_context.consScroll) screen->scrollDown(window);
|
|
}
|
|
|
|
}
|
|
|
|
| 0x09 $nop
|
|
|
|
| 0x0a ${
|
|
/*
|
|
Line Feed
|
|
|
|
Causes the cursor to move down one line. If the cursor was at the bottom edge of the text port and consScroll is TRUE, the text port scrolls up one line.
|
|
*/
|
|
if (cursor.y < window.maxY()-1) cursor.y++;
|
|
else if (_context.consScroll)
|
|
screen->scrollUp(window);
|
|
}
|
|
|
|
| 0x0b ${
|
|
/*
|
|
Clear to End of Text Port
|
|
|
|
Clears all characters from the cursor to the end of the current text port and sets them to be equal to the current consFill character.
|
|
*/
|
|
|
|
iRect tmp;
|
|
tmp.origin = cursor;
|
|
tmp.size = iSize(window.size.width - cursor.x, 1);
|
|
|
|
screen->eraseRect(tmp);
|
|
|
|
tmp = window;
|
|
tmp.origin.y = cursor.y+1;
|
|
tmp.size.height -= cursor.y+1;
|
|
screen->eraseRect(tmp);
|
|
}
|
|
|
|
|
|
| 0x0c ${
|
|
/*
|
|
Clear Text Port and Home Cursor
|
|
|
|
Clears the entire text port and resets the cursor to windLeft, windTop.
|
|
*/
|
|
|
|
screen->eraseRect(window);
|
|
cursor = window.origin;
|
|
}
|
|
|
|
| 0x0d ${
|
|
/*
|
|
Carriage Return
|
|
|
|
Resets the cursor to the left edge of the text port; if consLF is TRUE, performs a line feed (see $0A, Line Feed).
|
|
*/
|
|
|
|
cursor.x = window.minX();
|
|
if (_context.consLF) {
|
|
if (cursor.y < window.maxY() - 1) cursor.y++;
|
|
else if (_context.consScroll) screen->scrollUp(window);
|
|
}
|
|
}
|
|
|
|
| 0x0e ${
|
|
/*
|
|
Set Normal Display Mode
|
|
|
|
After this character, displays all subsequent characters in normal mode,
|
|
*/
|
|
_context.clearFlagBit(Screen::FlagInverse);
|
|
_context.consFill = 0xa0;
|
|
_context.consVideo = true;
|
|
}
|
|
|
|
| 0x0f ${
|
|
/*
|
|
Set Inverse Display Mode
|
|
After this character, displays all subsequent characters in inverse mode.
|
|
*/
|
|
_context.setFlagBit(Screen::FlagInverse);
|
|
_context.consFill = 0x20;
|
|
_context.consVideo = false;
|
|
}
|
|
|
|
| 0x10 any ${
|
|
/*
|
|
DLE Space Expansion
|
|
|
|
If consDLE is TRUE, interprets the next character as number of spaces + 32, and the
|
|
correct number of spaces is issued to die screen. If consDLE is FALSE, the DLE character is ignored and the following character is processed normally.
|
|
*/
|
|
if (_context.consDLE) {
|
|
unsigned count = (fc - 32) & 0xff;
|
|
//char_info ci = remap_fill(_context);
|
|
|
|
if (_context.consAdvance) {
|
|
while (count--) {
|
|
screen->putc(' ', _context);
|
|
if (_context.consAdvance) advance(screen, _context);
|
|
}
|
|
}
|
|
else {
|
|
if (count) screen->putc(' ', _context);
|
|
}
|
|
}
|
|
else { fhold; }
|
|
}
|
|
|
|
| 0x11 ${ /* 40 column mode */ }
|
|
| 0x12 ${ /* 80 column mode */ }
|
|
|
|
| 0x13 ${
|
|
|
|
}
|
|
|
|
| 0x14 any ${
|
|
/*
|
|
Set Horizontal Position
|
|
|
|
Interprets the next byte as a text port-relative horizontal position + 32. If the destination is outside the current text port, the cursor is moved to the nearest edge.
|
|
*/
|
|
unsigned n = clamp(fc - 32, 0, window.width() - 1);
|
|
cursor.x = window.minX() + n;
|
|
}
|
|
|
|
| 0x15 any ${
|
|
/*
|
|
Set Cursor Movement Word
|
|
|
|
Interprets the next byte as cursor movement control, and sets the values of these Boolean flags:
|
|
*/
|
|
|
|
unsigned flags = fc;
|
|
_context.consAdvance = flags & 0x01;
|
|
_context.consLF = flags & 0x02;
|
|
_context.consWrap = flags & 0x04;
|
|
_context.consScroll = flags & 0x08;
|
|
_context.consDLE = flags & 0x10;
|
|
}
|
|
|
|
| 0x16 ${
|
|
/*
|
|
Scroll Down One Line
|
|
|
|
Scrolls the text port down one line. Does not move the cursor.
|
|
*/
|
|
screen->scrollDown(window);
|
|
}
|
|
|
|
| 0x16 ${
|
|
/*
|
|
Scroll Up One Line
|
|
|
|
Scrolls the text port up one line. Does not move the cursor.
|
|
*/
|
|
screen->scrollUp(window);
|
|
}
|
|
|
|
| 0x18 ${
|
|
/*
|
|
Disable MouseText Mapping
|
|
|
|
When MouseText is disabled, uppercase inverse characters are displayed as such (see the section "Character Set Mapping" earlier in this chapter).
|
|
*/
|
|
_context.clearFlagBit(Screen::FlagMouseText);
|
|
_context.consMouse = false;
|
|
}
|
|
|
|
| 0x19 ${
|
|
/*
|
|
Home Cursor
|
|
|
|
Resets the cursor to the upper-left corner of the text port.
|
|
*/
|
|
cursor = window.origin;
|
|
}
|
|
|
|
| 0x1a ${
|
|
/*
|
|
Clear Line
|
|
|
|
Clears the line that the cursor is on. Resets the cursor to the leftmost column in the window.
|
|
*/
|
|
iRect tmp;
|
|
tmp.origin = iPoint(window.origin.x, cursor.y);
|
|
tmp.size = iSize(window.size.width, 1);
|
|
screen->eraseRect(tmp);
|
|
}
|
|
|
|
| 0x1b ${
|
|
/*
|
|
Enable MouseText Mapping
|
|
|
|
When MouseText is enabled, uppercase inverse letters are displayed as MouseText symbols (see the section "Character Set Mapping" earlier in this chapter).
|
|
*/
|
|
_context.setFlagBit(Screen::FlagMouseText);
|
|
_context.consMouse = true;
|
|
}
|
|
|
|
| 0x1c ${
|
|
/*
|
|
Move Cursor Right
|
|
|
|
Performs a nondestructive forward-space of the cursor. If consWrap is TRUE,
|
|
the cursor may go to the next line; if consScroll is TRUE, the screen may scroll up one line.
|
|
*/
|
|
advance(screen, _context);
|
|
}
|
|
|
|
| 0x1d ${
|
|
/*
|
|
Clear to End of Line
|
|
|
|
Clears from the position underneath the cursor to the end of the current line.
|
|
*/
|
|
iRect tmp;
|
|
tmp.origin = cursor;
|
|
tmp.size = iSize(window.size.width - cursor.x, 1);
|
|
|
|
screen->eraseRect(tmp);
|
|
|
|
}
|
|
|
|
| 0x1e arg1 arg2 ${
|
|
/*
|
|
Go to X,Y
|
|
|
|
Adjusts the cursor position relative to the text port. The parameters passed are X+32
|
|
and Y+32. If the new locations are outside the current text port, the cursor is placed on the nearest edge.
|
|
*/
|
|
|
|
iPoint dca;
|
|
|
|
dca.x = clamp(_scratch[0], 0, window.width() - 1);
|
|
dca.y = clamp(_scratch[1], 0, window.height() - 1);
|
|
cursor.x = dca.x + window.minX();
|
|
cursor.y = dca.y + window.minY();
|
|
cursor = dca;
|
|
}
|
|
|
|
| 0x1f ${
|
|
/*
|
|
Move Cursor Up
|
|
|
|
Moves the cursor up one line (reverse line feed). If the cursor is already on the
|
|
uppermost line of the textport and consScroll isTRUE, it will cause a reverse scroll.
|
|
*/
|
|
if (cursor.y > window.minY()) cursor.y--;
|
|
else if (_context.consScroll) screen->scrollDown(window);
|
|
}
|
|
|
|
| 0x20 .. 0x7f ${
|
|
screen->putc(fc, _context);
|
|
} $advance_if
|
|
|
|
| 0x80 .. 0x9f ${
|
|
/* uppercase inverse/normal */
|
|
uint8_t flags = _context.flags ^ Screen::FlagInverse;
|
|
screen->putc(fc - 0x40, cursor, flags);
|
|
} $advance_if
|
|
|
|
| 0xa0 .. 0xbf ${
|
|
/* special inverse/normal */
|
|
uint8_t flags = _context.flags ^ Screen::FlagInverse;
|
|
screen->putc(fc - 0x80, cursor, flags);
|
|
} $advance_if
|
|
|
|
| 0xc0 .. 0xdf ${
|
|
/* uppercase normal / mouse text. */
|
|
uint8_t flags = _context.flags ^ Screen::FlagInverse;
|
|
if (flags) flags |= Screen::FlagMouseText;
|
|
screen->putc(fc - 0x80, cursor, flags);
|
|
} $advance_if
|
|
|
|
| 0xe0 .. 0xff ${
|
|
/* special inverse/normal */
|
|
uint8_t flags = _context.flags ^ Screen::FlagInverse;
|
|
screen->putc(fc - 0x80, cursor, flags);
|
|
} $advance_if
|
|
|
|
)* $err{ fgoto main; };
|
|
|
|
write data;
|
|
}%%
|
|
|
|
@implementation GSOSConsole
|
|
|
|
+(void)load
|
|
{
|
|
[EmulatorManager registerClass: self];
|
|
}
|
|
|
|
+(NSString *)name
|
|
{
|
|
return @"GS/OS Console";
|
|
}
|
|
|
|
-(NSString *)name
|
|
{
|
|
return @"GS/OS Console";
|
|
}
|
|
|
|
-(const char *)termName
|
|
{
|
|
return "gsos-console";
|
|
}
|
|
|
|
|
|
|
|
-(BOOL)resizable
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
-(struct winsize)defaultSize
|
|
{
|
|
struct winsize ws = { 24, 80, 0, 0 };
|
|
|
|
return ws;
|
|
}
|
|
|
|
-(void)initTerm: (struct termios *)term
|
|
{
|
|
// Control-U is used by the up-arrow key.
|
|
term->c_cc[VKILL] = CTRL('X');
|
|
// \t is a NOP so expand to spaces.
|
|
term->c_oflag |= OXTABS;
|
|
}
|
|
|
|
-(id)init
|
|
{
|
|
if ((self = [super init]))
|
|
{
|
|
[self reset: YES];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
-(void)reset: (BOOL)hard
|
|
{
|
|
|
|
%%write init;
|
|
|
|
if (hard) {
|
|
_context_stack.clear();
|
|
|
|
_context.window = iRect(0, 0, 80, 24);
|
|
_context.cursor = iPoint(0,0);
|
|
}
|
|
|
|
_context.consWrap = true;
|
|
_context.consAdvance = true;
|
|
_context.consLF = true;
|
|
_context.consScroll = true;
|
|
_context.consVideo = true;
|
|
_context.consDLE = true;
|
|
_context.consMouse = false;
|
|
_context.consFill = 0xa0;
|
|
|
|
_cursorType = Screen::CursorTypeUnderscore;
|
|
|
|
}
|
|
|
|
|
|
-(void)processData: (uint8_t *)data length: (size_t)length screen:(Screen *)screen output:(OutputChannel *)output
|
|
{
|
|
|
|
const uint8_t *eof = nullptr;
|
|
const uint8_t *p = data;
|
|
const uint8_t *pe = data + length;
|
|
|
|
auto &cursor = _context.cursor;
|
|
auto &window = _context.window;
|
|
%%write exec;
|
|
screen->setCursor(cursor);
|
|
}
|
|
|
|
-(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];
|
|
|
|
switch (uc)
|
|
{
|
|
case NSEnterCharacter:
|
|
output->write(CTRL('M'));
|
|
break;
|
|
|
|
// todo -- verify...
|
|
#if 0
|
|
case NSDeleteCharacter:
|
|
output->write(0x7f);
|
|
break;
|
|
#endif
|
|
|
|
// todo -- verify...
|
|
case NSBackspaceCharacter:
|
|
output->write(0x7f);
|
|
break;
|
|
|
|
case NSLeftArrowFunctionKey:
|
|
output->write(CTRL('H'));
|
|
break;
|
|
|
|
case NSRightArrowFunctionKey:
|
|
output->write(CTRL('U'));
|
|
break;
|
|
|
|
case NSUpArrowFunctionKey:
|
|
output->write(CTRL('K'));
|
|
break;
|
|
|
|
case NSDownArrowFunctionKey:
|
|
output->write(CTRL('J'));
|
|
break;
|
|
|
|
|
|
default:
|
|
if (uc <= 0x7f)
|
|
{
|
|
char c = uc;
|
|
if (flags & (NSShiftKeyMask | NSAlphaShiftKeyMask))
|
|
{
|
|
c = toupper(c);
|
|
}
|
|
if (flags & NSControlKeyMask)
|
|
c = CTRL(c);
|
|
|
|
output->write(c);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
@end
|