Compare commits

...

29 Commits
r4 ... master

Author SHA1 Message Date
Kelvin Sherlock
43cb3c9e4f esc [0J was dropping through to esc[1J 2022-11-25 14:49:22 -05:00
ksherlock
6cb2b91ac0
fix answer back string
testing with an actual vt100, answer back is a string defined in the setup B screen. It is unrelated to the device attributes.
2021-04-22 23:10:55 -04:00
Kelvin Sherlock
8a5f5a9e61 bump submodule 2021-03-27 23:55:54 -04:00
Kelvin Sherlock
b1340d2a56 mousetext flag was blocking inverse flag. 2021-03-27 23:53:52 -04:00
ksherlock
5a971ac08c
Update xcodebuild.yml 2020-11-14 20:42:15 -05:00
ksherlock
254dfd548e
Update xcodebuild.yml 2020-11-14 20:14:30 -05:00
ksherlock
bd6d13e51b
Create xcodebuild.yml 2020-11-14 17:38:47 -05:00
Kelvin Sherlock
54335e2e3b xcode upgrade, adjust signing settings. 2020-11-14 17:17:29 -05:00
Kelvin Sherlock
98dc438fa5 vt100: up/down/left/right were always limited to 1 position. fixed. 2019-01-08 23:28:49 -05:00
Kelvin Sherlock
ceb1349199 Project update. 2018-12-25 13:56:10 -05:00
Kelvin Sherlock
22266914ed Add Apple III console support. Not usable via termcap since cursor positioning uses 0x00 which is converted by terminfo to 0x80 which has a very different meaning (last column/row vs first column/row). 2018-12-25 13:55:36 -05:00
Kelvin Sherlock
542dddd335 OS X Mojave hardening. 2018-11-24 14:29:16 -05:00
Kelvin Sherlock
6b28a1e170 version bump. 2018-11-24 13:39:42 -05:00
Kelvin Sherlock
fd50370e8d project update. 2018-11-24 13:27:03 -05:00
Kelvin Sherlock
92d89dd630 missing release. 2018-11-24 13:26:55 -05:00
Kelvin Sherlock
6f409db93e vt100 wip. 2018-11-24 13:26:47 -05:00
Kelvin Sherlock
30df8aef9e convert characters to 8-bit greyscale to work around OS X Mojave CreateImageMask() bug. 2018-11-24 12:28:56 -05:00
Kelvin Sherlock
99f5d60be8 rewrite VT05 / VT50 / VT50H with ragel. 2018-03-03 10:23:57 -05:00
Kelvin Sherlock
020b095542 reset / hard reset menus. 2018-02-28 21:06:06 -05:00
Kelvin Sherlock
bcf03e124b add 40-column Apple console. 2018-02-28 10:23:58 -05:00
Kelvin Sherlock
0bc20c6fa8 char gen. 2018-02-28 10:23:44 -05:00
Kelvin Sherlock
e7ebb7b573 emulators can specify their character generator. 2018-02-28 10:22:55 -05:00
Kelvin Sherlock
b766854f80 multiple character generators. 2018-02-28 10:22:20 -05:00
Kelvin Sherlock
5b8c8fec17 version bump 2018-02-16 22:22:29 -05:00
Kelvin Sherlock
92e1805694 move colors, etc, to a defaults file... 2018-02-16 17:56:21 -05:00
Kelvin Sherlock
24248e768b add icons. 2018-02-16 17:54:36 -05:00
Kelvin Sherlock
01c89f4715 retina character generator. 2018-02-16 17:52:41 -05:00
Kelvin Sherlock
bf7e733e8b emulator view - init fd to -1 2018-02-14 11:14:51 -05:00
Kelvin Sherlock
49b165a56d add close button to config window. 2018-02-14 11:14:36 -05:00
67 changed files with 2851 additions and 202 deletions

21
.github/workflows/xcodebuild.yml vendored Normal file
View File

@ -0,0 +1,21 @@
name: xcodebuild
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: brew
run: brew install ragel
- name: xcodebuild
run: xcodebuild -target TwoTerm | xcpretty

View File

@ -63,8 +63,8 @@
</items>
</menu>
</menuItem>
<menuItem title="File" id="83">
<menu key="submenu" title="File" id="81">
<menuItem title="Shell" id="83">
<menu key="submenu" title="Shell" id="81">
<items>
<menuItem title="New" keyEquivalent="n" id="82">
<connections>
@ -112,6 +112,19 @@
<action selector="revertDocumentToSaved:" target="-1" id="364"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="1Bf-fE-VAI"/>
<menuItem title="Reset" keyEquivalent="r" id="h3z-Y4-dyj">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="resetTerminal:" target="-1" id="Afh-EL-aOa"/>
</connections>
</menuItem>
<menuItem title="Hard Reset" keyEquivalent="r" id="CX0-e3-Dg4">
<modifierMask key="keyEquivalentModifierMask" control="YES" option="YES" command="YES"/>
<connections>
<action selector="hardResetTerminal:" target="-1" id="bVS-nP-oag"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="74">
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>

View File

@ -236,9 +236,24 @@
</subviews>
</view>
</box>
<button misplaced="YES" id="b5f-6s-ZI2">
<rect key="frame" x="451" y="357" width="24" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="TabClose" imagePosition="only" alignment="center" alternateImage="TabClose_Pressed" inset="2" id="Fi8-z7-gn2">
<behavior key="behavior" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="performClose:" target="Ezq-gE-d2Y" id="CcO-2x-8Vw"/>
</connections>
</button>
</subviews>
<point key="canvasLocation" x="-521" y="413"/>
</view>
<userDefaultsController representsSharedInstance="YES" id="mq9-aV-tAe"/>
</objects>
<resources>
<image name="TabClose" width="12" height="13"/>
<image name="TabClose_Pressed" width="12" height="13"/>
</resources>
</document>

View File

@ -8,15 +8,24 @@
#import <Cocoa/Cocoa.h>
enum {
CGApple80,
CGApple40,
CGVT52,
CGVT100
};
@interface CharacterGenerator : NSObject
{
CGImageRef _image;
NSMutableArray *_characters;
NSImage *_image;
NSImage *_characters[256];
NSSize _size;
}
+(CharacterGenerator *)generator;
+(CharacterGenerator *)generatorForCharacterSet: (unsigned)characterSet;
@property (nonatomic, readonly) NSSize characterSize;

View File

@ -10,10 +10,16 @@
#import "CharacterGenerator.h"
@interface CharacterGenerator ()
-(void)loadImageNamed: (NSString *)imageName;
-(id)initWithImageNamed: (NSString *)imageName;
@end
@implementation CharacterGenerator
@synthesize characterSize = _size;
#if 0
static CGImageRef PNGImage(NSString *path)
{
CGImageRef image = NULL;
@ -31,89 +37,117 @@ static CGImageRef PNGImage(NSString *path)
return image;
}
#endif
+(CharacterGenerator *)generatorForCharacterSet: (unsigned)characterSet {
static CharacterGenerator *singletons[4] = {};
static NSString *names[] = {
@"a2-charset-80",
@"a2-charset-40",
@"vt52-charset",
@"vt100-charset",
};
constexpr unsigned MaxCharSet = sizeof(names) / sizeof(names[0]);
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
for (unsigned i = 0; i < MaxCharSet; ++i)
singletons[i] = [[CharacterGenerator alloc] initWithImageNamed: names[i]];
});
if (characterSet >= MaxCharSet) return nil;
return singletons[characterSet];
}
+(id)generator
{
return [[self new] autorelease];
return [self generatorForCharacterSet: CGApple80];
}
-(id)init
{
-(id)initWithImageNamed: (NSString *)imageName {
if ((self = [super init]))
{
NSBundle *mainBundle;
NSString *imagePath;
CGImageRef mask;
CGImageRef src;
NSSize size;
mainBundle = [NSBundle mainBundle];
imagePath = [mainBundle pathForResource: @"a2-charset-80" ofType: @"png"];
//imagePath = [mainBundle pathForResource: @"vt100-charset" ofType: @"png"];
//imagePath = [mainBundle pathForResource: @"vt52-charset" ofType: @"png"];
_characters = [[NSMutableArray alloc] initWithCapacity: 256];
_size = NSMakeSize(7, 16);
src = PNGImage(imagePath);
size.width = CGImageGetWidth(src);
size.height = CGImageGetHeight(src);
size.width /= 16;
size.height /= 16;
_size = size;
if (src)
{
mask = CGImageMaskCreate(CGImageGetWidth(src),
CGImageGetHeight(src),
CGImageGetBitsPerComponent(src),
CGImageGetBitsPerPixel(src),
CGImageGetBytesPerRow(src),
CGImageGetDataProvider(src),
NULL, NO);
for (unsigned i = 0; i < 16; ++i)
{
for (unsigned j = 0; j < 16; ++j)
{
CGImageRef cgimg = CGImageCreateWithImageInRect(mask, CGRectMake(j * _size.width, i * _size.height, _size.width, _size.height));
NSImage *nsimg = [[NSImage alloc] initWithCGImage: cgimg size: _size];
[_characters addObject: nsimg];
CGImageRelease(cgimg);
[nsimg release];
}
}
CGImageRelease(src);
CGImageRelease(mask);
}
{
[self loadImageNamed: imageName];
}
return self;
}
/*
* This loads the image then split it up into 256 images.
*
* All representations are handled so it retins any @2x artwork.
*
*/
-(void)loadImageNamed:(NSString *)imageName {
_image = [[NSImage imageNamed: imageName] retain];
_size = [_image size];
_size.width /= 16;
_size.height /= 16;
for (unsigned i = 0; i < sizeof(_characters) / sizeof(_characters[0]); ++i)
_characters[i] = [[NSImage alloc] initWithSize: _size];
for (NSImageRep *rep in [_image representations]) {
CGImageRef mask;
CGImageRef src;
NSSize size;
/* src will auto release */
src = [rep CGImageForProposedRect: NULL context: nil hints: nil];
size.width = CGImageGetWidth(src) / 16;
size.height = CGImageGetHeight(src) / 16;
mask = CGImageMaskCreate(CGImageGetWidth(src),
CGImageGetHeight(src),
CGImageGetBitsPerComponent(src),
CGImageGetBitsPerPixel(src),
CGImageGetBytesPerRow(src),
CGImageGetDataProvider(src),
NULL, NO);
for (unsigned i = 0; i < 16; ++i)
{
for (unsigned j = 0; j < 16; ++j)
{
CGImageRef cgimg = CGImageCreateWithImageInRect(mask, CGRectMake(j * size.width, i * size.height, size.width, size.height));
NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithCGImage: cgimg];
NSImage *nsimg = _characters[i * 16 + j];
[nsimg addRepresentation: rep];
[rep release];
CGImageRelease(cgimg);
}
}
CGImageRelease(mask);
}
}
-(void)dealloc
{
if (_image) CGImageRelease(_image);
[_characters release];
[_image release];
for (auto &o : _characters) [o release];
[super dealloc];
}
@ -121,14 +155,17 @@ static CGImageRef PNGImage(NSString *path)
-(NSImage *)imageForCharacter: (unsigned)character
{
if (character > [_characters count]) return nil;
if (character >= sizeof(_characters) / sizeof(_characters[0])) return nil;
return (NSImage *)[_characters objectAtIndex: character];
return _characters[character];
}
-(void)drawCharacter: (unsigned)character atPoint: (NSPoint)point
{
NSImage *img = [self imageForCharacter: character];
if (character >= sizeof(_characters) / sizeof(_characters[0])) return;
NSImage *img = _characters[character];
if (!img) return;

24
Defaults.plist Normal file
View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<dict>
<key>Name</key>
<string>Green Black</string>
<key>Foreground</key>
<integer>65280</integer>
<key>Background</key>
<integer>0</integer>
<key>Effects</key>
<false/>
<key>Bloom</key>
<integer>0</integer>
<key>Backlight</key>
<integer>0</integer>
<key>Scanlines</key>
<integer>0</integer>
<key>Vignette</key>
<integer>0</integer>
</dict>
</array>
</plist>

33
Emulators/Apple3.h Normal file
View File

@ -0,0 +1,33 @@
//
// Apple3.h
// 2Term
//
// Created by Kelvin Sherlock on 12/24/2018.
// Copyright 2018 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import "Emulator.h"
#include "iGeometry.h"
#include "Screen.h"
struct iii_context : public context {
unsigned cursor_control = 0b1101;
unsigned fg_color = 0;
unsigned bg_color = 0;
unsigned mode = 2;
};
@interface Apple3 : NSObject <Emulator>
{
unsigned cs;
iii_context _context;
iii_context _saved_context;
Screen::CursorType _cursorType;
}
@end

466
Emulators/Apple3.mm.ragel Normal file
View File

@ -0,0 +1,466 @@
//
// Apple3.mm
// 2Term
//
// Created by Kelvin Sherlock on 12/24/2018.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//
/*
* See Apple III Standard Device Drivers Manual (ch 3, The Console Driver)
* Also implemented via AppleWorks (which started life as III EZ Pieces)
*
* Apple III console can be 24 x 40 (BW), 24 x 40 (16 color), or 24 x 80 (BW).
*
*/
#import "Apple3.h"
#include <sys/ttydefaults.h>
#include "OutputChannel.h"
#include "Screen.h"
#include "algorithm.h"
#import "CharacterGenerator.h"
enum {
kAdvance = 1 << 0,
kLineFeed = 1 << 1,
kWrap = 1 << 2,
kScroll = 1 << 3,
};
%%{
machine console;
alphtype unsigned int;
action nop {}
action advance {
// advance cursor
if (_context.cursor_control & kAdvance) {
switch (_context.cursor_control & (kWrap | kScroll)) {
default:
if (cursor.x < window.maxX() - 1) ++cursor.x;
break;
case kWrap:
if (cursor.x < window.maxX() - 1) ++cursor.x;
else {
cursor.x = window.minX();
if (cursor.y < window.maxY() - 1) ++cursor.y;
}
break;
case kWrap | kScroll:
if (cursor.x < window.maxX() - 1) ++cursor.x;
else {
cursor.x = window.minX();
if (cursor.y < window.maxY() - 1) ++cursor.y;
else screen->scrollUp(window);
}
break;
}
}
}
action setx {
/* horizontal position */
int x = (unsigned)fc;
x += window.minX();
cursor.x = std::min(x, window.maxX() - 1);
}
action sety {
/* vertical position */
int y = (unsigned)fc;
y += window.minY();
cursor.y = std::min(y, window.maxY() - 1);
}
# arg1 = any ${ _scratch[0] = fc; };
# arg2 = any ${ _scratch[1] = fc; };
main := (
0x00 $nop
| 0x01 ${
/* save viewport and reset */
_saved_context = _context;
window = iRect(0, 0, 80, 24);
/* currently ignores mode */
}
| 0x02 ${
/* set viewport top */
iPoint tl = cursor;
iPoint br = window.bottomRight();
_context.window = iRect(tl, br);
}
| 0x03 ${
/* set viewport bottom */
iPoint tl = _context.window.topLeft();
iPoint br = _context.cursor.offset(1,1);
_context.window = iRect(tl, br);
}
| 0x04 ${
/* restore viewport */
_context = _saved_context;
}
| 0x05 ${
/* E - $05 - Turns cursor on (enables cursor display) */
screen->setCursorType(Screen::CursorTypeUnderscore);
}
| 0x06 ${
/* F - $06 - Turns cursor off (disables cursor display) */
screen->setCursorType(Screen::CursorTypeNone);
}
| 0x07 ${
/* Beep */
NSBeep();
}
| 0x08 ${
/* Moves cursor left */
if (cursor.x > window.minX()) --cursor.x;
else if (_context.cursor_control & kWrap) {
cursor.x = window.maxX() - 1;
if (cursor.y > window.minY()) --cursor.y;
else if (_context.cursor_control & kScroll) {
screen->scrollDown(window);
}
}
}
| 0x09 ${
/* move cursor right */
if (cursor.x < window.maxX()-1) ++cursor.x;
else if (_context.cursor_control & kWrap) {
cursor.x = window.minX();
if (cursor.y < window.maxY()-1) ++cursor.y;
else if (_context.cursor_control & kScroll) {
screen->scrollUp(window);
}
}
}
| 0x0a ${
/* move cursor down */
if (cursor.y < window.maxY() - 1) ++cursor.y;
else if (_context.cursor_control & kScroll) {
screen->scrollUp(window);
}
}
| 0x0b ${
/* move cursor up */
if (cursor.y > window.minY()) --cursor.y;
else if (_context.cursor_control & kScroll) {
screen->scrollDown(window);
}
}
| 0x0c ${
/* home */
cursor = iPoint(window.topLeft());
}
| 0x0d ${
/* carriage return */
/* also LF depending on cursor motion */
cursor.x = window.minX();
if (_context.cursor_control & kLineFeed) {
if (cursor.y < window.maxY() - 1) ++cursor.y;
else if (_context.cursor_control & kScroll) {
screen->scrollUp(window);
}
}
}
| 0x0e ${
/* turn screen off ... */
}
| 0x0f ${
/* turn screen on ... */
}
| 0x10 any ${
/* set text mode (40 vs 80, color vs bw) */
_context.mode = fc & 0x03;
}
| 0x11 ${
/* normal text */
_context.clearFlagBit(Screen::FlagInverse);
}
| 0x12 ${
/* invert text */
_context.setFlagBit(Screen::FlagInverse);
}
| 0x13 any ${
/* set foreground text color */
_context.fg_color = fc & 0x0f;
}
| 0x14 any ${
/* set background text color */
_context.bg_color = fc & 0x0f;
}
| 0x15 any ${
/* set cursor motion control */
_context.cursor_control = fc & 0x0f;
}
# sync
| 0x16 $nop
| 0x17 any ${
/* horizontal shift */
int count = (int8_t)fc;
/* if > 0, shift right */
if (count > 0) screen->scrollRight(window, count);
/* if < 0, shift left */
if (count < 0) screen->scrollLeft(window, -count);
}
# horizontal position
| 0x18 any $setx
# vertical position
| 0x19 any $sety
# horizontal + vertical position
| 0x1a any $setx any $sety
# esc - reserved
| 0x1b $nop
| 0x1c ${
/* HOME + clear viewport */
cursor = window.topLeft();
screen->eraseRect(window);
}
| 0x1d ${
/* clear to end of viewport */
iRect r(cursor, window.bottomRight());
r.size.height = 1;
screen->eraseRect(r);
r = iRect(window.minX(), cursor.y + 1, window.width(), window.maxY() - cursor.y - 1);
screen->eraseRect(r);
}
| 0x1e ${
/* move to left edge of viewport, clear line */
cursor.y = window.minY();
iRect r(cursor, window.bottomRight());
r.size.height = 1;
screen->eraseRect(r);
}
| 0x1f ${
/* clear to end of line */
iRect r(cursor, window.bottomRight());
r.size.height = 1;
screen->eraseRect(r);
}
| 0x20 .. 0x7f ${
screen->putc(fc, _context);
} $advance
| 0x80 .. 0x9f ${
/* display control char? */
}
| 0xa0 .. 0xff ${
screen->putc(fc & 0x7f, _context);
} $advance
)* $err{ fgoto main; };
write data;
}%%
@implementation Apple3
+(void)load {
[EmulatorManager registerClass: self];
}
- (NSString *)name {
return @"Apple III";
}
+ (NSString *)name {
return @"Apple III";
}
- (const char *)termName {
return "appleIII";
}
-(void)reset: (BOOL)hard
{
%%write init;
_context.flags = 0;
_cursorType = Screen::CursorTypeUnderscore;
_context.fg_color = 15; /* white */
_context.bg_color = 0; /* black */
_context.cursor_control = kScroll | kWrap | kAdvance;
if (hard) {
_context.window = iRect(0, 0, 80, 24);
_context.cursor = iPoint(0,0);
_context.mode = 2;
_saved_context = iii_context();
}
}
-(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');
// tab is right arrow, so expand to spaces.
term->c_oflag |= OXTABS;
}
-(id)init
{
if ((self = [super init]))
{
[self reset: YES];
}
return self;
}
-(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;
auto &cursor_control = _context.cursor_control;
%%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;
/*
case NSDeleteCharacter:
output->write(0x7f);
break;
*/
// the Apple II keyboard had a delete where the backspace key was.
// it functions as a backspace key.
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;
//NSLog(@"%@", event);
if (flags & NSAlphaShiftKeyMask)
{
c = flags & NSShiftKeyMask ? tolower(c) : toupper(c);
}
if (flags & NSControlKeyMask)
c = CTRL(c);
output->write(c);
}
break;
}
}
}
@end

View File

@ -12,12 +12,19 @@
#include "iGeometry.h"
#include "Screen.h"
@interface Apple80 : NSObject <Emulator> {
@interface AppleX : NSObject <Emulator> {
unsigned cs;
unsigned _columns;
int _scratch[4];
context _context;
}
@end
@interface Apple40 : AppleX
@end
@interface Apple80 : AppleX
@end

View File

@ -21,6 +21,7 @@
#include "Screen.h"
#include "algorithm.h"
#import "CharacterGenerator.h"
%%{
machine console;
@ -30,7 +31,7 @@
action advance {
// advance cursor
if (++cursor.x == 80) {
if (++cursor.x == _columns) {
cursor.x = 0;
if (cursor.y >= 24-1) {
screen->scrollUp();
@ -74,7 +75,7 @@
if (cursor.x) cursor.x--;
else {
cursor.x = 80-1;
cursor.x = _columns-1;
// go up, possibly scrolling.
if (cursor.y) cursor.y--;
}
@ -101,11 +102,11 @@
iRect tmp;
tmp.origin = cursor;
tmp.size = iSize(80 - cursor.x, 1);
tmp.size = iSize(_columns - cursor.x, 1);
screen->eraseRect(tmp);
tmp = iRect(0, 0, 80, 24);
tmp = iRect(0, 0, _columns, 24);
tmp.origin.y = cursor.y+1;
tmp.size.height -= cursor.y+1;
screen->eraseRect(tmp);
@ -172,7 +173,7 @@
/* clear line */
iRect tmp;
tmp.origin = iPoint(0, cursor.y);
tmp.size = iSize(80, 1);
tmp.size = iSize(_columns, 1);
screen->eraseRect(tmp);
}
@ -187,7 +188,7 @@
/* Moves cursor right one column; if at end of line, does Control-M */
// n.b. - BASIC ^M also moves to next line.
cursor.x++;
if (cursor.x == 80) cursor.x = 0;
if (cursor.x == _columns) cursor.x = 0;
}
| 0x1d ${
@ -195,7 +196,7 @@
/* clear to end of line */
iRect tmp;
tmp.origin = cursor;
tmp.size = iSize(80 - cursor.x, 1);
tmp.size = iSize(_columns - cursor.x, 1);
screen->eraseRect(tmp);
}
@ -204,7 +205,7 @@
// CTRL('^'):
/* goto x y */
// todo - verify behavior for illegal values.
cursor.x = clamp(_scratch[0], 0, 80 - 1);
cursor.x = clamp(_scratch[0], 0, (int)_columns - 1);
cursor.y = clamp(_scratch[1], 0, 24 - 1);
}
@ -248,37 +249,35 @@
write data;
}%%
@implementation Apple80
@implementation AppleX
+(void)load
{
[EmulatorManager registerClass: self];
- (NSString *)name {
return @"Apple X";
}
+(NSString *)name
{
return @"Apple 80";
+ (NSString *)name {
return @"Apple X";
}
-(NSString *)name
{
return @"Apple 80";
}
-(const char *)termName
{
- (const char *)termName {
return "appleIIe";
}
-(void)reset
-(void)reset: (BOOL)hard
{
%%write init;
_context.window = iRect(0, 0, 80, 24);
_context.cursor = iPoint(0,0);
_context.flags = 0;
if (hard) {
_context.window = iRect(0, 0, _columns, 24);
_context.cursor = iPoint(0,0);
}
}
-(BOOL)resizable
@ -288,7 +287,7 @@
-(struct winsize)defaultSize
{
struct winsize ws = { 24, 80, 0, 0 };
struct winsize ws = { 24, (unsigned short)_columns, 0, 0 };
return ws;
}
@ -303,7 +302,7 @@
{
if ((self = [super init]))
{
[self reset];
[self reset: YES];
}
return self;
@ -393,4 +392,70 @@
}
}
@end
@implementation Apple40
+(void)load
{
[EmulatorManager registerClass: self];
}
+(NSString *)name
{
return @"Apple 40";
}
-(NSString *)name
{
return @"Apple 40";
}
-(const char *)termName
{
return "appleIIe";
}
-(void) reset: (BOOL)hard {
_columns = 40;
[super reset: hard];
}
-(CharacterGenerator *)characterGenerator {
return [CharacterGenerator generatorForCharacterSet: CGApple40];
}
@end
@implementation Apple80
+(void)load
{
[EmulatorManager registerClass: self];
}
+(NSString *)name
{
return @"Apple 80";
}
-(NSString *)name
{
return @"Apple 80";
}
-(const char *)termName
{
return "appleIIe";
}
-(void) reset: (BOOL)hard {
_columns = 80;
[super reset: hard];
}
@end

View File

@ -28,6 +28,7 @@ extern "C" unsigned EventCharacters(NSEvent *event, std::u32string &rv);
#import "iGeometry.h"
@class CharacterGenerator;
@interface EmulatorManager : NSObject
@ -49,7 +50,7 @@ extern "C" unsigned EventCharacters(NSEvent *event, std::u32string &rv);
-(void)keyDown: (NSEvent *)event screen: (Screen *)screen output: (OutputChannel *)output;
-(void)reset;
-(void)reset: (BOOL)hard;
+(NSString *)name;
-(NSString *)name;
@ -63,5 +64,6 @@ extern "C" unsigned EventCharacters(NSEvent *event, std::u32string &rv);
@optional
-(void)initTerm: (struct termios *)term;
-(CharacterGenerator *)characterGenerator;
@end

View File

@ -129,7 +129,6 @@
screen->scrollUp(window);
} else cursor.y++;
}
}
arg1 = any ${ _scratch[0] = ((fc & 0x7f) - 32); };
@ -411,19 +410,19 @@
}
-(void)reset
-(void)reset: (BOOL)hard
{
%%write init;
_context.flags = 0;
_context.window = iRect(0, 0, 80, 24);
_context.cursor = iPoint(0,0);
_cursorType = Screen::CursorTypeUnderscore;
// set flags to plain text.
if (hard) {
_context.window = iRect(0, 0, 80, 24);
_context.cursor = iPoint(0,0);
}
}
-(BOOL)resizable
@ -448,7 +447,7 @@
{
if ((self = [super init]))
{
[self reset];
[self reset: YES];
}
return self;

View File

@ -496,20 +496,23 @@ static void advance(Screen *screen, gsos_context &ctx) {
{
if ((self = [super init]))
{
[self reset];
[self reset: YES];
}
return self;
}
-(void)reset
-(void)reset: (BOOL)hard
{
%%write init;
_context.window = iRect(0, 0, 80, 24);
_context.cursor = iPoint(0,0);
if (hard) {
_context_stack.clear();
_context.window = iRect(0, 0, 80, 24);
_context.cursor = iPoint(0,0);
}
_context.consWrap = true;
_context.consAdvance = true;

View File

@ -306,20 +306,15 @@
return "proterm-special";
}
-(void)reset: (Screen *)screen
{
[self reset];
if (screen) screen->eraseScreen();
}
-(void)reset
-(void)reset: (BOOL)hard
{
%%write init;
_context.cursor = iPoint(0,0);
_context.window = iRect(0,0,80,24);
_context.flags = 0;
if (hard) {
_context.cursor = iPoint(0,0);
_context.window = iRect(0,0,80,24);
}
}
-(BOOL)resizable
@ -336,7 +331,7 @@
-(id)init
{
[self reset];
[self reset: YES];
return self;
}

View File

@ -15,11 +15,10 @@
@interface VT05 : NSObject <Emulator> {
unsigned _state;
unsigned cs;
unsigned _scratch[2];
context _context;
BOOL _upperCase;
}
-(void)tab: (Screen *)screen;
@end

236
Emulators/VT05.mm.ragel Normal file
View File

@ -0,0 +1,236 @@
//
// VT05.mm.m
// TwoTerm
//
// Created by Kelvin Sherlock on 3/3/2018.
//
// Disabled because linefeed only scrolls when on the last line... not very useful!
//
//
/*
* http://vt100.net/docs/vt05-rm/contents.html
*/
#include <sys/ttydefaults.h>
#include <cctype>
#import "VT05.h"
#include "OutputChannel.h"
#include "Screen.h"
enum {
VTBell = 07,
VTCursorLeft = 010,
VTTab = 011,
VTLineFeed = 012,
VTCursorDown = 013,
VTCarriageReturn = 015,
VTCAD = 016,
VTCursorRight = 030,
VTCursorUp = 032,
VTHome = 035,
VTEOL = 036,
VTEOS = 037
};
%%{
machine console;
alphtype unsigned int;
action nop {}
action tab {
if (cursor.x < 64) cursor.x = (cursor.x + 8) & ~7;
else if (cursor.x < window.maxX() -1) cursor.x++;
}
action linefeed {
if (cursor.y == window.maxY() -1)
screen->scrollUp();
}
action dca {
unsigned y = _scratch[0];
if (y < window.maxY()) cursor.y = y;
unsigned x = _scratch[1];
if (x < window.maxX()) cursor.x = x;
}
action erase_eos {
iRect tmp;
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);
}
action erase_eol {
iRect tmp;
tmp.origin = cursor;
tmp.size = iSize( window.maxX() - cursor.x, 1);
screen->eraseRect(tmp);
}
arg1 = 0x00* (any-0x00) ${ _scratch[0] = ((fc & 0x7f) - 32); };
arg2 = 0x00* (any-0x00) ${ _scratch[1] = ((fc & 0x7f) - 32); };
control_codes = (
0x07 ${ NSBeep(); }
| 0x08 ${ if (cursor.x) cursor.x--; }
| 0x09 $tab
| 0x0a $linefeed
| 0x0b ${ if (cursor.y < window.maxY() -1) cursor.y++; }
| 0x0d ${ cursor.x = 0; }
| 0x0e arg1 arg2 $dca
| 0x18 ${ if (cursor.x < window.maxX() -1) cursor.x++; }
| 0x1a ${ if (cursor.y) cursor.y--; }
| 0x1d ${ cursor = iPoint(0,0); }
| 0x1e $erase_eol
| 0x1f $erase_eos
);
main := (
control_codes
| 0x20 .. 0x7e ${
uint8_t c = fc;
if (c & 0x40) c &= ~0x20;
screen->putc(c, _context);
if (cursor.x < window.maxX() - 1) cursor.x++;
}
| any
)** $err{ fgoto main; };
write data;
}%%
@implementation VT05
+(void)load {
[EmulatorManager registerClass: self];
}
+(NSString *)name {
return @"VT05";
}
-(NSString *)name {
return @"VT05";
}
-(const char *)termName {
return "vt05";
}
-(void)reset: (BOOL)hard {
%% write init;
if (hard) {
_context.cursor = iPoint(0,0);
_context.window = iRect(0, 0, 72, 20);
}
}
-(BOOL)resizable {
return NO;
}
-(struct winsize)defaultSize {
struct winsize ws = { 20, 72, 0, 0 };
return ws;
}
-(id)init {
if ((self = [super init])) {
[self reset: YES];
}
return self;
}
-(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 = data + length;
iPoint &cursor = _context.cursor;
const iRect &window = _context.window;
%%write exec;
if (cursor.x == window.maxX()) screen->setCursor(iPoint(window.maxX() - 1, cursor.y));
else 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];
uint8_t c;
switch (uc)
{
case NSLeftArrowFunctionKey:
output->write(VTCursorLeft);
break;
case NSRightArrowFunctionKey:
output->write(VTCursorRight);
break;
case NSUpArrowFunctionKey:
output->write(VTCursorUp);
break;
case NSDownArrowFunctionKey:
output->write(VTCursorDown);
break;
case NSHomeFunctionKey:
output->write(VTHome);
break;
case NSDeleteCharacter:
output->write(0x7f);
break;
default:
if (uc > 0x7f) break;
c = uc;
if (flags & NSControlKeyMask)
{
c = CTRL(c);
}
output->write(c);
break;
}
}
}
@end

View File

@ -9,35 +9,54 @@
#import <Cocoa/Cocoa.h>
#import "Emulator.h"
#include "iGeometry.h"
#include "Screen.h"
#ifdef __cplusplus
#include <vector>
#include <bitset>
struct vt100_context : public context {
enum {
G0, G1
};
enum {
CharSet_A,
CharSet_B,
CharSet_0,
CharSet_1,
CharSet_2,
};
unsigned G0_charset = CharSet_B;
unsigned G1_charset = CharSet_B;
unsigned charset = G0;
};
#endif
@interface VT100 : NSObject <Emulator> {
unsigned _state;
BOOL _altKeyPad;
unsigned cs;
vt100_context _context;
Screen::CursorType _cursorType;
BOOL _private;
vt100_context _saved_context;
BOOL _keyMode;
BOOL _vt52Mode;
BOOL _graphics;
iPoint _dca;
struct __vt100flags {
unsigned int DECANM:1; // vt52 mode
unsigned int DECANM:1; // ANSI/vt52 mode
unsigned int DECARM:1; // auto repeat mode.
unsigned int DECAWM:1; // autowrap mode
unsigned int DECCKM:1; // cursor key mode.
unsigned int DECKPAM:1; // alternate keypad.
unsigned int DECKPNM:1; // not alternate keypad.
//unsigned int DECKPNM:1; // not alternate keypad.
unsigned int DECCOLM:1; // 80/132 mode.
unsigned int DECSCLM:1; // scrolling
unsigned int DECSCNM:1; // screen
@ -46,17 +65,15 @@
unsigned int LNM:1; // line feed new line mode.
unsigned int VT52GM:1; // vt52 graphics mode
} _flags;
#ifdef __cplusplus
std::vector<int> _parms;
std::vector<unsigned> _args;
std::bitset<80> _tabs;
#endif
}
-(void)tab: (Screen *)screen;
@end

View File

@ -89,8 +89,6 @@ enum {
_flags.DECOM = 0;
_flags.DECINLM = 0;
_flags.LNM = 0;
}

772
Emulators/VT100.mm.ragel Normal file
View File

@ -0,0 +1,772 @@
//
// VT100.mm.ragel
// TwoTerm
//
// Created by Kelvin Sherlock on 4/6/2018.
//
#include <sys/ttydefaults.h>
#include <cctype>
#include <cstdio>
#include <numeric>
#include <algorithm>
#include <utility>
#import "VT100.h"
#include "OutputChannel.h"
#include "Screen.h"
#include "algorithm.h"
#define ESC "\x1b"
%%{
machine vt100;
alphtype unsigned int;
esc = 0x1b;
cancel = 0x30 | 0x32;
action clear_args {
_args.clear();
_args.push_back(0);
}
action answerback {
/* no answerback string */
}
# DECOM - locked to scrolling region
# DECAWM - autowrap
action forward {
if (cursor.x > window_x.second) {
cursor.x = window_x.first;
if (_flags.DECAWM) {
if (cursor_in_region()) {
if (cursor.y == region_y.second)
screen->scrollUp(window);
else {
if (cursor.y < window_y.second) cursor.y++;
}
}
}
}
}
#
# ROM test - scrolling only happens if at bottom of scrolling region.
#
action linefeed {
/*
* ROM test - scrolling ONLY happens if at bottom line of scrolling region.
*
*/
if (cursor.y == region_y.second) {
screen->scrollUp(window);
} else {
cursor.y = std::min(cursor.y + 1u, window_y.second);
}
}
action reverse_linefeed {
if (cursor.y == region_y.first) {
screen->scrollDown(window);
} else {
cursor.y = std::min(cursor.y - 1u, window_y.first);
}
}
#
# ROM TEST - up/down can't escape the scrolling region.
#
action cursor_up {
/* cursor up */
unsigned count = _flags.DECANM ? std::max(1u, _args.front()) : 1;
if (cursor_in_region()) {
cursor.y = clamp(cursor.y - count, region_y.first, region_y.second);
} else {
cursor.y = clamp(cursor.y - count, window_y.first, window_y.second);
}
}
action cursor_down {
/* cursor down */
unsigned count = _flags.DECANM ? std::max(1u, _args.front()) : 1;
if (cursor_in_region()) {
cursor.y = clamp(cursor.y + count, region_y.first, region_y.second);
} else {
cursor.y = clamp(cursor.y + count, window_y.first, window_y.second);
}
}
action cursor_left {
/* cursor left */
unsigned count = _flags.DECANM ? std::max(1u, _args.front()) : 1;
cursor.x = clamp(cursor.x - count, window_x.first, window_x.second);
}
action cursor_right {
/* cursor right */
unsigned count = _flags.DECANM ? std::max(1u, _args.front()) : 1;
cursor.x = clamp(cursor.x + count, window_x.first, window_x.second);
}
action cup {
/* Cursor Position aka DCA */
/* todo - numbering of lines depends on DECOM */
unsigned y = _args.size() > 0 ? _args[0] : 0;
unsigned x = _args.size() > 1 ? _args[1] : 0;
if (x == 0) x = 1;
if (y == 0) y = 1;
if (_flags.DECOM) {
cursor.y = clamp(y - 1 + region_y.first, region_y.first, region_y.second);
cursor.x = clamp(x - 1, window_x.first, window_x.second);
} else {
cursor.y = clamp(y - 1, window_y.first, window_y.second);
cursor.x = clamp(x - 1, window_x.first, window_x.second);
}
}
action vt52_dca {
/* todo - how are invalid values handled? */
/* todo - DECOM supported? */
if (_flags.DECOM) {
cursor.y = clamp(_args[0] + region_y.first, region_y.first, region_y.second);
cursor.x = clamp(_args[1], window_x.first, window_x.second);
} else {
cursor.y = clamp(_args[0], window_y.first, window_y.second);
cursor.x = clamp(_args[1], window_x.first, window_x.second);
}
}
action stbm {
/* Set Top and Bottom Margins */
/* aka scrolling region */
unsigned top = _args.size() > 0 ? _args[0] : 0;
unsigned bottom = _args.size() > 1 ? _args[1] : 0;
if (top == 0) top = 1;
if (bottom == 0) bottom = 24;
if (top < bottom && bottom <= 24) {
--top;
window = iRect(0, top, 80, bottom - top);
// also home the cursor ... depeds on DECOM.
if (_flags.DECOM) cursor = window.origin;
else cursor = iPoint(0, 0);
region_y.first = top;
region_y.second = bottom - 1;
}
}
action reset_mode {
for (auto m : _args) {
switch (m) {
case 1: if (_private) _flags.DECCKM = 0; break;
case 2: if (_private) _flags.DECANM = 0; break;
case 3: if (_private) _flags.DECCOLM = 0; break;
case 4: if (_private) _flags.DECSCLM = 0; break;
case 5: if (_private) _flags.DECSCNM = 0; break;
case 6: if (_private) {
_flags.DECOM = 0;
/* also move to new origin */
cursor = iPoint(0, 0);
}
break;
case 7: if (_private) _flags.DECAWM = 0; break;
case 8: if (_private) _flags.DECARM = 0; break;
case 9: if (_private) _flags.DECINLM = 0; break;
case 20: if (!_private) _flags.LNM = 0; break;
}
}
}
action set_mode {
for (auto m : _args) {
switch (m) {
case 1: if (_private) _flags.DECCKM = 1; break;
case 2: if (_private) _flags.DECANM = 1; break;
case 3: if (_private) _flags.DECCOLM = 1; break;
case 4: if (_private) _flags.DECSCLM = 1; break;
case 5: if (_private) _flags.DECSCNM = 1; break;
case 6: if (_private) {
_flags.DECOM = 1;
/* also move to new origin */
cursor = window.origin;
}
case 7: if (_private) _flags.DECAWM = 1; break;
case 8: if (_private) _flags.DECARM = 1; break;
case 9: if (_private) _flags.DECINLM = 1; break;
case 20: if (!_private) _flags.LNM = 1; break;
}
}
}
action tbc {
/* TBC Tabulation Clear */
for (auto arg : _args) {
switch (arg) {
case 0: if (cursor.x <= 79) _tabs[cursor.x] = 0; break;
case 3: _tabs.reset(); break;
}
}
}
action dsr {
/* Device Status Report */
for (auto arg : _args) {
switch(arg) {
default: break;
case 5: /* report status */
output->write(ESC "[0n");
break;
case 6: { /* cursor position report */
char buffer[16];
iPoint pt = cursor;
pt.x++;
pt.y++;
if (_flags.DECOM) pt.y -= region_y.first;
snprintf(buffer, sizeof(buffer)-1, ESC "[%u;%uR", pt.y, pt.x);
output->write(buffer);
break;
}
}
}
}
action erase_line {
// mode 0 and 1 erase cursor.x
iRect r(0, cursor.y, 80, 1);
for (auto arg : _args) {
switch (arg) {
case 0: {
// x ... eos
r.origin.x = cursor.x;
r.size.width = 80 - cursor.x;
screen->eraseRect(r);
break;
}
case 1: {
// 0 ... x
r.origin.x = 0;
r.size.width = cursor.x + 1;
screen->eraseRect(r);
break;
}
case 2: {
// 0 .. eos
r.origin.x = 0;
r.size.width = 80;
screen->eraseRect(r);
break;
}
}
}
}
action erase_screen {
iRect r(0, cursor.y, 80, 1);
for (auto arg : _args) {
switch (arg) {
case 0: {
// x .. eos
r.origin.x = cursor.x;
r.size.width = 80 - cursor.x;
screen->eraseRect(r);
iRect tmp(0, cursor.y + 1, 80, 24 - cursor.y - 1);
screen->eraseRect(tmp);
break;
}
case 1: {
// 0 ... x
// 0 ... x
r.origin.x = 0;
r.size.width = cursor.x + 1;
screen->eraseRect(r);
iRect tmp(0, 0, 80, cursor.y - 1);
screen->eraseRect(tmp);
break;
}
case 2: {
iRect tmp(0, 0, 80, 24);
screen->eraseRect(tmp);
break;
}
}
}
}
action vt52_erase_line {
iRect r(cursor.x, cursor.y, 80 - cursor.x, 1);
screen->eraseRect(r);
}
action vt52_erase_screen {
iRect r(cursor.x, cursor.y, 80 - cursor.x, 1);
screen->eraseRect(r);
r = iRect(0, cursor.y + 1, 80, 24 - cursor.y - 1);
screen->eraseRect(r);
}
action sgr {
/* Select Graphical Rendition */
for (auto arg : _args) {
switch(arg) {
case 0: _context.flags = Screen::FlagNormal; break;
case 1: _context.flags |= Screen::FlagBold; break;
case 4: _context.flags |= Screen::FlagUnderscore; break;
case 5: _context.flags |= Screen::FlagBlink; break;
case 7: _context.flags |= Screen::FlagInverse; break;
}
}
}
action sc {
/* Save Cursor */
_saved_context = _context;
}
# todo -- what if DECOM changes?
action rc {
_context.cursor = _saved_context.cursor;
_context.charset = _saved_context.charset;
_context.G0_charset = _saved_context.G0_charset;
_context.G1_charset = _saved_context.G1_charset;
}
control_codes = (
0x05 $answerback
| 0x07 ${ NSBeep(); }
| 0x08 ${ if (cursor.x) cursor.x--; }
| 0x09 ${ cursor.x = tab(cursor.x); }
| (0x0a | 0x0b | 0x0c) ${ if (_flags.LNM) cursor.x = 0; } $linefeed
| 0x0d ${ cursor.x = 0; }
| 0x0e ${ _context.charset = vt100_context::G1; }
| 0x0f ${ _context.charset = vt100_context::G0; }
| 0x11 ${ /* xon */ }
| 0x13 ${ /* xoff */ }
| cntrl - esc
);
args = (
';' ${ _args.push_back(0); }
| [0-9] ${ _args.back() *= 10; _args.back() += fc - '0'; }
)**
# >to(clear_args)
;
lbrace = (
'A' $cursor_up
| 'B' $cursor_down
| 'C' $cursor_right
| 'D' $cursor_left
| ('H' | 'f') $cup
| 'K' $erase_line
| 'J' $erase_screen
| 'm' $sgr
| 'r' $stbm
| 'g' $tbc
| 'h' $set_mode
| 'l' $reset_mode
| 'n' $dsr
| 'c' ${ if (_args.front() == 0) output->write(ESC "[?1;0c"); }
| 'y' ${ /* tests */ }
| 'q' ${ /* led lights */ }
| 'x' # DECREQTPARM
)
;
lparen = [AB012] ${
switch (fc) {
case 'A': _context.G0_charset = vt100_context::CharSet_A; break;
case 'B': _context.G0_charset = vt100_context::CharSet_B; break;
case '0': _context.G0_charset = vt100_context::CharSet_0; break;
case '1': _context.G0_charset = vt100_context::CharSet_1; break;
case '2': _context.G0_charset = vt100_context::CharSet_2; break;
}
};
rparen = [AB012] ${
switch (fc) {
case 'A': _context.G1_charset = vt100_context::CharSet_A; break;
case 'B': _context.G1_charset = vt100_context::CharSet_B; break;
case '0': _context.G1_charset = vt100_context::CharSet_0; break;
case '1': _context.G1_charset = vt100_context::CharSet_1; break;
case '2': _context.G1_charset = vt100_context::CharSet_2; break;
}
};
# #3 = DECDHL - Double-Height Line (top half)
# #4 = DECDHL - Double-Height Line (bottom half)
# #5 = DECSWL Single-width Line
# #6 = DECDWL Double-Width Line
pound = (
'3'
| '4'
| '5'
| '6'
| '7' # DECHCP - Hard Copy
| '8' ${
/* DECALN */
screen->fillScreen(char_info('E', 0));
}
);
dca_arg = any ${ _args.push_back((fc & 0x7f) - 32); } ;
escape_vt52 := (
'A' $cursor_up
| 'B' $cursor_down
| 'C' $cursor_right
| 'D' $cursor_left
| 'F' ${ _flags.VT52GM = 1; }
| 'G' ${ _flags.VT52GM = 0; }
| 'H' ${ cursor = window.origin; } # todo - DECOM?
| 'I' $reverse_linefeed
| 'J' $vt52_erase_screen
| 'K' $vt52_erase_line
| 'Y' ${ _args.clear(); } dca_arg dca_arg $vt52_dca
| 'Z' ${ output->write(ESC "/Z"); }
| '=' ${ _flags.DECKPAM = 1;}
| '>' ${ _flags.DECKPAM = 0; }
| '<' ${ _flags.DECANM = 1; }
)
@{ fgoto main; }
$err{ fgoto main; }
;
csi = '[' ${ _args.clear(); _args.push_back(0); _private = NO; };
private_flag = ('?' ${ _private = YES; })?;
escape := (
( control_codes | esc )**
| cancel ${ /* cancel */ fgoto main; }
| csi private_flag args lbrace
| '(' lparen
| ')' rparen
| '#' pound
| 'D' $linefeed
| 'E' ${cursor.x = 0; } $linefeed
| 'H' ${ if (cursor.x <= 79) _tabs[cursor.x] = 1; }
| 'M' $reverse_linefeed
| 'Z' ${ output->write(ESC "[?1;0c"); }
| '7' $sc
| '8' $rc
| '=' ${ _flags.DECKPAM = 1;}
| '>' ${ _flags.DECKPAM = 0; }
# | 'N'
# | 'O'
| 'c' ${ [self reset: YES]; /* should also clear the screen */ }
| '1' # DECG - graphic processor on (vt105?)
| '2' # DECG - graphic processor off (vt105?)
)
@{ fnext main; }
$err{ fgoto main; }
;
main := (
control_codes
| esc ${
if (_flags.DECANM) fgoto escape; else fgoto escape_vt52;
}
| 0x20 .. 0x7f $forward ${
screen->putc(fc, _context);
cursor.x++;
}
)**
$err{ fgoto main; }
;
write data;
}%%
@implementation VT100
+(void)load
{
[EmulatorManager registerClass: self];
}
-(id)init
{
self = [super init];
[self reset: YES];
return self;
}
+(NSString *)name
{
return @"VT100";
}
-(NSString *)name
{
return @"VT100";
}
-(const char *)termName
{
return "vt100";
}
-(BOOL)resizable
{
return NO;
}
-(struct winsize)defaultSize
{
struct winsize ws = { 24, 80, 0, 0};
return ws;
}
-(void)reset: (BOOL)hard
{
%% write init;
_args.clear();
_flags.DECANM = 1; // ansi/vt100 mode
_flags.DECARM = 0;
_flags.DECAWM = 1;
_flags.DECCKM = 0;
_flags.DECKPAM = 0;
//_flags.DECKPNM = 1;
_flags.DECCOLM = 0;
_flags.DECSCLM = 0;
_flags.DECSCNM = 0;
_flags.DECOM = 0;
_flags.DECINLM = 0;
_flags.LNM = 0;
_flags.VT52GM = 0;
if (hard) {
_context.cursor = iPoint(0,0);
_context.window = iRect(0, 0, 80, 24);
_tabs.reset();
_tabs[8] = true;
_tabs[16] = true;
_tabs[24] = true;
_tabs[32] = true;
_tabs[40] = true;
_tabs[48] = true;
_tabs[56] = true;
_tabs[64] = true;
_tabs[72] = true;
}
_context.flags = 0;
_context.charset = vt100_context::G0;
_context.G0_charset = vt100_context::CharSet_B;
_context.G1_charset = vt100_context::CharSet_B;
}
-(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;
});
if (p == pe) return;
iPoint &cursor = _context.cursor;
iRect &window = _context.window;
std::pair<unsigned, unsigned> window_x(0u, 80-1);
std::pair<unsigned, unsigned> window_y(0u, 24-1);
std::pair<unsigned, unsigned> region_y(window.minY(), window.maxY()-1);
auto cursor_in_region = [&](){
return cursor.y >= region_y.first && cursor.y <= region_y.second;
};
/* todo - vt100 rom sometimes tabs to 80, sometimes doesn't. */
/* difference being: tab, x < x wraps to next line? */
auto tab = [&](unsigned x) -> unsigned {
if (x >= 79) return x;
for (x = x + 1; x < 80; ++x) {
if (_tabs[x]) return x;
}
return 79; //?
};
%%write exec;
if (cursor.x == 80) screen->setCursor(iPoint(79, cursor.y));
else screen->setCursor(cursor);
}
static const char *RemapKey(unichar uc, NSEventModifierFlags flags, struct __vt100flags vt100flags)
{
auto DECKPAM = vt100flags.DECKPAM;
/*
DECKPAM Keypad Application Mode (DEC Private)
The auxiliary keypad keys will transmit control sequences as defined in Tables 3-7 and 3-8.
*/
auto DECCKM = vt100flags.DECCKM;
/*
DECCKM Cursor Keys Mode (DEC Private)
This is a private parameter applicable to set mode (SM) and reset mode (RM) control sequences. This mode is only effective when the terminal is in keypad application mode (see DECKPAM) and the ANSI/VT52 mode (DECANM) is set (see DECANM). Under these conditions, if the cursor key mode is reset, the four cursor function keys will send ANSI cursor control commands. If cursor key mode is set, the four cursor function keys will send application functions.
*/
auto LNM = vt100flags.LNM;
auto DECANM = vt100flags.DECANM;
/*
DECANM ANSI/VT52 Mode (DEC Private)
This is a private parameter applicable to set mode (SM) and reset mode (RM) control sequences. The reset state causes only VT52 compatible escape sequences to be interpreted and executed. The set state causes only ANSI "compatible" escape and control sequences to be interpreted and executed.
*/
#if 0
/* Device-independent bits found in event modifier flags */
typedef NS_OPTIONS(NSUInteger, NSEventModifierFlags) {
NSEventModifierFlagCapsLock = 1 << 16, // Set if Caps Lock key is pressed.
NSEventModifierFlagShift = 1 << 17, // Set if Shift key is pressed.
NSEventModifierFlagControl = 1 << 18, // Set if Control key is pressed.
NSEventModifierFlagOption = 1 << 19, // Set if Option or Alternate key is pressed.
NSEventModifierFlagCommand = 1 << 20, // Set if Command key is pressed.
NSEventModifierFlagNumericPad = 1 << 21, // Set if any key in the numeric keypad is pressed.
NSEventModifierFlagHelp = 1 << 22, // Set if the Help key is pressed.
NSEventModifierFlagFunction = 1 << 23, // Set if any function key is pressed.
// Used to retrieve only the device-independent modifier flags, allowing applications to mask off the device-dependent modifier flags, including event coalescing information.
NSEventModifierFlagDeviceIndependentFlagsMask = 0xffff0000UL
};
#endif
/*
NSEnterCharacter: keypad Enter Key
NSNewlineCharacter: \n
NSCarriageReturnCharacter: \r (main Enter/Return key)
*/
//NSLog(@"%c %02x %08lx", isprint(uc) ? uc : '.', uc, (unsigned long)flags);
if (DECKPAM && (flags & NSNumericPadKeyMask)) {
switch (uc) {
case '0': return DECANM ? ESC "Op" : ESC "?p";
case '1': return DECANM ? ESC "Oq" : ESC "?q";
case '2': return DECANM ? ESC "Or" : ESC "?r";
case '3': return DECANM ? ESC "Os" : ESC "?s";
case '4': return DECANM ? ESC "Ot" : ESC "?t";
case '5': return DECANM ? ESC "Ou" : ESC "?u";
case '6': return DECANM ? ESC "Ov" : ESC "?v";
case '7': return DECANM ? ESC "Ow" : ESC "?w";
case '8': return DECANM ? ESC "Ox" : ESC "?x";
case '9': return DECANM ? ESC "Oy" : ESC "?y";
case ',': return DECANM ? ESC "Ol" : ESC "?l";
case '-': return DECANM ? ESC "Om" : ESC "?m";
case '.': return DECANM ? ESC "On" : ESC "?n";
case NSEnterCharacter: return DECANM ? ESC "OM" : ESC "?M";
}
}
switch(uc) {
case NSUpArrowFunctionKey:
if (DECANM) return DECKPAM ? ESC "OA" : ESC "[A";
return ESC "A";
case NSDownArrowFunctionKey:
if (DECANM) return DECKPAM ? ESC "OB" : ESC "[B";
return ESC "B";
case NSRightArrowFunctionKey:
if (DECANM) return DECKPAM ? ESC "OC" : ESC "[C";
return ESC "C";
case NSLeftArrowFunctionKey:
if (DECANM) return DECKPAM ? ESC "OD" : ESC "[D";
return ESC "D";
case NSF1FunctionKey: return DECANM ? ESC "OP" : ESC "P";
case NSF2FunctionKey: return DECANM ? ESC "OQ" : ESC "Q";
case NSF3FunctionKey: return DECANM ? ESC "OR" : ESC "R";
case NSF4FunctionKey: return DECANM ? ESC "OS" : ESC "S";
/* return/enter key is CR */
case NSEnterCharacter: case NSNewlineCharacter: case NSCarriageReturnCharacter:
return LNM ? "\n\r" : "\r";
// NSDeleteCharacter IS 0x7f.
//case NSDeleteCharacter: return "\x7f"; //
}
return nullptr;
}
-(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];
const char *str = RemapKey(uc, flags, _flags);
if (str) {
output->write(str);
} else if (uc <= 0x7f) {
uint8_t c = uc;
if (flags & NSControlKeyMask) c = CTRL(c);
output->write(c);
}
}
}
@end

33
Emulators/VT50.h Normal file
View File

@ -0,0 +1,33 @@
//
// VT50.h
// TwoTerm
//
// Created by Kelvin Sherlock on 3/2/2018.
//
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#import "Emulator.h"
#include "iGeometry.h"
#include "Screen.h"
@interface VT50x : NSObject <Emulator> {
unsigned cs;
int _scratch[2];
context _context;
unsigned _model;
BOOL _altKeyPad;
BOOL _graphics;
}
@end
@interface VT50 : VT50x
@end
@interface VT50H : VT50x
@end

447
Emulators/VT50.mm.ragel Normal file
View File

@ -0,0 +1,447 @@
//
// 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 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

View File

@ -28,11 +28,14 @@
@interface VT52 : VT5x
@end
/*
@interface VT50H : VT5x
@end
@interface VT50 : VT5x
@end
*/
@interface VT55 : VT5x
@end

View File

@ -43,7 +43,7 @@ enum {
#if 0
@implementation VT50
+(void)load {
@ -72,7 +72,7 @@ enum {
-(id)init {
if ((self = [super init])) {
_model = ModelVT50;
[self reset];
[self reset: YES];
}
return self;
@ -104,7 +104,7 @@ enum {
-(id)init {
if ((self = [super init])) {
_model = ModelVT50H;
[self reset];
[self reset: YES];
}
return self;
@ -119,6 +119,8 @@ enum {
@end
#endif
@implementation VT52
+(void)load {
@ -141,7 +143,7 @@ enum {
-(id)init {
if ((self = [super init])) {
_model = ModelVT52;
[self reset];
[self reset: YES];
}
return self;
}
@ -168,7 +170,7 @@ enum {
-(id)init {
if ((self = [super init])) {
_model = ModelVT55;
[self reset];
[self reset: YES];
}
return self;
@ -319,15 +321,18 @@ enum {
}
}
-(void)reset
-(void)reset: (BOOL)hard
{
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);
if (hard) {
_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;
}

View File

@ -6,8 +6,6 @@
<string>English</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
@ -17,11 +15,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>1.0r6</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>4</string>
<string>6</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>

View File

@ -57,6 +57,9 @@
-(void)setParameters: (NSDictionary *)parameters;
-(IBAction)resetTerminal: (id)sender;
-(IBAction)hardResetTerminal: (id)sender;
@end

View File

@ -248,8 +248,14 @@
[_emulatorView processData: (uint8_t *)buffer size: size];
}
-(IBAction)resetTerminal: (id)sender {
[_emulator reset: NO];
}
-(IBAction)hardResetTerminal: (id)sender {
[_emulator reset: YES];
[_emulatorView reset];
}
#pragma mark -
#pragma mark NSWindowDelegate
@ -276,6 +282,13 @@
[_colorView setWantsLayer: YES];
[_colorView setContentFilters: [self effectsFilter]];
if ([_emulator respondsToSelector: @selector(characterGenerator)]) {
id tmp = [_emulator characterGenerator];
[(EmulatorWindow *)window setTitleCharacterGenerator:tmp];
}
[self initPTY];
}
@ -406,6 +419,7 @@
}
}
- (IBAction)foregroundColor:(id)sender {
[self updateForegroundColor];
}
@ -456,7 +470,6 @@
[_emulatorView setBackgroundColor: color];
}
@end

View File

@ -12,6 +12,8 @@
8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; };
8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; };
8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
B609404C205ACD730077A69C /* VT05.mm.ragel in Sources */ = {isa = PBXBuildFile; fileRef = B683F711204AE32900470B99 /* VT05.mm.ragel */; };
B60942042082BCA200141159 /* VT100.mm.ragel in Sources */ = {isa = PBXBuildFile; fileRef = B60942022077F24300141159 /* VT100.mm.ragel */; };
B60EBD1211E8DEEF00C1974F /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B60EBD1111E8DEEF00C1974F /* QuartzCore.framework */; };
B60EBDE311E90FC300C1974F /* ScanLineFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = B60EBDE211E90FC300C1974F /* ScanLineFilter.m */; };
B60EBE2B11E918D500C1974F /* ScanLineFilter.cikernel in Resources */ = {isa = PBXBuildFile; fileRef = B60EBDE711E9143F00C1974F /* ScanLineFilter.cikernel */; };
@ -43,11 +45,26 @@
B67B3CE512B6FA040033AE07 /* a2-charset-40.png in Resources */ = {isa = PBXBuildFile; fileRef = B67B3CE312B6FA040033AE07 /* a2-charset-40.png */; };
B67B3CE612B6FA040033AE07 /* a2-charset-80.png in Resources */ = {isa = PBXBuildFile; fileRef = B67B3CE412B6FA040033AE07 /* a2-charset-80.png */; };
B6801BD912EB549300B22E9E /* vt100-charset.png in Resources */ = {isa = PBXBuildFile; fileRef = B6801BD812EB549300B22E9E /* vt100-charset.png */; };
B683F7102049E7B000470B99 /* VT50.mm.ragel in Sources */ = {isa = PBXBuildFile; fileRef = B683F70F2049E7B000470B99 /* VT50.mm.ragel */; };
B68A37D221A9B9D9004CBDE4 /* a2-charset-80@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B6C21CD720349C8C00671774 /* a2-charset-80@2x.png */; };
B68A37D321A9C15C004CBDE4 /* a2-charset-40@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B6C21CDA20349C8E00671774 /* a2-charset-40@2x.png */; };
B68A37D421A9C161004CBDE4 /* vt52-charset@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B6C21CD920349C8D00671774 /* vt52-charset@2x.png */; };
B68A37D521A9C164004CBDE4 /* vt100-charset@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B6C21CD820349C8D00671774 /* vt100-charset@2x.png */; };
B68E632A12FF909D00EAFF5F /* ExampleView.m in Sources */ = {isa = PBXBuildFile; fileRef = B68E632912FF909C00EAFF5F /* ExampleView.m */; };
B69D0FBA202799B10073CCB7 /* TermConfig.xib in Resources */ = {isa = PBXBuildFile; fileRef = B69D0FB8202799B10073CCB7 /* TermConfig.xib */; };
B69E32A920221C9E0086D7B1 /* ChildMonitor.mm in Sources */ = {isa = PBXBuildFile; fileRef = B69E32A820221C9E0086D7B1 /* ChildMonitor.mm */; };
B6ACA2AD1E614E38000E774B /* VT52.mm in Sources */ = {isa = PBXBuildFile; fileRef = B612F46312DD5DF1005D1B77 /* VT52.mm */; };
B6ACA2AF1E635CEC000E774B /* vt52-charset.png in Resources */ = {isa = PBXBuildFile; fileRef = B6ACA2AE1E635CEC000E774B /* vt52-charset.png */; };
B6C0909021D292E20067F7A4 /* Apple3.mm.ragel in Sources */ = {isa = PBXBuildFile; fileRef = B6C0908D21D1BBE70067F7A4 /* Apple3.mm.ragel */; };
B6C21CCE2033382B00671774 /* TabClose_Busy.tiff in Resources */ = {isa = PBXBuildFile; fileRef = B6C21CC82033382A00671774 /* TabClose_Busy.tiff */; };
B6C21CCF2033382B00671774 /* TabClose_Busy_Pressed.tiff in Resources */ = {isa = PBXBuildFile; fileRef = B6C21CC92033382A00671774 /* TabClose_Busy_Pressed.tiff */; };
B6C21CD02033382B00671774 /* TabClose.tiff in Resources */ = {isa = PBXBuildFile; fileRef = B6C21CCA2033382A00671774 /* TabClose.tiff */; };
B6C21CD12033382B00671774 /* TabClose_Pressed.tiff in Resources */ = {isa = PBXBuildFile; fileRef = B6C21CCB2033382B00671774 /* TabClose_Pressed.tiff */; };
B6C21CD22033382B00671774 /* TabClose_Rollover.tiff in Resources */ = {isa = PBXBuildFile; fileRef = B6C21CCC2033382B00671774 /* TabClose_Rollover.tiff */; };
B6C21CD32033382B00671774 /* TabClose_Busy_Rollover.tiff in Resources */ = {isa = PBXBuildFile; fileRef = B6C21CCD2033382B00671774 /* TabClose_Busy_Rollover.tiff */; };
B6C21CD62033580200671774 /* RolloverButton.m in Sources */ = {isa = PBXBuildFile; fileRef = B6C21CD52033580200671774 /* RolloverButton.m */; };
B6C21CE1203510CC00671774 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B6C21CE0203510CC00671774 /* Images.xcassets */; };
B6C21CE52035262200671774 /* Defaults.plist in Resources */ = {isa = PBXBuildFile; fileRef = B6C21CE42035262200671774 /* Defaults.plist */; };
B6C704EF15CCC64100CC0401 /* titlebar-center@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B6C704EC15CCC64100CC0401 /* titlebar-center@2x.png */; };
B6C704F015CCC64100CC0401 /* titlebar-left@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B6C704ED15CCC64100CC0401 /* titlebar-left@2x.png */; };
B6C704F115CCC64100CC0401 /* titlebar-right@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B6C704EE15CCC64100CC0401 /* titlebar-right@2x.png */; };
@ -61,6 +78,8 @@
isa = PBXBuildRule;
compilerSpec = "com.apple.build-tasks.copy-strings-file";
fileType = sourcecode.glsl;
inputFiles = (
);
isEditable = 1;
outputFiles = (
);
@ -71,6 +90,8 @@
compilerSpec = com.apple.compilers.proxy.script;
filePatterns = "*.mm.ragel";
fileType = pattern.proxy;
inputFiles = (
);
isEditable = 1;
outputFiles = (
"$(DERIVED_SOURCES_DIR)/$(INPUT_FILE_BASE)",
@ -92,10 +113,8 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
089C165DFE840E0CC02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = "<absolute>"; };
1DDD58150DA1D0A300B32029 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/MainMenu.xib; sourceTree = "<group>"; };
256AC3D80F4B6AC300CF3369 /* TwoTermAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TwoTermAppDelegate.h; sourceTree = "<group>"; };
256AC3D90F4B6AC300CF3369 /* TwoTermAppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TwoTermAppDelegate.mm; sourceTree = "<group>"; };
256AC3F00F4B6AF500CF3369 /* TwoTerm_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TwoTerm_Prefix.pch; sourceTree = "<group>"; };
@ -104,6 +123,7 @@
29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
8D1107320486CEB800E47090 /* TwoTerm.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TwoTerm.app; sourceTree = BUILT_PRODUCTS_DIR; };
B60942022077F24300141159 /* VT100.mm.ragel */ = {isa = PBXFileReference; lastKnownFileType = text; path = VT100.mm.ragel; sourceTree = "<group>"; };
B60B98712022BAF100E688E3 /* appleIIgs.ti */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = appleIIgs.ti; sourceTree = "<group>"; };
B60B98732022BAF100E688E3 /* gno-console.ti */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "gno-console.ti"; sourceTree = "<group>"; };
B60B98742022BAF100E688E3 /* gsos-console.ti */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "gsos-console.ti"; sourceTree = "<group>"; };
@ -146,10 +166,8 @@
B61D0D68125B8E06001C713B /* Defaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Defaults.m; sourceTree = "<group>"; };
B61EF7C31481561E008C1891 /* titlebar-corner.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "titlebar-corner.png"; sourceTree = "<group>"; };
B61EF7C41481561E008C1891 /* titlebar-middle.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "titlebar-middle.png"; sourceTree = "<group>"; };
B61EF7C814815AF8008C1891 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/NewTerminal.xib; sourceTree = "<group>"; };
B61EF7CA14815E07008C1891 /* TitleBarView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TitleBarView.h; sourceTree = "<group>"; };
B61EF7CB14815E07008C1891 /* TitleBarView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TitleBarView.m; sourceTree = "<group>"; };
B61EF7CE148163E7008C1891 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/TitleBarView.xib; sourceTree = "<group>"; };
B61EF7D41482FB6D008C1891 /* titlebar-center.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "titlebar-center.png"; sourceTree = "<group>"; };
B61EF7D51482FB6D008C1891 /* titlebar-left.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "titlebar-left.png"; sourceTree = "<group>"; };
B61EF7D61482FB6D008C1891 /* titlebar-right.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "titlebar-right.png"; sourceTree = "<group>"; };
@ -159,20 +177,44 @@
B66412381480A070003BC8D3 /* EmulatorWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EmulatorWindow.m; sourceTree = "<group>"; };
B676063911DEAD3500D6B66C /* TermWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TermWindowController.h; sourceTree = "<group>"; };
B676063A11DEAD3500D6B66C /* TermWindowController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TermWindowController.mm; sourceTree = "<group>"; };
B676064D11DEBAE300D6B66C /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/TermWindow.xib; sourceTree = "<group>"; };
B67B3CE312B6FA040033AE07 /* a2-charset-40.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "a2-charset-40.png"; sourceTree = "<group>"; };
B67B3CE412B6FA040033AE07 /* a2-charset-80.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "a2-charset-80.png"; sourceTree = "<group>"; };
B6801BD812EB549300B22E9E /* vt100-charset.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "vt100-charset.png"; sourceTree = "<group>"; };
B683F70E2049E7B000470B99 /* VT50.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VT50.h; sourceTree = "<group>"; };
B683F70F2049E7B000470B99 /* VT50.mm.ragel */ = {isa = PBXFileReference; lastKnownFileType = text; path = VT50.mm.ragel; sourceTree = "<group>"; };
B683F711204AE32900470B99 /* VT05.mm.ragel */ = {isa = PBXFileReference; lastKnownFileType = text; path = VT05.mm.ragel; sourceTree = "<group>"; };
B6887810235CD76A00407374 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
B6887811235CD76A00407374 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/TermWindow.xib; sourceTree = "<group>"; };
B6887812235CD76A00407374 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/NewTerminal.xib; sourceTree = "<group>"; };
B6887813235CD76B00407374 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/TermConfig.xib; sourceTree = "<group>"; };
B6887814235CD76B00407374 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/TitleBarView.xib; sourceTree = "<group>"; };
B6887815235CD77600407374 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
B68A37D621A9D45A004CBDE4 /* TwoTerm.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = TwoTerm.entitlements; path = TwoTerm/TwoTerm.entitlements; sourceTree = "<group>"; };
B68E632812FF909C00EAFF5F /* ExampleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ExampleView.h; sourceTree = "<group>"; };
B68E632912FF909C00EAFF5F /* ExampleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ExampleView.m; sourceTree = "<group>"; };
B69D0FB9202799B10073CCB7 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/TermConfig.xib; sourceTree = "<group>"; };
B69E32A820221C9E0086D7B1 /* ChildMonitor.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ChildMonitor.mm; sourceTree = "<group>"; };
B69E32AA20221CCA0086D7B1 /* ChildMonitor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ChildMonitor.h; sourceTree = "<group>"; };
B6ACA2AB1E5C8BEC000E774B /* GNOConsole.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = GNOConsole.mm; sourceTree = "<group>"; };
B6ACA2AE1E635CEC000E774B /* vt52-charset.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "vt52-charset.png"; sourceTree = "<group>"; };
B6C0908D21D1BBE70067F7A4 /* Apple3.mm.ragel */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Apple3.mm.ragel; sourceTree = "<group>"; };
B6C0908F21D1D2070067F7A4 /* Apple3.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Apple3.h; sourceTree = "<group>"; };
B6C173901D31D2B80024E360 /* GSOSConsole.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GSOSConsole.h; sourceTree = "<group>"; };
B6C173911D31D2B80024E360 /* GSOSConsole.mm.ragel */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = GSOSConsole.mm.ragel; sourceTree = "<group>"; };
B6C173941D35546A0024E360 /* algorithm.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = algorithm.h; sourceTree = "<group>"; };
B6C21CC82033382A00671774 /* TabClose_Busy.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = TabClose_Busy.tiff; sourceTree = "<group>"; };
B6C21CC92033382A00671774 /* TabClose_Busy_Pressed.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = TabClose_Busy_Pressed.tiff; sourceTree = "<group>"; };
B6C21CCA2033382A00671774 /* TabClose.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = TabClose.tiff; sourceTree = "<group>"; };
B6C21CCB2033382B00671774 /* TabClose_Pressed.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = TabClose_Pressed.tiff; sourceTree = "<group>"; };
B6C21CCC2033382B00671774 /* TabClose_Rollover.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = TabClose_Rollover.tiff; sourceTree = "<group>"; };
B6C21CCD2033382B00671774 /* TabClose_Busy_Rollover.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = TabClose_Busy_Rollover.tiff; sourceTree = "<group>"; };
B6C21CD42033580200671774 /* RolloverButton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RolloverButton.h; sourceTree = "<group>"; };
B6C21CD52033580200671774 /* RolloverButton.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RolloverButton.m; sourceTree = "<group>"; };
B6C21CD720349C8C00671774 /* a2-charset-80@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "a2-charset-80@2x.png"; sourceTree = "<group>"; };
B6C21CD820349C8D00671774 /* vt100-charset@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "vt100-charset@2x.png"; sourceTree = "<group>"; };
B6C21CD920349C8D00671774 /* vt52-charset@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "vt52-charset@2x.png"; sourceTree = "<group>"; };
B6C21CDA20349C8E00671774 /* a2-charset-40@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "a2-charset-40@2x.png"; sourceTree = "<group>"; };
B6C21CE0203510CC00671774 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = TwoTerm/Images.xcassets; sourceTree = "<group>"; };
B6C21CE42035262200671774 /* Defaults.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Defaults.plist; sourceTree = "<group>"; };
B6C704EC15CCC64100CC0401 /* titlebar-center@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "titlebar-center@2x.png"; sourceTree = "<group>"; };
B6C704ED15CCC64100CC0401 /* titlebar-left@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "titlebar-left@2x.png"; sourceTree = "<group>"; };
B6C704EE15CCC64100CC0401 /* titlebar-right@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "titlebar-right@2x.png"; sourceTree = "<group>"; };
@ -245,6 +287,7 @@
29B97314FDCFA39411CA2CEA /* 2Term */ = {
isa = PBXGroup;
children = (
B68A37D621A9D45A004CBDE4 /* TwoTerm.entitlements */,
B612F46B12DD5E02005D1B77 /* Views */,
B612F45512DD5DF1005D1B77 /* Emulators */,
B612F44612DD5DAD005D1B77 /* cpp */,
@ -271,10 +314,12 @@
29B97317FDCFA39411CA2CEA /* Resources */ = {
isa = PBXGroup;
children = (
B6C21CE0203510CC00671774 /* Images.xcassets */,
B60B98702022BAF100E688E3 /* a2-terminfo */,
B60EBDE711E9143F00C1974F /* ScanLineFilter.cikernel */,
B66979CE11E6BCAE002ED475 /* images */,
8D1107310486CEB800E47090 /* Info.plist */,
B6C21CE42035262200671774 /* Defaults.plist */,
089C165CFE840E0CC02AAC07 /* InfoPlist.strings */,
1DDD58140DA1D0A300B32029 /* MainMenu.xib */,
B676065011DEBAE900D6B66C /* TermWindow.xib */,
@ -308,10 +353,10 @@
B612F44612DD5DAD005D1B77 /* cpp */ = {
isa = PBXGroup;
children = (
B612F44812DD5DAD005D1B77 /* iGeometry.h */,
B612F44712DD5DAD005D1B77 /* iGeometry.cpp */,
B6D5A0831E3996BD004FC1AC /* ring_buffer.h */,
B6C173941D35546A0024E360 /* algorithm.h */,
B612F44812DD5DAD005D1B77 /* iGeometry.h */,
B612F44912DD5DAD005D1B77 /* Lock.cpp */,
B612F44A12DD5DAD005D1B77 /* Lock.h */,
B612F44B12DD5DAD005D1B77 /* OutputChannel.cpp */,
@ -329,7 +374,9 @@
B612F45912DD5DF1005D1B77 /* EmulatorManager.mm */,
B612F45612DD5DF1005D1B77 /* Apple80.h */,
B612F45712DD5DF1005D1B77 /* Apple80.mm.ragel */,
B6C0908D21D1BBE70067F7A4 /* Apple3.mm.ragel */,
B612F45A12DD5DF1005D1B77 /* GNOConsole.h */,
B6C0908F21D1D2070067F7A4 /* Apple3.h */,
B612F45B12DD5DF1005D1B77 /* GNOConsole.mm.ragel */,
B6ACA2AB1E5C8BEC000E774B /* GNOConsole.mm */,
B6C173901D31D2B80024E360 /* GSOSConsole.h */,
@ -338,10 +385,14 @@
B612F45D12DD5DF1005D1B77 /* PTSE.mm.ragel */,
B612F45E12DD5DF1005D1B77 /* VT05.h */,
B612F45F12DD5DF1005D1B77 /* VT05.mm */,
B683F711204AE32900470B99 /* VT05.mm.ragel */,
B612F46012DD5DF1005D1B77 /* VT100.h */,
B612F46112DD5DF1005D1B77 /* VT100.mm */,
B612F46212DD5DF1005D1B77 /* VT52.h */,
B612F46312DD5DF1005D1B77 /* VT52.mm */,
B683F70E2049E7B000470B99 /* VT50.h */,
B683F70F2049E7B000470B99 /* VT50.mm.ragel */,
B60942022077F24300141159 /* VT100.mm.ragel */,
);
path = Emulators;
sourceTree = "<group>";
@ -365,6 +416,8 @@
B6ECFF261D2EEA2B00871A81 /* TextLabel.m */,
B638188014A179D60027D007 /* ColorView.h */,
B638188114A179D60027D007 /* ColorView.m */,
B6C21CD42033580200671774 /* RolloverButton.h */,
B6C21CD52033580200671774 /* RolloverButton.m */,
);
path = Views;
sourceTree = "<group>";
@ -372,6 +425,12 @@
B66979CE11E6BCAE002ED475 /* images */ = {
isa = PBXGroup;
children = (
B6C21CC92033382A00671774 /* TabClose_Busy_Pressed.tiff */,
B6C21CCD2033382B00671774 /* TabClose_Busy_Rollover.tiff */,
B6C21CC82033382A00671774 /* TabClose_Busy.tiff */,
B6C21CCB2033382B00671774 /* TabClose_Pressed.tiff */,
B6C21CCC2033382B00671774 /* TabClose_Rollover.tiff */,
B6C21CCA2033382A00671774 /* TabClose.tiff */,
B61EF7D41482FB6D008C1891 /* titlebar-center.png */,
B61EF7D51482FB6D008C1891 /* titlebar-left.png */,
B61EF7D61482FB6D008C1891 /* titlebar-right.png */,
@ -384,6 +443,10 @@
B6ACA2AE1E635CEC000E774B /* vt52-charset.png */,
B67B3CE312B6FA040033AE07 /* a2-charset-40.png */,
B67B3CE412B6FA040033AE07 /* a2-charset-80.png */,
B6C21CDA20349C8E00671774 /* a2-charset-40@2x.png */,
B6C21CD720349C8C00671774 /* a2-charset-80@2x.png */,
B6C21CD920349C8D00671774 /* vt52-charset@2x.png */,
B6C21CD820349C8D00671774 /* vt100-charset@2x.png */,
);
path = images;
sourceTree = "<group>";
@ -419,15 +482,25 @@
29B97313FDCFA39411CA2CEA /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0910;
LastUpgradeCheck = 1110;
TargetAttributes = {
8D1107260486CEB800E47090 = {
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.HardenedRuntime = {
enabled = 1;
};
};
};
};
};
buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "TwoTerm" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
developmentRegion = en;
hasScannedForEncodings = 1;
knownRegions = (
en,
English,
Base,
);
mainGroup = 29B97314FDCFA39411CA2CEA /* 2Term */;
projectDirPath = "";
@ -443,24 +516,36 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B6C21CE52035262200671774 /* Defaults.plist in Resources */,
B60EBE2B11E918D500C1974F /* ScanLineFilter.cikernel in Resources */,
B6C21CD12033382B00671774 /* TabClose_Pressed.tiff in Resources */,
B6C21CCF2033382B00671774 /* TabClose_Busy_Pressed.tiff in Resources */,
8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */,
B69D0FBA202799B10073CCB7 /* TermConfig.xib in Resources */,
B68A37D421A9C161004CBDE4 /* vt52-charset@2x.png in Resources */,
1DDD58160DA1D0A300B32029 /* MainMenu.xib in Resources */,
B676065111DEBAE900D6B66C /* TermWindow.xib in Resources */,
B67B3CE512B6FA040033AE07 /* a2-charset-40.png in Resources */,
B6C21CD02033382B00671774 /* TabClose.tiff in Resources */,
B67B3CE612B6FA040033AE07 /* a2-charset-80.png in Resources */,
B6801BD912EB549300B22E9E /* vt100-charset.png in Resources */,
B68A37D321A9C15C004CBDE4 /* a2-charset-40@2x.png in Resources */,
B6C21CCE2033382B00671774 /* TabClose_Busy.tiff in Resources */,
B6C21CD32033382B00671774 /* TabClose_Busy_Rollover.tiff in Resources */,
B61EF7C51481561E008C1891 /* titlebar-corner.png in Resources */,
B61EF7C61481561E008C1891 /* titlebar-middle.png in Resources */,
B68A37D521A9C164004CBDE4 /* vt100-charset@2x.png in Resources */,
B68A37D221A9B9D9004CBDE4 /* a2-charset-80@2x.png in Resources */,
B61EF7C914815AF8008C1891 /* NewTerminal.xib in Resources */,
B61EF7CF148163E7008C1891 /* TitleBarView.xib in Resources */,
B61EF7D71482FB6D008C1891 /* titlebar-center.png in Resources */,
B6C21CE1203510CC00671774 /* Images.xcassets in Resources */,
B61EF7D81482FB6D008C1891 /* titlebar-left.png in Resources */,
B61EF7D91482FB6D008C1891 /* titlebar-right.png in Resources */,
B6C704EF15CCC64100CC0401 /* titlebar-center@2x.png in Resources */,
B6C704F015CCC64100CC0401 /* titlebar-left@2x.png in Resources */,
B6C704F115CCC64100CC0401 /* titlebar-right@2x.png in Resources */,
B6C21CD22033382B00671774 /* TabClose_Rollover.tiff in Resources */,
B6ACA2AF1E635CEC000E774B /* vt52-charset.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -489,10 +574,15 @@
buildActionMask = 2147483647;
files = (
B675F4A91E561D20004B0D9C /* Apple80.mm.ragel in Sources */,
B6C0909021D292E20067F7A4 /* Apple3.mm.ragel in Sources */,
B675F4AC1E56A7F2004B0D9C /* GSOSConsole.mm.ragel in Sources */,
B6D1CD071E577E7D00C4A6BC /* PTSE.mm.ragel in Sources */,
B69E32A920221C9E0086D7B1 /* ChildMonitor.mm in Sources */,
B683F7102049E7B000470B99 /* VT50.mm.ragel in Sources */,
B6407805201CE93500D3F2D1 /* GNOConsole.mm.ragel in Sources */,
B609404C205ACD730077A69C /* VT05.mm.ragel in Sources */,
B60942042082BCA200141159 /* VT100.mm.ragel in Sources */,
B6C21CD62033580200671774 /* RolloverButton.m in Sources */,
B69E32A920221C9E0086D7B1 /* ChildMonitor.mm in Sources */,
8D11072D0486CEB800E47090 /* main.m in Sources */,
256AC3DA0F4B6AC300CF3369 /* TwoTermAppDelegate.mm in Sources */,
B676063B11DEAD3500D6B66C /* TermWindowController.mm in Sources */,
@ -523,7 +613,7 @@
089C165CFE840E0CC02AAC07 /* InfoPlist.strings */ = {
isa = PBXVariantGroup;
children = (
089C165DFE840E0CC02AAC07 /* English */,
B6887815235CD77600407374 /* en */,
);
name = InfoPlist.strings;
sourceTree = "<group>";
@ -531,7 +621,7 @@
1DDD58140DA1D0A300B32029 /* MainMenu.xib */ = {
isa = PBXVariantGroup;
children = (
1DDD58150DA1D0A300B32029 /* English */,
B6887810235CD76A00407374 /* Base */,
);
name = MainMenu.xib;
sourceTree = "<group>";
@ -539,7 +629,7 @@
B61EF7C714815AF8008C1891 /* NewTerminal.xib */ = {
isa = PBXVariantGroup;
children = (
B61EF7C814815AF8008C1891 /* English */,
B6887812235CD76A00407374 /* Base */,
);
name = NewTerminal.xib;
sourceTree = "<group>";
@ -547,7 +637,7 @@
B61EF7CD148163E7008C1891 /* TitleBarView.xib */ = {
isa = PBXVariantGroup;
children = (
B61EF7CE148163E7008C1891 /* English */,
B6887814235CD76B00407374 /* Base */,
);
name = TitleBarView.xib;
sourceTree = "<group>";
@ -555,7 +645,7 @@
B676065011DEBAE900D6B66C /* TermWindow.xib */ = {
isa = PBXVariantGroup;
children = (
B676064D11DEBAE300D6B66C /* English */,
B6887811235CD76A00407374 /* Base */,
);
name = TermWindow.xib;
sourceTree = "<group>";
@ -563,7 +653,7 @@
B69D0FB8202799B10073CCB7 /* TermConfig.xib */ = {
isa = PBXVariantGroup;
children = (
B69D0FB9202799B10073CCB7 /* English */,
B6887813235CD76B00407374 /* Base */,
);
name = TermConfig.xib;
sourceTree = "<group>";
@ -575,8 +665,14 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_OBJC_WEAK = YES;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = YES;
GCC_DYNAMIC_NO_PIC = NO;
GCC_MODEL_TUNING = G5;
GCC_OPTIMIZATION_LEVEL = 0;
@ -586,6 +682,7 @@
INSTALL_PATH = "$(HOME)/Applications";
PRODUCT_BUNDLE_IDENTIFIER = "com.ksherlock.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = TwoTerm;
PROVISIONING_PROFILE_SPECIFIER = "";
};
name = Debug;
};
@ -593,8 +690,13 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_OBJC_WEAK = YES;
CODE_SIGN_IDENTITY = "-";
COMBINE_HIDPI_IMAGES = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = YES;
GCC_MODEL_TUNING = G5;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = TwoTerm_Prefix.pch;
@ -615,11 +717,13 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
@ -654,11 +758,13 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;

View File

@ -0,0 +1,66 @@
{
"images" : [
{
"idiom" : "mac",
"size" : "16x16",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "16x16",
"scale" : "2x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "icon-32.png",
"scale" : "1x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "icon-32@2x.png",
"scale" : "2x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "icon-128.png",
"scale" : "1x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "icon-128@2x.png",
"scale" : "2x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "icon-256.png",
"scale" : "1x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "icon-256@2x.png",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "icon-512.png",
"scale" : "1x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "icon-512@2x.png",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 KiB

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>

View File

@ -111,6 +111,8 @@ private:
-(void)processData: (uint8_t *)data size: (size_t)size;
-(void)childFinished: (int)status;
-(void)childBegan;
-(void)reset;
@end

View File

@ -143,19 +143,39 @@
_screen.setFD(fd);
}
-(void)setCharacterGenerator: (CharacterGenerator *)generator {
if (generator != _charGen) {
[_charGen release];
_charGen = [generator retain];
NSSize size = [_charGen characterSize];
_charWidth = size.width;
_charHeight = size.height;
}
}
-(void)setEmulator:(NSObject<Emulator> *)emulator {
if (_emulator == emulator) return;
[_emulator release];
_emulator = [emulator retain];
if ([emulator respondsToSelector: @selector(characterGenerator)]) {
id cg = [emulator characterGenerator];
if (cg) [self setCharacterGenerator: cg];
}
}
#pragma mark -
-(void)awakeFromNib
{
NSSize size;
_charWidth = 7;
_charHeight = 16;
_paddingLeft = 8;
_paddingTop = 8;
_paddingBottom = 8;
_fd = 1;
_fd = -1;
//_foregroundColor = [[NSColor greenColor] retain];
//_backgroundColor = [[NSColor blackColor] retain];
@ -168,7 +188,7 @@
_screen.setFD(_fd);
_screen.setView(self);
_charGen = [[CharacterGenerator generator] retain];
[self setCharacterGenerator: [CharacterGenerator generatorForCharacterSet: CGApple80]];
_cursorType = Screen::CursorTypeUnderscore;
@ -179,10 +199,6 @@
_cursors[Screen::CursorTypeCrossHatch] = [[_charGen imageForCharacter: 0x7f] retain];
size = [_charGen characterSize];
_charWidth = size.width;
_charHeight = size.height;
// enable drag+drop for files/urls.
@ -233,6 +249,10 @@
[self becomeFirstResponder];
}
-(BOOL)canBecomeKeyView {
return YES;
}
-(BOOL)acceptsFirstResponder
{
return YES;
@ -323,9 +343,9 @@
{
// mouse text actually requires mouse text and inverse to be on.
if (flag & Screen::FlagMouseText)
if ((flag & Screen::FlagMouseText) && c >= '@' && c <= '_')
{
if (c >= '@' && c <= '_') c |= 0x80;
c |= 0x80;
}
else
{
@ -868,7 +888,12 @@
}
-(void)reset {
_screen.eraseScreen();
_screen.setCursor(iPoint(0,0));
_cursorOn = YES;
[self setNeedsDisplay: YES];
}
@end

View File

@ -8,6 +8,7 @@
#import <AppKit/AppKit.h>
@class TextLabel;
@class CharacterGenerator;
@interface EmulatorWindow : NSWindow
{
@ -16,5 +17,5 @@
@property (assign) IBOutlet TextLabel *textLabel;
-(void)setTitleTextColor: (NSColor *)color;
-(void)setTitleCharacterGenerator: (CharacterGenerator *)characterGenerator;
@end

View File

@ -85,6 +85,11 @@
[super setBackgroundColor: color];
}
-(void)setTitleCharacterGenerator: (CharacterGenerator *)characterGenerator {
[_textLabel setCharacterGenerator: characterGenerator];
}
-(void)awakeFromNib
{

16
Views/RolloverButton.h Normal file
View File

@ -0,0 +1,16 @@
//
// RolloverButton.h
// TwoTerm
//
// Created by Kelvin Sherlock on 2/13/2018.
//
#import <Cocoa/Cocoa.h>
@interface RolloverButton : NSButton {
NSImage *_image;
NSImage *_rolloverImage;
NSTrackingArea *_trackingArea;
BOOL _rollOver;
}
@end

98
Views/RolloverButton.m Normal file
View File

@ -0,0 +1,98 @@
//
// RolloverButton.m
// TwoTerm
//
// Created by Kelvin Sherlock on 2/13/2018.
//
#import "RolloverButton.h"
@implementation RolloverButton
#if 0
- (void)createTrackingArea
{
NSTrackingAreaOptions focusTrackingAreaOptions = 0;
focusTrackingAreaOptions |= NSTrackingActiveInActiveApp;
focusTrackingAreaOptions |= NSTrackingMouseEnteredAndExited;
//focusTrackingAreaOptions |= NSTrackingAssumeInside;
focusTrackingAreaOptions |= NSTrackingInVisibleRect;
NSTrackingArea *focusTrackingArea = [[NSTrackingArea alloc] initWithRect:NSZeroRect
options:focusTrackingAreaOptions
owner:self userInfo:nil];
[self addTrackingArea:focusTrackingArea];
[focusTrackingArea release];
}
#endif
- (void) updateTrackingAreas {
[super updateTrackingAreas];
if (_trackingArea) {
[self removeTrackingArea: _trackingArea];
[_trackingArea release];
}
NSTrackingAreaOptions options = 0;
options |= NSTrackingActiveInActiveApp;
options |= NSTrackingMouseEnteredAndExited;
//options |= NSTrackingAssumeInside;
options |= NSTrackingInVisibleRect;
_trackingArea = [[NSTrackingArea alloc] initWithRect:NSZeroRect
options:options
owner:self
userInfo:nil];
[self addTrackingArea: _trackingArea];
}
-(void)setImage:(NSImage *)image {
if (_image != image) {
[_image release];
_image = [image retain];
}
if (!_rollOver) [super setImage: image];
}
-(void)setRolloverImage: (NSImage *)image {
if (_rolloverImage != image) {
[_rolloverImage release];
_rolloverImage = [image retain];
}
if (_rollOver) [super setImage: image];
}
-(void)awakeFromNib {
[super awakeFromNib];
//[self createTrackingArea];
[self setImage: [NSImage imageNamed: @"TabClose"]];
[self setRolloverImage: [NSImage imageNamed: @"TabClose_Rollover"]];
[[self cell] setHighlightsBy: NSContentsCellMask];
}
-(void)dealloc {
[_image release];
[_rolloverImage release];
[super dealloc];
}
-(void)mouseExited:(NSEvent *)event {
[[self cell] setImage: _image];
_rollOver = NO;
[super mouseExited: event];
}
-(void) mouseEntered:(NSEvent *)event {
[[self cell] setImage: _rolloverImage];
_rollOver = YES;
[super mouseEntered: event];
}
@end

View File

@ -8,12 +8,15 @@
#import <Cocoa/Cocoa.h>
@class CharacterGenerator;
@interface TextLabel : NSView
{
NSString *_text;
NSColor *_color;
CharacterGenerator *_generator;
}
@property (nonatomic, retain) NSString *text;
@property (nonatomic, retain) NSColor *color;
@property (nonatomic, retain) CharacterGenerator *characterGenerator;
@end

View File

@ -14,6 +14,7 @@
@synthesize text = _text;
@synthesize color = _color;
@synthesize characterGenerator = _characterGenerator;
-(void) setText:(NSString *)text {
if (_text == text) return;
@ -29,6 +30,14 @@
[self setNeedsDisplay: YES];
}
-(void) setCharacterGenerator:(CharacterGenerator *)characterGenerator {
if (_characterGenerator == characterGenerator) return;
[_characterGenerator release];
_characterGenerator = [characterGenerator retain];
[self setNeedsDisplay: YES];
}
/*
-(BOOL)isFlipped {
return YES;
@ -42,6 +51,7 @@
[_text release];
[_color release];
[_characterGenerator release];
[super dealloc];
}
@ -55,9 +65,7 @@
if (!length) return;
if (!_color) return;
CharacterGenerator *gen = [CharacterGenerator generator];
NSSize sz = [gen characterSize];
NSSize sz = [_characterGenerator characterSize];
NSRect frame = [self frame];
@ -75,7 +83,7 @@
for (unsigned i = 0; i < length; ++i) {
unichar c = [_text characterAtIndex: i];
[gen drawCharacter: c atPoint: point];
[_characterGenerator drawCharacter: c atPoint: point];
point.x += sz.width;
if (point.x > NSWidth(frame)) break;
}
@ -89,6 +97,7 @@
if (!_text) _text = [@"Testing!" retain];
if (!_color) _color = [[NSColor greenColor] retain];
if (!_characterGenerator) _characterGenerator = [[CharacterGenerator generatorForCharacterSet: CGApple80] retain];
}

@ -1 +1 @@
Subproject commit b91763ed705d32ec0fc7cbdca84f9d597696b050
Subproject commit c7b95dda17a1239b303d01eeca8c8f9fba3d2131

BIN
images/TabClose.tiff Normal file

Binary file not shown.

100
images/TabCloseButton.pdf Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

BIN
images/TabClose_Busy.tiff Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
images/TabNewButton.tiff Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
images/a2-charset-40@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
images/a2-charset-80@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
images/vt100-charset@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
images/vt52-charset@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB