1/* 2 ** i_main.mm 3 ** 4 **--------------------------------------------------------------------------- 5 ** Copyright 2012-2015 Alexey Lysiuk 6 ** All rights reserved. 7 ** 8 ** Redistribution and use in source and binary forms, with or without 9 ** modification, are permitted provided that the following conditions 10 ** are met: 11 ** 12 ** 1. Redistributions of source code must retain the above copyright 13 ** notice, this list of conditions and the following disclaimer. 14 ** 2. Redistributions in binary form must reproduce the above copyright 15 ** notice, this list of conditions and the following disclaimer in the 16 ** documentation and/or other materials provided with the distribution. 17 ** 3. The name of the author may not be used to endorse or promote products 18 ** derived from this software without specific prior written permission. 19 ** 20 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 21 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 22 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 23 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 24 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 25 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 29 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 **--------------------------------------------------------------------------- 31 ** 32 */ 33 34#include "i_common.h" 35 36#include <sys/sysctl.h> 37#include <unistd.h> 38 39// Avoid collision between DObject class and Objective-C 40#define Class ObjectClass 41 42#include "c_console.h" 43#include "c_cvars.h" 44#include "cmdlib.h" 45#include "d_main.h" 46#include "doomerrors.h" 47#include "i_system.h" 48#include "m_argv.h" 49#include "s_sound.h" 50#include "st_console.h" 51#include "version.h" 52 53#undef Class 54 55 56#define ZD_UNUSED(VARIABLE) ((void)(VARIABLE)) 57 58 59// --------------------------------------------------------------------------- 60 61 62EXTERN_CVAR(Int, vid_defwidth ) 63EXTERN_CVAR(Int, vid_defheight) 64EXTERN_CVAR(Bool, vid_vsync ) 65EXTERN_CVAR(Bool, fullscreen ) 66 67 68// --------------------------------------------------------------------------- 69 70 71namespace 72{ 73 74// The maximum number of functions that can be registered with atterm. 75const size_t MAX_TERMS = 64; 76 77void (*TermFuncs[MAX_TERMS])(); 78const char *TermNames[MAX_TERMS]; 79size_t NumTerms; 80 81void call_terms() 82{ 83 while (NumTerms > 0) 84 { 85 TermFuncs[--NumTerms](); 86 } 87} 88 89} // unnamed namespace 90 91 92void addterm(void (*func)(), const char *name) 93{ 94 // Make sure this function wasn't already registered. 95 96 for (size_t i = 0; i < NumTerms; ++i) 97 { 98 if (TermFuncs[i] == func) 99 { 100 return; 101 } 102 } 103 104 if (NumTerms == MAX_TERMS) 105 { 106 func(); 107 I_FatalError("Too many exit functions registered."); 108 } 109 110 TermNames[NumTerms] = name; 111 TermFuncs[NumTerms] = func; 112 113 ++NumTerms; 114} 115 116void popterm() 117{ 118 if (NumTerms) 119 { 120 --NumTerms; 121 } 122} 123 124 125void Mac_I_FatalError(const char* const message) 126{ 127 I_SetMainWindowVisible(false); 128 129 FConsoleWindow::GetInstance().ShowFatalError(message); 130} 131 132 133DArgs* Args; // command line arguments 134 135 136namespace 137{ 138 139const int ARGC_MAX = 64; 140 141int s_argc; 142char* s_argv[ARGC_MAX]; 143 144TArray<FString> s_argvStorage; 145 146bool s_restartedFromWADPicker; 147 148 149void NewFailure() 150{ 151 I_FatalError("Failed to allocate memory from system heap"); 152} 153 154 155int OriginalMain(int argc, char** argv) 156{ 157 printf(GAMENAME" %s - %s - Cocoa version\nCompiled on %s\n\n", 158 GetVersionString(), GetGitTime(), __DATE__); 159 160 seteuid(getuid()); 161 std::set_new_handler(NewFailure); 162 163 // Set LC_NUMERIC environment variable in case some library decides to 164 // clear the setlocale call at least this will be correct. 165 // Note that the LANG environment variable is overridden by LC_* 166 setenv("LC_NUMERIC", "C", 1); 167 setlocale(LC_ALL, "C"); 168 169 // Set reasonable default values for video settings 170 171 const NSSize screenSize = [[NSScreen mainScreen] frame].size; 172 vid_defwidth = static_cast<int>(screenSize.width); 173 vid_defheight = static_cast<int>(screenSize.height); 174 vid_vsync = true; 175 fullscreen = true; 176 177 try 178 { 179 Args = new DArgs(argc, argv); 180 181 /* 182 killough 1/98: 183 184 This fixes some problems with exit handling 185 during abnormal situations. 186 187 The old code called I_Quit() to end program, 188 while now I_Quit() is installed as an exit 189 handler and exit() is called to exit, either 190 normally or abnormally. Seg faults are caught 191 and the error handler is used, to prevent 192 being left in graphics mode or having very 193 loud SFX noise because the sound card is 194 left in an unstable state. 195 */ 196 197 atexit(call_terms); 198 atterm(I_Quit); 199 200 NSString* exePath = [[NSBundle mainBundle] executablePath]; 201 progdir = [[exePath stringByDeletingLastPathComponent] UTF8String]; 202 progdir += "/"; 203 204 C_InitConsole(80 * 8, 25 * 8, false); 205 D_DoomMain(); 206 } 207 catch(const CDoomError& error) 208 { 209 const char* const message = error.GetMessage(); 210 211 if (NULL != message) 212 { 213 fprintf(stderr, "%s\n", message); 214 Mac_I_FatalError(message); 215 } 216 217 exit(-1); 218 } 219 catch(...) 220 { 221 call_terms(); 222 throw; 223 } 224 225 return 0; 226} 227 228} // unnamed namespace 229 230 231// --------------------------------------------------------------------------- 232 233 234@interface ApplicationController : NSResponder 235#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 236 <NSFileManagerDelegate> 237#endif 238{ 239} 240 241- (void)keyDown:(NSEvent*)theEvent; 242- (void)keyUp:(NSEvent*)theEvent; 243 244- (void)applicationDidBecomeActive:(NSNotification*)aNotification; 245- (void)applicationWillResignActive:(NSNotification*)aNotification; 246 247- (void)applicationDidFinishLaunching:(NSNotification*)aNotification; 248 249- (BOOL)application:(NSApplication*)theApplication openFile:(NSString*)filename; 250 251- (void)processEvents:(NSTimer*)timer; 252 253@end 254 255 256ApplicationController* appCtrl; 257 258 259@implementation ApplicationController 260 261- (void)keyDown:(NSEvent*)theEvent 262{ 263 // Empty but present to avoid playing of 'beep' alert sound 264 265 ZD_UNUSED(theEvent); 266} 267 268- (void)keyUp:(NSEvent*)theEvent 269{ 270 // Empty but present to avoid playing of 'beep' alert sound 271 272 ZD_UNUSED(theEvent); 273} 274 275 276- (void)applicationDidBecomeActive:(NSNotification*)aNotification 277{ 278 ZD_UNUSED(aNotification); 279 280 S_SetSoundPaused(1); 281} 282 283- (void)applicationWillResignActive:(NSNotification*)aNotification 284{ 285 ZD_UNUSED(aNotification); 286 287 S_SetSoundPaused(0); 288} 289 290 291- (void)applicationDidFinishLaunching:(NSNotification*)aNotification 292{ 293 // When starting from command line with real executable path, e.g. ZDoom.app/Contents/MacOS/ZDoom 294 // application remains deactivated for an unknown reason. 295 // The following call resolves this issue 296 [NSApp activateIgnoringOtherApps:YES]; 297 298 // Setup timer for custom event loop 299 300 NSTimer* timer = [NSTimer timerWithTimeInterval:0 301 target:self 302 selector:@selector(processEvents:) 303 userInfo:nil 304 repeats:YES]; 305 [[NSRunLoop currentRunLoop] addTimer:timer 306 forMode:NSDefaultRunLoopMode]; 307 308 FConsoleWindow::CreateInstance(); 309 atterm(FConsoleWindow::DeleteInstance); 310 311 exit(OriginalMain(s_argc, s_argv)); 312} 313 314 315- (BOOL)application:(NSApplication*)theApplication openFile:(NSString*)filename 316{ 317 ZD_UNUSED(theApplication); 318 319 if (s_restartedFromWADPicker 320 || 0 == [filename length] 321 || s_argc + 2 >= ARGC_MAX) 322 { 323 return FALSE; 324 } 325 326 // Some parameters from command line are passed to this function 327 // These parameters need to be skipped to avoid duplication 328 // Note: SDL has different approach to fix this issue, see the same method in SDLMain.m 329 330 const char* const charFileName = [filename UTF8String]; 331 332 for (int i = 0; i < s_argc; ++i) 333 { 334 if (0 == strcmp(s_argv[i], charFileName)) 335 { 336 return FALSE; 337 } 338 } 339 340 s_argvStorage.Push("-file"); 341 s_argv[s_argc++] = s_argvStorage.Last().LockBuffer(); 342 343 s_argvStorage.Push([filename UTF8String]); 344 s_argv[s_argc++] = s_argvStorage.Last().LockBuffer(); 345 346 return TRUE; 347} 348 349 350- (void)processEvents:(NSTimer*)timer 351{ 352 ZD_UNUSED(timer); 353 354 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; 355 356 while (true) 357 { 358 NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask 359 untilDate:[NSDate dateWithTimeIntervalSinceNow:0] 360 inMode:NSDefaultRunLoopMode 361 dequeue:YES]; 362 if (nil == event) 363 { 364 break; 365 } 366 367 I_ProcessEvent(event); 368 369 [NSApp sendEvent:event]; 370 } 371 372 [NSApp updateWindows]; 373 374 [pool release]; 375} 376 377@end 378 379 380// --------------------------------------------------------------------------- 381 382 383namespace 384{ 385 386NSMenuItem* CreateApplicationMenu() 387{ 388 NSMenu* menu = [NSMenu new]; 389 390 [menu addItemWithTitle:[@"About " stringByAppendingString:@GAMENAME] 391 action:@selector(orderFrontStandardAboutPanel:) 392 keyEquivalent:@""]; 393 [menu addItem:[NSMenuItem separatorItem]]; 394 [menu addItemWithTitle:[@"Hide " stringByAppendingString:@GAMENAME] 395 action:@selector(hide:) 396 keyEquivalent:@"h"]; 397 [[menu addItemWithTitle:@"Hide Others" 398 action:@selector(hideOtherApplications:) 399 keyEquivalent:@"h"] 400 setKeyEquivalentModifierMask:NSAlternateKeyMask | NSCommandKeyMask]; 401 [menu addItemWithTitle:@"Show All" 402 action:@selector(unhideAllApplications:) 403 keyEquivalent:@""]; 404 [menu addItem:[NSMenuItem separatorItem]]; 405 [menu addItemWithTitle:[@"Quit " stringByAppendingString:@GAMENAME] 406 action:@selector(terminate:) 407 keyEquivalent:@"q"]; 408 409 NSMenuItem* menuItem = [NSMenuItem new]; 410 [menuItem setSubmenu:menu]; 411 412 if ([NSApp respondsToSelector:@selector(setAppleMenu:)]) 413 { 414 [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; 415 } 416 417 return menuItem; 418} 419 420NSMenuItem* CreateEditMenu() 421{ 422 NSMenu* menu = [[NSMenu alloc] initWithTitle:@"Edit"]; 423 424 [menu addItemWithTitle:@"Undo" 425 action:@selector(undo:) 426 keyEquivalent:@"z"]; 427 [menu addItemWithTitle:@"Redo" 428 action:@selector(redo:) 429 keyEquivalent:@"Z"]; 430 [menu addItem:[NSMenuItem separatorItem]]; 431 [menu addItemWithTitle:@"Cut" 432 action:@selector(cut:) 433 keyEquivalent:@"x"]; 434 [menu addItemWithTitle:@"Copy" 435 action:@selector(copy:) 436 keyEquivalent:@"c"]; 437 [menu addItemWithTitle:@"Paste" 438 action:@selector(paste:) 439 keyEquivalent:@"v"]; 440 [menu addItemWithTitle:@"Delete" 441 action:@selector(delete:) 442 keyEquivalent:@""]; 443 [menu addItemWithTitle:@"Select All" 444 action:@selector(selectAll:) 445 keyEquivalent:@"a"]; 446 447 NSMenuItem* menuItem = [NSMenuItem new]; 448 [menuItem setSubmenu:menu]; 449 450 return menuItem; 451} 452 453NSMenuItem* CreateWindowMenu() 454{ 455 NSMenu* menu = [[NSMenu alloc] initWithTitle:@"Window"]; 456 [NSApp setWindowsMenu:menu]; 457 458 [menu addItemWithTitle:@"Minimize" 459 action:@selector(performMiniaturize:) 460 keyEquivalent:@"m"]; 461 [menu addItemWithTitle:@"Zoom" 462 action:@selector(performZoom:) 463 keyEquivalent:@""]; 464 [menu addItem:[NSMenuItem separatorItem]]; 465 [menu addItemWithTitle:@"Bring All to Front" 466 action:@selector(arrangeInFront:) 467 keyEquivalent:@""]; 468 469 NSMenuItem* menuItem = [NSMenuItem new]; 470 [menuItem setSubmenu:menu]; 471 472 return menuItem; 473} 474 475void CreateMenu() 476{ 477 NSMenu* menuBar = [NSMenu new]; 478 [menuBar addItem:CreateApplicationMenu()]; 479 [menuBar addItem:CreateEditMenu()]; 480 [menuBar addItem:CreateWindowMenu()]; 481 482 [NSApp setMainMenu:menuBar]; 483} 484 485void ReleaseApplicationController() 486{ 487 if (NULL != appCtrl) 488 { 489 [NSApp setDelegate:nil]; 490 [NSApp deactivate]; 491 492 [appCtrl release]; 493 appCtrl = NULL; 494 } 495} 496 497} // unnamed namespace 498 499 500int main(int argc, char** argv) 501{ 502 for (int i = 0; i <= argc; ++i) 503 { 504 const char* const argument = argv[i]; 505 506 if (NULL == argument || '\0' == argument[0]) 507 { 508 continue; 509 } 510 511 if (0 == strcmp(argument, "-wad_picker_restart")) 512 { 513 s_restartedFromWADPicker = true; 514 } 515 else 516 { 517 s_argvStorage.Push(argument); 518 s_argv[s_argc++] = s_argvStorage.Last().LockBuffer(); 519 } 520 } 521 522 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; 523 524 [NSApplication sharedApplication]; 525 526 // The following code isn't mandatory, 527 // but it enables to run the application without a bundle 528 if ([NSApp respondsToSelector:@selector(setActivationPolicy:)]) 529 { 530 [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; 531 } 532 533 CreateMenu(); 534 535 atterm(ReleaseApplicationController); 536 537 appCtrl = [ApplicationController new]; 538 [NSApp setDelegate:appCtrl]; 539 [NSApp run]; 540 541 [pool release]; 542 543 return EXIT_SUCCESS; 544} 545