1/* SDLMain.m - main entry point for our Cocoa-ized SDL app 2 Initial Version: Darrell Walisser <dwaliss1@purdue.edu> 3 Non-NIB-Code & other changes: Max Horn <max@quendi.de> 4 5 Feel free to customize this file to suit your needs 6*/ 7 8#include "SDL.h" 9#include "SDLMain.h" 10#include <sys/param.h> /* for MAXPATHLEN */ 11#include <unistd.h> 12 13/* For some reaon, Apple removed setAppleMenu from the headers in 10.4, 14 but the method still is there and works. To avoid warnings, we declare 15 it ourselves here. */ 16@interface NSApplication(SDL_Missing_Methods) 17- (void)setAppleMenu:(NSMenu *)menu; 18@end 19 20/* Use this flag to determine whether we use SDLMain.nib or not */ 21#define SDL_USE_NIB_FILE 0 22 23/* Use this flag to determine whether we use CPS (docking) or not */ 24#define SDL_USE_CPS 1 25#ifdef SDL_USE_CPS 26/* Portions of CPS.h */ 27typedef struct CPSProcessSerNum 28{ 29 UInt32 lo; 30 UInt32 hi; 31} CPSProcessSerNum; 32 33extern OSErr CPSGetCurrentProcess( CPSProcessSerNum *psn); 34extern OSErr CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5); 35extern OSErr CPSSetFrontProcess( CPSProcessSerNum *psn); 36 37#endif /* SDL_USE_CPS */ 38 39static int gArgc; 40static char **gArgv; 41static BOOL gFinderLaunch; 42static BOOL gCalledAppMainline = FALSE; 43 44static NSString *getApplicationName(void) 45{ 46 const NSDictionary *dict; 47 NSString *appName = 0; 48 49 /* Determine the application name */ 50 dict = (const NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetMainBundle()); 51 if (dict) 52 appName = [dict objectForKey: @"CFBundleName"]; 53 54 if (![appName length]) 55 appName = [[NSProcessInfo processInfo] processName]; 56 57 return appName; 58} 59 60#if SDL_USE_NIB_FILE 61/* A helper category for NSString */ 62@interface NSString (ReplaceSubString) 63- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString; 64@end 65#endif 66 67@interface SDLApplication : NSApplication 68@end 69 70@implementation SDLApplication 71/* Invoked from the Quit menu item */ 72- (void)terminate:(id)sender 73{ 74 /* Post a SDL_QUIT event */ 75 SDL_Event event; 76 event.type = SDL_QUIT; 77 SDL_PushEvent(&event); 78} 79@end 80 81/* The main class of the application, the application's delegate */ 82@implementation SDLMain 83 84/* Set the working directory to the .app's parent directory */ 85- (void) setupWorkingDirectory:(BOOL)shouldChdir 86{ 87 if (shouldChdir) 88 { 89 char parentdir[MAXPATHLEN]; 90 CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle()); 91 CFURLRef url2 = CFURLCreateCopyDeletingLastPathComponent(0, url); 92 if (CFURLGetFileSystemRepresentation(url2, 1, (UInt8 *)parentdir, MAXPATHLEN)) { 93 chdir(parentdir); /* chdir to the binary app's parent */ 94 } 95 CFRelease(url); 96 CFRelease(url2); 97 } 98} 99 100#if SDL_USE_NIB_FILE 101 102/* Fix menu to contain the real app name instead of "SDL App" */ 103- (void)fixMenu:(NSMenu *)aMenu withAppName:(NSString *)appName 104{ 105 NSRange aRange; 106 NSEnumerator *enumerator; 107 NSMenuItem *menuItem; 108 109 aRange = [[aMenu title] rangeOfString:@"SDL App"]; 110 if (aRange.length != 0) 111 [aMenu setTitle: [[aMenu title] stringByReplacingRange:aRange with:appName]]; 112 113 enumerator = [[aMenu itemArray] objectEnumerator]; 114 while ((menuItem = [enumerator nextObject])) 115 { 116 aRange = [[menuItem title] rangeOfString:@"SDL App"]; 117 if (aRange.length != 0) 118 [menuItem setTitle: [[menuItem title] stringByReplacingRange:aRange with:appName]]; 119 if ([menuItem hasSubmenu]) 120 [self fixMenu:[menuItem submenu] withAppName:appName]; 121 } 122 [ aMenu sizeToFit ]; 123} 124 125#else 126 127static void setApplicationMenu(void) 128{ 129 /* warning: this code is very odd */ 130 NSMenu *appleMenu; 131 NSMenuItem *menuItem; 132 NSString *title; 133 NSString *appName; 134 135 appName = getApplicationName(); 136 appleMenu = [[NSMenu alloc] initWithTitle:@""]; 137 138 /* Add menu items */ 139 title = [@"About " stringByAppendingString:appName]; 140 [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; 141 142 [appleMenu addItem:[NSMenuItem separatorItem]]; 143 144 title = [@"Hide " stringByAppendingString:appName]; 145 [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"]; 146 147 menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; 148 [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; 149 150 [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; 151 152 [appleMenu addItem:[NSMenuItem separatorItem]]; 153 154 title = [@"Quit " stringByAppendingString:appName]; 155 [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; 156 157 158 /* Put menu into the menubar */ 159 menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; 160 [menuItem setSubmenu:appleMenu]; 161 [[NSApp mainMenu] addItem:menuItem]; 162 163 /* Tell the application object that this is now the application menu */ 164 [NSApp setAppleMenu:appleMenu]; 165 166 /* Finally give up our references to the objects */ 167 [appleMenu release]; 168 [menuItem release]; 169} 170 171/* Create a window menu */ 172static void setupWindowMenu(void) 173{ 174 NSMenu *windowMenu; 175 NSMenuItem *windowMenuItem; 176 NSMenuItem *menuItem; 177 178 windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; 179 180 /* "Minimize" item */ 181 menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; 182 [windowMenu addItem:menuItem]; 183 [menuItem release]; 184 185 /* Put menu into the menubar */ 186 windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""]; 187 [windowMenuItem setSubmenu:windowMenu]; 188 [[NSApp mainMenu] addItem:windowMenuItem]; 189 190 /* Tell the application object that this is now the window menu */ 191 [NSApp setWindowsMenu:windowMenu]; 192 193 /* Finally give up our references to the objects */ 194 [windowMenu release]; 195 [windowMenuItem release]; 196} 197 198/* Replacement for NSApplicationMain */ 199static void CustomApplicationMain (int argc, char **argv) 200{ 201 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 202 SDLMain *sdlMain; 203 204 /* Ensure the application object is initialised */ 205 [SDLApplication sharedApplication]; 206 207#ifdef SDL_USE_CPS 208 { 209 CPSProcessSerNum PSN; 210 /* Tell the dock about us */ 211 if (!CPSGetCurrentProcess(&PSN)) 212 if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103)) 213 if (!CPSSetFrontProcess(&PSN)) 214 [SDLApplication sharedApplication]; 215 } 216#endif /* SDL_USE_CPS */ 217 218 /* Set up the menubar */ 219 [NSApp setMainMenu:[[NSMenu alloc] init]]; 220 setApplicationMenu(); 221 setupWindowMenu(); 222 223 /* Create SDLMain and make it the app delegate */ 224 sdlMain = [[SDLMain alloc] init]; 225 [NSApp setDelegate:sdlMain]; 226 227 /* Start the main event loop */ 228 [NSApp run]; 229 230 [sdlMain release]; 231 [pool release]; 232} 233 234#endif 235 236 237/* 238 * Catch document open requests...this lets us notice files when the app 239 * was launched by double-clicking a document, or when a document was 240 * dragged/dropped on the app's icon. You need to have a 241 * CFBundleDocumentsType section in your Info.plist to get this message, 242 * apparently. 243 * 244 * Files are added to gArgv, so to the app, they'll look like command line 245 * arguments. Previously, apps launched from the finder had nothing but 246 * an argv[0]. 247 * 248 * This message may be received multiple times to open several docs on launch. 249 * 250 * This message is ignored once the app's mainline has been called. 251 */ 252- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename 253{ 254 const char *temparg; 255 size_t arglen; 256 char *arg; 257 char **newargv; 258 259 if (!gFinderLaunch) /* MacOS is passing command line args. */ 260 return FALSE; 261 262 if (gCalledAppMainline) /* app has started, ignore this document. */ 263 return FALSE; 264 265 temparg = [filename UTF8String]; 266 arglen = SDL_strlen(temparg) + 1; 267 arg = (char *) SDL_malloc(arglen); 268 if (arg == NULL) 269 return FALSE; 270 271 newargv = (char **) realloc(gArgv, sizeof (char *) * (gArgc + 2)); 272 if (newargv == NULL) 273 { 274 SDL_free(arg); 275 return FALSE; 276 } 277 gArgv = newargv; 278 279 SDL_strlcpy(arg, temparg, arglen); 280 gArgv[gArgc++] = arg; 281 gArgv[gArgc] = NULL; 282 return TRUE; 283} 284 285 286/* Called when the internal event loop has just started running */ 287- (void) applicationDidFinishLaunching: (NSNotification *) note 288{ 289 int status; 290 291 /* Set the working directory to the .app's parent directory */ 292 [self setupWorkingDirectory:gFinderLaunch]; 293 294#if SDL_USE_NIB_FILE 295 /* Set the main menu to contain the real app name instead of "SDL App" */ 296 [self fixMenu:[NSApp mainMenu] withAppName:getApplicationName()]; 297#endif 298 299 /* Hand off to main application code */ 300 gCalledAppMainline = TRUE; 301 status = SDL_main (gArgc, gArgv); 302 303 /* We're done, thank you for playing */ 304 exit(status); 305} 306@end 307 308 309@implementation NSString (ReplaceSubString) 310 311- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString 312{ 313 unsigned int bufferSize; 314 unsigned int selfLen = [self length]; 315 unsigned int aStringLen = [aString length]; 316 unichar *buffer; 317 NSRange localRange; 318 NSString *result; 319 320 bufferSize = selfLen + aStringLen - aRange.length; 321 buffer = (unichar *)NSAllocateMemoryPages(bufferSize*sizeof(unichar)); 322 323 /* Get first part into buffer */ 324 localRange.location = 0; 325 localRange.length = aRange.location; 326 [self getCharacters:buffer range:localRange]; 327 328 /* Get middle part into buffer */ 329 localRange.location = 0; 330 localRange.length = aStringLen; 331 [aString getCharacters:(buffer+aRange.location) range:localRange]; 332 333 /* Get last part into buffer */ 334 localRange.location = aRange.location + aRange.length; 335 localRange.length = selfLen - localRange.location; 336 [self getCharacters:(buffer+aRange.location+aStringLen) range:localRange]; 337 338 /* Build output string */ 339 result = [NSString stringWithCharacters:buffer length:bufferSize]; 340 341 NSDeallocateMemoryPages(buffer, bufferSize); 342 343 return result; 344} 345 346@end 347 348 349 350#ifdef main 351# undef main 352#endif 353 354 355/* Main entry point to executable - should *not* be SDL_main! */ 356int main (int argc, char **argv) 357{ 358 /* Copy the arguments into a global variable */ 359 /* This is passed if we are launched by double-clicking */ 360 if ( argc >= 2 && strncmp (argv[1], "-psn", 4) == 0 ) { 361 gArgv = (char **) SDL_malloc(sizeof (char *) * 2); 362 gArgv[0] = argv[0]; 363 gArgv[1] = NULL; 364 gArgc = 1; 365 gFinderLaunch = YES; 366 } else { 367 int i; 368 gArgc = argc; 369 gArgv = (char **) SDL_malloc(sizeof (char *) * (argc+1)); 370 for (i = 0; i <= argc; i++) 371 gArgv[i] = argv[i]; 372 gFinderLaunch = NO; 373 } 374 375#if SDL_USE_NIB_FILE 376 [SDLApplication poseAsClass:[NSApplication class]]; 377 NSApplicationMain (argc, argv); 378#else 379 CustomApplicationMain (argc, argv); 380#endif 381 return 0; 382} 383 384