/* SDLMain.m - main entry point for our Cocoa-ized SDL app Initial Version: Darrell Walisser Non-NIB-Code & other changes: Max Horn Feel free to customize this file to suit your needs */ #import "SDL.h" #import "SDLMain.h" #import /* for MAXPATHLEN */ #import /* For some reaon, Apple removed setAppleMenu from the headers in 10.4, but the method still is there and works. To avoid warnings, we declare it ourselves here. */ @ interface NSApplication (SDL_Missing_Methods) - (void) setAppleMenu:(NSMenu *) menu; @end /* Use this flag to determine whether we use SDLMain.nib or not */ #define SDL_USE_NIB_FILE 0 /* Use this flag to determine whether we use CPS (docking) or not */ #define SDL_USE_CPS 1 #ifdef SDL_USE_CPS /* Portions of CPS.h */ typedef struct CPSProcessSerNum { UInt32 lo; UInt32 hi; } CPSProcessSerNum; extern OSErr CPSGetCurrentProcess (CPSProcessSerNum * psn); extern OSErr CPSEnableForegroundOperation (CPSProcessSerNum * psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5); extern OSErr CPSSetFrontProcess (CPSProcessSerNum * psn); #endif /* SDL_USE_CPS */ static int gArgc; static char ** gArgv; static BOOL gFinderLaunch; static BOOL gCalledAppMainline = FALSE; static NSString * getApplicationName (void) { NSDictionary *dict; NSString *appName = 0; /* Determine the application name */ dict = (NSDictionary *) CFBundleGetInfoDictionary (CFBundleGetMainBundle ()); if (dict) appName =[dict objectForKey:@"CFBundleName"]; if (![appName length]) appName =[[NSProcessInfo processInfo] processName]; return appName; } #if SDL_USE_NIB_FILE /* A helper category for NSString */ @interface NSString (ReplaceSubString) - (NSString *) stringByReplacingRange:(NSRange) aRange with:(NSString *) aString; @end #endif @ interface SDLApplication:NSApplication @ end @ implementation SDLApplication /* Invoked from the Quit menu item */ - (void) terminate:(id) sender { /* Post a SDL_QUIT event */ SDL_Event event; event.type = SDL_QUIT; SDL_PushEvent (&event); } @end /* The main class of the application, the application's delegate */ @ implementation SDLMain /* Set the working directory to the .app's parent directory */ - (void) setupWorkingDirectory:(BOOL) shouldChdir { if (shouldChdir) { char parentdir[MAXPATHLEN]; CFURLRef url = CFBundleCopyBundleURL (CFBundleGetMainBundle ()); CFURLRef url2 = CFURLCreateCopyDeletingLastPathComponent (0, url); if (CFURLGetFileSystemRepresentation (url2, true, (UInt8 *) parentdir, MAXPATHLEN)) { assert (chdir (parentdir) == 0); /* chdir to the binary app's parent */ } CFRelease (url); CFRelease (url2); } } #if SDL_USE_NIB_FILE /* Fix menu to contain the real app name instead of "SDL App" */ -(void) fixMenu:(NSMenu *) aMenu withAppName:(NSString *) appName { NSRange aRange; NSEnumerator *enumerator; NSMenuItem *menuItem; aRange =[[aMenu title] rangeOfString:@"SDL App"]; if (aRange.length != 0) [aMenu setTitle: [[aMenu title] stringByReplacingRange: aRange with:appName]]; enumerator =[[aMenu itemArray] objectEnumerator]; while ((menuItem =[enumerator nextObject])) { aRange =[[menuItem title] rangeOfString:@"SDL App"]; if (aRange.length != 0) [menuItem setTitle: [[menuItem title] stringByReplacingRange: aRange with:appName]]; if ([menuItem hasSubmenu]) [self fixMenu: [menuItem submenu] withAppName:appName]; } [aMenu sizeToFit]; } #else static void setApplicationMenu (void) { /* warning: this code is very odd */ NSMenu *appleMenu; NSMenuItem *menuItem; NSString *title; NSString *appName; appName = getApplicationName (); appleMenu =[[NSMenu alloc] initWithTitle:@""]; /* Add menu items */ title =[@"About " stringByAppendingString:appName]; [appleMenu addItemWithTitle: title action: @selector (orderFrontStandardAboutPanel: )keyEquivalent:@""]; [appleMenu addItem:[NSMenuItem separatorItem]]; title =[@"Hide " stringByAppendingString:appName]; [appleMenu addItemWithTitle: title action: @selector (hide: )keyEquivalent:@"h"]; menuItem = (NSMenuItem *)[appleMenu addItemWithTitle: @"Hide Others" action: @selector (hideOtherApplications: )keyEquivalent:@"h"]; [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask | NSCommandKeyMask)]; [appleMenu addItemWithTitle: @"Show All" action: @selector (unhideAllApplications: )keyEquivalent:@""]; [appleMenu addItem:[NSMenuItem separatorItem]]; title =[@"Quit " stringByAppendingString:appName]; [appleMenu addItemWithTitle: title action: @selector (terminate: )keyEquivalent:@"q"]; /* Put menu into the menubar */ menuItem =[[NSMenuItem alloc] initWithTitle: @"" action: nil keyEquivalent:@""]; [menuItem setSubmenu:appleMenu]; [[NSApp mainMenu] addItem:menuItem]; /* Tell the application object that this is now the application menu */ [NSApp setAppleMenu:appleMenu]; /* Finally give up our references to the objects */ [appleMenu release]; [menuItem release]; } /* Create a window menu */ static void setupWindowMenu (void) { NSMenu *windowMenu; NSMenuItem *windowMenuItem; NSMenuItem *menuItem; windowMenu =[[NSMenu alloc] initWithTitle:@"Window"]; /* "Minimize" item */ menuItem =[[NSMenuItem alloc] initWithTitle: @"Minimize" action: @selector (performMiniaturize: )keyEquivalent:@"m"]; [windowMenu addItem:menuItem]; [menuItem release]; /* Put menu into the menubar */ windowMenuItem =[[NSMenuItem alloc] initWithTitle: @"Window" action: nil keyEquivalent:@""]; [windowMenuItem setSubmenu:windowMenu]; [[NSApp mainMenu] addItem:windowMenuItem]; /* Tell the application object that this is now the window menu */ [NSApp setWindowsMenu:windowMenu]; /* Finally give up our references to the objects */ [windowMenu release]; [windowMenuItem release]; } /* Replacement for NSApplicationMain */ static void CustomApplicationMain (int argc, char **argv) { NSAutoreleasePool *pool =[[NSAutoreleasePool alloc] init]; SDLMain *sdlMain; /* Ensure the application object is initialised */ [SDLApplication sharedApplication]; #ifdef SDL_USE_CPS { CPSProcessSerNum PSN; /* Tell the dock about us */ if (!CPSGetCurrentProcess (&PSN)) if (!CPSEnableForegroundOperation (&PSN, 0x03, 0x3C, 0x2C, 0x1103)) if (!CPSSetFrontProcess (&PSN)) [SDLApplication sharedApplication]; } #endif /* SDL_USE_CPS */ /* Set up the menubar */ [NSApp setMainMenu:[[NSMenu alloc] init]]; setApplicationMenu (); setupWindowMenu (); /* Create SDLMain and make it the app delegate */ sdlMain =[[SDLMain alloc] init]; [NSApp setDelegate:sdlMain]; /* Start the main event loop */ [NSApp run]; [sdlMain release]; [pool release]; } #endif /* * Catch document open requests...this lets us notice files when the app * was launched by double-clicking a document, or when a document was * dragged/dropped on the app's icon. You need to have a * CFBundleDocumentsType section in your Info.plist to get this message, * apparently. * * Files are added to gArgv, so to the app, they'll look like command line * arguments. Previously, apps launched from the finder had nothing but * an argv[0]. * * This message may be received multiple times to open several docs on launch. * * This message is ignored once the app's mainline has been called. */ -(BOOL) application:(NSApplication *) theApplication openFile:(NSString *) filename { const char *temparg; size_t arglen; char *arg; char **newargv; if (!gFinderLaunch) /* MacOS is passing command line args. */ return FALSE; if (gCalledAppMainline) /* app has started, ignore this document. */ return FALSE; temparg =[filename UTF8String]; arglen = SDL_strlen (temparg) + 1; arg = (char *) SDL_malloc (arglen); if (arg == NULL) return FALSE; newargv = (char **) realloc (gArgv, sizeof (char *) * (gArgc + 2)); if (newargv == NULL) { SDL_free (arg); return FALSE; } gArgv = newargv; SDL_strlcpy (arg, temparg, arglen); gArgv[gArgc++] = arg; gArgv[gArgc] = NULL; return TRUE; } /* Called when the internal event loop has just started running */ -(void) applicationDidFinishLaunching:(NSNotification *) note { int status; /* Set the working directory to the .app's parent directory */ [self setupWorkingDirectory:gFinderLaunch]; #if SDL_USE_NIB_FILE /* Set the main menu to contain the real app name instead of "SDL App" */ [self fixMenu: [NSApp mainMenu] withAppName:getApplicationName ()]; #endif /* Hand off to main application code */ gCalledAppMainline = TRUE; status = SDL_main (gArgc, gArgv); /* We're done, thank you for playing */ exit (status); } @end @ implementation NSString (ReplaceSubString) - (NSString *) stringByReplacingRange:(NSRange) aRange with:(NSString *) aString { unsigned int bufferSize; unsigned int selfLen =[self length]; unsigned int aStringLen =[aString length]; unichar *buffer; NSRange localRange; NSString *result; bufferSize = selfLen + aStringLen - aRange.length; buffer = NSAllocateMemoryPages (bufferSize * sizeof (unichar)); /* Get first part into buffer */ localRange.location = 0; localRange.length = aRange.location; [self getCharacters: buffer range:localRange]; /* Get middle part into buffer */ localRange.location = 0; localRange.length = aStringLen; [aString getCharacters: (buffer + aRange.location) range:localRange]; /* Get last part into buffer */ localRange.location = aRange.location + aRange.length; localRange.length = selfLen - localRange.location; [self getCharacters: (buffer + aRange.location + aStringLen) range:localRange]; /* Build output string */ result =[NSString stringWithCharacters: buffer length:bufferSize]; NSDeallocateMemoryPages (buffer, bufferSize); return result; } @end #ifdef main # undef main #endif /* Main entry point to executable - should *not* be SDL_main! */ int main (int argc, char **argv) { /* Copy the arguments into a global variable */ /* This is passed if we are launched by double-clicking */ if (argc >= 2 && strncmp (argv[1], "-psn", 4) == 0) { gArgv = (char **) SDL_malloc (sizeof (char *) * 2); gArgv[0] = argv[0]; gArgv[1] = NULL; gArgc = 1; gFinderLaunch = YES; } else { int i; gArgc = argc; gArgv = (char **) SDL_malloc (sizeof (char *) * (argc + 1)); for (i = 0; i <= argc; i++) gArgv[i] = argv[i]; gFinderLaunch = NO; } #if SDL_USE_NIB_FILE [SDLApplication poseAsClass:[NSApplication class]]; NSApplicationMain (argc, argv); #else CustomApplicationMain (argc, argv); #endif return 0; }