File: programming/cocoa/MacScare-Source.zip/MacScare-Source/GlkSession.m


//
//  GlkSession.m
//  CocoaGlk
//
//  Created by Andrew Hunter on Wed Jun 11 2003.
//  Copyright (c) 2003 Andrew Hunter. All rights reserved.
//
 
#import "GlkSession.h"
#import "GlkWindowView.h"
#import "GlkFileStream.h"
#import "GlkMemoryStream.h"
#import "GlkStatus.h"
#import "glkstart.h"
 
@implementation GlkSession
 
#define propI    @"Gill Sans"
#define propII   @"Times-Italic"
#define propIII  @"Times-Bold"
 
#define fixFont  @"Courier"
#define propSize 14
#define fixSize  12
 
NSString* GlkSessionKey = @"GlkSessionKey";
NSString* GlkStatusKey  = @"GlkStatus";
 
NSString* GlkFlushYourBuffers = @"GlkFlushDemBuffers";
 
enum glkSessionConditions {
    GlkSessionNoEvents = 0,
    GlkSessionEventsWaiting
};
 
#define runningInMainThread ([NSThread currentThread] == mainThread)
 
// == Startup ==
 
+ (void) initialize {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
 
    NSDictionary *appDefaults = [NSDictionary
        dictionaryWithObject:NSHomeDirectory() forKey:@"SaveDirectory"];
 
    [defaults registerDefaults:appDefaults];
}
 
// == Initialisation ==
- (id) init {
    self = [super init];
 
    if (self) {
        // Setup
        rootWindow    = [[GlkWindow allocWithZone: [self zone]] initWithSession: self];
        currentWindow = nil;
        currentStream = nil;
        currentWindow = [rootWindow retain];
        currentStream = [[currentWindow stream] retain];
 
        arrangeWindow = nil;
 
        mainThread  = [NSThread currentThread];
        mainRunLoop = [NSRunLoop currentRunLoop];
 
        objectValue = nil;
 
        // Images
        imageCache = [[NSMutableArray allocWithZone: [self zone]] init];
        imageCacheNumbers = [[NSMutableArray allocWithZone: [self zone]] init];
 
        // Events
        eventQueue = [[NSMutableArray allocWithZone: [self zone]] init];
        currentEvent = nil;
 
        // Threads
        mainthreadConnection = nil;
        glkthreadConnection  = nil;
        port1                = nil;
        port2                = nil;
        
        // Create the user interface elements
        sessionWindow = [[NSWindow allocWithZone: [self zone]] initWithContentRect: NSMakeRect(100, 0, 800, 800)
                                                                         styleMask:NSTitledWindowMask|NSClosableWindowMask|NSMiniaturizableWindowMask|NSResizableWindowMask
                                                                           backing: NSBackingStoreBuffered
                                                                             defer: YES];
        [sessionWindow setDelegate: self];
 
        // Fonts
        fixedFont = [[NSFont fontWithName: fixFont
                                     size: fixSize]
            retain];
        propFont  = [[NSFont fontWithName: propI
                                     size: propSize]
            retain];
 
        // Styles
        int x;
        for (x=0; x<style_NUMSTYLES; x++) {
            int y;
 
            for (y=0; y<stylehint_NUMHINTS; y++) {
                defaultHint[y][x] = 0;
            }
 
            defaultHint[stylehint_TextColor][x] = 0;
            defaultHint[stylehint_BackColor][x] = 0xffffff;
            defaultHint[stylehint_Justification][x] = stylehint_just_LeftFlush;
            defaultHint[stylehint_Proportional][x]  = 1;
        }
 
        // Default stylehints
        defaultHint[stylehint_Weight][style_Subheader]     = 1;
        defaultHint[stylehint_Weight][style_Alert]         = 1;
        defaultHint[stylehint_Weight][style_Note]          = -1;
        defaultHint[stylehint_Weight][style_BlockQuote]    = 1;
        defaultHint[stylehint_Weight][style_Input]         = 1;
 
        defaultHint[stylehint_Oblique][style_Emphasized]   = 1;
        defaultHint[stylehint_Oblique][style_Alert]        = 1;
 
        defaultHint[stylehint_Size][style_Alert]           = 1;
        defaultHint[stylehint_Size][style_Header]          = 4;
        defaultHint[stylehint_Size][style_Subheader]       = 1;
 
        defaultHint[stylehint_Justification][style_Header] = stylehint_just_Centered;
 
        defaultHint[stylehint_Proportional][style_Preformatted] = 0;
 
        defaultHint[stylehint_Indentation][style_BlockQuote] = 10;
        
        for (x=0; x<style_NUMSTYLES; x++) {
            int y;
 
            for (y=0; y<stylehint_NUMHINTS; y++) {
                styleHint[y][x] = defaultHint[y][x];
            }
        }
        
        // Setup the UI, display the window
        [sessionWindow setDelegate: self];
        [sessionWindow setContentView: [rootWindow view]];
        [sessionWindow orderFront: self];
    }
 
    return self;
}
 
- (void) dealloc {
    [rootWindow    release];
    [currentWindow release];
    [currentStream release];
    [sessionWindow release];
 
    [fixedFont release];
    [propFont  release];
 
    if (mainthreadConnection) [mainthreadConnection release];
    if (glkthreadConnection)  [glkthreadConnection release];
    if (port1)                [port1 release];
    if (port2)                [port2 release];
    if (objectValue)          [objectValue release];
 
    [imageCache release];
    [imageCacheNumbers release];
 
    [eventQueue release];
    if (currentEvent) [currentEvent release];
 
    [super dealloc];
}
 
// == User functions ==
 
// -----------------------------------------------------------------------------
//	glkMain:
//		Run the specified game in this session. This synthesizes a command line
//		based on the file path passed in and hands that to the user's
//		glkunix_startup_code() function. Since we only allow one running game
//		right now, this quits the application after that.
//
//	REVISIONS:
//		2004-03-13	witness	Created.
// -----------------------------------------------------------------------------
 
- (void) glkMain: (NSString*)filename
{
	char*				args[2];
	glkunix_startup_t   fakeArgs = { 2, args };
	GlkStatus* stat = [[GlkStatus allocWithZone: [self zone]] init];
 
    [stat autorelease];
    [[[NSThread currentThread] threadDictionary] setObject: stat
                                                    forKey: GlkStatusKey];
	
	args[0] = [[[NSBundle mainBundle] executablePath] fileSystemRepresentation];
	args[1] = [filename fileSystemRepresentation];
	
	glkunix_startup_code( &fakeArgs );
	glk_main();
	
	[NSApp terminate: self];
}
 
 
// -----------------------------------------------------------------------------
//	_threadMain:
//		This hands on the file name of the game file to run to glkMain:.
//
//	REVISIONS:
//		2004-03-13	witness	Added filename parameter.
// -----------------------------------------------------------------------------
 
- (void) _threadMain: (NSString*)filename
{    
    threadPool = [[NSAutoreleasePool allocWithZone: [self zone]] init];
 
    glkThread = [NSThread currentThread];
    glkRunLoop = [NSRunLoop currentRunLoop];
    
    [[glkThread threadDictionary] setObject: self
                                     forKey: GlkSessionKey];
 
    [[NSRunLoop currentRunLoop] addPort: port2
                                forMode: NSDefaultRunLoopMode];
 
    glkthreadConnection = [[NSConnection allocWithZone: [self zone]]
        initWithReceivePort: port2
                   sendPort: port1];
    [glkthreadConnection setRootObject: self];
    
    [self glkMain: filename];
    [self exit];
}
 
- (void) threadPoolFree {
    [threadPool release];
    threadPool = [[NSAutoreleasePool allocWithZone: [self zone]] init];
}
 
// -----------------------------------------------------------------------------
//	queueTheMusic:
//		Actually starts this session and runs it in a separate thread.
//		This hands on the file name of the game file to run to _threadMain:.
//
//	REVISIONS:
//		2004-03-13	witness	Added filename parameter.
// -----------------------------------------------------------------------------
 
- (void) queueTheMusic: (NSString*)filename
{
    if (mainthreadConnection != nil) {
        return;
    }
 
    mainThread  = [NSThread currentThread];
    mainRunLoop = [NSRunLoop currentRunLoop];
 
    port1 = [[NSPort port] retain];
    port2 = [[NSPort port] retain];
 
    mainthreadConnection = [[NSConnection allocWithZone: [self zone]]
        initWithReceivePort: port1
                   sendPort: port2];
    [mainthreadConnection setRootObject: self];
    
    [NSThread detachNewThreadSelector: @selector(_threadMain:)
                             toTarget: self
                           withObject: filename];
}
 
// == Session management ==
 
// Top-level glk functions
- (void) exit {
    if (runningInMainThread) {
        NSLog(@"Calling exit from the main thread not implemented yet");
        abort();
    } else {
        [[NSNotificationCenter defaultCenter] postNotificationName: GlkFlushYourBuffers
                                                            object: self];
        [[self rootWindow] flushBuffer];
        [[self rootWindow] updateOpportunity];
 
        [[[NSThread currentThread] threadDictionary] removeObjectForKey: GlkSessionKey];
        [[[NSThread currentThread] threadDictionary] removeObjectForKey: GlkStatusKey];
        
        [threadPool release];
 
        threadPool = [[NSAutoreleasePool allocWithZone: [self zone]] init];
 
        [glkthreadConnection release];
        [mainthreadConnection release];
 
        [port1 release];
        [port2 release];
 
        glkthreadConnection = nil;
        mainthreadConnection = nil;
        port1 = nil;
        port2 = nil;
        
        [threadPool release];
        
        [NSThread exit];
    }
}
 
- (void) tick {
    NSDate* tock = [NSDate date];
    [tock addTimeInterval: 0.01];
    
    [[NSRunLoop currentRunLoop] acceptInputForMode: NSDefaultRunLoopMode
                                        beforeDate: tock];    
}
 
- (void) setInterruptHandler: (id) handler
                withSelector: (SEL) selector {
    BUG(@"*** BUG: FUNCTION NOT IMPLEMENTED\n");
}
 
 
    // Gestalt functions
- (glui32)  gestaltForSel: (glui32)  sel
                  withVal: (glui32)  val {
    return [self gestaltForSel: sel
                       withVal: val
                           buf: NULL
                        length: 0];
}
 
- (glui32)  gestaltForSel: (glui32)  sel
                  withVal: (glui32)  val
                      buf: (glui32*) buf
                   length: (glui32)  bufLen {
    switch (sel) {
        case gestalt_Version:
            return 0x00000601;
            
        case gestalt_CharInput:
            // Grr
            if (val > 32 || (glsi32)val < 0)
                return 1;
            else
                return 0;
            break;
 
        case gestalt_LineInput:
            if ((glsi32)val > 32 && (glsi32)val < 256) {
                return 1;
            } else {
                return 0;
            }
            break;
 
        case gestalt_CharOutput:
            if ((glsi32)val > 32 && (glsi32)val < 256) {
                if (buf) *buf = 1;
                return gestalt_CharOutput_ExactPrint;
            } else {
                if (buf) *buf = 0;
                return 0;
            }
            break;
 
        case gestalt_MouseInput:
            return 0; // IMPLEMENT ME :-)
 
        case gestalt_Timer:
            return 0; // DITTO
 
        case gestalt_Graphics:
            return 1;
 
        case gestalt_DrawImage:
            switch (val) {
                case 0:
                    return 1;
 
                case wintype_Graphics:
                    return 1;
                    
                case wintype_TextBuffer:
                    return 1; // IMPLEMENT ME
                    
                case wintype_TextGrid:
                default:
                    return 0;
            }
            break;
            
        case gestalt_GraphicsTransparency:
            return 1;
           
        case gestalt_Sound:
        case gestalt_SoundVolume:
        case gestalt_SoundNotify:
        case gestalt_Hyperlinks:
        case gestalt_HyperlinkInput:
        case gestalt_SoundMusic:
        default:
            return 0; // IMPLEMENT THIS LOT
    }
}
 
- (unsigned char) charToLower: (unsigned char) ch {
    BUG(@"*** BUG: FUNCTION NOT IMPLEMENTED\n");
}
 
- (unsigned char) charToUpper: (unsigned char) ch {
    BUG(@"*** BUG: FUNCTION NOT IMPLEMENTED\n");
}
 
// == Window management ==
- (GlkWindow*) rootWindow {
    if (runningInMainThread) {
        // Return the window in this thread
        return rootWindow;
    } else {
        // Return a reference to the window
        return [(GlkSession*)[glkthreadConnection rootProxy] rootWindow];
    }
    // return rootWindow;
}
 
- (void) setGlkWindow: (GlkWindow*) window {
    if (currentWindow)
        [currentWindow autorelease];
    
    currentWindow = [window retain];
    [self setCurrentStream: [currentWindow stream]];
}
 
// == Stream management ==
- (GlkStream*) openFile: (GlkFileRef*) ref
               withMode: (glui32) mode
                   rock: (glui32) rock {
    GlkFileStream* str;
 
    str = [[GlkFileStream allocWithZone: [self zone]] initByOpeningFileRef: ref
                                                                  withMode: mode
                                                                      rock: rock];
 
    if (str) {
        return [str autorelease];
    } else {
        return nil;
    }
}
 
 
// -----------------------------------------------------------------------------
//	openFileWithForcedName:usage:withMode:rock:
//		This method allows opening a GlkStream for *any* file. This is used
//		by glkunix_stream_open_pathname() to allow opening streams to game files
//		at startup.
//
//	REVISIONS:
//		2004-03-13	witness	Created.
// -----------------------------------------------------------------------------
 
-(GlkStream*)   openFileWithForcedName: (NSString*) fname
							usage: (glui32)usage
							withMode: (glui32) mode
							rock: (glui32) rock
{
    GlkFileStream*  str;
	GlkFileRef*		ref = [[GlkFileRef allocWithZone: [self zone]] initWithForcedName: fname
									withUsage: usage
									forSession: self];
	
    str = [[GlkFileStream allocWithZone: [self zone]] initByOpeningFileRef: ref
                                                                  withMode: mode
                                                                      rock: rock];
 
    if (str) {
        return [str autorelease];
    } else {
        return nil;
    }
}
 
- (GlkStream*) openMemory: (char*) buf
                   length: (glui32) buflen
                 withMode: (glui32) mode
                     rock: (glui32) rock {
    // Memory is owned by whoever creates the stream
    // Currently we ignore mode...
    
    GlkMemoryStream* str;
 
    str = [[GlkMemoryStream allocWithZone: [self zone]] initWithBuffer: buf
                                                                length: buflen
                                                                  rock: rock];
 
    return [str autorelease];
}
 
- (void) setCurrentStream: (GlkStream*) stream {
    if (currentStream)
        [currentStream release];
    currentStream = [stream retain];
}
 
- (GlkStream*) currentStream {
    return currentStream;
}
 
- (void)       putChar: (unsigned char) ch {
    [currentStream putChar: ch];
}
 
- (void)       putString: (const char*) s {
    [currentStream putString: [NSString stringWithCString: s]];
}
 
- (void)       putBuffer: (const char*) buf
                  length: (glui32) len {
    [currentStream putBuffer: [NSData dataWithBytes: buf
                                             length: len]];
}
 
- (void)       setGlkStyle: (glui32) styl {
    [currentStream setGlkStyle: styl];
}
 
- (void) setStyleHint: (glui32) winType
                style: (glui32) styl
                 hint: (glui32) hint
                value: (glsi32) val {
    if (!runningInMainThread) {
        [(GlkSession*)[glkthreadConnection rootProxy] setStyleHint: winType
                                                             style: styl
                                                              hint: hint
                                                             value: val];
        return;
    }
 
    styleHint[hint][styl] = val;
}
 
- (void) clearStyleHint: (glui32) wintype
                  style: (glui32) styl
                   hint: (glui32) hint {
    if (!runningInMainThread) {
        [(GlkSession*)[glkthreadConnection rootProxy] clearStyleHint: wintype
                                                               style: styl
                                                                hint: hint];
        return;
    }
    
    styleHint[hint][styl] = defaultHint[hint][styl];
}
 
- (glsi32) hintForStyle: (glui32) styl
                   hint: (glui32) hint
                winType: (glui32) wintype {
    return styleHint[hint][styl];
}
 
// == Filerefs ==
- (GlkFileRef*) fileRefTemp: (glui32) rock
                  withUsage: (glui32) usage {
    GlkFileRef* res = [[GlkFileRef allocWithZone: [self zone]]
        initWithTempFileForSession: self
                         withUsage: usage];
 
    if (res) {
        [res setRock: rock];
        return [res autorelease];
    }
 
    return nil;
}
 
- (GlkFileRef*) fileRefForName: (char*) name
                          rock: (glui32) rock
                     withUsage: (glui32) usage {
    GlkFileRef* res = [[GlkFileRef allocWithZone: [self zone]]
        initWithName: [NSString stringWithCString: name]
           withUsage: usage
          forSession: self];
 
    if (res) {
        [res setRock: rock];
        return [res autorelease];
    }
 
    return nil;
}
 
- (void) doneSaving: (NSString*) filename {
    if (runningInMainThread) {
        [(GlkSession*)[mainthreadConnection rootProxy] doneSaving: filename];
    } else {
        savePanelFinished = YES;
 
        if (filename) {
            savePanelFilename = [filename retain];
        } else {
            savePanelFilename = nil;
        }
    }
}
 
- (GlkFileRef*) fileRefByPromptingForMode: (glui32) fmode
                                     rock: (glui32) rock
                                withUsage: (glui32) usage {
    if (!runningInMainThread) {
        // Send a request that the dialog be shown
        savePanelFinished = NO;
 
        [(GlkSession*)[glkthreadConnection rootProxy] showSavePanelForMode: [NSNumber numberWithInt: fmode]];
 
        // Wait for the dialog to finish
        while (!savePanelFinished) {
            NSAutoreleasePool* loopPool = [[NSAutoreleasePool allocWithZone: [self zone]] init];
 
            [glkRunLoop acceptInputForMode: NSDefaultRunLoopMode
                                beforeDate: [NSDate distantFuture]];
 
            [loopPool release];            
        }
 
        if (savePanelFilename) {
            GlkFileRef* ref;
 
            ref = [[GlkFileRef allocWithZone: [self zone]] initWithForcedName: savePanelFilename
                                                                    withUsage: usage
                                                                   forSession: self];
            [savePanelFilename release];
 
            return [ref autorelease];
        }
 
        return nil;
            
        /*
        return [(GlkSession*)[glkthreadConnection rootProxy] fileRefByPromptingForMode: fmode
                                                                                  rock: rock
                                                                             withUsage: usage];
         */
    } else {
        NSLog(@"fileRefByPromptingForMode cannot be run in the main thread");
        return nil;
    }
}
 
- (GlkFileRef*) fileRefFromFileRef: (GlkFileRef*) ref
                              rock: (glui32) rock
                         withUsage: (glui32) usage {
    BUG(@"*** BUG: FUNCTION NOT IMPLEMENTED\n");
}
 
// == Dealing with save/open panels ==
- (void) showSavePanelForMode: (NSNumber*) fm {
    glui32 fmode = [fm intValue];
 
    NSSavePanel* thePanel = nil;
    BOOL openPanel = NO;
 
    switch (fmode) {
        case filemode_Write:
        case filemode_WriteAppend:
            thePanel = [NSSavePanel savePanel];
            break;
 
        case filemode_Read:
        case filemode_ReadWrite:
            thePanel = [NSOpenPanel openPanel];
            openPanel = YES;
            break;
    }
 
    if (thePanel == nil) {
        // Unknown mode
        [glkRunLoop performSelector: @selector(doneSaving:)
                             target: self
                           argument: nil
                              order: 128
                              modes: [NSArray arrayWithObjects:
                                  NSDefaultRunLoopMode,
                                  NSModalPanelRunLoopMode,
                                  nil]];
        return;
    }
 
    // Display the panel
    if (!openPanel) {
        [thePanel beginSheetForDirectory:[[NSUserDefaults standardUserDefaults] objectForKey: @"SaveDirectory"]
                                    file:nil
                          modalForWindow:sessionWindow
                           modalDelegate:self
                          didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:)
                             contextInfo:nil];
    } else {
        [(NSOpenPanel*)thePanel beginSheetForDirectory:[[NSUserDefaults standardUserDefaults] objectForKey: @"SaveDirectory"]
                                                  file:nil
                                                 types:nil
                                        modalForWindow:sessionWindow
                                         modalDelegate:self
                                        didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:)
                                           contextInfo:nil];
    }
}
 
- (void)savePanelDidEnd:(NSSavePanel *)sheet
             returnCode:(int)returnCode
            contextInfo:(void  *)contextInfo {
    if (returnCode == NSOKButton) {
        [[NSUserDefaults standardUserDefaults] setObject: [sheet directory]
                                                  forKey: @"SaveDirectory"];
        [self doneSaving: [sheet filename]];
    } else {
        [self doneSaving: nil];
    }
}
 
- (void)openPanelDidEnd:(NSOpenPanel *)sheet
             returnCode:(int)returnCode
            contextInfo:(void  *)contextInfo {
    if (returnCode == NSOKButton) {
        [[NSUserDefaults standardUserDefaults] setObject: [sheet directory]
                                                  forKey: @"SaveDirectory"];
        [self doneSaving: [sheet filename]];
    } else {
        [self doneSaving: nil];
    }
}
 
// == The iteration functions ==
- (GlkWindow*) windowIterate: (GlkWindow*) win
                        rock: (glui32*)    rock {
    if (win == nil) {
        return rootWindow;
    }
    
    if ([win left]) {
        return [win left];
    } else {
        GlkWindow* newWin, *lastWin;
 
        lastWin = win;
        newWin  = [win parent];
 
        while (newWin != nil &&
               [newWin left] != lastWin) {
            lastWin = newWin;
            newWin = [newWin parent];
        }
 
        if (newWin == nil) {
            return nil;
        }
 
        return [newWin right];
    }
}
 
- (GlkStream*) streamIterate: (GlkStream*) stream
                        rock: (glui32*)    rock {
    BUG(@"*** BUG: FUNCTION NOT IMPLEMENTED\n");
}
 
// == Glk events ==
- (void) _preSelect {
    [[NSNotificationCenter defaultCenter] postNotificationName: GlkFlushYourBuffers
                                                        object: self];
 
    if (arrangeWindow != nil) {
        // Queue an arrange event
        GlkEvent* evt = [GlkEvent eventWithType: evtype_Arrange
                                            win: arrangeWindow
                                           val1: 0
                                           val2: 0];
        arrangeWindow = nil;
        
        [self queueEvent: evt];
    }
}
 
- (void) _wakeupThread {
    // Dummy method called to force an NSRunloop to retun
    wakeup = YES;
}
 
- (GlkEvent*) select: (event_t*) event {
    GlkEvent* theEvent;
 
    if (runningInMainThread) {
        NSLog(@"Warning: select: called from main thread");
    }
 
    // Setup
    [self _preSelect];
 
    // Flush any buffers
    [[self rootWindow] flushBuffer];
    [[self rootWindow] updateOpportunity];
 
    // Release any autoreleased objects that are pending
    [threadPool release];
    threadPool = [[NSAutoreleasePool allocWithZone: [self zone]] init];
    
    // Get the next event
    theEvent = [GlkEvent eventWithEvent: [self nextEvent]];
 
    // Wait for an event to arrive (if none are waiting)
    while (theEvent == nil) {
        NSAutoreleasePool* loopPool = [[NSAutoreleasePool allocWithZone: [self zone]] init];
 
        // Do the RunLoop thang (allow other threads to call us)
        [[NSRunLoop currentRunLoop] acceptInputForMode: NSDefaultRunLoopMode
                                            beforeDate: [NSDate distantFuture]];
 
        [loopPool release];
 
        if (wakeup) {
            // Use eventWithEvent to create a local copy of the event
            theEvent = [GlkEvent eventWithEvent: [self nextEvent]];
            wakeup = NO;
        }
    }
    
    if ([theEvent type] == evtype_LineInput) {
        GlkWindow* theWin = [theEvent win];
        
        theEvent = [theWin cancelLineEvent];
    }
    
    event->type = [theEvent type];
    event->win  = (winid_t)[theEvent win];
    event->val1 = [theEvent val1];
    event->val2 = [theEvent val2];
 
    return theEvent;
}
 
- (GlkEvent*) selectPoll {
    // Run only in the main thread
    if (!runningInMainThread) {
        return [(GlkSession*)[glkthreadConnection rootProxy] selectPoll];
    }
 
    NSEnumerator* evEnum = [eventQueue objectEnumerator];
    GlkEvent* ev;
 
    while (ev = [evEnum nextObject]) {
        glui32 type = [ev type];
 
        if (type != evtype_CharInput &&
            type != evtype_LineInput &&
            type != evtype_MouseInput) {
            break;
        }
    }
 
    if (ev == nil) {
        return nil;
    }
    
    [ev retain];
    [eventQueue removeObjectIdenticalTo: ev];
 
    return [ev autorelease];
}
 
- (void) requestTimerEvents: (glui32) millisecs {
    BUG(@"*** BUG: FUNCTION NOT IMPLEMENTED\n");
}
 
// == Housekeeping ==
- (void) setRootWindow: (GlkWindow*) newRoot {
    if (newRoot != nil) {
        [newRoot retain];
        [rootWindow release];
        rootWindow = newRoot;
 
        [[newRoot view] forceViewReorder];
 
        [sessionWindow setContentView: [newRoot view]];
    } else {
        [sessionWindow setContentView: nil];
        [rootWindow release];
        rootWindow = nil;
    }
}
 
- (void) windowNeedsArranging {
    [rootWindow windowNeedsArranging];
    [[rootWindow view] setNeedsDisplay: YES];
}
 
- (NSFont*) fixedPitchFont {
    return fixedFont;
}
 
- (NSFont*) proportionalFont {
    return propFont;
}
 
- (id) objectValue {
    return objectValue;
}
 
- (void) setObjectValue: (id) object {
    objectValue = [(NSObject*)object retain];
}
 
// Threading
- (BOOL) isMainThread {
    return [NSThread currentThread] == mainThread;
}
 
// The window
- (NSWindow*) window {
    return sessionWindow;
}
 
// == Event handling ==
- (void) queueEvent: (GlkEvent*) event {
    if (!runningInMainThread) {
        [(GlkSession*)[glkthreadConnection rootProxy] queueEvent: event];
    } else {
        if (event == nil) {
            NSLog(@"Attempt to queue nil event");
            return;
        }
 
        [eventQueue addObject: event];
 
        // Signal the thread
        if (mainthreadConnection) {
            [(GlkSession*)[mainthreadConnection rootProxy] _wakeupThread];
        }
    }
}
 
- (void) cancelEventsForWindow: (GlkWindow*) win {
    if (!runningInMainThread) {
        NSLog(@"Warning: cancelEventsForWindow called from the wrong thread");
        [(GlkSession*)[glkthreadConnection rootProxy] cancelEventsForWindow: win];
    } else {
        if (currentEvent != nil && [win isEqualTo: [currentEvent win]]) {
            [currentEvent release];
            currentEvent = nil;
        }
 
        NSEnumerator* evtEnum = [eventQueue objectEnumerator];
        NSMutableArray* toRemove = [NSMutableArray array];
        GlkEvent* evt;
 
        while (evt = [evtEnum nextObject]) {
            if ([win isEqualTo: [evt win]]) {
                [toRemove addObject: evt];
            }
        }
 
        evtEnum = [toRemove objectEnumerator];
        while (evt = [evtEnum nextObject]) {
            [eventQueue removeObjectIdenticalTo: evt];
        }
    }
}
 
- (GlkEvent*) nextEvent {
    // Only execute this in the main thread
    if (!runningInMainThread) {
        return [(GlkSession*)[glkthreadConnection rootProxy] nextEvent];
    } else {
        // Pop the next event
        if (currentEvent) { [currentEvent release]; currentEvent = nil; }
 
        if ([eventQueue count] <= 0) {
            // No events
            return nil;
        }
        
        currentEvent = [[eventQueue objectAtIndex: 0] retain];
        [eventQueue removeObjectAtIndex: 0];
 
        // Unlock the lock
        return currentEvent;
    }
}
 
- (void) windowHasBeenArranged: (GlkWindow*) win {
    if (!runningInMainThread) {
        NSLog(@"windowHasBeenArranged can only be called from the main thread");
        return;
    }
    
    if (arrangeWindow != nil) {
        if (arrangeWindow != win) {
            arrangeWindow = rootWindow;
        }
    } else {
        arrangeWindow = win;
    }
}
 
// == Window events ==
- (void)windowDidResize:(NSNotification *)aNotification {
    if (arrangeWindow != nil) {
        // Queue an arrange event
        GlkEvent* evt = [GlkEvent eventWithType: evtype_Arrange
                                            win: arrangeWindow
                                           val1: 0
                                           val2: 0];
        arrangeWindow = nil;
 
        [self queueEvent: evt];
    }
}
 
- (void)windowWillClose:(NSNotification *)aNotification {
    if (rootWindow) {
        [rootWindow close];
    }
 
    if (rootWindow) {
        [rootWindow release];
        rootWindow = nil;
    }
 
    [sessionWindow setContentView: nil];
 
    if (currentWindow) {
        [currentWindow release];
        currentWindow = nil;
    }
 
    if (currentStream) {
        [currentStream release];
        currentStream = nil;
    }
 
    if (currentEvent) {
        [currentEvent release];
        currentEvent = nil;
    }
 
    [sessionWindow setDelegate: nil];
    sessionWindow = nil;
    
    [self release];
}
 
// == Images ==
static const int IMAGECACHESIZE = 32;
 
- (NSImage*) createImageResource:  (glui32) image {
    return nil;
}
 
- (NSImage*) getImageResource: (glui32) image {
    if (!runningInMainThread) {
        return [(GlkSession*)[glkthreadConnection rootProxy] getImageResource: image];
    }
 
    int cachePos = [imageCacheNumbers indexOfObject: [NSNumber numberWithInt: image]];
 
    if (cachePos == NSNotFound) {
        // Create the image
        NSImage* img = [[self createImageResource: image] retain];
 
        if (img == nil) {
            NSLog(@"Image %i not found", image);
            return nil;
        }
 
        [imageCache addObject: img];
        [imageCacheNumbers addObject: [NSNumber numberWithInt: image]];
 
        if ([imageCache count] > IMAGECACHESIZE) {
            [imageCache removeObjectAtIndex: 0];
            [imageCacheNumbers removeObjectAtIndex: 0];
        }
 
        return [img autorelease];
    } else {
        // Get the image fromt the cache
        NSImage* img = [[imageCache objectAtIndex: cachePos] retain];
 
        // Move the object to the front of the cache
        [imageCache removeObjectAtIndex: cachePos];
        [imageCacheNumbers removeObjectAtIndex: cachePos];
        
        [imageCache addObject: img];
        [imageCacheNumbers addObject: [NSNumber numberWithInt: image]];
        
        return [img autorelease];
    }
}
 
- (void) uncacheImageResource: (glui32) image {
 
    int cachePos = [imageCacheNumbers indexOfObject: [NSNumber numberWithInt: image]];
 
    if (cachePos != NSNotFound) {
        [imageCache removeObjectAtIndex: cachePos];
        [imageCacheNumbers removeObjectAtIndex: cachePos];
    }
}
 
- (NSImage*) logoImage {
    if (!runningInMainThread) {
        return [(GlkSession*)[glkthreadConnection rootProxy] logoImage];
    }
    
    return [[[NSImage allocWithZone:[self zone]] initWithContentsOfFile:
        [[NSBundle mainBundle] pathForResource:@"logo" ofType:@"png" inDirectory:nil]] autorelease];
}
 
@end

This code uses the PclZip Zip File reading code, which is subject to the GNU LGPL. It also uses the GeSHi syntax highlighter, subject to the GPL. Ask if you want this for your own web site, it's free.