|
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 } |
|
384 |