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