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)