1 /*
2  * SDLMain.m - main entry point for our Cocoa-ized SDL app
3  * Initial Version: Darrell Walisser <dwaliss1@purdue.edu>
4  * Non-NIB-Code & other changes: Max Horn <max@quendi.de>
5  * Port to the D programming language: Jacob Carlborg <jacob.carlborg@gmail.com>
6  *
7  * Feel free to customize this file to suit your needs
8  */
9 module derelict.sdl.macinit.SDLMain;
10 
11 version(DigitalMars) version(OSX) version = darwin;
12 
13 version (darwin)
14 {
15 
16 private
17 {
18     version (Tango)
19     {
20         import tango.stdc.posix.unistd;
21         import tango.stdc.stdlib;
22         import tango.stdc.string;
23     }
24 
25     else
26     {
27         import std.c.linux.linux;
28         import std.c.stdlib;
29         import std.c.string;
30         import std.file;
31         static import std.string;
32     }
33 
34     import derelict.sdl.sdltypes;
35     import derelict.sdl.sdlfuncs;
36     import derelict.sdl.macinit.CoreFoundation;
37     import derelict.sdl.macinit.DerelictSDLMacLoader;
38     import derelict.sdl.macinit.ID;
39     import derelict.sdl.macinit.MacTypes;
40     import derelict.sdl.macinit.NSApplication;
41     import derelict.sdl.macinit.NSAutoreleasePool;
42     import derelict.sdl.macinit.NSDictionary;
43     import derelict.sdl.macinit.NSEnumerator;
44     import derelict.sdl.macinit.NSEvent;
45     import derelict.sdl.macinit.NSGeometry;
46     import derelict.sdl.macinit.NSMenu;
47     import derelict.sdl.macinit.NSMenuItem;
48     import derelict.sdl.macinit.NSNotification;
49     import derelict.sdl.macinit.NSObject;
50     import derelict.sdl.macinit.NSProcessInfo;
51     import derelict.sdl.macinit.NSString;
52     import derelict.sdl.macinit.runtime;
53     import derelict.sdl.macinit.selectors;
54     import derelict.sdl.macinit.string;
55     import derelict.util.compat;
56     import derelict.util.loader;
57 }
58 
59 private:
60 
61 enum
62 {
63     MAXPATHLEN = 1024 // from sys/param.h
64 }
65 
66 /* Use this flag to determine whether we use CPS (docking) or not */
67 version = SDL_USE_CPS;
68 
69 version (SDL_USE_CPS)
70 {
71     struct CPSProcessSerNum
72     {
73         uint lo;
74         uint hi;
75     }
76 
77     extern (C)
78     {
79         mixin(gsharedString!() ~ "
80         OSErr function (CPSProcessSerNum *psn) CPSGetCurrentProcess;
81         OSErr function (CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5) CPSEnableForegroundOperation;
82         OSErr function (CPSProcessSerNum *psn) CPSSetFrontProcess;");
83     }
84 
85     void load (void delegate(void**, string, bool doThrow = true) bindFunc)
86     {
87         bindFunc(cast(void**)&CPSGetCurrentProcess, "CPSGetCurrentProcess");
88         bindFunc(cast(void**)&CPSEnableForegroundOperation, "CPSEnableForegroundOperation");
89         bindFunc(cast(void**)&CPSSetFrontProcess, "CPSSetFrontProcess");
90     }
91 }
92 
93 else
94     version = NO_SDL_USE_CPS;
95 
96 private
97 {
98     NSAutoreleasePool pool;
99     SDLMain sdlMain;
100 }
101 
102 static this ()
103 {
104     version (SDL_USE_CPS)
105         load(&DerelictSDLMac.bindFunc);
106 
107     registerSubclasses();
108     CustomApplicationMain();
109 }
110 
111 static ~this()
112 {
113     if(pool !is null)
114         pool.release();
115 
116     if(sdlMain !is null)
117         sdlMain.release;
118 
119     DerelictSDLMac.unload();
120 }
121 
122 private void registerSubclasses ()
123 {
124     objc_method terminateMethod;
125     terminateMethod.method_imp = cast(IMP) &terminate;
126     terminateMethod.method_name = sel_terminate;
127     terminateMethod.method_types = "v@:";
128 
129     objc_method_list* terminateMethodList = cast(objc_method_list*) calloc(1, (objc_method_list).sizeof);
130     terminateMethodList.method_count = 1;
131     terminateMethodList.method_list[0] = terminateMethod;
132 
133 
134 
135     objc_method setupWorkingDirectoryMethod;
136     setupWorkingDirectoryMethod.method_imp = cast(IMP) &setupWorkingDirectory;
137     setupWorkingDirectoryMethod.method_name = sel_setupWorkingDirectory;
138     setupWorkingDirectoryMethod.method_types = "v@:B";
139 
140     objc_method_list* setupWorkingDirectoryMethodList = cast(objc_method_list*) calloc(1, (objc_method_list).sizeof);
141     setupWorkingDirectoryMethodList.method_count = 1;
142     setupWorkingDirectoryMethodList.method_list[0] = setupWorkingDirectoryMethod;
143 
144 
145 
146     objc_method applicationMethod;
147     applicationMethod.method_imp = cast(IMP) &application;
148     applicationMethod.method_name = sel_application;
149     applicationMethod.method_types = "B@:@@";
150 
151     objc_method_list* applicationMethodList = cast(objc_method_list*) calloc(1, (objc_method_list).sizeof);
152     applicationMethodList.method_count = 1;
153     applicationMethodList.method_list[0] = applicationMethod;
154 
155 
156 
157     objc_method applicationDidFinishLaunchingMethod;
158     applicationDidFinishLaunchingMethod.method_imp = cast(IMP) &applicationDidFinishLaunching;
159     applicationDidFinishLaunchingMethod.method_name = sel_applicationDidFinishLaunching;
160     applicationDidFinishLaunchingMethod.method_types = "v@:@";
161 
162     objc_method_list* applicationDidFinishLaunchingMethodList = cast(objc_method_list*) calloc(1, (objc_method_list).sizeof);
163     applicationDidFinishLaunchingMethodList.method_count = 1;
164     applicationDidFinishLaunchingMethodList.method_list[0] = applicationDidFinishLaunchingMethod;
165 
166 
167 
168     auto sdlApplicationMethodList = [terminateMethodList];
169     auto sdlMainMethodList = [setupWorkingDirectoryMethodList, applicationMethodList, applicationDidFinishLaunchingMethodList];
170 
171     registerClass!("SDLApplication")(cast(Class) class_NSApplication, sdlApplicationMethodList);
172     registerClass!("SDLMain")(cast(Class) class_NSObject, sdlMainMethodList);
173 
174     class_SDLApplication = objc_getClass!("SDLApplication");
175 }
176 
177 private void registerClass (string className) (Class superClass, objc_method_list*[] methodList)
178 {
179     Class newClass;
180 
181     // Leopard and above
182     if (!objc_addClass)
183     {
184         newClass = objc_allocateClassPair!(className)(cast(Class) superClass, 0);
185 
186         foreach (m ; methodList)
187         {
188             auto method = m.method_list[0];            
189             class_addMethod(newClass, method.method_name, method.method_imp, method.method_types);
190         }
191 
192         objc_registerClassPair(newClass);
193     }
194 
195     // Tiger and below
196     else
197     {
198         enum
199         {
200             CLS_CLASS = 0x1,
201             CLS_META = 0x2
202         }
203 
204         Class metaClass;
205         Class rootClass = superClass;
206 
207         // Find the root class
208         while (rootClass.super_class !is null)
209             rootClass = rootClass.super_class;
210 
211         // Allocate space for the class and its metaclass
212         newClass = cast(Class) calloc(2, objc_class.sizeof);
213         metaClass = &newClass[1];
214 
215         // Setup class
216         newClass.isa = metaClass;
217         newClass.info = CLS_CLASS;
218         metaClass.info = CLS_META;
219 
220         /*
221          * Create a copy of the class name.
222          * For efficiency, we have the metaclass and the class itself
223          * to share this copy of the name, but this is not a requirement
224          * imposed by the runtime.
225          */
226         newClass.name = toCString(className);
227         metaClass.name = newClass.name;
228 
229         // Allocate method lists.
230         newClass.methodLists = cast(objc_method_list**) calloc(1, (objc_method_list*).sizeof);
231         *(newClass.methodLists) = cast(objc_method_list*) -1;
232         metaClass.methodLists = cast(objc_method_list**) calloc(1, (objc_method_list*).sizeof);
233         *(metaClass.methodLists) = cast(objc_method_list*) -1;
234 
235         foreach (method ; methodList)
236             class_addMethods(newClass, method);
237 
238         /*
239          * Connect the class definition to the class hierarchy:
240          * Connect the class to the superclass.
241          * Connect the metaclass to the metaclass of the superclass.
242          * Connect the metaclass of the metaclass to the metaclass of  the root class.
243          */
244         newClass.super_class = superClass;
245         metaClass.super_class = superClass.isa;
246         metaClass.isa = rootClass.isa;
247 
248         // Set the sizes of the class and the metaclass.
249         newClass.instance_size = superClass.instance_size;
250         metaClass.instance_size = metaClass.super_class.instance_size;
251 
252         // Finally, register the class with the runtime.
253         objc_addClass(newClass);
254     }
255 }
256 
257 private NSString getApplicationName ()
258 {
259     NSDictionary dict;
260     NSString appName;
261 
262     /* Determine the application name */
263     dict = new NSDictionary(cast(id)CFBundleGetInfoDictionary(CFBundleGetMainBundle()));
264 
265     if (dict)
266         appName = cast(NSString) dict.objectForKey(NSString.stringWith("CFBundleName"));
267 
268     if (appName is null || !appName.length)
269         appName = NSProcessInfo.processInfo.processName;
270 
271     return appName;
272 }
273 
274 class SDLApplication : NSApplication
275 {
276     this ()
277     {
278         id_ = null;
279     }
280 
281     this (id id_)
282     {
283         this.id_ = id_;
284     }
285 
286     static SDLApplication alloc ()
287     {
288         id result = objc_msgSend(cast(id)class_, sel_alloc);
289         return result ? new SDLApplication(result) : null;
290     }
291 
292     static Class class_ ()
293     {
294         return cast(Class) objc_getClass!(this.stringof);
295     }
296 
297     static void poseAsClass (Class aClass)
298     {
299         objc_msgSend(class_SDLApplication, sel_poseAsClass, aClass);
300     }
301 
302     override SDLApplication init ()
303     {
304         id result = objc_msgSend(this.id_, sel_init);
305         return result ? this : null;
306     }
307 
308     /* Invoked from the Quit menu item */
309     void terminate ()
310     {
311         objc_msgSend(this.id_, sel_terminate);
312     }
313 }
314 
315 /* Invoked from the Quit menu item */
316 extern (C) id terminate (id self, SEL selector)
317 {
318     /* Post a SDL_QUIT event */
319     SDL_Event event;
320     event.type = SDL_QUIT;
321     SDL_PushEvent(&event);
322 
323     return null;
324 }
325 
326 /* The main class of the application, the application's delegate */
327 class SDLMain : NSObject
328 {
329     this ()
330     {
331         id_ = null;
332     }
333 
334     this (id id_)
335     {
336         this.id_ = id_;
337     }
338 
339     static SDLMain alloc ()
340     {
341         id result = objc_msgSend(cast(id)class_, sel_alloc);
342         return result ? new SDLMain(result) : null;
343     }
344 
345     static Class class_ ()
346     {
347         return cast(Class) objc_getClass!(this.stringof);
348     }
349 
350     override SDLMain init ()
351     {
352         id result = objc_msgSend(this.id_, sel_init);
353         return result ? this : null;
354     }
355 
356     void setupWorkingDirectory (bool shouldChdir)
357     {
358         objc_msgSend(this.id_, sel_setupWorkingDirectory, shouldChdir);
359     }
360 
361     bool application (NSApplication theApplication, NSString filename)
362     {
363         return objc_msgSend(this.id_, sel_application, theApplication ? theApplication.id_ : null, filename ? filename.id_ : null) !is null;
364     }
365 
366     /* Called when the internal event loop has just started running */
367     void applicationDidFinishLaunching (NSNotification note)
368     {
369         objc_msgSend(this.id_, sel_applicationDidFinishLaunching, note ? note.id_ : null);
370     }
371 }
372 
373 extern (C)
374 {
375     id setupWorkingDirectory (id sender, SEL selector, bool shouldChdir)
376     {
377         if (shouldChdir)
378         {
379             char parentdir[MAXPATHLEN];
380 
381             CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle());
382             CFURLRef url2 = CFURLCreateCopyDeletingLastPathComponent(null, url);
383 
384             if (CFURLGetFileSystemRepresentation(url2, true, cast(ubyte*) parentdir, MAXPATHLEN))
385                 chdir(parentdir); /* chdir to the binary app's parent */
386 
387             CFRelease(url);
388             CFRelease(url2);
389         }
390 
391         return null;
392     }
393 
394     id application (id sender, SEL selector, id arg0, id arg1)
395     {
396 
397         return cast(id) true;
398     }
399 
400     /* Called when the internal event loop has just started running */
401     id applicationDidFinishLaunching (id sender, SEL selector, id arg0)
402     {
403         NSNotification note = arg0 ? new NSNotification(arg0) : null;
404 
405         int status;
406 
407         /* Set the working directory to the .app's parent directory */
408         setupWorkingDirectory(sender, selector, false);
409 
410         NSApp.stop(null);
411 
412         return null;
413     }
414 }
415 
416 private void setApplicationMenu ()
417 {
418     /* warning: this code is very odd */
419     NSMenu appleMenu;
420     NSMenuItem menuItem;
421     NSString title;
422     NSString appName;
423 
424     appName = getApplicationName();
425     appleMenu = NSMenu.alloc.initWithTitle(NSString.stringWith(""));
426 
427     /* Add menu items */
428     title = NSString.stringWith("About ").stringByAppendingString(appName);
429     appleMenu.addItemWithTitle(title, sel_registerName!("orderFrontStandardAboutPanel:"), NSString.stringWith(""));
430 
431     appleMenu.addItem(NSMenuItem.separatorItem);
432 
433     title = NSString.stringWith("Hide ").stringByAppendingString(appName);
434     appleMenu.addItemWithTitle(title, sel_registerName!("hide:"), NSString.stringWith("h"));
435 
436     menuItem = appleMenu.addItemWithTitle(NSString.stringWith("Hide Others"), sel_registerName!("hideOtherApplications:"), NSString.stringWith("h"));
437     menuItem.setKeyEquivalentModifierMask(NSAlternateKeyMask | NSCommandKeyMask);
438 
439     appleMenu.addItemWithTitle(NSString.stringWith("Show All"), sel_registerName!("unhideAllApplications:"), NSString.stringWith(""));
440 
441     appleMenu.addItem(NSMenuItem.separatorItem);
442 
443     title = NSString.stringWith("Quit ").stringByAppendingString(appName);
444     appleMenu.addItemWithTitle(title, sel_registerName!("terminate:"), NSString.stringWith("q"));
445 
446 
447     /* Put menu into the menubar */
448     menuItem = NSMenuItem.alloc;
449     menuItem = menuItem.initWithTitle(NSString.stringWith(""), null, NSString.stringWith(""));
450     menuItem.setSubmenu = appleMenu;
451     NSApp.mainMenu.addItem(menuItem);
452 
453     /* Tell the application object that this is now the application menu */
454     NSApp.setAppleMenu = appleMenu;
455 
456     /* Finally give up our references to the objects */
457     appleMenu.release;
458     menuItem.release;
459 }
460 
461 /* Create a window menu */
462 private void setupWindowMenu ()
463 {
464     NSMenu windowMenu;
465     NSMenuItem windowMenuItem;
466     NSMenuItem menuItem;
467 
468     windowMenu = NSMenu.alloc.initWithTitle(NSString.stringWith("Window"));
469 
470     /* "Minimize" item */
471     menuItem = NSMenuItem.alloc;
472     menuItem = menuItem.initWithTitle(NSString.stringWith("Minimize"), sel_registerName!("performMiniaturize:"), NSString.stringWith("m"));
473     windowMenu.addItem(menuItem);
474     menuItem.release;
475 
476     /* Put menu into the menubar */
477     windowMenuItem = NSMenuItem.alloc;
478     windowMenuItem = windowMenuItem.initWithTitle(NSString.stringWith("Window"), null, NSString.stringWith(""));
479     windowMenuItem.setSubmenu = windowMenu;
480     NSApp.mainMenu.addItem(windowMenuItem);
481 
482     /* Tell the application object that this is now the window menu */
483     NSApp.setWindowsMenu = windowMenu;
484 
485     /* Finally give up our references to the objects */
486     windowMenu.release;
487     windowMenuItem.release;
488 }
489 
490 /* Replacement for NSApplicationMain */
491 private void CustomApplicationMain ()
492 {
493     pool = NSAutoreleasePool.alloc.init;
494 
495     /* Ensure the application object is initialised */
496     SDLApplication.sharedApplication;
497 
498     version (SDL_USE_CPS)
499     {
500         CPSProcessSerNum PSN;
501 
502         /* Tell the dock about us */
503         if (!CPSGetCurrentProcess(&PSN))
504             if (!CPSEnableForegroundOperation(&PSN, 0x03, 0x3C, 0x2C, 0x1103))
505                 if (!CPSSetFrontProcess(&PSN))
506                     SDLApplication.sharedApplication;
507     }
508 
509     /* Set up the menubar */
510     NSApp.setMainMenu = NSMenu.alloc.init;
511     setApplicationMenu();
512     setupWindowMenu();
513 
514 
515     /* Create SDLMain and make it the app delegate */
516     sdlMain = SDLMain.alloc.init;
517     NSApp.setDelegate = sdlMain;
518 
519     /* Start the main event loop */
520     NSApp.run;
521 }
522 
523 } // version(darwin)