mirror of
https://github.com/ksherlock/TwoTerm.git
synced 2024-12-22 22:29:51 +00:00
0dd6473956
git-svn-id: svn://qnap.local/TwoTerm/trunk@2020 5590a31f-7b70-45f8-8c82-aa3a8e5f4507
777 lines
17 KiB
Plaintext
777 lines
17 KiB
Plaintext
//
|
|
// EmulatorView.m
|
|
// 2Term
|
|
//
|
|
// Created by Kelvin Sherlock on 7/3/2010.
|
|
// Copyright 2010 __MyCompanyName__. All rights reserved.
|
|
//
|
|
|
|
#import <QuartzCore/QuartzCore.h>
|
|
#include <termios.h>
|
|
#include <sys/ioctl.h>
|
|
|
|
#import "EmulatorView.h"
|
|
|
|
#import "CharacterGenerator.h"
|
|
|
|
#import "VT52.h"
|
|
#import "VT100.h"
|
|
|
|
|
|
#include "OutputChannel.h"
|
|
|
|
#import "ScanLineFilter.h"
|
|
|
|
@implementation EmulatorView
|
|
|
|
@synthesize fd = _fd;
|
|
@synthesize emulator = _emulator;
|
|
|
|
@synthesize foregroundColor = _foregroundColor;
|
|
@synthesize backgroundColor = _backgroundColor;
|
|
@synthesize scanLines = _scanLines;
|
|
|
|
|
|
#pragma mark -
|
|
#pragma mark properties
|
|
|
|
-(void)setFd: (int)fd
|
|
{
|
|
_fd = fd;
|
|
_screen.setFD(fd);
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
-(void)awakeFromNib
|
|
{
|
|
NSSize size;
|
|
|
|
_charWidth = 7;
|
|
_charHeight = 16;
|
|
|
|
_paddingLeft = 8;
|
|
_paddingTop = 8;
|
|
|
|
|
|
//_foregroundColor = [[NSColor greenColor] retain];
|
|
//_backgroundColor = [[NSColor blackColor] retain];
|
|
_boldColor = [[NSColor redColor] retain];
|
|
//_foregroundColor = [[NSColor whiteColor] retain];
|
|
//_backgroundColor = [[NSColor blueColor] retain];
|
|
|
|
|
|
|
|
_screen.setFD(_fd);
|
|
_screen.setView(self);
|
|
|
|
_charGen = [[CharacterGenerator generator] retain];
|
|
|
|
_cursorImg = [[_charGen imageForCharacter: '_'] retain];
|
|
|
|
|
|
size = [_charGen characterSize];
|
|
_charWidth = size.width;
|
|
_charHeight = size.height;
|
|
|
|
// enable drag+drop for files/urls.
|
|
|
|
|
|
[self registerForDraggedTypes:[NSArray arrayWithObjects: NSFilenamesPboardType, NSURLPboardType , nil]];
|
|
|
|
}
|
|
|
|
-(void)setBackgroundColor:(NSColor *)color
|
|
{
|
|
if (_backgroundColor == color) return;
|
|
|
|
[_backgroundColor release];
|
|
_backgroundColor = [color retain];
|
|
|
|
[self setNeedsDisplay: YES];
|
|
}
|
|
|
|
-(void)setForegroundColor:(NSColor *)color
|
|
{
|
|
if (_foregroundColor == color) return;
|
|
|
|
[_foregroundColor release];
|
|
_foregroundColor = [color retain];
|
|
|
|
[self setNeedsDisplay: YES];
|
|
}
|
|
|
|
-(void)setScanLines:(BOOL)scanLines
|
|
{
|
|
if (_scanLines == scanLines) return;
|
|
|
|
_scanLines = scanLines;
|
|
|
|
if (_scanLines)
|
|
{
|
|
NSMutableArray *filters;
|
|
CIFilter *filter;
|
|
|
|
[self setWantsLayer: YES];
|
|
|
|
filters = [NSMutableArray arrayWithCapacity: 3];
|
|
|
|
|
|
|
|
//add the scanlines
|
|
|
|
filter = [[ScanLineFilter new] autorelease];
|
|
[filter setValue: [NSNumber numberWithFloat: 1.0] forKey: @"inputDarken"];
|
|
[filter setValue: [NSNumber numberWithFloat: 0.025] forKey: @"inputLighten"];
|
|
[filters addObject: filter];
|
|
|
|
//blur it a bit...
|
|
|
|
filter = [CIFilter filterWithName: @"CIGaussianBlur"];
|
|
[filter setDefaults];
|
|
[filter setValue: [NSNumber numberWithFloat: 0.33] forKey: @"inputRadius"];
|
|
|
|
[filters addObject: filter];
|
|
|
|
|
|
|
|
|
|
[self setContentFilters: filters];
|
|
}
|
|
else
|
|
{
|
|
[self setContentFilters: nil];
|
|
}
|
|
}
|
|
|
|
-(BOOL)isFlipped
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
|
|
-(void)viewDidMoveToWindow
|
|
{
|
|
[self becomeFirstResponder];
|
|
|
|
|
|
|
|
_cursorTimer = [[NSTimer scheduledTimerWithTimeInterval: .5
|
|
target: self
|
|
selector: @selector(cursorTimer:)
|
|
userInfo: nil
|
|
repeats: YES] retain];
|
|
|
|
|
|
}
|
|
|
|
-(void)viewDidMoveToSuperview
|
|
{
|
|
[self becomeFirstResponder];
|
|
}
|
|
|
|
-(BOOL)acceptsFirstResponder
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
-(void)drawRect:(NSRect)dirtyRect
|
|
{
|
|
//NSRect bounds = [self bounds];
|
|
|
|
NSRect screenRect = dirtyRect;
|
|
|
|
unsigned x, y;
|
|
|
|
|
|
|
|
NSColor *currentFront;
|
|
NSColor *currentBack;
|
|
BOOL setFront = YES;
|
|
|
|
screenRect.origin.x -= _paddingLeft;
|
|
screenRect.origin.y -= _paddingTop;
|
|
|
|
if (screenRect.origin.x < 0)
|
|
{
|
|
screenRect.size.width -= screenRect.origin.x;
|
|
screenRect.origin.x = 0;
|
|
}
|
|
if (screenRect.origin.y < 0)
|
|
{
|
|
screenRect.size.width -= screenRect.origin.y;
|
|
screenRect.origin.y = 0;
|
|
}
|
|
|
|
int minX = floor(screenRect.origin.x / _charWidth);
|
|
int maxX = ceil((screenRect.origin.x + screenRect.size.width) / _charWidth);
|
|
|
|
int minY = floor(screenRect.origin.y / _charHeight);
|
|
int maxY = ceil((screenRect.origin.y + screenRect.size.height) / _charHeight);
|
|
|
|
// x/y are 0-indexed here.
|
|
|
|
maxY = std::min(_screen.height() - 1, maxY);
|
|
maxX = std::min(_screen.width() - 1, maxX);
|
|
|
|
[_backgroundColor setFill];
|
|
NSRectFill(dirtyRect);
|
|
|
|
[_foregroundColor setFill];
|
|
|
|
currentFront = _foregroundColor;
|
|
currentBack = _backgroundColor;
|
|
|
|
_screen.lock();
|
|
|
|
|
|
for (x = minX; x <= maxX; ++x)
|
|
{
|
|
for (y = minY; y <= maxY; ++y)
|
|
{
|
|
NSRect charRect = NSMakeRect(_paddingLeft + x * _charWidth, _paddingTop + y *_charHeight, _charWidth, _charHeight);
|
|
//NSImage *img;
|
|
CharInfo ci = _screen.getc(x, y);
|
|
unsigned flag = ci.flag;
|
|
uint8_t c = ci.c;
|
|
|
|
// todo -- check flags to determine fg/bg color, etc.
|
|
// need to draw background individually....
|
|
|
|
|
|
currentBack = _backgroundColor;
|
|
currentFront = _foregroundColor;
|
|
|
|
if (flag & Screen::FlagBold)
|
|
currentFront = _boldColor;
|
|
|
|
|
|
|
|
|
|
|
|
//img = [_charGen imageForCharacter: c];
|
|
|
|
if (flag & Screen::FlagInverse)
|
|
{
|
|
|
|
// mouse text actually requires mouse text and inverse to be on.
|
|
if (flag & Screen::FlagMouseText)
|
|
{
|
|
if (c >= '@' && c <= '_') c |= 0x80;
|
|
}
|
|
else
|
|
{
|
|
std::swap(currentBack, currentFront);
|
|
}
|
|
}
|
|
|
|
if (currentBack != _backgroundColor)
|
|
{
|
|
[currentBack setFill];
|
|
NSRectFill(charRect);
|
|
setFront = YES;
|
|
}
|
|
|
|
if (_foregroundColor != currentFront) setFront = YES;
|
|
if (setFront) [currentFront setFill];
|
|
|
|
// need to apply the scanline filter here.
|
|
|
|
[_charGen drawCharacter: c
|
|
atPoint: NSMakePoint(_paddingLeft + x * _charWidth, _paddingTop + y * _charHeight)];
|
|
|
|
|
|
// strikethrough -- draw a centered line.
|
|
if (flag & Screen::FlagStrike)
|
|
{
|
|
|
|
NSRectFill(NSMakeRect(_paddingLeft + x * _charWidth,
|
|
_paddingTop + y * _charHeight + floor(_charHeight / 2) - 1,
|
|
_charWidth,
|
|
2));
|
|
}
|
|
|
|
// underscore -- draw a bottom line. (eg, ``_'')
|
|
if (flag & Screen::FlagUnderscore)
|
|
{
|
|
NSRectFill(NSMakeRect(_paddingLeft + x * _charWidth,
|
|
_paddingTop + y * _charHeight + _charHeight - 2,
|
|
_charWidth,
|
|
2));
|
|
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|
|
// cursor.
|
|
iPoint cursor = _screen.cursor();
|
|
if (_cursorOn && iRect(minX, minY, maxX - minX, maxY - minY).contains(cursor))
|
|
{
|
|
NSRect charRect = NSMakeRect(_paddingLeft + cursor.x * _charWidth, _paddingTop + cursor.y *_charHeight, _charWidth, _charHeight);
|
|
|
|
[_foregroundColor setFill];
|
|
|
|
[_cursorImg drawInRect: charRect
|
|
fromRect: NSZeroRect operation: NSCompositeCopy
|
|
fraction: 1.0
|
|
respectFlipped: YES
|
|
hints: nil];
|
|
|
|
}
|
|
|
|
|
|
_screen.unlock();
|
|
|
|
|
|
//[_scanLine setFill];
|
|
//NSRectFillUsingOperation(screenRect, NSCompositeSourceOver);
|
|
//NSRectFill(screenRect);
|
|
|
|
|
|
}
|
|
|
|
|
|
-(void)dealloc
|
|
{
|
|
close(_fd);
|
|
|
|
[_foregroundColor release];
|
|
[_backgroundColor release];
|
|
|
|
[_readerThread release];
|
|
|
|
[_emulator release];
|
|
[_cursorImg release];
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
-(void)keyDown:(NSEvent *)theEvent
|
|
{
|
|
OutputChannel channel(_fd);
|
|
iRect updateRect; // should be nil but whatever...
|
|
|
|
|
|
|
|
[NSCursor setHiddenUntilMouseMoves: YES];
|
|
|
|
_screen.beginUpdate();
|
|
|
|
[_emulator keyDown: theEvent screen: &_screen output: &channel];
|
|
|
|
updateRect = _screen.endUpdate();
|
|
|
|
[self invalidateIRect: updateRect];
|
|
}
|
|
|
|
|
|
-(void)autoTypeText:(NSString *)text
|
|
{
|
|
|
|
NSData *data = [text dataUsingEncoding: NSASCIIStringEncoding allowLossyConversion: YES];
|
|
|
|
unsigned length = [data length];
|
|
|
|
OutputChannel channel(_fd);
|
|
|
|
|
|
|
|
if (!length) return;
|
|
|
|
// bad form to write directly rather than going through Emulator object?
|
|
channel.write([data bytes], length);
|
|
}
|
|
|
|
|
|
|
|
|
|
-(void)startBackgroundReader
|
|
{
|
|
return;
|
|
|
|
if (_readerThread) return;
|
|
|
|
_readerThread = [[NSThread alloc] initWithTarget: self selector: @selector(_readerThread) object: nil];
|
|
|
|
[_readerThread start];
|
|
}
|
|
-(void)_readerThread
|
|
{
|
|
// I would prefer to poll(2) but it's broken on os x for ptys.
|
|
|
|
int fd = _fd;
|
|
|
|
|
|
for(;;)
|
|
{
|
|
int n;
|
|
|
|
fd_set read_set;
|
|
fd_set error_set;
|
|
|
|
FD_ZERO(&read_set);
|
|
FD_SET(fd, &read_set);
|
|
FD_ZERO(&error_set);
|
|
FD_SET(fd, &error_set);
|
|
|
|
|
|
n = select(fd + 1, &read_set, NULL, &error_set, NULL);
|
|
|
|
if (n == 0) continue;
|
|
if (n < 0) break;
|
|
|
|
if (FD_ISSET(fd, &error_set)) break;
|
|
if (FD_ISSET(fd, &read_set)) [self dataAvailable];
|
|
}
|
|
|
|
}
|
|
|
|
|
|
-(void)dataAvailable
|
|
{
|
|
typedef void (*ProcessCharFX)(id, SEL, uint8_t, Screen *, OutputChannel *);
|
|
|
|
ProcessCharFX fx;
|
|
SEL cmd;
|
|
OutputChannel channel(_fd);
|
|
|
|
cmd = @selector(processCharacter: screen: output:);
|
|
fx = (ProcessCharFX)[_emulator methodForSelector: cmd];
|
|
|
|
for(;;)
|
|
{
|
|
NSAutoreleasePool *pool;
|
|
iRect updateRect;
|
|
uint8_t buffer[512];
|
|
ssize_t size;
|
|
|
|
|
|
size = read(_fd, buffer, sizeof(buffer));
|
|
|
|
if (size == 0) break;
|
|
if (size < 0)
|
|
{
|
|
if (errno == EINTR || errno == EAGAIN) continue;
|
|
|
|
perror("[EmulatorView dataAvailable]");
|
|
break;
|
|
}
|
|
|
|
#if 0
|
|
FILE *debug = fopen("/tmp/debug.txt", "a");
|
|
fwrite(buffer, 1, size, debug);
|
|
fflush(debug);
|
|
fclose(debug);
|
|
#endif
|
|
pool = [NSAutoreleasePool new];
|
|
_screen.beginUpdate();
|
|
|
|
|
|
for (unsigned i = 0; i < size; ++i)
|
|
{
|
|
fx(_emulator,cmd, buffer[i], &_screen, &channel);
|
|
}
|
|
|
|
updateRect = _screen.endUpdate();
|
|
|
|
[self invalidateIRect: updateRect];
|
|
|
|
[pool release];
|
|
}
|
|
}
|
|
|
|
|
|
-(void)invalidateIRect: (iRect)updateRect
|
|
{
|
|
NSRect rect;
|
|
|
|
if (updateRect.size.width <= 0 || updateRect.size.height <= 0) return;
|
|
|
|
rect.origin.x = updateRect.origin.x;
|
|
rect.origin.y = updateRect.origin.y;
|
|
rect.size.width = updateRect.size.width;
|
|
rect.size.height = updateRect.size.height;
|
|
|
|
rect.origin.x *= _charWidth;
|
|
rect.origin.y *= _charHeight;
|
|
rect.size.width *= _charWidth;
|
|
rect.size.height *= _charHeight;
|
|
|
|
rect.origin.x += _paddingLeft;
|
|
rect.origin.y += _paddingTop;
|
|
|
|
[self setNeedsDisplayInRect: rect];
|
|
|
|
}
|
|
|
|
|
|
-(void)resizeTo: (iSize)size animated: (BOOL)animated
|
|
{
|
|
NSWindow *window = [self window];
|
|
NSRect bounds = [self bounds];
|
|
NSSize newSize;
|
|
NSRect wframe = [window frame];
|
|
|
|
|
|
// TODO -- left/right padding...
|
|
newSize.width = size.width * _charWidth + _paddingLeft * 2;
|
|
newSize.height = size.height * _charHeight + _paddingTop * 2;
|
|
|
|
// best case -- no change.
|
|
if (NSEqualSizes(newSize, bounds.size)) return;
|
|
|
|
|
|
// ok, change needed.
|
|
|
|
wframe.origin.y -= (newSize.height - bounds.size.height);
|
|
|
|
wframe.size.height += newSize.height - bounds.size.height;
|
|
wframe.size.width += newSize.width - bounds.size.width;
|
|
|
|
_inResizeTo = YES;
|
|
[window setFrame: wframe display: YES animate: animated];
|
|
_inResizeTo = NO;
|
|
}
|
|
|
|
-(void)resizeTo: (iSize)size
|
|
{
|
|
[self resizeTo: size animated: YES];
|
|
}
|
|
|
|
|
|
|
|
-(void)cursorTimer: (NSTimer *)timer
|
|
{
|
|
|
|
_screen.lock();
|
|
|
|
_cursorOn = !_cursorOn;
|
|
|
|
iRect r(_screen.cursor(), iSize(1,1));
|
|
|
|
[self invalidateIRect: r];
|
|
|
|
_screen.unlock();
|
|
}
|
|
|
|
|
|
#if 0
|
|
-(void)viewWillStartLiveResize
|
|
{
|
|
//NSLog(@"%s", sel_getName(_cmd));
|
|
|
|
}
|
|
#endif
|
|
-(void)viewDidEndLiveResize
|
|
{
|
|
//NSLog(@"%s", sel_getName(_cmd));
|
|
[super viewDidEndLiveResize];
|
|
|
|
[self setNeedsDisplay: YES];
|
|
}
|
|
|
|
|
|
/*
|
|
* inLiveResize indicates the user is resizing the window -or- the -(void)setWindowFrame: animated: YES.
|
|
* non-live resize indicates zooming or resizeTo w/o animation.
|
|
*
|
|
*
|
|
*/
|
|
-(void)setFrame:(NSRect)frameRect
|
|
{
|
|
//NSLog(@"%s", sel_getName(_cmd));
|
|
|
|
BOOL inLiveResize = [self inLiveResize];
|
|
|
|
[super setFrame: frameRect];
|
|
|
|
|
|
if (inLiveResize && _inResizeTo)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (/* inLiveResize */ YES)
|
|
{
|
|
// user is resizing window.
|
|
// or zoom.
|
|
unsigned width = floor((frameRect.size.width - _paddingLeft * 2) / _charWidth);
|
|
unsigned height = floor((frameRect.size.height - _paddingTop * 2) / _charHeight);
|
|
|
|
_screen.lock();
|
|
_screen.setSize(width, height, false);
|
|
_screen.unlock();
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#pragma mark -
|
|
#pragma mark IBActions
|
|
|
|
|
|
-(IBAction)paste: (id)sender
|
|
{
|
|
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
|
|
NSArray *classArray = [NSArray arrayWithObject:[NSString class]];
|
|
NSDictionary *options = [NSDictionary dictionary];
|
|
|
|
|
|
BOOL ok = [pasteboard canReadObjectForClasses:classArray options:options];
|
|
|
|
if (ok)
|
|
{
|
|
NSArray *objectsToPaste = [pasteboard readObjectsForClasses:classArray options:options];
|
|
NSString *string = [objectsToPaste objectAtIndex: 0];
|
|
NSLog(@"%@", objectsToPaste);
|
|
|
|
[self autoTypeText: string];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
-(IBAction)copy: (id)sender
|
|
{
|
|
}
|
|
|
|
|
|
|
|
|
|
#pragma mark -
|
|
#pragma mark Drag/Drop
|
|
|
|
|
|
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
|
|
|
|
NSPasteboard *pboard;
|
|
pboard = [sender draggingPasteboard];
|
|
|
|
NSArray *types = [pboard types];
|
|
|
|
if ([types containsObject: NSFilenamesPboardType]) return NSDragOperationCopy;
|
|
if ([types containsObject: NSURLPboardType]) return NSDragOperationCopy;
|
|
|
|
|
|
return NSDragOperationNone;
|
|
}
|
|
|
|
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
|
|
NSPasteboard *pboard;
|
|
NSDragOperation sourceDragMask;
|
|
NSArray *types;
|
|
|
|
sourceDragMask = [sender draggingSourceOperationMask];
|
|
pboard = [sender draggingPasteboard];
|
|
|
|
|
|
types = [pboard types];
|
|
|
|
|
|
if ([types containsObject: NSFilenamesPboardType])
|
|
{
|
|
NSArray *array = [pboard propertyListForType: NSFilenamesPboardType];
|
|
NSString *string = (NSString *)[array objectAtIndex: 0];
|
|
|
|
string = [string stringByReplacingOccurrencesOfString: @"\\" withString: @"\\\\"];
|
|
string = [string stringByReplacingOccurrencesOfString: @" " withString: @"\\ "];
|
|
|
|
[self autoTypeText: string];
|
|
|
|
|
|
//NSArray *array = [pboard propertyListForType: NSFilenamesPboardType];
|
|
//NSLog(@"%@", [array class]);
|
|
//NSLog(@"%@", [pboard propertyListForType: NSFilenamesPboardType]);
|
|
return YES;
|
|
}
|
|
|
|
|
|
|
|
|
|
if ([types containsObject: NSURLPboardType])
|
|
{
|
|
NSArray *array = [pboard propertyListForType: NSURLPboardType];
|
|
NSObject *object = (NSObject *)[array objectAtIndex: 0];
|
|
|
|
if ([object isKindOfClass: [NSString class]])
|
|
{
|
|
[self autoTypeText: (NSString *)object];
|
|
return YES;
|
|
}
|
|
|
|
if ([object isKindOfClass: [NSURL class]])
|
|
{
|
|
[self autoTypeText: [(NSURL *)object absoluteString]];
|
|
return YES;
|
|
}
|
|
|
|
// if file://, use the pathname?
|
|
|
|
|
|
//NSLog(@"%@", [array class]);
|
|
//NSLog(@"%@", [pboard propertyListForType: NSURLPboardType]);
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
@implementation EmulatorView (ChildMonitor)
|
|
|
|
-(void)childDataAvailable: (ChildMonitor *)monitor
|
|
{
|
|
// this is called from a separate thread.
|
|
[self dataAvailable];
|
|
}
|
|
|
|
-(void)childFinished: (ChildMonitor *)monitor
|
|
{
|
|
NSLog(@"[process complete]");
|
|
// writeline "[process complete]"
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
|
|
void ViewScreen::setSize(unsigned width, unsigned height)
|
|
{
|
|
setSize(width, height, true);
|
|
}
|
|
|
|
void ViewScreen::setSize(unsigned width, unsigned height, bool resizeView)
|
|
{
|
|
//
|
|
struct winsize ws;
|
|
ws.ws_row = height;
|
|
ws.ws_col = width;
|
|
|
|
ws.ws_xpixel = 0;
|
|
ws.ws_ypixel = 0;
|
|
|
|
Screen::setSize(width, height);
|
|
|
|
ioctl(_fd, TIOCSWINSZ, &ws);
|
|
|
|
if (resizeView)
|
|
{
|
|
[_view resizeTo: iSize(width, height) animated: YES];
|
|
}
|
|
}
|
|
|
|
void ViewScreen::setCursorType(CursorType cursorType)
|
|
{
|
|
Screen::setCursorType(cursorType);
|
|
}
|
|
|
|
|
|
|