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