This commit is contained in:
jvernet 2015-12-22 22:09:09 +01:00 committed by Aaron Culliney
parent 5d86735a69
commit 746dcb2e3d
20 changed files with 537 additions and 2105 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +0,0 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2013-2015 Aaron Culliney
*
*/
#import <Cocoa/Cocoa.h>
typedef void (^DiskCompletionHandler)(NSOpenPanel *openPanel, NSInteger result);
@interface EmulatorDiskController : NSWindowController
+ (NSSet *)emulatorFileTypes;
+ (NSString *)extensionForPath:(NSString *)path;
+ (void)chooseDiskForWindow:(NSWindow *)window completionHandler:(DiskCompletionHandler)handler;
@end

View File

@ -1,324 +0,0 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2014, 2015 Aaron Culliney
*
*/
#import "EmulatorDiskController.h"
#import "EmulatorPrefsController.h"
#import "common.h"
#define READONLY_CHOICE_INDEX 0
#define NO_DISK_INSERTED @"(No Disk Inserted)"
#define DSK_PROPERTIES @".dsk 143360 bytes"
#define NIB_PROPERTIES @".nib 232960 bytes"
#define kApple2DisksURL @"kApple2DisksURL"
@interface EmulatorDiskController ()
@property (assign) IBOutlet NSWindow *disksWindow;
@property (assign) IBOutlet NSTextField *diskInA;
@property (assign) IBOutlet NSTextField *diskInB;
@property (assign) IBOutlet NSTextField *diskAProperties;
@property (assign) IBOutlet NSTextField *diskBProperties;
@property (assign) IBOutlet NSMatrix *diskAProtection;
@property (assign) IBOutlet NSMatrix *diskBProtection;
@property (assign) IBOutlet NSButton *chooseDiskA;
@property (assign) IBOutlet NSButton *chooseDiskB;
@property (assign) IBOutlet NSButton *startupLoadDiskA;
@property (assign) IBOutlet NSButton *startupLoadDiskB;
@end
@implementation EmulatorDiskController
- (void)awakeFromNib
{
#if CRASH_APP_ON_LOAD_BECAUSE_YAY_GJ_APPLE
glGetError();
#endif
[self.diskInA setStringValue:NO_DISK_INSERTED];
[self.diskAProperties setStringValue:@""];
[self.diskInB setStringValue:NO_DISK_INSERTED];
[self.diskBProperties setStringValue:@""];
[self.chooseDiskA setKeyEquivalent:@"\r"];
[self.chooseDiskA setBezelStyle:NSRoundedBezelStyle];
[self.startupLoadDiskA setState:NSOffState];
[self.startupLoadDiskB setState:NSOffState];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *startupDiskA = [defaults stringForKey:kApple2PrefStartupDiskA];
BOOL readOnlyA = [defaults boolForKey:kApple2PrefStartupDiskAProtected];
if (startupDiskA)
{
const char *err = disk6_insert(0, [[NSString stringWithFormat:@"%@.gz", startupDiskA] UTF8String], readOnlyA);
if (!err)
{
[self.diskInA setStringValue:[[startupDiskA pathComponents] lastObject]];
[self.startupLoadDiskA setState:NSOnState];
[self.diskAProtection setState:(readOnlyA ? NSOnState : NSOffState) atRow:0 column:0];
[self.diskAProtection setState:(!readOnlyA ? NSOnState : NSOffState) atRow:0 column:1];
}
}
NSString *startupDiskB = [defaults stringForKey:kApple2PrefStartupDiskB];
BOOL readOnlyB = [defaults boolForKey:kApple2PrefStartupDiskBProtected];
if (startupDiskB)
{
const char *err = disk6_insert(1, [[NSString stringWithFormat:@"%@.gz", startupDiskB] UTF8String], readOnlyB);
if (!err)
{
[self.diskInB setStringValue:[[startupDiskB pathComponents] lastObject]];
[self.startupLoadDiskB setState:NSOnState];
[self.diskBProtection setState:(readOnlyB ? NSOnState : NSOffState) atRow:0 column:0];
[self.diskBProtection setState:(!readOnlyB ? NSOnState : NSOffState) atRow:0 column:1];
}
}
}
- (void)_savePrefs
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults removeObjectForKey:kApple2PrefStartupDiskA];
[defaults removeObjectForKey:kApple2PrefStartupDiskB];
[defaults removeObjectForKey:kApple2PrefStartupDiskAProtected];
[defaults removeObjectForKey:kApple2PrefStartupDiskBProtected];
if ([self.startupLoadDiskA state] == NSOnState)
{
if (disk6.disk[0].fd >= 0)
{
NSString *diskA = [NSString stringWithUTF8String:disk6.disk[0].file_name];
[defaults setObject:diskA forKey:kApple2PrefStartupDiskA];
NSButtonCell *readOnlyChoice = (NSButtonCell *)[[[self diskAProtection] cells] firstObject];
[defaults setBool:([readOnlyChoice state] == NSOnState) forKey:kApple2PrefStartupDiskAProtected];
}
}
if ([self.startupLoadDiskB state] == NSOnState)
{
if (disk6.disk[1].fd >= 0)
{
NSString *diskB = [NSString stringWithUTF8String:disk6.disk[1].file_name];
[defaults setObject:diskB forKey:kApple2PrefStartupDiskB];
NSButtonCell *readOnlyChoice = (NSButtonCell *)[[[self diskBProtection] cells] firstObject];
[defaults setBool:([readOnlyChoice state] == NSOnState) forKey:kApple2PrefStartupDiskBProtected];
}
}
}
- (void)_protectionChangedForDrive:(int)drive
{
if (disk6.disk[drive].fd < 0)
{
return;
}
// HACK NOTE : dispatch so that state of outlet property is set properly
dispatch_async(dispatch_get_main_queue(), ^{
NSButtonCell *readOnlyChoice = (NSButtonCell *)[[(drive == 0 ? [self diskAProtection] : [self diskBProtection]) cells] firstObject];
NSString *path = [NSString stringWithUTF8String:disk6.disk[drive].file_name];
[self _insertDisketteInDrive:drive path:path type:[EmulatorDiskController extensionForPath:path] readOnly:([readOnlyChoice state] == NSOnState)];
[self _savePrefs];
});
}
- (IBAction)diskAProtectionChanged:(id)sender
{
[self _protectionChangedForDrive:0];
}
- (IBAction)diskBProtectionChanged:(id)sender
{
[self _protectionChangedForDrive:1];
}
- (BOOL)_insertDisketteInDrive:(int)drive path:(NSString *)path type:(NSString *)type readOnly:(BOOL)readOnly
{
disk6_eject(drive);
const char *errMsg = disk6_insert(drive, [path UTF8String], readOnly);
if (errMsg)
{
path = [NSString stringWithFormat:@"%@.gz", path];
errMsg = disk6_insert(drive, [path UTF8String], readOnly);
if (errMsg)
{
NSAlert *alert = [NSAlert alertWithError:[NSError errorWithDomain:[NSString stringWithUTF8String:errMsg] code:-1 userInfo:nil]];
[alert beginSheetModalForWindow:[self disksWindow] completionHandler:nil];
if (!drive)
{
[[self diskInA] setStringValue:NO_DISK_INSERTED];
[[self diskAProperties] setStringValue:@""];
}
else
{
[[self diskInB] setStringValue:NO_DISK_INSERTED];
[[self diskBProperties] setStringValue:@""];
}
return NO;
}
}
path = [NSString stringWithUTF8String:disk6.disk[drive].file_name];
NSString *imageName = [[path pathComponents] lastObject];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if (drive == 0)
{
[[self diskInA] setStringValue:imageName];
if ([[defaults stringForKey:kApple2PrefStartupDiskA] isEqualToString:path])
{
[self.startupLoadDiskA setState:NSOnState];
//[self.diskAProtection setState:(readOnly ? NSOnState : NSOffState) atRow:0 column:0];
//[self.diskAProtection setState:(!readOnly ? NSOnState : NSOffState) atRow:0 column:1];
}
}
else
{
[[self diskInB] setStringValue:imageName];
if ([[defaults stringForKey:kApple2PrefStartupDiskB] isEqualToString:path])
{
[self.startupLoadDiskB setState:NSOnState];
//[self.diskBProtection setState:(readOnly ? NSOnState : NSOffState) atRow:0 column:0];
//[self.diskBProtection setState:(!readOnly ? NSOnState : NSOffState) atRow:0 column:1];
}
}
if ([type isEqualToString:@"dsk"] || [type isEqualToString:@"do"] || [type isEqualToString:@"po"])
{
if (!drive)
{
[[self diskAProperties] setStringValue:DSK_PROPERTIES];
}
else
{
[[self diskBProperties] setStringValue:DSK_PROPERTIES];
}
}
else
{
if (!drive)
{
[[self diskAProperties] setStringValue:NIB_PROPERTIES];
}
else
{
[[self diskBProperties] setStringValue:NIB_PROPERTIES];
}
}
return YES;
}
+ (NSSet *)emulatorFileTypes
{
static NSSet *set = nil;
static dispatch_once_t onceToken = 0L;
dispatch_once(&onceToken, ^{
set = [[NSSet alloc] initWithObjects:@"dsk", @"nib", @"do", @"po", @"gz", nil];
});
return [[set retain] autorelease];
}
+ (NSString *)extensionForPath:(NSString *)path
{
NSString *extension0 = [path pathExtension];
NSString *extension1 = [[path stringByDeletingPathExtension] pathExtension];
if ([extension0 isEqualToString:@"gz"])
{
extension0 = extension1;
}
return extension0;
}
+ (void)chooseDiskForWindow:(NSWindow *)window completionHandler:(DiskCompletionHandler)handler
{
NSOpenPanel *openPanel = [[NSOpenPanel openPanel] retain];
[openPanel setTitle:@"Choose a disk image"];
NSURL *url = [[NSUserDefaults standardUserDefaults] URLForKey:kApple2DisksURL];
if (!url)
{
url = [[NSBundle mainBundle] URLForResource:@"images" withExtension:nil];
}
[openPanel setDirectoryURL:url];
[openPanel setShowsResizeIndicator:YES];
[openPanel setShowsHiddenFiles:NO];
[openPanel setCanChooseFiles:YES];
[openPanel setCanChooseDirectories:NO];
[openPanel setCanCreateDirectories:NO];
[openPanel setAllowsMultipleSelection:NO];
// NOTE : Doesn't appear to be a way to specify ".dsk.gz" ... so we may inadvertently allow files of ".foo.gz" here
NSSet *fileTypes = [EmulatorDiskController emulatorFileTypes];
[openPanel setAllowedFileTypes:[fileTypes allObjects]];
handler = Block_copy(handler);
[openPanel beginSheetModalForWindow:window completionHandler:^(NSInteger result) {
handler(openPanel, result);
Block_release(handler);
[openPanel autorelease];
}];
}
- (void)_chooseDisk:(int)drive readOnly:(BOOL)readOnly
{
[EmulatorDiskController chooseDiskForWindow:[self disksWindow] completionHandler:^(NSOpenPanel *openPanel, NSInteger result) {
if (result == NSOKButton)
{
NSURL *selection = [[openPanel URLs] firstObject];
NSString *path = [[selection path] stringByResolvingSymlinksInPath];
NSString *extension = [EmulatorDiskController extensionForPath:path];
NSSet *fileTypes = [EmulatorDiskController emulatorFileTypes];
NSString *directory = [path stringByDeletingLastPathComponent];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setURL:[NSURL URLWithString:directory] forKey:kApple2DisksURL];
if (![fileTypes containsObject:extension])
{
NSAlert *alert = [NSAlert alertWithError:[NSError errorWithDomain:@"Disk image must have file extension of .dsk, .do, .po, or .nib only" code:-1 userInfo:nil]];
[alert beginSheetModalForWindow:[self disksWindow] completionHandler:nil];
return;
}
[(drive == 0) ? self.startupLoadDiskA : self.startupLoadDiskB setState:NSOffState];
[self _insertDisketteInDrive:drive path:path type:extension readOnly:readOnly];
}
}];
}
- (IBAction)chooseDriveA:(id)sender
{
NSButtonCell *readOnlyChoice = (NSButtonCell *)[[[self diskAProtection] cells] firstObject];
[self _chooseDisk:0 readOnly:([readOnlyChoice state] == NSOnState)];
}
- (IBAction)chooseDriveB:(id)sender
{
NSButtonCell *readOnlyChoice = (NSButtonCell *)[[[self diskBProtection] cells] firstObject];
[self _chooseDisk:1 readOnly:([readOnlyChoice state] == NSOnState)];
}
- (IBAction)startupDiskAChoiceChanged:(id)sender
{
[self _savePrefs];
}
- (IBAction)startupDiskBChoiceChanged:(id)sender
{
[self _savePrefs];
}
- (IBAction)disksOK:(id)sender
{
[[self disksWindow] close];
}
@end

View File

@ -1,18 +0,0 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2014, 2015 Aaron Culliney
*
*/
// Based on sample code from https://developer.apple.com/library/mac/samplecode/GLEssentials/Introduction/Intro.html
#import <Cocoa/Cocoa.h>
@interface EmulatorFullscreenWindow : NSWindow
@end

View File

@ -1,54 +0,0 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2014, 2015 Aaron Culliney
*
*/
// Based on sample code from https://developer.apple.com/library/mac/samplecode/GLEssentials/Introduction/Intro.html
#import "EmulatorFullscreenWindow.h"
@implementation EmulatorFullscreenWindow
- (id)init
{
// Create a screen-sized window on the display you want to take over
NSRect screenRect = [[NSScreen mainScreen] frame];
// Initialize the window making it size of the screen and borderless
self = [super initWithContentRect:screenRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
// Set the window level to be above the menu bar to cover everything else
[self setLevel:NSMainMenuWindowLevel+1];
// Set opaque
[self setOpaque:YES];
// Hide this when user switches to another window (or app)
[self setHidesOnDeactivate:YES];
return self;
}
- (BOOL)canBecomeKeyWindow
{
// Return yes so that this borderless window can receive input
return YES;
}
- (void)keyUp:(NSEvent *)event
{
[[self windowController] keyUp:event];
}
- (void)keyDown:(NSEvent *)event
{
[[self windowController] keyDown:event];
}
@end

View File

@ -1,21 +0,0 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2014, 2015 Aaron Culliney
*
*/
// Based on sample code from https://developer.apple.com/library/mac/samplecode/GLEssentials/Introduction/Intro.html
#import <Cocoa/Cocoa.h>
#import <QuartzCore/CVDisplayLink.h>
#import "modelUtil.h"
#import "imageUtil.h"
@interface EmulatorGLView : NSOpenGLView
@end

View File

@ -1,321 +0,0 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2014, 2015 Aaron Culliney
*
*/
// Based on sample code from https://developer.apple.com/library/mac/samplecode/GLEssentials/Introduction/Intro.html
#import "EmulatorGLView.h"
// Apple //e common routines
#import "common.h"
#if TARGET_OS_MAC
#define USE_DISPLAYLINK 0
#define BROKEN_DISPLAYLINK 1
#else
// iOS uses CADisplayLink
#define USE_DISPLAYLINK 1
#endif
#define SUPPORT_RETINA_RESOLUTION 1
const NSString *kDrawTimerNotification = @"kDrawTimerNotification";
@interface EmulatorGLView ()
#if USE_DISPLAYLINK
@property (nonatomic, assign) CVDisplayLinkRef displayLink;
#else
@property (nonatomic, retain) NSTimer *timer;
#endif
- (void)initGL;
@end
@implementation EmulatorGLView
#if USE_DISPLAYLINK
@synthesize displayLink = _displayLink;
#else
@synthesize timer = _timer;
#endif
#pragma mark CVDisplayLink / NSTimer stuff
#if USE_DISPLAYLINK
- (CVReturn)getFrameForTime:(const CVTimeStamp *)outputTime
{
// There is no autorelease pool when this method is called
// because it will be called from a background thread.
// It's important to create one or app can leak objects.
@autoreleasepool {
// We draw on a secondary thread through the display link
// When resizing the view, -reshape is called automatically on the main
// thread. Add a mutex around to avoid the threads accessing the context
// simultaneously when resizing
#if BROKEN_DISPLAYLINK
#warning ASC NOTE 2014/09/27 multi-threaded OpenGL on Mac considered harmful to developer sanity
// 2014/09/27 Kinda defeats the purpose of using CVDisplayLink ... but keeps it from crashing to dispatch to main queue
dispatch_async(dispatch_get_main_queue(), ^{
#endif
[[self openGLContext] makeCurrentContext];
[self drawView];
#if BROKEN_DISPLAYLINK
});
#endif
}
return kCVReturnSuccess;
}
// This is the renderer output callback function
static CVReturn displayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *now, const CVTimeStamp *outputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext)
{
CVReturn result = [(EmulatorGLView *)displayLinkContext getFrameForTime:outputTime];
return result;
}
#else // use NSTimer
- (void)targetMethod:(NSTimer *)theTimer
{
NSAssert([NSThread isMainThread], @"Timer fired on non-main thread!");
[[self openGLContext] makeCurrentContext];
[self drawView];
}
#endif
#pragma mark -
- (void)awakeFromNib
{
NSOpenGLPixelFormatAttribute attrs[] =
{
NSOpenGLPFADoubleBuffer,
NSOpenGLPFADepthSize, 24,
// Must specify the 3.2 Core Profile to use OpenGL 3.2
NSOpenGLPFAOpenGLProfile,
NSOpenGLProfileVersion3_2Core,
0
};
NSOpenGLPixelFormat *pf = [[[NSOpenGLPixelFormat alloc] initWithAttributes:attrs] autorelease];
if (!pf)
{
NSLog(@"No OpenGL pixel format");
}
NSOpenGLContext* context = [[[NSOpenGLContext alloc] initWithFormat:pf shareContext:nil] autorelease];
#if defined(DEBUG)
// When we're using a CoreProfile context, crash if we call a legacy OpenGL function
// This will make it much more obvious where and when such a function call is made so
// that we can remove such calls.
// Without this we'd simply get GL_INVALID_OPERATION error for calling legacy functions
// but it would be more difficult to see where that function was called.
CGLEnable([context CGLContextObj], kCGLCECrashOnRemovedFunctions);
#endif
[self setPixelFormat:pf];
[self setOpenGLContext:context];
#if SUPPORT_RETINA_RESOLUTION
// Opt-In to Retina resolution
[self setWantsBestResolutionOpenGLSurface:YES];
#endif // SUPPORT_RETINA_RESOLUTION
}
- (void)prepareOpenGL
{
[super prepareOpenGL];
// Make all the OpenGL calls to setup rendering
// and build the necessary rendering objects
[self initGL];
#if USE_DISPLAYLINK
// Create a display link capable of being used with all active displays
CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink);
// Set the renderer output callback function
CVDisplayLinkSetOutputCallback(_displayLink, &displayLinkCallback, self);
// Set the display link for the current renderer
CGLContextObj cglContext = [[self openGLContext] CGLContextObj];
CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj];
CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(_displayLink, cglContext, cglPixelFormat);
// Activate the display link
CVDisplayLinkStart(_displayLink);
#else
[self.timer invalidate];
self.timer = nil;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0/60.0 target:self selector:@selector(targetMethod:) userInfo:nil repeats:YES];
#endif
// Register to be notified when the window closes so we can stop the displaylink
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowWillClose:) name:NSWindowWillCloseNotification object:[self window]];
}
- (void)windowWillClose:(NSNotification*)notification
{
// Stop the display link when the window is closing because default
// OpenGL render buffers will be destroyed. If display link continues to
// fire without renderbuffers, OpenGL draw calls will set errors.
#if USE_DISPLAYLINK
CVDisplayLinkStop(_displayLink);
#else
[self.timer invalidate];
self.timer = nil;
#endif
}
- (void)initGL
{
// The reshape function may have changed the thread to which our OpenGL
// context is attached before prepareOpenGL and initGL are called. So call
// makeCurrentContext to ensure that our OpenGL context current to this
// thread (i.e. makeCurrentContext directs all OpenGL calls on this thread
// to [self openGLContext])
[[self openGLContext] makeCurrentContext];
emulator_start();
// Synchronize buffer swaps with vertical refresh rate
GLint swapInt = 1;
[[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
// Init our renderer. Use 0 for the defaultFBO which is appropriate for
// OSX (but not iOS since iOS apps must create their own FBO)
#if TARGET_OS_MAC
video_backend->init(0);
#elif TARGET_OS_IPHONE
# error "FBO FIXME TODO"
video_backend->init(otherFBO);
#else
# error "unknown/unsupported Apple platform
#endif
}
- (void)reshape
{
[super reshape];
// We draw on a secondary thread through the display link. However, when
// resizing the view, -drawRect is called on the main thread.
// Add a mutex around to avoid the threads accessing the context
// simultaneously when resizing.
CGLLockContext([[self openGLContext] CGLContextObj]);
// Get the view size in Points
NSRect viewRectPoints = [self bounds];
#if SUPPORT_RETINA_RESOLUTION
// Rendering at retina resolutions will reduce aliasing, but at the potential
// cost of framerate and battery life due to the GPU needing to render more
// pixels.
// Any calculations the renderer does which use pixel dimentions, must be
// in "retina" space. [NSView convertRectToBacking] converts point sizes
// to pixel sizes. Thus the renderer gets the size in pixels, not points,
// so that it can set it's viewport and perform and other pixel based
// calculations appropriately.
// viewRectPixels will be larger (2x) than viewRectPoints for retina displays.
// viewRectPixels will be the same as viewRectPoints for non-retina displays
NSRect viewRectPixels = [self convertRectToBacking:viewRectPoints];
#else //if !SUPPORT_RETINA_RESOLUTION
// App will typically render faster and use less power rendering at
// non-retina resolutions since the GPU needs to render less pixels. There
// is the cost of more aliasing, but it will be no-worse than on a Mac
// without a retina display.
// Points:Pixels is always 1:1 when not supporting retina resolutions
NSRect viewRectPixels = viewRectPoints;
#endif // !SUPPORT_RETINA_RESOLUTION
// Set the new dimensions in our renderer
video_backend->reshape((int)viewRectPixels.size.width, (int)viewRectPixels.size.height);
CGLUnlockContext([[self openGLContext] CGLContextObj]);
}
- (void)renewGState
{
// Called whenever graphics state updated (such as window resize)
// OpenGL rendering is not synchronous with other rendering on the OSX.
// Therefore, call disableScreenUpdatesUntilFlush so the window server
// doesn't render non-OpenGL content in the window asynchronously from
// OpenGL content, which could cause flickering. (non-OpenGL content
// includes the title bar and drawing done by the app with other APIs)
[[self window] disableScreenUpdatesUntilFlush];
[super renewGState];
}
- (void)drawRect:(NSRect)theRect
{
NSAssert([NSThread isMainThread], @"drawRect called on non-main thread!");
// Called during resize operations
// Avoid flickering during resize by drawing
[[self openGLContext] makeCurrentContext];
[self drawView];
}
- (void)drawView
{
CGLLockContext([[self openGLContext] CGLContextObj]);
video_backend->render();
CGLFlushDrawable([[self openGLContext] CGLContextObj]);
CGLUnlockContext([[self openGLContext] CGLContextObj]);
[[NSNotificationCenter defaultCenter] postNotificationName:(NSString *)kDrawTimerNotification object:nil];
}
- (void)dealloc
{
// Stop the display link BEFORE releasing anything in the view
// otherwise the display link thread may call into the view and crash
// when it encounters something that has been release
#if USE_DISPLAYLINK
CVDisplayLinkStop(_displayLink);
CVDisplayLinkRelease(_displayLink);
#else
[self.timer invalidate];
self.timer = nil;
#endif
// shut down common OpenGL stuff AFTER display link has been released
emulator_shutdown();
[super dealloc];
}
#pragma mark -
#pragma mark Application Delegate methods
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)application
{
return YES;
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)application
{
disk6_eject(0);
disk6_eject(1);
return NSTerminateNow;
}
@end

View File

@ -1,16 +0,0 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2014, 2015 Aaron Culliney
*
*/
#import <Cocoa/Cocoa.h>
@interface EmulatorJoystickCalibrationView : NSView
@end

View File

@ -1,67 +0,0 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2013-2015 Aaron Culliney
*
*/
#import "EmulatorJoystickCalibrationView.h"
#import "common.h"
#import <QuartzCore/QuartzCore.h>
@implementation EmulatorJoystickCalibrationView
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
[self drawView];
}
- (void)drawView
{
static int pulseSize = 1;
CGRect bounds = [self bounds];
NSAssert(bounds.size.height == JOY_RANGE, @"view should be 256pts high");
NSAssert(bounds.size.width == JOY_RANGE, @"view should be 256pts wide");
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
CGContextSaveGState(context);
CGContextSetLineWidth(context, 1);
if (joy_clip_to_radius && (joy_mode == JOY_PCJOY))
{
CGContextSetRGBStrokeColor(context, 0, 0, 0, 1);
CGContextAddEllipseInRect(context, bounds);
CGContextStrokePath(context);
}
CGFloat x_val = joy_x;
CGFloat y_val = JOY_RANGE-joy_y;
CGContextSetRGBStrokeColor(context, 1, 0, 0, 1);
CGRect cursor = CGRectMake(x_val-(pulseSize/2.f), y_val-(pulseSize/2.f), pulseSize, pulseSize);
CGContextAddEllipseInRect(context, cursor);
CGContextStrokePath(context);
CGContextRestoreGState(context);
pulseSize = ((pulseSize+1) & 0x7) +1;
}
- (void)keyUp:(NSEvent *)event
{
[[self nextResponder] keyUp:event];
}
- (void)keyDown:(NSEvent *)event
{
[[self nextResponder] keyDown:event];
}
@end

View File

@ -1,16 +0,0 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2013-2015 Aaron Culliney
*
*/
#import <Foundation/Foundation.h>
@interface EmulatorJoystickController : NSObject
@end

View File

@ -1,228 +0,0 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2014, 2015 Aaron Culliney
*
*/
#import "EmulatorJoystickController.h"
#import "EmulatorWindowController.h"
#import "common.h"
#import <DDHidLib/DDHidJoystick.h>
@interface EmulatorJoystickController()
@property (nonatomic, retain) NSDictionary *allJoysticks;
+ (EmulatorJoystickController *)sharedInstance;
- (void)resetJoysticks;
@end
void gldriver_joystick_reset(void) {
[EmulatorJoystickController sharedInstance];
}
@implementation EmulatorJoystickController
@synthesize allJoysticks = _allJoysticks;
+ (EmulatorJoystickController *)sharedInstance
{
static dispatch_once_t onceToken = 0L;
static EmulatorJoystickController *joystickController = nil;
dispatch_once(&onceToken, ^{
joystickController = [[EmulatorJoystickController alloc] init];
});
return joystickController;
}
- (instancetype)init
{
self = [super init];
if (self)
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(connectivityPoll:) name:(NSString *)kDrawTimerNotification object:nil];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
self.allJoysticks = nil;
[super dealloc];
}
- (void)resetJoysticks
{
for (DDHidJoystick *joystick in [self.allJoysticks allValues])
{
@try {
[joystick setDelegate:nil];
[joystick stopListening];
} @catch (NSException *e) {
// hot-plugging joysticks can cause glitches
NSLog(@"Joystick device library raised exception on close : %@", e);
}
}
NSArray *joysticks = [DDHidJoystick allJoysticks];
NSMutableDictionary *allJoysticks = [NSMutableDictionary dictionary];
for (DDHidJoystick *joystick in joysticks) {
NSString *key =[NSString stringWithFormat:@"%@-%@-%@", [joystick manufacturer], [joystick serialNumber], [joystick transport]];
[allJoysticks setObject:joystick forKey:key];
}
self.allJoysticks = allJoysticks;
dispatch_async(dispatch_get_main_queue(), ^ {
for (DDHidJoystick *joystick in [self.allJoysticks allValues])
{
NSLog(@"found joystick : '%@' '%@' '%@' %@", [joystick manufacturer], [joystick serialNumber], [joystick transport], joystick);
@try {
[joystick setDelegate:self];
[joystick startListening];
} @catch (NSException *e) {
// hot-plugging joystick can cause glitches
NSLog(@"Joystick device library raised exception on start : %@", e);
}
}
});
}
#pragma mark -
#pragma mark Joystick connectivity polling
- (void)connectivityPoll:(NSNotification *)notification
{
static unsigned int counter = 0;
counter = (counter+1) % 60;
if (counter == 0)
{
NSArray *joysticks = [DDHidJoystick allJoysticks];
BOOL changed = ([joysticks count] != [self.allJoysticks count]);
for (DDHidJoystick *joystick in joysticks)
{
NSString *key =[NSString stringWithFormat:@"%@-%@-%@", [joystick manufacturer], [joystick serialNumber], [joystick transport]];
if (![self.allJoysticks objectForKey:key])
{
changed = YES;
break;
}
}
if (changed)
{
[self resetJoysticks];
}
}
#ifdef KEYPAD_JOYSTICK
if (joy_mode == JOY_KPAD)
{
c_keys_handle_input(-1, 0, 0);
}
#endif
}
#pragma mark -
#pragma mark NSObject(DDHidJoystickDelegate)
#define DDHID_JOYSTICK_NORMALIZER ((float)JOY_RANGE/(DDHID_JOYSTICK_VALUE_MAX*2))
#define QUARTER_JOY (HALF_JOY_RANGE>>1)
static inline void clampBeyondRadius(void) {
CGFloat half_x = joy_x - HALF_JOY_RANGE;
CGFloat half_y = joy_y - HALF_JOY_RANGE;
CGFloat r = sqrtf(half_x*half_x + half_y*half_y);
bool shouldClip = (r > HALF_JOY_RANGE);
if (joy_clip_to_radius && shouldClip) {
if (joy_x < HALF_JOY_RANGE) {
joy_x = (joy_x < QUARTER_JOY) ? 0.f : joy_x;
} else {
joy_x = (joy_x < HALF_JOY_RANGE+QUARTER_JOY) ? joy_x : JOY_RANGE-1;
}
if (joy_y < HALF_JOY_RANGE) {
joy_y = (joy_y < QUARTER_JOY) ? 0.f : joy_y;
} else {
joy_y = (joy_y < HALF_JOY_RANGE+QUARTER_JOY) ? joy_y : JOY_RANGE-1;
}
}
}
- (void)ddhidJoystick:(DDHidJoystick *)joystick stick:(unsigned int)stick xChanged:(int)value
{
#ifdef KEYPAD_JOYSTICK
if (joy_mode == JOY_KPAD) {
return;
}
#endif
joy_x = (uint16_t)((value+DDHID_JOYSTICK_VALUE_MAX) * DDHID_JOYSTICK_NORMALIZER);
if (joy_x > 0xFF) {
joy_x = 0xFF;
}
clampBeyondRadius();
}
- (void)ddhidJoystick:(DDHidJoystick *)joystick stick:(unsigned int)stick yChanged:(int)value
{
#ifdef KEYPAD_JOYSTICK
if (joy_mode == JOY_KPAD) {
return;
}
#endif
joy_y = (uint16_t)((value+DDHID_JOYSTICK_VALUE_MAX) * DDHID_JOYSTICK_NORMALIZER);
if (joy_y > 0xFF) {
joy_y = 0xFF;
}
clampBeyondRadius();
}
- (void)ddhidJoystick:(DDHidJoystick *)joystick stick:(unsigned int)stick otherAxis:(unsigned)otherAxis valueChanged:(int)value
{
// NOOP ...
}
- (void)ddhidJoystick:(DDHidJoystick *)joystick stick:(unsigned int)stick povNumber:(unsigned)povNumber valueChanged:(int)value
{
// NOOP ...
}
- (void)ddhidJoystick:(DDHidJoystick *)joystick buttonDown:(unsigned int)buttonNumber
{
#ifdef KEYPAD_JOYSTICK
if (joy_mode == JOY_KPAD) {
return;
}
#endif
// sample buttons only if apple keys aren't pressed. keys get set to 0xff, and js buttons are set to 0x80.
if ((buttonNumber == 0x01) && !(joy_button0 & 0x7f)) {
joy_button0 = 0x80;
}
if ((buttonNumber == 0x02) && !(joy_button1 & 0x7f)) {
joy_button1 = 0x80;
}
}
- (void)ddhidJoystick:(DDHidJoystick *)joystick buttonUp:(unsigned int)buttonNumber
{
#ifdef KEYPAD_JOYSTICK
if (joy_mode == JOY_KPAD) {
return;
}
#endif
// sample buttons only if apple keys aren't pressed. keys get set to 0xff, and js buttons are set to 0x80.
if ((buttonNumber == 0x01) && !(joy_button0 & 0x7f)) {
joy_button0 = 0x0;
}
if ((buttonNumber == 0x02) && !(joy_button1 & 0x7f)) {
joy_button1 = 0x0;
}
}
@end

View File

@ -1,21 +0,0 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2013-2015 Aaron Culliney
*
*/
#import <Cocoa/Cocoa.h>
#define kApple2PrefStartupDiskA @"kApple2PrefStartupDiskA"
#define kApple2PrefStartupDiskAProtected @"kApple2PrefStartupDiskAProtected"
#define kApple2PrefStartupDiskB @"kApple2PrefStartupDiskB"
#define kApple2PrefStartupDiskBProtected @"kApple2PrefStartupDiskBProtected"
@interface EmulatorPrefsController : NSWindowController
@end

View File

@ -1,353 +0,0 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2013-2015 Aaron Culliney
*
*/
#import "EmulatorPrefsController.h"
#import "EmulatorDiskController.h"
#import "EmulatorJoystickCalibrationView.h"
#import "EmulatorWindowController.h"
#import "common.h"
#define kApple2SavedPrefs @"kApple2SavedPrefs"
#define kApple2CPUSpeed @"kApple2CPUSpeed"
#define kApple2CPUSpeedIsMax @"kApple2CPUSpeedIsMax"
#define kApple2AltSpeed @"kApple2AltSpeed"
#define kApple2AltSpeedIsMax @"kApple2AltSpeedIsMax"
#define kApple2SoundcardConfig @"kApple2SoundcardConfig"
#define kApple2ColorConfig @"kApple2ColorConfig"
#define kApple2JoystickConfig @"kApple2JoystickConfig"
#define kApple2JoystickAutoRecenter @"kApple2JoystickAutoRecenter"
#define kApple2JoystickClipToRadius @"kApple2JoystickClipToRadius"
#define kApple2JoystickStep @"kApple2JoystickStep"
@interface EmulatorPrefsController ()
@property (assign) IBOutlet NSSlider *cpuSlider;
@property (assign) IBOutlet NSSlider *altSlider;
@property (assign) IBOutlet NSTextField *cpuSliderLabel;
@property (assign) IBOutlet NSTextField *altSliderLabel;
@property (assign) IBOutlet NSButton *cpuMaxChoice;
@property (assign) IBOutlet NSButton *altMaxChoice;
@property (assign) IBOutlet NSMatrix *soundCardChoice;
@property (assign) IBOutlet NSPopUpButton *colorChoice;
@property (assign) IBOutlet NSPopUpButton *joystickChoice;
@property (assign) IBOutlet NSButton *joystickRecenter;
@property (assign) IBOutlet NSButton *joystickClipToRadius;
@property (assign) IBOutlet NSTextField *joystickStepLabel;
@property (assign) IBOutlet NSStepper *joystickStepper;
@property (assign) IBOutlet NSTextField *joystickStepperLabel;
@property (assign) IBOutlet NSTextField *joystickKPadNotes;
@property (assign) IBOutlet NSTextField *joystickDeviceNotes;
@property (assign) IBOutlet NSTextField *button0Pressed;
@property (assign) IBOutlet NSTextField *button1Pressed;
@property (assign) IBOutlet EmulatorJoystickCalibrationView *joystickCalibrationView;
@end
@implementation EmulatorPrefsController
- (void)awakeFromNib
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
BOOL firstTime = ![defaults boolForKey:kApple2SavedPrefs];
if (firstTime)
{
[defaults setBool:YES forKey:kApple2SavedPrefs];
[defaults setDouble:1.0 forKey:kApple2CPUSpeed];
[defaults setDouble:CPU_SCALE_SLOWEST forKey:kApple2AltSpeed];
[defaults setBool:NO forKey:kApple2CPUSpeedIsMax];
[defaults setBool:NO forKey:kApple2AltSpeedIsMax];
[defaults setInteger:COLOR_INTERP forKey:kApple2ColorConfig];
[defaults setInteger:JOY_KPAD forKey:kApple2JoystickConfig];
[defaults setBool:YES forKey:kApple2JoystickAutoRecenter];
[defaults removeObjectForKey:kApple2PrefStartupDiskA];
[defaults removeObjectForKey:kApple2PrefStartupDiskB];
}
cpu_scale_factor = [defaults doubleForKey:kApple2CPUSpeed];
[self.cpuSlider setDoubleValue:cpu_scale_factor];
[self.cpuSliderLabel setStringValue:[NSString stringWithFormat:@"%.0f%%", cpu_scale_factor*100]];
if ([defaults boolForKey:kApple2CPUSpeedIsMax])
{
cpu_scale_factor = CPU_SCALE_FASTEST;
[self.cpuMaxChoice setState:NSOnState];
[self.cpuSlider setEnabled:NO];
}
else
{
[self.cpuMaxChoice setState:NSOffState];
[self.cpuSlider setEnabled:YES];
}
cpu_altscale_factor = [defaults doubleForKey:kApple2AltSpeed];
[self.altSlider setDoubleValue:cpu_altscale_factor];
[self.altSliderLabel setStringValue:[NSString stringWithFormat:@"%.0f%%", cpu_altscale_factor*100]];
if ([defaults boolForKey:kApple2AltSpeedIsMax])
{
cpu_altscale_factor = CPU_SCALE_FASTEST;
[self.altMaxChoice setState:NSOnState];
[self.altSlider setEnabled:NO];
}
else
{
[self.altMaxChoice setState:NSOffState];
[self.altSlider setEnabled:YES];
}
#warning TODO : actually implement sound card choices
[self.soundCardChoice deselectAllCells];
[self.soundCardChoice selectCellAtRow:1 column:0];
NSInteger mode = [defaults integerForKey:kApple2ColorConfig];
if (! ((mode >= COLOR_NONE) && (mode < NUM_COLOROPTS)) )
{
mode = COLOR_NONE;
}
[self.colorChoice selectItemAtIndex:mode];
color_mode = (color_mode_t)mode;
mode = [defaults integerForKey:kApple2JoystickConfig];
if (! ((mode >= JOY_PCJOY) && (mode < NUM_JOYOPTS)) )
{
mode = JOY_PCJOY;
}
joy_mode = (joystick_mode_t)mode;
[self.joystickChoice selectItemAtIndex:mode];
#ifdef KEYPAD_JOYSTICK
joy_auto_recenter = [defaults integerForKey:kApple2JoystickAutoRecenter];
[self.joystickRecenter setState:joy_auto_recenter ? NSOnState : NSOffState];
joy_step = [defaults integerForKey:kApple2JoystickStep];
if (!joy_step)
{
joy_step = 1;
}
[self.joystickStepLabel setIntegerValue:joy_step];
[self.joystickStepper setIntegerValue:joy_step];
#endif
joy_clip_to_radius = [defaults boolForKey:kApple2JoystickClipToRadius];
[self.joystickClipToRadius setState:joy_clip_to_radius ? NSOnState : NSOffState];
[self _setupJoystickUI];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(drawJoystickCalibration:) name:(NSString *)kDrawTimerNotification object:nil];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (void)_savePrefs
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:YES forKey:kApple2SavedPrefs];
[defaults setDouble:cpu_scale_factor forKey:kApple2CPUSpeed];
[defaults setDouble:cpu_altscale_factor forKey:kApple2AltSpeed];
[defaults setBool:([self.cpuMaxChoice state] == NSOnState) forKey:kApple2CPUSpeedIsMax];
[defaults setBool:([self.altMaxChoice state] == NSOnState) forKey:kApple2AltSpeedIsMax];
[defaults setInteger:color_mode forKey:kApple2ColorConfig];
[defaults setInteger:joy_mode forKey:kApple2JoystickConfig];
[defaults setInteger:joy_step forKey:kApple2JoystickStep];
[defaults setBool:joy_auto_recenter forKey:kApple2JoystickAutoRecenter];
[defaults setBool:joy_clip_to_radius forKey:kApple2JoystickClipToRadius];
}
- (IBAction)sliderDidMove:(id)sender
{
NSSlider *slider = (NSSlider *)sender;
double value = [slider doubleValue];
if (slider == self.cpuSlider)
{
cpu_scale_factor = value;
[self.cpuSliderLabel setStringValue:[NSString stringWithFormat:@"%.0f%%", value*100]];
}
else
{
cpu_altscale_factor = value;
[self.altSliderLabel setStringValue:[NSString stringWithFormat:@"%.0f%%", value*100]];
}
timing_initialize();
[self _savePrefs];
}
- (IBAction)peggedChoiceChanged:(id)sender
{
NSButton *maxButton = (NSButton *)sender;
if (maxButton == self.cpuMaxChoice)
{
[self.cpuSlider setEnabled:([maxButton state] != NSOnState)];
cpu_scale_factor = ([maxButton state] == NSOnState) ? CPU_SCALE_FASTEST : [self.cpuSlider doubleValue];
}
else
{
[self.altSlider setEnabled:([maxButton state] != NSOnState)];
cpu_altscale_factor = ([maxButton state] == NSOnState) ? CPU_SCALE_FASTEST : [self.altSlider doubleValue];
}
timing_initialize();
[self _savePrefs];
}
- (IBAction)colorChoiceChanged:(id)sender
{
NSInteger mode = [self.colorChoice indexOfSelectedItem];
if (! ((mode >= COLOR_NONE) && (mode < NUM_COLOROPTS)) )
{
mode = COLOR_NONE;
}
color_mode = (color_mode_t)mode;
[self _savePrefs];
#warning HACK TODO FIXME need to refactor video resetting procedure
video_reset();
video_setpage(!!(softswitches & SS_SCREEN));
video_redraw();
}
- (IBAction)soundCardChoiceChanged:(id)sender
{
#warning TODO : make soundcard configurable at runtime
}
#pragma mark -
#pragma mark Joystick preferences
- (void)_setupJoystickUI
{
[self.joystickRecenter setHidden:(joy_mode == JOY_PCJOY)];
[self.joystickClipToRadius setHidden:(joy_mode != JOY_PCJOY)];
[self.joystickStepLabel setHidden:(joy_mode == JOY_PCJOY)];
[self.joystickStepper setHidden:(joy_mode == JOY_PCJOY)];
[self.joystickStepperLabel setHidden:(joy_mode == JOY_PCJOY)];
[self.joystickKPadNotes setHidden:(joy_mode == JOY_PCJOY)];
[self.joystickDeviceNotes setHidden:(joy_mode != JOY_PCJOY)];
}
- (IBAction)joystickChoiceChanged:(id)sender
{
NSInteger mode = [self.joystickChoice indexOfSelectedItem];
if (! ((mode >= JOY_PCJOY) && (mode < NUM_JOYOPTS)) )
{
mode = JOY_PCJOY;
}
joy_mode = (joystick_mode_t)mode;
[self _setupJoystickUI];
[self _savePrefs];
}
- (IBAction)autoRecenterChoiceChanged:(id)sender
{
joy_auto_recenter = ([self.joystickRecenter state] == NSOnState);
[self _savePrefs];
}
- (IBAction)clipToRadiusChoiceChanged:(id)sender
{
joy_clip_to_radius = ([self.joystickClipToRadius state] == NSOnState);
[self _savePrefs];
}
- (IBAction)stepValueChanged:(id)sender
{
joy_step = [self.joystickStepper intValue];
[self.joystickStepLabel setIntegerValue:joy_step];
[self _savePrefs];
}
#pragma mark -
#pragma Joystick calibration
- (void)drawJoystickCalibration:(NSNotification *)notification
{
if (![self.joystickCalibrationView isHidden])
{
[self.joystickCalibrationView setNeedsDisplay:YES];
[self.button0Pressed setHidden:!(joy_button0)];
[self.button1Pressed setHidden:!(joy_button1)];
}
}
- (void)flagsChanged:(NSEvent*)event
{
// NOTE : yay, awesome! checking for ([event modifierFlags] & NSAlternateKeyMask) does not work properly if both were pressed and then one ALT key is unpressed ... kudoes to Apple for an excellent key-handling API /sarc
static BOOL leftAltEngaged = NO;
static BOOL rightAltEngaged = NO;
switch ([event keyCode])
{
case ALT_LT:
leftAltEngaged = !leftAltEngaged;
c_keys_handle_input(SCODE_L_ALT, /*pressed:*/leftAltEngaged, /*cooked:*/0);
break;
case ALT_RT:
rightAltEngaged = !rightAltEngaged;
c_keys_handle_input(SCODE_R_ALT, /*pressed:*/rightAltEngaged, /*cooked:*/0);
break;
default:
break;
}
if (!([event modifierFlags] & NSAlternateKeyMask))
{
// But we can trust the system state if no alt modifier exists ... this resets b0rken edge cases
leftAltEngaged = NO;
rightAltEngaged = NO;
}
[self.button0Pressed setHidden:!(joy_button0)];
[self.button1Pressed setHidden:!(joy_button1)];
}
- (void)_handleKeyEvent:(NSEvent *)event pressed:(BOOL)pressed
{
unichar c = [[event charactersIgnoringModifiers] characterAtIndex:0];
int scode = (int)c;
switch (scode)
{
case NSUpArrowFunctionKey:
c_keys_handle_input(SCODE_U, pressed, /*cooked:*/0);
break;
case NSDownArrowFunctionKey:
c_keys_handle_input(SCODE_D, pressed, /*cooked:*/0);
break;
case NSLeftArrowFunctionKey:
c_keys_handle_input(SCODE_L, pressed, /*cooked:*/0);
break;
case NSRightArrowFunctionKey:
c_keys_handle_input(SCODE_R, pressed, /*cooked:*/0);
break;
default:
break;
}
}
- (void)keyUp:(NSEvent *)event
{
[self _handleKeyEvent:event pressed:NO];
// Allow other character to be handled (or not and beep)
//[super keyDown:event];
}
- (void)keyDown:(NSEvent *)event
{
[self _handleKeyEvent:event pressed:YES];
// Allow other character to be handled (or not and beep)
//[super keyDown:event];
}
@end

View File

@ -1,16 +0,0 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2013-2015 Aaron Culliney
*
*/
#import <Cocoa/Cocoa.h>
@interface EmulatorWindow : NSWindow
@end

View File

@ -1,26 +0,0 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2013-2015 Aaron Culliney
*
*/
#import "EmulatorWindow.h"
@implementation EmulatorWindow
- (void)keyUp:(NSEvent *)event
{
[[self windowController] keyUp:event];
}
- (void)keyDown:(NSEvent *)event
{
[[self windowController] keyDown:event];
}
@end

View File

@ -1,29 +0,0 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2013-2015 Aaron Culliney
*
*/
// Based on sample code from https://developer.apple.com/library/mac/samplecode/GLEssentials/Introduction/Intro.html
#import <Cocoa/Cocoa.h>
#import "EmulatorGLView.h"
#define CAPS_LOCK 0x39
#define SHIFT_LT 0x38
#define SHIFT_RT 0x3c
#define CTRL_LT 0x3b
#define CTRL_RT 0x3e
#define ALT_LT 0x3a
#define ALT_RT 0x3d
extern const NSString *kDrawTimerNotification;
@interface EmulatorWindowController : NSWindowController
@end

View File

@ -1,404 +0,0 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2013-2015 Aaron Culliney
*
*/
// Based on sample code from https://developer.apple.com/library/mac/samplecode/GLEssentials/Introduction/Intro.html
#import "EmulatorWindowController.h"
#import "EmulatorFullscreenWindow.h"
#import "common.h"
@interface EmulatorWindowController ()
@property (nonatomic, assign) BOOL paused;
@property (assign) IBOutlet EmulatorGLView *view;
@property (assign) IBOutlet NSWindow *disksWindow;
@property (assign) IBOutlet NSWindow *prefsWindow;
@property (assign) IBOutlet NSMenuItem *pauseMenuItem;
@property (assign) IBOutlet NSToolbar *toolBar;
@property (assign) IBOutlet NSToolbarItem *pauseItem;
@property (assign) IBOutlet NSToolbarItem *disk1Item;
@property (assign) IBOutlet NSToolbarItem *disk2Item;
@property (nonatomic, retain) EmulatorFullscreenWindow *fullscreenWindow;
@property (nonatomic, retain) NSWindow *standardWindow;
@end
@implementation EmulatorWindowController
@synthesize paused = _paused;
@synthesize fullscreenWindow = _fullscreenWindow;
@synthesize standardWindow = _standardWindow;
- (id)initWithWindow:(NSWindow *)window
{
self = [super initWithWindow:window];
if (self)
{
// Initialize to nil since it indicates app is not fullscreen
self.fullscreenWindow = nil;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowWillClose:) name:NSWindowWillCloseNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowWillOpen:) name:NSWindowDidBecomeKeyNotification object:nil];
}
return self;
}
- (void)dealloc
{
#warning TODO FIXME ... probably should exit emulator if this gets invoked ...
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (IBAction)reboot:(id)sender
{
[[self disksWindow] close];
cpu65_reboot();
}
- (IBAction)showDisksWindow:(id)sender
{
if (self.fullscreenWindow)
{
[self toggleFullScreen:nil];
[self.standardWindow setFrame:[[NSScreen mainScreen] frame] display:YES];
}
[self.disksWindow makeKeyAndOrderFront:sender];
}
- (IBAction)showPreferences:(id)sender
{
if (self.fullscreenWindow)
{
[self toggleFullScreen:nil];
[self.standardWindow setFrame:[[NSScreen mainScreen] frame] display:YES];
}
[self.prefsWindow makeKeyAndOrderFront:sender];
}
- (IBAction)toggleFullScreen:(id)sender
{
if (self.fullscreenWindow)
{
[NSCursor unhide];
[self goWindow];
}
else
{
[NSCursor hide];
[self goFullscreen];
}
}
- (IBAction)toggleCPUSpeed:(id)sender
{
cpu_pause();
timing_toggleCPUSpeed();
if (video_backend && video_backend->animation_showCPUSpeed)
{
video_backend->animation_showCPUSpeed();
}
cpu_resume();
}
- (IBAction)togglePause:(id)sender
{
NSAssert(pthread_main_np(), @"Pause emulation called from non-main thread");
self.paused = !_paused;
}
- (void)setPaused:(BOOL)paused
{
if (_paused == paused)
{
return;
}
_paused = paused;
if (paused)
{
[[self pauseMenuItem] setTitle:@"Resume Emulation"];
[[self pauseItem] setImage:[NSImage imageNamed:@"Run"]];
[[self pauseItem] setLabel:@"Paused"];
cpu_pause();
}
else
{
[[self pauseMenuItem] setTitle:@"Pause Emulation"];
[[self pauseItem] setImage:[NSImage imageNamed:@"Stop"]];
[[self pauseItem] setLabel:@"Running"];
cpu_resume();
}
if (video_backend && video_backend->animation_showPaused)
{
video_backend->animation_showPaused();
}
}
- (void)windowWillOpen:(NSNotification *)notification
{
if ((self.prefsWindow == notification.object) || (self.disksWindow == notification.object))
{
self.paused = YES;
}
}
- (void)windowWillClose:(NSNotification *)notification
{
if (self.prefsWindow == notification.object)
{
self.paused = [self.disksWindow isVisible];
}
if (self.disksWindow == notification.object)
{
self.paused = [self.prefsWindow isVisible];
}
}
- (void)goFullscreen
{
// If app is already fullscreen...
if (self.fullscreenWindow)
{
//...don't do anything
return;
}
// Allocate a new fullscreen window
self.fullscreenWindow = [[[EmulatorFullscreenWindow alloc] init] autorelease];
// Resize the view to screensize
NSRect viewRect = [self.fullscreenWindow frame];
// Set the view to the size of the fullscreen window
[self.view setFrameSize: viewRect.size];
// Set the view in the fullscreen window
[self.fullscreenWindow setContentView:self.view];
self.standardWindow = [self window];
// Hide non-fullscreen window so it doesn't show up when switching out
// of this app (i.e. with CMD-TAB)
[self.standardWindow orderOut:self];
// Set controller to the fullscreen window so that all input will go to
// this controller (self)
[self setWindow:self.fullscreenWindow];
// Show the window and make it the key window for input
[self.fullscreenWindow makeKeyAndOrderFront:self];
}
- (void)goWindow
{
// If controller doesn't have a full screen window...
if (self.fullscreenWindow == nil)
{
//...app is already windowed so don't do anything
return;
}
// Get the rectangle of the original window
NSRect viewRect = [self.standardWindow frame];
// Set the view rect to the new size
[self.view setFrame:viewRect];
// Set controller to the standard window so that all input will go to
// this controller (self)
[self setWindow:self.standardWindow];
// Set the content of the orginal window to the view
[[self window] setContentView:self.view];
// Show the window and make it the key window for input
[[self window] makeKeyAndOrderFront:self];
// Release/nilify fullscreenWindow
self.fullscreenWindow = nil;
}
- (void)flagsChanged:(NSEvent*)event
{
static BOOL modified_caps_lock = NO;
switch ([event keyCode])
{
case CAPS_LOCK:
{
if ([event modifierFlags] & NSAlphaShiftKeyMask)
{
modified_caps_lock = YES;
caps_lock = true;
}
else
{
caps_lock = false;
}
}
case SHIFT_LT:
c_keys_handle_input(SCODE_L_SHIFT, ([event modifierFlags] & NSShiftKeyMask), 0);
break;
case SHIFT_RT:
c_keys_handle_input(SCODE_R_SHIFT, ([event modifierFlags] & NSShiftKeyMask), 0);
break;
case CTRL_LT:
c_keys_handle_input(SCODE_L_CTRL, ([event modifierFlags] & NSControlKeyMask), 0);
break;
case CTRL_RT:
c_keys_handle_input(SCODE_R_CTRL, ([event modifierFlags] & NSControlKeyMask), 0);
break;
case ALT_LT:
c_keys_handle_input(SCODE_L_ALT, ([event modifierFlags] & NSAlternateKeyMask), 0);
break;
case ALT_RT:
c_keys_handle_input(SCODE_R_ALT, ([event modifierFlags] & NSAlternateKeyMask), 0);
break;
default:
break;
}
}
- (void)_handleKeyEvent:(NSEvent *)event pressed:(BOOL)pressed
{
unichar c = [[event charactersIgnoringModifiers] characterAtIndex:0];
int scode = (int)c;
int cooked = 0;
switch (scode)
{
case 0x1b:
scode = SCODE_ESC;
break;
case NSUpArrowFunctionKey:
scode = SCODE_U;
break;
case NSDownArrowFunctionKey:
scode = SCODE_D;
break;
case NSLeftArrowFunctionKey:
scode = SCODE_L;
break;
case NSRightArrowFunctionKey:
scode = SCODE_R;
break;
case NSF1FunctionKey:
scode = SCODE_F1;
break;
case NSF2FunctionKey:
scode = SCODE_F2;
break;
case NSF3FunctionKey:
scode = SCODE_F3;
break;
case NSF4FunctionKey:
scode = SCODE_F4;
break;
case NSF5FunctionKey:
scode = SCODE_F5;
break;
case NSF6FunctionKey:
scode = SCODE_F6;
break;
case NSF7FunctionKey:
scode = SCODE_F7;
break;
case NSF8FunctionKey:
scode = SCODE_F8;
break;
case NSF9FunctionKey:
scode = SCODE_F9;
break;
case NSF10FunctionKey:
scode = SCODE_F10;
break;
case NSF11FunctionKey:
scode = SCODE_F11;
break;
case NSF12FunctionKey:
scode = SCODE_F12;
break;
case NSInsertFunctionKey:
scode = SCODE_INS;
break;
case NSDeleteFunctionKey:
scode = SCODE_DEL;
break;
case NSHomeFunctionKey:
scode = SCODE_HOME;
break;
case NSEndFunctionKey:
scode = SCODE_END;
break;
case NSPageUpFunctionKey:
scode = SCODE_PGUP;
break;
case NSPageDownFunctionKey:
scode = SCODE_PGDN;
break;
case NSPrintScreenFunctionKey:
scode = SCODE_PRNT;
break;
case NSPauseFunctionKey:
scode = SCODE_PAUSE;
break;
case NSBreakFunctionKey:
scode = SCODE_BRK;
break;
default:
if ([event modifierFlags] & NSControlKeyMask)
{
scode = c_keys_ascii_to_scancode(scode);
cooked = 0;
}
else
{
cooked = 1;
}
break;
}
c_keys_handle_input(scode, pressed, cooked);
}
- (void)keyUp:(NSEvent *)event
{
[self _handleKeyEvent:event pressed:NO];
// Allow other character to be handled (or not and beep)
//[super keyDown:event];
}
- (void)keyDown:(NSEvent *)event
{
[self _handleKeyEvent:event pressed:YES];
// Allow other character to be handled (or not and beep)
//[super keyDown:event];
}
@end

View File

@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9060" systemVersion="14F27" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9531" systemVersion="15C50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9051"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
</dependencies>
<scenes>
<!--View Controller-->
@ -63,7 +62,7 @@
<rect key="frame" x="0.0" y="0.0" width="640" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="U1j-AP-Luf" id="rJ2-oE-iBp">
<rect key="frame" x="0.0" y="0.0" width="640" height="43.5"/>
<rect key="frame" x="0.0" y="0.0" width="640" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Emulator settings" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mbz-lM-DIP">
@ -85,7 +84,7 @@
<rect key="frame" x="0.0" y="44" width="640" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="oOH-E6-lMK" id="Fmr-bG-eyO">
<rect key="frame" x="0.0" y="0.0" width="640" height="43.5"/>
<rect key="frame" x="0.0" y="0.0" width="640" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Load disk image" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gOe-OR-ycd">
@ -107,7 +106,7 @@
<rect key="frame" x="0.0" y="88" width="640" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="ITu-uc-66C" id="gFu-ft-Q65">
<rect key="frame" x="0.0" y="0.0" width="640" height="43.5"/>
<rect key="frame" x="0.0" y="0.0" width="640" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Save &amp; restore" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fJA-S4-XoG">
@ -129,7 +128,7 @@
<rect key="frame" x="0.0" y="132" width="640" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="3WS-Nv-AcH" id="4iZ-VH-juB">
<rect key="frame" x="0.0" y="0.0" width="640" height="43.5"/>
<rect key="frame" x="0.0" y="0.0" width="640" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Reboot or quit emulator" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0WI-e2-xoP">

View File

@ -23,7 +23,10 @@
@property (assign) IBOutlet NSWindow *disksWindow;
@property (assign) IBOutlet NSWindow *prefsWindow;
@property (assign) IBOutlet NSMenuItem *pauseMenuItem;
@property (assign) IBOutlet NSToolbar *toolBar;
@property (assign) IBOutlet NSToolbarItem *pauseItem;
@property (assign) IBOutlet NSToolbarItem *disk1Item;
@property (assign) IBOutlet NSToolbarItem *disk2Item;
@property (nonatomic, retain) EmulatorFullscreenWindow *fullscreenWindow;
@property (nonatomic, retain) NSWindow *standardWindow;
@ -126,13 +129,21 @@
if (paused)
{
[[self pauseMenuItem] setTitle:@"Resume Emulation"];
[[self pauseItem] setImage:[NSImage imageNamed:@"Run"]];
[[self pauseItem] setLabel:@"Paused"];
cpu_pause();
}
else
{
[[self pauseMenuItem] setTitle:@"Pause Emulation"];
[[self pauseItem] setImage:[NSImage imageNamed:@"Stop"]];
[[self pauseItem] setLabel:@"Running"];
cpu_resume();
}
if (video_backend && video_backend->animation_showPaused)
{
video_backend->animation_showPaused();
}
}
- (void)windowWillOpen:(NSNotification *)notification