rubidium@6871: /* $Id$ */ rubidium@6871: rubidium@6871: /****************************************************************************** rubidium@6871: * Cocoa video driver * rubidium@6871: * Known things left to do: * rubidium@6871: * Nothing at the moment. * rubidium@6871: ******************************************************************************/ rubidium@6871: rubidium@6871: #ifdef WITH_COCOA rubidium@6871: rubidium@6871: #define MAC_OS_X_VERSION_MIN_REQUIRED MAC_OS_X_VERSION_10_3 rubidium@6871: #include rubidium@6871: rubidium@6871: #import rubidium@6871: #import /* gettimeofday */ rubidium@6871: #import /* for MAXPATHLEN */ rubidium@6871: #import rubidium@6871: rubidium@6871: /** rubidium@6871: * Important notice regarding all modifications!!!!!!! rubidium@6871: * There are certain limitations because the file is objective C++. rubidium@6871: * gdb has limitations. rubidium@6871: * C++ and objective C code can't be joined in all cases (classes stuff). rubidium@6871: * Read http://developer.apple.com/releasenotes/Cocoa/Objective-C++.html for more information. rubidium@6871: */ rubidium@6871: rubidium@6871: rubidium@6871: /* Portions of CPS.h */ rubidium@6871: struct CPSProcessSerNum { rubidium@6871: UInt32 lo; rubidium@6871: UInt32 hi; rubidium@6871: }; rubidium@6871: rubidium@6871: extern "C" OSErr CPSGetCurrentProcess(CPSProcessSerNum* psn); rubidium@6871: extern "C" OSErr CPSEnableForegroundOperation(CPSProcessSerNum* psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5); rubidium@6871: extern "C" OSErr CPSSetFrontProcess(CPSProcessSerNum* psn); rubidium@6871: rubidium@6871: /* Disables a warning. This is needed since the method exists but has been dropped from the header, supposedly as of 10.4. */ rubidium@6871: #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4) rubidium@6871: @interface NSApplication(NSAppleMenu) rubidium@6871: - (void)setAppleMenu:(NSMenu *)menu; rubidium@6871: @end rubidium@6871: #endif rubidium@6871: rubidium@6871: rubidium@6871: /* Defined in stdbool.h */ rubidium@6871: #ifndef __cplusplus rubidium@6871: # ifndef __BEOS__ rubidium@6871: # undef bool rubidium@6871: # undef false rubidium@6871: # undef true rubidium@6871: # endif rubidium@6871: #endif rubidium@6871: rubidium@6871: rubidium@6871: #include "../../stdafx.h" rubidium@6871: #include "../../openttd.h" rubidium@6871: #include "../../debug.h" rubidium@6871: #include "../../variables.h" rubidium@6872: #include "../../core/geometry_type.hpp" rubidium@6871: #include "cocoa_v.h" rubidium@6871: #include "../../blitter/factory.hpp" rubidium@6871: #include "../../fileio.h" rubidium@6872: #include "../../gfx_func.h" rubidium@6871: rubidium@6871: rubidium@6871: @interface OTTDMain : NSObject rubidium@6871: @end rubidium@6871: rubidium@6871: rubidium@6871: static NSAutoreleasePool *_ottd_autorelease_pool; rubidium@6871: static OTTDMain *_ottd_main; rubidium@6871: static bool _cocoa_video_started = false; rubidium@6871: static bool _cocoa_video_dialog = false; rubidium@6871: rubidium@6871: CocoaSubdriver* _cocoa_subdriver = NULL; rubidium@6871: rubidium@6871: rubidium@6871: rubidium@6871: /* The main class of the application, the application's delegate */ rubidium@6871: @implementation OTTDMain rubidium@6871: /* Called when the internal event loop has just started running */ rubidium@6871: - (void) applicationDidFinishLaunching: (NSNotification*) note rubidium@6871: { rubidium@6871: /* Hand off to main application code */ rubidium@6871: QZ_GameLoop(); rubidium@6871: rubidium@6871: /* We're done, thank you for playing */ rubidium@6871: [ NSApp stop:_ottd_main ]; rubidium@6871: } rubidium@6871: rubidium@6871: /* Display the in game quit confirmation dialog */ rubidium@6871: - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*) sender rubidium@6871: { rubidium@6871: rubidium@6871: HandleExitGameRequest(); rubidium@6871: rubidium@6871: return NSTerminateCancel; // NSTerminateLater ? rubidium@6871: } rubidium@6871: @end rubidium@6871: rubidium@6871: static void setApplicationMenu() rubidium@6871: { rubidium@6871: /* warning: this code is very odd */ rubidium@6871: NSMenu *appleMenu; rubidium@6871: NSMenuItem *menuItem; rubidium@6871: NSString *title; rubidium@6871: NSString *appName; rubidium@6871: rubidium@6871: appName = @"OTTD"; rubidium@6871: appleMenu = [[NSMenu alloc] initWithTitle:appName]; rubidium@6871: rubidium@6871: /* Add menu items */ rubidium@6871: title = [@"About " stringByAppendingString:appName]; rubidium@6871: [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; rubidium@6871: rubidium@6871: [appleMenu addItem:[NSMenuItem separatorItem]]; rubidium@6871: rubidium@6871: title = [@"Hide " stringByAppendingString:appName]; rubidium@6871: [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"]; rubidium@6871: rubidium@6871: menuItem = (NSMenuItem*)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; rubidium@6871: [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; rubidium@6871: rubidium@6871: [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; rubidium@6871: rubidium@6871: [appleMenu addItem:[NSMenuItem separatorItem]]; rubidium@6871: rubidium@6871: title = [@"Quit " stringByAppendingString:appName]; rubidium@6871: [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; rubidium@6871: rubidium@6871: rubidium@6871: /* Put menu into the menubar */ rubidium@6871: menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; rubidium@6871: [menuItem setSubmenu:appleMenu]; rubidium@6871: [[NSApp mainMenu] addItem:menuItem]; rubidium@6871: rubidium@6871: /* Tell the application object that this is now the application menu */ rubidium@6871: [NSApp setAppleMenu:appleMenu]; rubidium@6871: rubidium@6871: /* Finally give up our references to the objects */ rubidium@6871: [appleMenu release]; rubidium@6871: [menuItem release]; rubidium@6871: } rubidium@6871: rubidium@6871: /* Create a window menu */ rubidium@6871: static void setupWindowMenu() rubidium@6871: { rubidium@6871: NSMenu* windowMenu; rubidium@6871: NSMenuItem* windowMenuItem; rubidium@6871: NSMenuItem* menuItem; rubidium@6871: rubidium@6871: windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; rubidium@6871: rubidium@6871: /* "Minimize" item */ rubidium@6871: menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; rubidium@6871: [windowMenu addItem:menuItem]; rubidium@6871: [menuItem release]; rubidium@6871: rubidium@6871: /* Put menu into the menubar */ rubidium@6871: windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""]; rubidium@6871: [windowMenuItem setSubmenu:windowMenu]; rubidium@6871: [[NSApp mainMenu] addItem:windowMenuItem]; rubidium@6871: rubidium@6871: /* Tell the application object that this is now the window menu */ rubidium@6871: [NSApp setWindowsMenu:windowMenu]; rubidium@6871: rubidium@6871: /* Finally give up our references to the objects */ rubidium@6871: [windowMenu release]; rubidium@6871: [windowMenuItem release]; rubidium@6871: } rubidium@6871: rubidium@6871: static void setupApplication() rubidium@6871: { rubidium@6871: CPSProcessSerNum PSN; rubidium@6871: rubidium@6871: /* Ensure the application object is initialised */ rubidium@6871: [NSApplication sharedApplication]; rubidium@6871: rubidium@6871: /* Tell the dock about us */ rubidium@6871: if (!CPSGetCurrentProcess(&PSN) && rubidium@6871: !CPSEnableForegroundOperation(&PSN, 0x03, 0x3C, 0x2C, 0x1103) && rubidium@6871: !CPSSetFrontProcess(&PSN)) { rubidium@6871: [NSApplication sharedApplication]; rubidium@6871: } rubidium@6871: rubidium@6871: /* Set up the menubar */ rubidium@6871: [NSApp setMainMenu:[[NSMenu alloc] init]]; rubidium@6871: setApplicationMenu(); rubidium@6871: setupWindowMenu(); rubidium@6871: rubidium@6871: /* Create OTTDMain and make it the app delegate */ rubidium@6871: _ottd_main = [[OTTDMain alloc] init]; rubidium@6871: [NSApp setDelegate:_ottd_main]; rubidium@6871: } rubidium@6871: rubidium@6871: rubidium@6871: static void QZ_UpdateVideoModes() rubidium@6871: { rubidium@6871: uint i, count; rubidium@6872: OTTD_Point modes[32]; rubidium@6871: rubidium@6871: assert(_cocoa_subdriver != NULL); rubidium@6871: rubidium@6871: count = _cocoa_subdriver->ListModes(modes, lengthof(modes)); rubidium@6871: rubidium@6871: for (i = 0; i < count; i++) { rubidium@6871: _resolutions[i][0] = modes[i].x; rubidium@6871: _resolutions[i][1] = modes[i].y; rubidium@6871: } rubidium@6871: rubidium@6871: _num_resolutions = count; rubidium@6871: } rubidium@6871: rubidium@6871: rubidium@6871: void QZ_GameSizeChanged() rubidium@6871: { rubidium@6871: if (_cocoa_subdriver == NULL) return; rubidium@6871: rubidium@6871: /* Tell the game that the resolution has changed */ rubidium@6871: _screen.width = _cocoa_subdriver->GetWidth(); rubidium@6871: _screen.height = _cocoa_subdriver->GetHeight(); rubidium@6871: _screen.pitch = _cocoa_subdriver->GetWidth(); rubidium@6871: _fullscreen = _cocoa_subdriver->IsFullscreen(); rubidium@6871: rubidium@6871: GameSizeChanged(); rubidium@6871: } rubidium@6871: rubidium@6871: rubidium@6871: static CocoaSubdriver *QZ_CreateWindowSubdriver(int width, int height, int bpp) rubidium@6871: { rubidium@6871: CocoaSubdriver *ret; rubidium@6871: rubidium@6872: #ifdef ENABLE_COCOA_QUARTZ rubidium@6871: #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 rubidium@6872: /* The reason for the version mismatch is due to the fact that the 10.4 binary needs to work on 10.5 as well. */ rubidium@6872: if (MacOSVersionIsAtLeast(10, 5, 0)) { rubidium@6871: ret = QZ_CreateWindowQuartzSubdriver(width, height, bpp); rubidium@6871: if (ret != NULL) return ret; rubidium@6871: } rubidium@6871: #endif rubidium@6872: #endif rubidium@6871: rubidium@6872: #ifdef ENABLE_COCOA_QUICKDRAW rubidium@6871: ret = QZ_CreateWindowQuickdrawSubdriver(width, height, bpp); rubidium@6871: if (ret != NULL) return ret; rubidium@6872: #endif rubidium@6872: rubidium@6872: #ifdef ENABLE_COCOA_QUARTZ rubidium@6872: #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 rubidium@6872: /* rubidium@6872: * If we get here we are running 10.4 or earlier and either openttd was compiled without the quickdraw driver rubidium@6872: * or it failed to load for some reason. Fall back to Quartz if possible even though that driver is slower. rubidium@6872: */ rubidium@6872: if (MacOSVersionIsAtLeast(10, 4, 0)) { rubidium@6872: ret = QZ_CreateWindowQuartzSubdriver(width, height, bpp); rubidium@6872: if (ret != NULL) return ret; rubidium@6872: } rubidium@6872: #endif rubidium@6872: #endif rubidium@6871: rubidium@6871: return NULL; rubidium@6871: } rubidium@6871: rubidium@6871: rubidium@6871: static CocoaSubdriver *QZ_CreateSubdriver(int width, int height, int bpp, bool fullscreen, bool fallback) rubidium@6871: { rubidium@6871: CocoaSubdriver *ret; rubidium@6871: rubidium@6871: ret = fullscreen ? QZ_CreateFullscreenSubdriver(width, height, bpp) : QZ_CreateWindowSubdriver(width, height, bpp); rubidium@6871: if (ret != NULL) return ret; rubidium@6871: rubidium@6871: if (!fallback) return NULL; rubidium@6871: rubidium@6871: /* Try again in 640x480 windowed */ rubidium@6871: DEBUG(driver, 0, "Setting video mode failed, falling back to 640x480 windowed mode."); rubidium@6871: ret = QZ_CreateWindowSubdriver(640, 480, bpp); rubidium@6871: if (ret != NULL) return ret; rubidium@6871: rubidium@6871: #ifdef _DEBUG rubidium@6871: /* Try fullscreen too when in debug mode */ rubidium@6871: DEBUG(driver, 0, "Setting video mode failed, falling back to 640x480 fullscreen mode."); rubidium@6871: ret = QZ_CreateFullscreenSubdriver(640, 480, bpp); rubidium@6871: if (ret != NULL) return ret; rubidium@6871: #endif rubidium@6871: rubidium@6871: return NULL; rubidium@6871: } rubidium@6871: rubidium@6871: rubidium@6871: static FVideoDriver_Cocoa iFVideoDriver_Cocoa; rubidium@6871: rubidium@6871: void VideoDriver_Cocoa::Stop() rubidium@6871: { rubidium@6871: if (!_cocoa_video_started) return; rubidium@6871: rubidium@6871: delete _cocoa_subdriver; rubidium@6871: _cocoa_subdriver = NULL; rubidium@6871: rubidium@6871: [_ottd_main release]; rubidium@6871: rubidium@6871: _cocoa_video_started = false; rubidium@6871: } rubidium@6871: rubidium@6871: const char *VideoDriver_Cocoa::Start(const char * const *parm) rubidium@6871: { rubidium@6871: int width, height, bpp; rubidium@6871: rubidium@6871: if (!MacOSVersionIsAtLeast(10, 3, 0)) return "The Cocoa video driver requires Mac OS X 10.3 or later."; rubidium@6871: rubidium@6871: if (_cocoa_video_started) return "Already started"; rubidium@6871: _cocoa_video_started = true; rubidium@6871: rubidium@6871: setupApplication(); rubidium@6871: rubidium@6871: /* Don't create a window or enter fullscreen if we're just going to show a dialog. */ rubidium@6871: if (_cocoa_video_dialog) return NULL; rubidium@6871: rubidium@6871: width = _cur_resolution[0]; rubidium@6871: height = _cur_resolution[1]; rubidium@6871: bpp = BlitterFactoryBase::GetCurrentBlitter()->GetScreenDepth(); rubidium@6871: rubidium@6871: _cocoa_subdriver = QZ_CreateSubdriver(width, height, bpp, _fullscreen, true); rubidium@6871: if (_cocoa_subdriver == NULL) { rubidium@6871: Stop(); rubidium@6871: return "Could not create subdriver"; rubidium@6871: } rubidium@6871: rubidium@6871: QZ_GameSizeChanged(); rubidium@6871: rubidium@6871: QZ_UpdateVideoModes(); rubidium@6871: rubidium@6871: return NULL; rubidium@6871: } rubidium@6871: rubidium@6871: void VideoDriver_Cocoa::MakeDirty(int left, int top, int width, int height) rubidium@6871: { rubidium@6871: assert(_cocoa_subdriver != NULL); rubidium@6871: rubidium@6871: _cocoa_subdriver->MakeDirty(left, top, width, height); rubidium@6871: } rubidium@6871: rubidium@6871: void VideoDriver_Cocoa::MainLoop() rubidium@6871: { rubidium@6871: /* Start the main event loop */ rubidium@6871: [NSApp run]; rubidium@6871: } rubidium@6871: rubidium@6871: bool VideoDriver_Cocoa::ChangeResolution(int w, int h) rubidium@6871: { rubidium@6871: bool ret; rubidium@6871: rubidium@6871: assert(_cocoa_subdriver != NULL); rubidium@6871: rubidium@6871: ret = _cocoa_subdriver->ChangeResolution(w, h); rubidium@6871: rubidium@6871: QZ_GameSizeChanged(); rubidium@6871: rubidium@6871: QZ_UpdateVideoModes(); rubidium@6871: rubidium@6871: return ret; rubidium@6871: } rubidium@6871: rubidium@6872: bool VideoDriver_Cocoa::ToggleFullscreen(bool full_screen) rubidium@6871: { rubidium@6871: bool oldfs; rubidium@6871: rubidium@6871: assert(_cocoa_subdriver != NULL); rubidium@6871: rubidium@6871: oldfs = _cocoa_subdriver->IsFullscreen(); rubidium@6871: if (full_screen != oldfs) { rubidium@6871: int width = _cocoa_subdriver->GetWidth(); rubidium@6871: int height = _cocoa_subdriver->GetHeight(); rubidium@6871: int bpp = BlitterFactoryBase::GetCurrentBlitter()->GetScreenDepth(); rubidium@6871: rubidium@6871: delete _cocoa_subdriver; rubidium@6871: _cocoa_subdriver = NULL; rubidium@6871: rubidium@6871: _cocoa_subdriver = QZ_CreateSubdriver(width, height, bpp, full_screen, false); rubidium@6871: if (_cocoa_subdriver == NULL) { rubidium@6871: _cocoa_subdriver = QZ_CreateSubdriver(width, height, bpp, oldfs, true); rubidium@6871: if (_cocoa_subdriver == NULL) error("Cocoa: Failed to create subdriver"); rubidium@6871: } rubidium@6871: } rubidium@6871: rubidium@6871: QZ_GameSizeChanged(); rubidium@6871: rubidium@6871: QZ_UpdateVideoModes(); rubidium@6872: return _cocoa_subdriver->IsFullscreen() == full_screen; rubidium@6871: } rubidium@6871: rubidium@6871: rubidium@6871: /* This is needed since sometimes assert is called before the videodriver is initialized */ rubidium@6871: void CocoaDialog(const char* title, const char* message, const char* buttonLabel) rubidium@6871: { rubidium@6871: bool wasstarted; rubidium@6871: rubidium@6871: _cocoa_video_dialog = true; rubidium@6871: rubidium@6871: wasstarted = _cocoa_video_started; rubidium@6871: if (_video_driver == NULL) { rubidium@6871: setupApplication(); // Setup application before showing dialog rubidium@6871: } else if (!_cocoa_video_started && _video_driver->Start(NULL) != NULL) { rubidium@6871: fprintf(stderr, "%s: %s\n", title, message); rubidium@6871: return; rubidium@6871: } rubidium@6871: rubidium@6871: NSRunAlertPanel([NSString stringWithCString: title], [NSString stringWithCString: message], [NSString stringWithCString: buttonLabel], nil, nil); rubidium@6871: rubidium@6871: if (!wasstarted && _video_driver != NULL) _video_driver->Stop(); rubidium@6871: rubidium@6871: _cocoa_video_dialog = false; rubidium@6871: } rubidium@6871: rubidium@6871: /* This is needed since OS X application bundles do not have a rubidium@6871: * current directory and the data files are 'somewhere' in the bundle */ rubidium@6871: void cocoaSetApplicationBundleDir() rubidium@6871: { rubidium@6871: char tmp[MAXPATHLEN]; rubidium@6871: CFURLRef url = CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle()); rubidium@6871: if (CFURLGetFileSystemRepresentation(url, true, (unsigned char*)tmp, MAXPATHLEN)) { rubidium@6871: AppendPathSeparator(tmp, lengthof(tmp)); rubidium@6871: _searchpaths[SP_APPLICATION_BUNDLE_DIR] = strdup(tmp); rubidium@6871: } else { rubidium@6871: _searchpaths[SP_APPLICATION_BUNDLE_DIR] = NULL; rubidium@6871: } rubidium@6871: rubidium@6871: CFRelease(url); rubidium@6871: } rubidium@6871: rubidium@6871: /* These are called from main() to prevent a _NSAutoreleaseNoPool error when rubidium@6871: * exiting before the cocoa video driver has been loaded rubidium@6871: */ rubidium@6871: void cocoaSetupAutoreleasePool() rubidium@6871: { rubidium@6871: _ottd_autorelease_pool = [[NSAutoreleasePool alloc] init]; rubidium@6871: } rubidium@6871: rubidium@6871: void cocoaReleaseAutoreleasePool() rubidium@6871: { rubidium@6871: [_ottd_autorelease_pool release]; rubidium@6871: } rubidium@6871: rubidium@6871: #endif /* WITH_COCOA */