diff options
-rw-r--r-- | Makefile | 29 | ||||
-rw-r--r-- | driver.c | 8 | ||||
-rw-r--r-- | fileio.c | 31 | ||||
-rw-r--r-- | fileio.h | 1 | ||||
-rw-r--r-- | makefiledir/Makefile.config_writer | 1 | ||||
-rw-r--r-- | makefiledir/Makefile.libdetection | 8 | ||||
-rw-r--r-- | os/macosx/Makefile | 1 | ||||
-rw-r--r-- | os/macosx/macos.m | 4 | ||||
-rw-r--r-- | os/macosx/splash.c | 145 | ||||
-rw-r--r-- | os/macosx/splash.h | 9 | ||||
-rw-r--r-- | os/macosx/splash.png | bin | 0 -> 20053 bytes | |||
-rw-r--r-- | sound/cocoa_s.c | 144 | ||||
-rw-r--r-- | sound/cocoa_s.h | 9 | ||||
-rw-r--r-- | unix.c | 26 | ||||
-rw-r--r-- | video/cocoa_v.h | 130 | ||||
-rw-r--r-- | video/cocoa_v.m | 2113 |
16 files changed, 2651 insertions, 8 deletions
@@ -43,6 +43,7 @@ # WITH_ZLIB: savegames using zlib # WITH_PNG: screenshots using PNG # WITH_SDL: SDL video driver support +# WITH_COCOA: Cocoa video driver support # # Summary of other defines: # MANUAL_CONFIG: do not use Makefile.config, config options set manually @@ -206,6 +207,14 @@ endif endif endif +ifdef WITH_COCOA +ifdef WITH_SDL +$(error You can not use both the SDL video driver and the Cocoa video driver at the same time) +endif +ifdef DEDICATED +$(error You can not use the Cocoa video driver in a dedicated server) +endif +else # Force SDL on UNIX platforms ifndef WITH_SDL ifdef UNIX @@ -214,6 +223,7 @@ $(error You need to have SDL installed in order to run OpenTTD on UNIX. Use DEDI endif endif endif +endif # remove the dependancy for sdl if DEDICALTED is used # and add -lpthread to LDFLAGS, because SDL normally adds that... @@ -502,7 +512,6 @@ else STRGEN_FLAGS= endif - # OSX specific setup ifdef OSX # set the endian flag for OSX, that can't fail @@ -519,6 +528,11 @@ ifdef OSX LIBS += -framework QuickTime endif + ifdef WITH_COCOA + CDEFS += -DWITH_COCOA + LIBS += -F/System/Library/Frameworks -framework Cocoa -framework Carbon -framework AudioUnit + endif + # OSX path setup ifndef SECOND_DATA_PATH SECOND_DATA_PATH:="$(OSXAPP)/Contents/Data/" @@ -744,10 +758,15 @@ else endif ifdef OSX - SRCS += os/macosx/macos.m - ifndef DEDICATED - SRCS += music/qtmidi.c - endif + SRCS += os/macosx/macos.m + ifndef DEDICATED + SRCS += music/qtmidi.c + endif + ifdef WITH_COCOA + SRCS += video/cocoa_v.m + SRCS += sound/cocoa_s.c + SRCS += os/macosx/splash.c + endif endif ifdef BEOS @@ -18,11 +18,13 @@ #include "sound/null_s.h" #include "sound/sdl_s.h" +#include "sound/cocoa_s.h" #include "sound/win32_s.h" #include "video/dedicated_v.h" #include "video/null_v.h" #include "video/sdl_v.h" +#include "video/cocoa_v.h" #include "video/win32_v.h" typedef struct DriverDesc { @@ -71,6 +73,9 @@ static const DriverDesc _sound_driver_descs[] = { #ifdef WITH_SDL M("sdl", "SDL Sound Driver", &_sdl_sound_driver), #endif +#ifdef WITH_COCOA + M("cocoa", "Cocoa Sound Driver", &_cocoa_sound_driver), +#endif M("null", "Null Sound Driver", &_null_sound_driver), M(NULL, NULL, NULL) }; @@ -82,6 +87,9 @@ static const DriverDesc _video_driver_descs[] = { #ifdef WITH_SDL M("sdl", "SDL Video Driver", &_sdl_video_driver), #endif +#ifdef WITH_COCOA + M("cocoa", "Cocoa Video Driver", &_cocoa_video_driver), +#endif M("null", "Null Video Driver", &_null_video_driver), #ifdef ENABLE_NETWORK M("dedicated", "Dedicated Video Driver", &_dedicated_video_driver), @@ -141,6 +141,37 @@ bool FiosCheckFileExists(const char *filename) } } +FILE *FioFOpenFile(const char *filename) +{ + FILE *f; + char buf[MAX_PATH]; + + sprintf(buf, "%s%s", _path.data_dir, filename); + + f = fopen(buf, "rb"); +#if !defined(WIN32) + if (f == NULL) { + char *s; + // Make lower case and try again + for(s=buf + strlen(_path.data_dir) - 1; *s != 0; s++) + *s = tolower(*s); + f = fopen(buf, "rb"); + +#if defined SECOND_DATA_DIR + // tries in the 2nd data directory + if (f == NULL) { + sprintf(buf, "%s%s", _path.second_data_dir, filename); + for(s=buf + strlen(_path.second_data_dir) - 1; *s != 0; s++) + *s = tolower(*s); + f = fopen(buf, "rb"); + } +#endif + } +#endif + + return f; +} + void FioOpenFile(int slot, const char *filename) { FILE *f; @@ -10,6 +10,7 @@ byte FioReadByte(void); uint16 FioReadWord(void); uint32 FioReadDword(void); void FioCloseAll(void); +FILE *FioFOpenFile(const char *filename); void FioOpenFile(int slot, const char *filename); void FioReadBlock(void *ptr, uint size); void FioSkipBytes(int n); diff --git a/makefiledir/Makefile.config_writer b/makefiledir/Makefile.config_writer index 4b56391b6..dff31a4e8 100644 --- a/makefiledir/Makefile.config_writer +++ b/makefiledir/Makefile.config_writer @@ -72,6 +72,7 @@ $(MAKE_CONFIG): $(call CONFIG_LINE,WITH_SDL:=$(WITH_SDL)) $(call CONFIG_LINE,WITH_PNG:=$(WITH_PNG)) $(call CONFIG_LINE,STATIC_ZLIB_PATH:=$(STATIC_ZLIB_PATH)) + $(call CONFIG_LINE,WITH_COCOA:=$(WITH_COCOA)) $(call CONFIG_LINE,) $(call CONFIG_LINE,\# Lib paths for OSX. Read os/MacOSX/Makefile for more info) diff --git a/makefiledir/Makefile.libdetection b/makefiledir/Makefile.libdetection index 9177061ee..f648c22bd 100644 --- a/makefiledir/Makefile.libdetection +++ b/makefiledir/Makefile.libdetection @@ -93,6 +93,14 @@ endif endif endif +ifdef OSX +# we prefer to use cocoa drivers rather than SDL drivers +# if you really want SDL drivers, you can always modify Makefile.config +WITH_COCOA:=1 +WITH_SDL:= +endif + + # workaround # cygwin have problems with libpng, so we will just disable it for now until the problem is solved ifdef CYGWIN diff --git a/os/macosx/Makefile b/os/macosx/Makefile index 577bf420a..3ea875843 100644 --- a/os/macosx/Makefile +++ b/os/macosx/Makefile @@ -45,6 +45,7 @@ $(BUILD_OSX_BUNDLE): $(TTD) $(FAT_BINARY) $(Q)cp os/macosx/openttd.icns "$(OSXAPP)"/Contents/Resources/openttd.icns $(Q)os/macosx/plistgen.sh "$(OSXAPP)" "$(REV)" $(Q)cp data/* "$(OSXAPP)"/Contents/Data/ + $(Q)cp os/macosx/splash.png "$(OSXAPP)"/Contents/Data/ $(Q)cp lang/*.lng "$(OSXAPP)"/Contents/Lang/ $(Q)cp $(TTD) "$(OSXAPP)"/Contents/MacOS/$(TTD) $(COPY_x86_SDL_LIB) diff --git a/os/macosx/macos.m b/os/macosx/macos.m index 936aaa7a0..6c5fa8791 100644 --- a/os/macosx/macos.m +++ b/os/macosx/macos.m @@ -15,12 +15,12 @@ void ShowMacDialog ( const char *title, const char *message, const char *buttonL void ShowMacAssertDialog ( const char *function, const char *file, const int line, const char *expression ) { - const char *buffer = + const char *buffer = [[NSString stringWithFormat:@"An assertion has failed and OpenTTD must quit.\n%s in %s (line %d)\n\"%s\"\n\nYou should report this error the OpenTTD developers if you think you found a bug.", function, file, line, expression] cStringUsingEncoding:NSASCIIStringEncoding]; NSLog(@"%s", buffer); ShowMacDialog( "Assertion Failed", buffer, "Quit" ); - + // abort so that a debugger has a chance to notice abort(); } diff --git a/os/macosx/splash.c b/os/macosx/splash.c new file mode 100644 index 000000000..99791cc21 --- /dev/null +++ b/os/macosx/splash.c @@ -0,0 +1,145 @@ + +#include "../../stdafx.h" +#include "../../openttd.h" +#include "../../variables.h" +#include "../../macros.h" +#include "../../debug.h" +#include "../../functions.h" +#include "../../gfx.h" +#include "../../fileio.h" + +#include "splash.h" + +#ifdef WITH_PNG + +#include <png.h> + +static void PNGAPI png_my_error(png_structp png_ptr, png_const_charp message) +{ + DEBUG(misc, 0) ("ERROR(libpng): %s - %s", message, (char *)png_get_error_ptr(png_ptr)); + longjmp(png_ptr->jmpbuf, 1); +} + +static void PNGAPI png_my_warning(png_structp png_ptr, png_const_charp message) +{ + DEBUG(misc, 0) ("WARNING(libpng): %s - %s", message, (char *)png_get_error_ptr(png_ptr)); +} + +void DisplaySplashImage(void) +{ + png_byte header[8]; + FILE *f; + png_structp png_ptr; + png_infop info_ptr, end_info; + uint width, height, bit_depth, color_type; + png_colorp palette; + int num_palette; + png_bytep *row_pointers; + uint8 *src, *dst; + uint y; + uint xoff, yoff; + int i; + + f = FioFOpenFile(SPLASH_IMAGE_FILE); + if (f == NULL) return; + + fread(header, 1, 8, f); + if (png_sig_cmp(header, 0, 8) != 0) { + fclose(f); + return; + } + + png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, (png_voidp) NULL, png_my_error, png_my_warning); + + if (png_ptr == NULL) { + fclose(f); + return; + } + + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) { + png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); + fclose(f); + return; + } + + end_info = png_create_info_struct(png_ptr); + if (end_info == NULL) { + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); + fclose(f); + return; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + fclose(f); + return; + } + + png_init_io(png_ptr, f); + png_set_sig_bytes(png_ptr, 8); + + png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); + + width = png_get_image_width(png_ptr, info_ptr); + height = png_get_image_height(png_ptr, info_ptr); + bit_depth = png_get_bit_depth(png_ptr, info_ptr); + color_type = png_get_color_type(png_ptr, info_ptr); + + if(color_type != PNG_COLOR_TYPE_PALETTE || bit_depth != 8) { + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + fclose(f); + return; + } + + if(!png_get_valid(png_ptr, info_ptr, PNG_INFO_PLTE)) { + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + fclose(f); + return; + } + + png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette); + + row_pointers = png_get_rows(png_ptr, info_ptr); + + memset(_screen.dst_ptr, 0xff, _screen.pitch * _screen.height); + + if(width > (uint) _screen.width) + width = _screen.width; + if(height > (uint) _screen.height) + height = _screen.height; + + xoff = (_screen.width - width) / 2; + yoff = (_screen.height - height) / 2; + for(y = 0; y < height; y++) { + src = row_pointers[y]; + dst = ((uint8 *) _screen.dst_ptr) + (yoff + y) * _screen.pitch + xoff; + + memcpy(dst, src, width); + } + + for (i = 0; i < num_palette; i++) { + _cur_palette[i].r = palette[i].red; + _cur_palette[i].g = palette[i].green; + _cur_palette[i].b = palette[i].blue; + } + + _cur_palette[0xff].r = 0; + _cur_palette[0xff].g = 0; + _cur_palette[0xff].b = 0; + + _pal_first_dirty = 0; + _pal_last_dirty = 0xff; + + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + fclose(f); + return; +} + + + +#else // WITH_PNG + +void DisplaySplashImage(void) {} + +#endif // WITH_PNG diff --git a/os/macosx/splash.h b/os/macosx/splash.h new file mode 100644 index 000000000..7454ccaa0 --- /dev/null +++ b/os/macosx/splash.h @@ -0,0 +1,9 @@ + +#ifndef SPLASH_H +#define SPLASH_H + +#define SPLASH_IMAGE_FILE "splash.png" + +void DisplaySplashImage(void); + +#endif diff --git a/os/macosx/splash.png b/os/macosx/splash.png Binary files differnew file mode 100644 index 000000000..e67d10da8 --- /dev/null +++ b/os/macosx/splash.png diff --git a/sound/cocoa_s.c b/sound/cocoa_s.c new file mode 100644 index 000000000..652944094 --- /dev/null +++ b/sound/cocoa_s.c @@ -0,0 +1,144 @@ +/****************************************************************************************** + * Cocoa sound driver * + * Known things left to do: * + * - Might need to do endian checking for it to work on both ppc and x86 * + ******************************************************************************************/ + +#ifdef WITH_COCOA + +#include <AudioUnit/AudioUnit.h> + +/* Name conflict */ +#define Rect OTTDRect +#define Point OTTDPoint +#define WindowClass OTTDWindowClass +/* Defined in stdbool.h */ +#ifndef __cplusplus +# ifndef __BEOS__ +# undef bool +# undef false +# undef true +# endif +#endif + +#include "../stdafx.h" +#include "../openttd.h" +#include "../debug.h" +#include "../driver.h" +#include "../mixer.h" +#include "../sdl.h" + +#include "cocoa_s.h" + +#undef WindowClass +#undef Point +#undef Rect + + +static AudioUnit _outputAudioUnit; + +/* The CoreAudio callback */ +static OSStatus audioCallback (void *inRefCon, AudioUnitRenderActionFlags inActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, AudioBuffer *ioData) +{ + MxMixSamples(_mixer, ioData->mData, ioData->mDataByteSize / 4); + + return noErr; +} + + +static const char *CocoaSoundStart(const char * const *parm) +{ + Component comp; + ComponentDescription desc; + struct AudioUnitInputCallback callback; + AudioStreamBasicDescription requestedDesc; + + DEBUG(driver, 1)("cocoa_s: CocoaSoundStart"); + + /* Setup a AudioStreamBasicDescription with the requested format */ + requestedDesc.mFormatID = kAudioFormatLinearPCM; + requestedDesc.mFormatFlags = kLinearPCMFormatFlagIsPacked; + requestedDesc.mChannelsPerFrame = 2; + requestedDesc.mSampleRate = GetDriverParamInt(parm, "hz", 11025); + + requestedDesc.mBitsPerChannel = 16; + requestedDesc.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; +#if 1 // Big endian? + requestedDesc.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian; +#endif + + requestedDesc.mFramesPerPacket = 1; + requestedDesc.mBytesPerFrame = requestedDesc.mBitsPerChannel * requestedDesc.mChannelsPerFrame / 8; + requestedDesc.mBytesPerPacket = requestedDesc.mBytesPerFrame * requestedDesc.mFramesPerPacket; + + + /* Locate the default output audio unit */ + desc.componentType = kAudioUnitComponentType; + desc.componentSubType = kAudioUnitSubType_Output; + desc.componentManufacturer = kAudioUnitID_DefaultOutput; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + comp = FindNextComponent (NULL, &desc); + if (comp == NULL) + return "cocoa_s: Failed to start CoreAudio: FindNextComponent returned NULL"; + + /* Open & initialize the default output audio unit */ + if(OpenAComponent(comp, &_outputAudioUnit) != noErr) + return "cocoa_s: Failed to start CoreAudio: OpenAComponent"; + + if(AudioUnitInitialize(_outputAudioUnit) != noErr) + return "cocoa_s: Failed to start CoreAudio: AudioUnitInitialize"; + + /* Set the input format of the audio unit. */ + if(AudioUnitSetProperty(_outputAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &requestedDesc, sizeof (requestedDesc)) != noErr) + return "cocoa_s: Failed to start CoreAudio: AudioUnitSetProperty (kAudioUnitProperty_StreamFormat)"; + + /* Set the audio callback */ + callback.inputProc = audioCallback; + callback.inputProcRefCon = NULL; + if(AudioUnitSetProperty(_outputAudioUnit, kAudioUnitProperty_SetInputCallback, kAudioUnitScope_Input, 0, &callback, sizeof(callback)) != noErr) + return "cocoa_s: Failed to start CoreAudio: AudioUnitSetProperty (kAudioUnitProperty_SetInputCallback)"; + + /* Finally, start processing of the audio unit */ + if(AudioOutputUnitStart (_outputAudioUnit) != noErr) + return "cocoa_s: Failed to start CoreAudio: AudioOutputUnitStart"; + + /* We're running! */ + return NULL; +} + + +static void CocoaSoundStop(void) +{ + struct AudioUnitInputCallback callback; + + DEBUG(driver, 1)("cocoa_s: CocoaSoundStop"); + + /* stop processing the audio unit */ + if(AudioOutputUnitStop(_outputAudioUnit) != noErr) { + DEBUG(driver, 1)("cocoa_s: Core_CloseAudio: AudioOutputUnitStop failed"); + return; + } + + /* Remove the input callback */ + callback.inputProc = 0; + callback.inputProcRefCon = 0; + if(AudioUnitSetProperty(_outputAudioUnit, kAudioUnitProperty_SetInputCallback, kAudioUnitScope_Input, 0, &callback, sizeof(callback)) != noErr) { + DEBUG(driver, 1)("cocoa_s: Core_CloseAudio: AudioUnitSetProperty (kAudioUnitProperty_SetInputCallback) failed"); + return; + } + + if (CloseComponent(_outputAudioUnit) != noErr) { + DEBUG(driver, 1)("cocoa_s: Core_CloseAudio: CloseComponent failed"); + return; + } +} + + +const HalSoundDriver _cocoa_sound_driver = { + CocoaSoundStart, + CocoaSoundStop, +}; + +#endif /* WITH_COCOA */ diff --git a/sound/cocoa_s.h b/sound/cocoa_s.h new file mode 100644 index 000000000..3d966165b --- /dev/null +++ b/sound/cocoa_s.h @@ -0,0 +1,9 @@ + +#ifndef SOUND_COCOA_H +#define SOUND_COCOA_H + +#include "../hal.h" + +const HalSoundDriver _cocoa_sound_driver; + +#endif @@ -444,8 +444,26 @@ void ShowOSErrorBox(const char *buf) #endif } +#ifdef WITH_COCOA +void cocoaSetWorkingDirectory(void); +void cocoaSetupAutoreleasePool(void); +void cocoaReleaseAutoreleasePool(void); +#endif + int CDECL main(int argc, char* argv[]) { + int ret; + +#ifdef WITH_COCOA + cocoaSetupAutoreleasePool(); + /* This is passed if we are launched by double-clicking */ + if(argc >= 2 && strncmp (argv[1], "-psn", 4) == 0) { + argv[1] = NULL; + argc = 1; + cocoaSetWorkingDirectory(); + } +#endif + // change the working directory to enable doubleclicking in UIs #if defined(__BEOS__) || defined(__linux__) ChangeWorkingDirectory(argv[0]); @@ -456,7 +474,13 @@ int CDECL main(int argc, char* argv[]) signal(SIGPIPE, SIG_IGN); - return ttd_main(argc, argv); + ret = ttd_main(argc, argv); + +#ifdef WITH_COCOA + cocoaReleaseAutoreleasePool(); +#endif + + return ret; } void DeterminePaths(void) diff --git a/video/cocoa_v.h b/video/cocoa_v.h new file mode 100644 index 000000000..2d32e0a57 --- /dev/null +++ b/video/cocoa_v.h @@ -0,0 +1,130 @@ + +#ifndef VIDEO_COCOA_H +#define VIDEO_COCOA_H + +#include "../hal.h" + +extern const HalVideoDriver _cocoa_video_driver; + +/* From SDL_QuartzKeys.h */ +/* These are the Macintosh key scancode constants -- from Inside Macintosh */ + +#define QZ_ESCAPE 0x35 +#define QZ_F1 0x7A +#define QZ_F2 0x78 +#define QZ_F3 0x63 +#define QZ_F4 0x76 +#define QZ_F5 0x60 +#define QZ_F6 0x61 +#define QZ_F7 0x62 +#define QZ_F8 0x64 +#define QZ_F9 0x65 +#define QZ_F10 0x6D +#define QZ_F11 0x67 +#define QZ_F12 0x6F +#define QZ_PRINT 0x69 +#define QZ_SCROLLOCK 0x6B +#define QZ_PAUSE 0x71 +#define QZ_POWER 0x7F +#define QZ_BACKQUOTE 0x32 +#define QZ_1 0x12 +#define QZ_2 0x13 +#define QZ_3 0x14 +#define QZ_4 0x15 +#define QZ_5 0x17 +#define QZ_6 0x16 +#define QZ_7 0x1A +#define QZ_8 0x1C +#define QZ_9 0x19 +#define QZ_0 0x1D +#define QZ_MINUS 0x1B +#define QZ_EQUALS 0x18 +#define QZ_BACKSPACE 0x33 +#define QZ_INSERT 0x72 +#define QZ_HOME 0x73 +#define QZ_PAGEUP 0x74 +#define QZ_NUMLOCK 0x47 +#define QZ_KP_EQUALS 0x51 +#define QZ_KP_DIVIDE 0x4B +#define QZ_KP_MULTIPLY 0x43 +#define QZ_TAB 0x30 +#define QZ_q 0x0C +#define QZ_w 0x0D +#define QZ_e 0x0E +#define QZ_r 0x0F +#define QZ_t 0x11 +#define QZ_y 0x10 +#define QZ_u 0x20 +#define QZ_i 0x22 +#define QZ_o 0x1F +#define QZ_p 0x23 +#define QZ_LEFTBRACKET 0x21 +#define QZ_RIGHTBRACKET 0x1E +#define QZ_BACKSLASH 0x2A +#define QZ_DELETE 0x75 +#define QZ_END 0x77 +#define QZ_PAGEDOWN 0x79 +#define QZ_KP7 0x59 +#define QZ_KP8 0x5B +#define QZ_KP9 0x5C +#define QZ_KP_MINUS 0x4E +#define QZ_CAPSLOCK 0x39 +#define QZ_a 0x00 +#define QZ_s 0x01 +#define QZ_d 0x02 +#define QZ_f 0x03 +#define QZ_g 0x05 +#define QZ_h 0x04 +#define QZ_j 0x26 +#define QZ_k 0x28 +#define QZ_l 0x25 +#define QZ_SEMICOLON 0x29 +#define QZ_QUOTE 0x27 +#define QZ_RETURN 0x24 +#define QZ_KP4 0x56 +#define QZ_KP5 0x57 +#define QZ_KP6 0x58 +#define QZ_KP_PLUS 0x45 +#define QZ_LSHIFT 0x38 +#define QZ_z 0x06 +#define QZ_x 0x07 +#define QZ_c 0x08 +#define QZ_v 0x09 +#define QZ_b 0x0B +#define QZ_n 0x2D +#define QZ_m 0x2E +#define QZ_COMMA 0x2B +#define QZ_PERIOD 0x2F +#define QZ_SLASH 0x2C +#if 1 /* Panther now defines right side keys */ +#define QZ_RSHIFT 0x3C +#endif +#define QZ_UP 0x7E +#define QZ_KP1 0x53 +#define QZ_KP2 0x54 +#define QZ_KP3 0x55 +#define QZ_KP_ENTER 0x4C +#define QZ_LCTRL 0x3B +#define QZ_LALT 0x3A +#define QZ_LMETA 0x37 +#define QZ_SPACE 0x31 +#if 1 /* Panther now defines right side keys */ +#define QZ_RMETA 0x36 +#define QZ_RALT 0x3D +#define QZ_RCTRL 0x3E +#endif +#define QZ_LEFT 0x7B +#define QZ_DOWN 0x7D +#define QZ_RIGHT 0x7C +#define QZ_KP0 0x52 +#define QZ_KP_PERIOD 0x41 + +/* Wierd, these keys are on my iBook under MacOS X */ +#define QZ_IBOOK_ENTER 0x34 +#define QZ_IBOOK_LEFT 0x3B +#define QZ_IBOOK_RIGHT 0x3C +#define QZ_IBOOK_DOWN 0x3D +#define QZ_IBOOK_UP 0x3E + + +#endif diff --git a/video/cocoa_v.m b/video/cocoa_v.m new file mode 100644 index 000000000..2dbc2c478 --- /dev/null +++ b/video/cocoa_v.m @@ -0,0 +1,2113 @@ +/****************************************************************************************** + * Cocoa video driver * + * Known things left to do: * + * Nothing at the moment. * + ******************************************************************************************/ + +#ifdef WITH_COCOA + +#import <Cocoa/Cocoa.h> +#import <sys/time.h> /* gettimeofday */ +#import <sys/param.h> /* for MAXPATHLEN */ +#import <unistd.h> + +/* Portions of CPS.h */ +typedef struct CPSProcessSerNum +{ + UInt32 lo; + UInt32 hi; +} CPSProcessSerNum; + +extern OSErr CPSGetCurrentProcess( CPSProcessSerNum *psn); +extern OSErr CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5); +extern OSErr CPSSetFrontProcess( CPSProcessSerNum *psn); + +/* From Menus.h (according to Xcode Developer Documentation) */ +extern void ShowMenuBar(void); +extern void HideMenuBar(void); + +/* Disables a warning. This is needed since the method exists but has been dropped from the header, supposedly as of 10.4. */ +@interface NSApplication(NSAppleMenu) +- (void)setAppleMenu:(NSMenu *)menu; +@end + + +/* Name conflict */ +#define Rect OTTDRect +#define Point OTTDPoint +/* Defined in ppc/param.h or i386/param.h included from sys/param.h */ +#undef ALIGN +/* Defined in stdbool.h */ +#ifndef __cplusplus +# ifndef __BEOS__ +# undef bool +# undef false +# undef true +# endif +#endif + +#include "../stdafx.h" +#include "../openttd.h" +#include "../debug.h" +#include "../functions.h" +#include "../gfx.h" +#include "../macros.h" +#include "../sdl.h" +#include "../window.h" +#include "../network.h" +#include "../variables.h" +#include "../os/macosx/splash.h" + +#include "cocoa_v.h" + +#undef Point +#undef Rect + + +/* Subclass of NSWindow to fix genie effect and support resize events */ +@interface OTTD_QuartzWindow : NSWindow +- (void)miniaturize:(id)sender; +- (void)display; +- (void)setFrame:(NSRect)frameRect display:(BOOL)flag; +- (void)appDidHide:(NSNotification*)note; +- (void)appWillUnhide:(NSNotification*)note; +- (void)appDidUnhide:(NSNotification*)note; +- (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag; +@end + +/* Delegate for our NSWindow to send ask for quit on close */ +@interface OTTD_QuartzWindowDelegate : NSObject +- (BOOL)windowShouldClose:(id)sender; +@end + +@interface OTTDMain : NSObject +@end + + +/* + Structure for rez switch gamma fades + We can hide the monitor flicker by setting the gamma tables to 0 +*/ +#define QZ_GAMMA_TABLE_SIZE 256 + +typedef struct { + CGGammaValue red[QZ_GAMMA_TABLE_SIZE]; + CGGammaValue green[QZ_GAMMA_TABLE_SIZE]; + CGGammaValue blue[QZ_GAMMA_TABLE_SIZE]; +} OTTD_QuartzGammaTable; + +/* + Add methods to get at private members of NSScreen. + Since there is a bug in Apple's screen switching code + that does not update this variable when switching + to fullscreen, we'll set it manually (but only for the + main screen). +*/ +@interface NSScreen (NSScreenAccess) + - (void) setFrame:(NSRect)frame; +@end + +@implementation NSScreen (NSScreenAccess) +- (void) setFrame:(NSRect)frame; +{ + _frame = frame; +} +@end + + +static void QZ_Draw(void); +static void QZ_UnsetVideoMode (void); +static void QZ_UpdatePalette(uint start, uint count); +static void QZ_WarpCursor (int x, int y); +void QZ_ShowMouse (void); +void QZ_HideMouse (void); +static void CocoaVideoFullScreen(bool full_screen); + + +static NSAutoreleasePool *_ottd_autorelease_pool; +static OTTDMain *_ottd_main; + + +static struct CocoaVideoData { + bool isset; + bool issetting; + + CGDirectDisplayID display_id; /* 0 == main display (only support single display) */ + CFDictionaryRef mode; /* current mode of the display */ + CFDictionaryRef save_mode; /* original mode of the display */ + CFArrayRef mode_list; /* list of available fullscreen modes */ + CGDirectPaletteRef palette; /* palette of an 8-bit display */ + + uint32 device_width; + uint32 device_height; + uint32 device_bpp; + + void *realpixels; + uint8 *pixels; + uint32 width; + uint32 height; + uint32 pitch; + bool fullscreen; + + unsigned int current_mods; + bool tab_is_down; + bool emulating_right_button; + + bool cursor_visible; + bool active; + +#ifdef _DEBUG + uint32 tEvent; +#endif + + OTTD_QuartzWindow *window; + NSQuickDrawView *qdview; + +#define MAX_DIRTY_RECTS 100 + OTTDRect dirty_rects[MAX_DIRTY_RECTS]; + int num_dirty_rects; + + uint16 palette16[256]; + uint32 palette32[256]; +} _cocoa_video_data; + + + + + + +/****************************************************************************************** + * Game loop and accessories * + ******************************************************************************************/ + +static uint32 GetTick(void) +{ + struct timeval tim; + + gettimeofday(&tim, NULL); + return tim.tv_usec / 1000 + tim.tv_sec * 1000; +} + +static void QZ_CheckPaletteAnim(void) +{ + if (_pal_last_dirty != -1) { + QZ_UpdatePalette(_pal_first_dirty, _pal_last_dirty - _pal_first_dirty + 1); + _pal_last_dirty = -1; + } +} + + + +extern void DoExitSave(void); + +static void QZ_AskQuit(void) +{ + if (_game_mode == GM_MENU) { // do not ask to quit on the main screen + _exit_game = true; + } else if (_patches.autosave_on_exit) { + DoExitSave(); + _exit_game = true; + } else + AskExitGame(); +} + + + +typedef struct VkMapping { + unsigned short vk_from; + byte map_to; +} VkMapping; + +#define AS(x, z) {x, z} + +static const VkMapping _vk_mapping[] = { + AS(10, WKC_BACKQUOTE), // key left of '1' + // Pageup stuff + up/down + //AM(SDLK_PAGEUP, SDLK_PAGEDOWN, WKC_PAGEUP, WKC_PAGEDOWN), <==== Does this include HOME/END? + AS(QZ_PAGEUP, WKC_PAGEUP), + AS(QZ_PAGEDOWN, WKC_PAGEDOWN), + + AS(QZ_UP, WKC_UP), + AS(QZ_DOWN, WKC_DOWN), + AS(QZ_LEFT, WKC_LEFT), + AS(QZ_RIGHT, WKC_RIGHT), + + AS(QZ_HOME, WKC_HOME), + AS(QZ_END, WKC_END), + + AS(QZ_INSERT, WKC_INSERT), + AS(QZ_DELETE, WKC_DELETE), + + // Letters. QZ_[a-z] is not in numerical order so we can't use AM(...) + AS(QZ_a, 'A'), + AS(QZ_b, 'B'), + AS(QZ_c, 'C'), + AS(QZ_d, 'D'), + AS(QZ_e, 'E'), + AS(QZ_f, 'F'), + AS(QZ_g, 'G'), + AS(QZ_h, 'H'), + AS(QZ_i, 'I'), + AS(QZ_j, 'J'), + AS(QZ_k, 'K'), + AS(QZ_l, 'L'), + AS(QZ_m, 'M'), + AS(QZ_n, 'N'), + AS(QZ_o, 'O'), + AS(QZ_p, 'P'), + AS(QZ_q, 'Q'), + AS(QZ_r, 'R'), + AS(QZ_s, 'S'), + AS(QZ_t, 'T'), + AS(QZ_u, 'U'), + AS(QZ_v, 'V'), + AS(QZ_w, 'W'), + AS(QZ_x, 'X'), + AS(QZ_y, 'Y'), + AS(QZ_z, 'Z'), + // Same thing for digits + AS(QZ_0, '0'), + AS(QZ_1, '1'), + AS(QZ_2, '2'), + AS(QZ_3, '3'), + AS(QZ_4, '4'), + AS(QZ_5, '5'), + AS(QZ_6, '6'), + AS(QZ_7, '7'), + AS(QZ_8, '8'), + AS(QZ_9, '9'), + + AS(QZ_ESCAPE, WKC_ESC), + AS(QZ_PAUSE, WKC_PAUSE), + AS(QZ_BACKSPACE, WKC_BACKSPACE), + + AS(QZ_SPACE, WKC_SPACE), + AS(QZ_RETURN, WKC_RETURN), + AS(QZ_TAB, WKC_TAB), + + // Function keys + AS(QZ_F1, WKC_F1), + AS(QZ_F2, WKC_F2), + AS(QZ_F3, WKC_F3), + AS(QZ_F4, WKC_F4), + AS(QZ_F5, WKC_F5), + AS(QZ_F6, WKC_F6), + AS(QZ_F7, WKC_F7), + AS(QZ_F8, WKC_F8), + AS(QZ_F9, WKC_F9), + AS(QZ_F10, WKC_F10), + AS(QZ_F11, WKC_F11), + AS(QZ_F12, WKC_F12), + + // Numeric part. + AS(QZ_KP0, WKC_NUM_0), + AS(QZ_KP1, WKC_NUM_1), + AS(QZ_KP2, WKC_NUM_2), + AS(QZ_KP3, WKC_NUM_3), + AS(QZ_KP4, WKC_NUM_4), + AS(QZ_KP5, WKC_NUM_5), + AS(QZ_KP6, WKC_NUM_6), + AS(QZ_KP7, WKC_NUM_7), + AS(QZ_KP8, WKC_NUM_8), + AS(QZ_KP9, WKC_NUM_9), + AS(QZ_KP_DIVIDE, WKC_NUM_DIV), + AS(QZ_KP_MULTIPLY, WKC_NUM_MUL), + AS(QZ_KP_MINUS, WKC_NUM_MINUS), + AS(QZ_KP_PLUS, WKC_NUM_PLUS), + AS(QZ_KP_ENTER, WKC_NUM_ENTER), + AS(QZ_KP_PERIOD, WKC_NUM_DECIMAL) +}; + + +static uint32 QZ_MapKey(unsigned short sym) +{ + const VkMapping *map; + uint32 key = 0; + + for (map = _vk_mapping; map != endof(_vk_mapping); ++map) { + if(sym == map->vk_from) { + key = map->map_to; + break; + } + } + + if(_cocoa_video_data.current_mods & NSShiftKeyMask) + key|= WKC_SHIFT; + if(_cocoa_video_data.current_mods & NSControlKeyMask) + key|= WKC_CTRL; + if(_cocoa_video_data.current_mods & NSAlternateKeyMask) + key|= WKC_ALT; + if(_cocoa_video_data.current_mods & NSCommandKeyMask) + key|= WKC_META; + + return key << 16; +} + +static void QZ_KeyEvent(unsigned short keycode, unsigned short unicode, BOOL down) +{ + switch(keycode) + { + case QZ_UP: + SB(_dirkeys, 1, 1, down); + break; + case QZ_DOWN: + SB(_dirkeys, 3, 1, down); + break; + case QZ_LEFT: + SB(_dirkeys, 0, 1, down); + break; + case QZ_RIGHT: + SB(_dirkeys, 2, 1, down); + break; + case QZ_TAB: + _cocoa_video_data.tab_is_down = down; + break; + case QZ_RETURN: + case QZ_f: + if(down && ((_cocoa_video_data.current_mods & NSControlKeyMask) || (_cocoa_video_data.current_mods & NSCommandKeyMask))) + CocoaVideoFullScreen(!_fullscreen); + + } + + if(down) { + _pressed_key = QZ_MapKey(keycode) | unicode; + DEBUG(driver, 2)("cocoa_v: QZ_KeyEvent: %x (%x), down, mapping: %x", keycode, unicode, _pressed_key); + } else { + DEBUG(driver, 2)("cocoa_v: QZ_KeyEvent: %x (%x), up", keycode, unicode); + } +} + +static void QZ_DoUnsidedModifiers (unsigned int newMods) { + + const int mapping[] = { QZ_CAPSLOCK, QZ_LSHIFT, QZ_LCTRL, QZ_LALT, QZ_LMETA }; + + int i; + int bit; + + if (_cocoa_video_data.current_mods == newMods) + return; + + /* Iterate through the bits, testing each against the current modifiers */ + for (i = 0, bit = NSAlphaShiftKeyMask; bit <= NSCommandKeyMask; bit <<= 1, ++i) { + unsigned int currentMask, newMask; + + currentMask = _cocoa_video_data.current_mods & bit; + newMask = newMods & bit; + + if ( currentMask && currentMask != newMask ) { /* modifier up event */ + /* If this was Caps Lock, we need some additional voodoo to make SDL happy (is this needed in ottd?) */ + if (bit == NSAlphaShiftKeyMask) + QZ_KeyEvent(mapping[i], 0, YES); + QZ_KeyEvent(mapping[i], 0, NO); + } + else if ( newMask && currentMask != newMask ) { /* modifier down event */ + QZ_KeyEvent(mapping[i], 0, YES); + /* If this was Caps Lock, we need some additional voodoo to make SDL happy (is this needed in ottd?) */ + if (bit == NSAlphaShiftKeyMask) + QZ_KeyEvent(mapping[i], 0, NO); + } + } + + _cocoa_video_data.current_mods = newMods; +} + +static void QZ_MouseMovedEvent(int x, int y) +{ + int dx, dy; + + if (_cursor.fix_at) { + dx = x - _cursor.pos.x; + dy = y - _cursor.pos.y; + if (dx != 0 || dy != 0) { + _cursor.delta.x += dx; + _cursor.delta.y += dy; + + QZ_WarpCursor(_cursor.pos.x, _cursor.pos.y); + } + } else { + _cursor.delta.x = x - _cursor.pos.x; + _cursor.delta.y = y - _cursor.pos.y; + _cursor.pos.x = x; + _cursor.pos.y = y; + _cursor.dirty = true; + } +} + +void QZ_MouseButtonEvent( int button, BOOL down ) +{ + switch(button) { + case 0: + if(down) { + _left_button_down = true; + } else { + _left_button_down = false; + _left_button_clicked = false; + } + break; + case 1: + if(down) { + _right_button_down = true; + _right_button_clicked = true; + } else { + _right_button_down = false; + } + break; + } +} + + +static inline NSPoint QZ_GetMouseLocation(NSEvent *event) +{ + NSPoint pt; + + if(_cocoa_video_data.fullscreen) { + pt = [ NSEvent mouseLocation ]; + pt.y = _cocoa_video_data.height - pt.y; + } else { + pt = [event locationInWindow]; + pt = [_cocoa_video_data.qdview convertPoint:pt fromView:nil]; + } + + return pt; +} + +static bool QZ_MouseIsInsideView(NSPoint *pt) +{ + if(_cocoa_video_data.fullscreen) + return pt->x >= 0 && pt->y >= 0 && pt->x < _cocoa_video_data.width && pt->y < _cocoa_video_data.height; + else + return [ _cocoa_video_data.qdview mouse:*pt inRect:[ _cocoa_video_data.qdview bounds ] ]; +} + + +static bool QZ_PollEvent(void) +{ + NSEvent *event; + NSPoint pt; + NSString *chars; +#ifdef _DEBUG + uint32 et0, et; +#endif + +#ifdef _DEBUG + et0 = GetTick(); +#endif + event = [ NSApp nextEventMatchingMask:NSAnyEventMask + untilDate: [ NSDate distantPast ] + inMode: NSDefaultRunLoopMode dequeue:YES ]; +#ifdef _DEBUG + et = GetTick(); + _cocoa_video_data.tEvent+= et - et0; +#endif + + if(event == nil) + return false; + if(!_cocoa_video_data.active) { + QZ_ShowMouse(); + [NSApp sendEvent:event]; + return true; + } + + QZ_DoUnsidedModifiers( [ event modifierFlags ] ); + + switch ([event type]) { + case NSMouseMoved: + case NSOtherMouseDragged: + case NSRightMouseDragged: + case NSLeftMouseDragged: + pt = QZ_GetMouseLocation(event); + if(!QZ_MouseIsInsideView(&pt) && !_cocoa_video_data.emulating_right_button) { + QZ_ShowMouse(); + [NSApp sendEvent:event]; + break; + } + + QZ_HideMouse(); + QZ_MouseMovedEvent((int) pt.x, (int) pt.y); + break; + + case NSLeftMouseDown: + if(!([ event modifierFlags ] & NSCommandKeyMask) || !QZ_MouseIsInsideView(&pt)) + [NSApp sendEvent:event]; + + pt = QZ_GetMouseLocation(event); + if(!QZ_MouseIsInsideView(&pt)) { + QZ_ShowMouse(); + break; + } + + QZ_HideMouse(); + QZ_MouseMovedEvent((int) pt.x, (int) pt.y); + + /* Right mouse button emulation */ + if([ event modifierFlags ] & NSCommandKeyMask) { + _cocoa_video_data.emulating_right_button = true; + QZ_MouseButtonEvent(1, YES); + } else + QZ_MouseButtonEvent(0, YES); + break; + case NSLeftMouseUp: + [NSApp sendEvent:event]; + + pt = QZ_GetMouseLocation(event); + if(!QZ_MouseIsInsideView(&pt)) { + QZ_ShowMouse(); + break; + } + + QZ_HideMouse(); + QZ_MouseMovedEvent((int) pt.x, (int) pt.y); + + /* Right mouse button emulation */ + if(_cocoa_video_data.emulating_right_button) { + _cocoa_video_data.emulating_right_button = false; + QZ_MouseButtonEvent(1, NO); + } else + QZ_MouseButtonEvent(0, NO); + break; + + case NSRightMouseDown: + pt = QZ_GetMouseLocation(event); + if(!QZ_MouseIsInsideView(&pt)) { + QZ_ShowMouse(); + [NSApp sendEvent:event]; + break; + } + + QZ_HideMouse(); + QZ_MouseMovedEvent((int) pt.x, (int) pt.y); + QZ_MouseButtonEvent(1, YES); + break; + case NSRightMouseUp: + pt = QZ_GetMouseLocation(event); + if(!QZ_MouseIsInsideView(&pt)) { + QZ_ShowMouse(); + [NSApp sendEvent:event]; + break; + } + + QZ_HideMouse(); + QZ_MouseMovedEvent((int) pt.x, (int) pt.y); + QZ_MouseButtonEvent(1, NO); + break; + + /* This is not needed since openttd currently only use two buttons */ + /* + case NSOtherMouseDown: + pt = QZ_GetMouseLocation(event); + if(!QZ_MouseIsInsideView(&pt)) { + QZ_ShowMouse(); + [NSApp sendEvent:event]; + break; + } + + QZ_HideMouse(); + QZ_MouseMovedEvent((int) pt.x, (int) pt.y); + QZ_MouseButtonEvent([ event buttonNumber ], YES); + break; + case NSOtherMouseUp: + pt = QZ_GetMouseLocation(event); + if(!QZ_MouseIsInsideView(&pt)) { + QZ_ShowMouse(); + [NSApp sendEvent:event]; + break; + } + + QZ_HideMouse(); + QZ_MouseMovedEvent((int) pt.x, (int) pt.y); + QZ_MouseButtonEvent([ event buttonNumber ], NO); + break; + */ + case NSKeyDown: + /* Quit, hide and minimize */ + switch([event keyCode]) { + case QZ_q: + case QZ_h: + case QZ_m: + if([ event modifierFlags ] & NSCommandKeyMask) + [NSApp sendEvent:event]; + break; + } + + chars = [ event characters ]; + QZ_KeyEvent([event keyCode], [ chars length ] ? [ chars characterAtIndex:0 ] : 0, YES); + break; + + case NSKeyUp: + /* Quit, hide and minimize */ + switch([event keyCode]) { + case QZ_q: + case QZ_h: + case QZ_m: + if([ event modifierFlags ] & NSCommandKeyMask) + [NSApp sendEvent:event]; + break; + } + + chars = [ event characters ]; + QZ_KeyEvent([event keyCode], [ chars length ] ? [ chars characterAtIndex:0 ] : 0, NO); + break; + + case NSScrollWheel: + if ( [ event deltaX ] > 0.0 || [ event deltaY ] > 0.0 ) /* Scroll up */ + _cursor.wheel--; + else /* Scroll down */ + _cursor.wheel++; + break; + + default: + [NSApp sendEvent:event]; + } + + return true; +} + + + + +static void QZ_GameLoop(void) +{ + uint32 next_tick = GetTick() + 30; + uint32 cur_ticks; + uint32 pal_tick = 0; +#ifdef _DEBUG + uint32 et0, et, st0, st; +#endif + int i; + + DEBUG(driver, 1)("cocoa_v: QZ_GameLoop"); + +#ifdef _DEBUG + et0 = GetTick(); + st = 0; +#endif + + _screen.dst_ptr = _cocoa_video_data.pixels; + DisplaySplashImage(); + QZ_CheckPaletteAnim(); + QZ_Draw(); + CSleep(1); + + for(i = 0; i < 2; i++) + GameLoop(); + + _screen.dst_ptr = _cocoa_video_data.pixels; + UpdateWindows(); + QZ_CheckPaletteAnim(); + QZ_Draw(); + CSleep(1); + + while(1) { + InteractiveRandom(); // randomness + + while (QZ_PollEvent()) {} + + if (_exit_game) + break; + +#if defined(_DEBUG) + if(_cocoa_video_data.current_mods & NSShiftKeyMask) +#else + if (_cocoa_video_data.tab_is_down) +#endif + { + if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2; + } else if (_fast_forward & 2) { + _fast_forward = 0; + } + + cur_ticks = GetTick(); + if ((_fast_forward && !_pause) || cur_ticks > next_tick) + next_tick = cur_ticks; + + if (cur_ticks == next_tick) { + next_tick += 30; + + _ctrl_pressed = !!(_cocoa_video_data.current_mods & NSControlKeyMask); + _shift_pressed = !!(_cocoa_video_data.current_mods & NSShiftKeyMask); +#ifdef _DEBUG + _dbg_screen_rect = !!(_cocoa_video_data.current_mods & NSAlphaShiftKeyMask); +#endif + + GameLoop(); + + _screen.dst_ptr = _cocoa_video_data.pixels; + UpdateWindows(); + if (++pal_tick > 4) { + QZ_CheckPaletteAnim(); + pal_tick = 1; + } + QZ_Draw(); + } else { +#ifdef _DEBUG + st0 = GetTick(); +#endif + CSleep(1); +#ifdef _DEBUG + st+= GetTick() - st0; +#endif + _screen.dst_ptr = _cocoa_video_data.pixels; + DrawTextMessage(); + DrawMouseCursor(); + QZ_Draw(); + } + } + +#ifdef _DEBUG + et = GetTick(); + + DEBUG(driver, 1)("cocoa_v: nextEventMatchingMask took %i ms total", _cocoa_video_data.tEvent); + DEBUG(driver, 1)("cocoa_v: game loop took %i ms total (%i ms without sleep)", et - et0, (et - et0) - st); + DEBUG(driver, 1)("cocoa_v: (nextEventMatchingMask total)/(game loop total) is %f%%", (double) _cocoa_video_data.tEvent / (double) (et - et0) * 100); + DEBUG(driver, 1)("cocoa_v: (nextEventMatchingMask total)/(game loop without sleep total) is %f%%", (double) _cocoa_video_data.tEvent / (double) ((et - et0) - st) * 100); +#endif +} + + + + + + + + + + +/****************************************************************************************** + * Windowed mode * + ******************************************************************************************/ + +/* + This function makes the *game region* of the window 100% opaque. + The genie effect uses the alpha component. Otherwise, + it doesn't seem to matter what value it has. +*/ +static void QZ_SetPortAlphaOpaque (void) { + if (_cocoa_video_data.device_bpp == 32) { + uint32 *pixels = (uint32*) _cocoa_video_data.realpixels; + uint32 rowPixels = _cocoa_video_data.pitch / 4; + uint32 i, j; + + for (i = 0; i < _cocoa_video_data.height; i++) + for (j = 0; j < _cocoa_video_data.width; j++) { + pixels[ (i * rowPixels) + j ] |= 0xFF000000; + } + } +} + + +@implementation OTTD_QuartzWindow + +/* we override these methods to fix the miniaturize animation/dock icon bug */ +- (void)miniaturize:(id)sender +{ + /* make the alpha channel opaque so anim won't have holes in it */ + QZ_SetPortAlphaOpaque (); + + /* window is hidden now */ + _cocoa_video_data.active = false; + + QZ_ShowMouse(); + + [ super miniaturize:sender ]; +} + +- (void)display +{ + /* + This method fires just before the window deminaturizes from the Dock. + + We'll save the current visible surface, let the window manager redraw any + UI elements, and restore the surface. This way, no expose event + is required, and the deminiaturize works perfectly. + */ + + QZ_SetPortAlphaOpaque (); + + /* save current visible surface */ + [ self cacheImageInRect:[ _cocoa_video_data.qdview frame ] ]; + + /* let the window manager redraw controls, border, etc */ + [ super display ]; + + /* restore visible surface */ + [ self restoreCachedImage ]; + + /* window is visible again */ + _cocoa_video_data.active = true; +} + +- (void)setFrame:(NSRect)frameRect display:(BOOL)flag +{ + NSRect newViewFrame; + CGrafPtr thePort; + + [ super setFrame:frameRect display:flag ]; + + /* Don't do anything if the window is currently beign created */ + if(_cocoa_video_data.issetting) + return; + + if(_cocoa_video_data.window == nil) + return; + + newViewFrame = [ _cocoa_video_data.qdview frame ]; + + /* Update the pixels and pitch */ + thePort = [ _cocoa_video_data.qdview qdPort ]; + LockPortBits ( thePort ); + + _cocoa_video_data.realpixels = GetPixBaseAddr ( GetPortPixMap ( thePort ) ); + _cocoa_video_data.pitch = GetPixRowBytes ( GetPortPixMap ( thePort ) ); + + /* + _cocoa_video_data.realpixels now points to the window's pixels + We want it to point to the *view's* pixels + */ + { + int vOffset = [ _cocoa_video_data.window frame ].size.height - newViewFrame.size.height - newViewFrame.origin.y; + int hOffset = newViewFrame.origin.x; + + _cocoa_video_data.realpixels = (uint8 *) _cocoa_video_data.realpixels + (vOffset * _cocoa_video_data.pitch) + hOffset * (_cocoa_video_data.device_bpp/8); + } + + UnlockPortBits ( thePort ); + + /* Allocate new buffer */ + if(_cocoa_video_data.pixels != NULL) + free(_cocoa_video_data.pixels); + _cocoa_video_data.pixels = (uint8 *) malloc(newViewFrame.size.width * newViewFrame.size.height); + assert(_cocoa_video_data.pixels != NULL); + + + /* Tell the game that the resolution changed */ + _cocoa_video_data.width = newViewFrame.size.width; + _cocoa_video_data.height = newViewFrame.size.height; + + _screen.width = _cocoa_video_data.width; + _screen.height = _cocoa_video_data.height; + _screen.pitch = _cocoa_video_data.width; + + GameSizeChanged(); + + /* Redraw screen */ + _cocoa_video_data.num_dirty_rects = MAX_DIRTY_RECTS; +} + +- (void)appDidHide:(NSNotification*)note +{ + _cocoa_video_data.active = false; +// DEBUG(driver, 1)("cocoa_v: appDidHide"); +} + + +- (void)appWillUnhide:(NSNotification*)note +{ + QZ_SetPortAlphaOpaque (); + + /* save current visible surface */ + [ self cacheImageInRect:[ _cocoa_video_data.qdview frame ] ]; +} + +- (void)appDidUnhide:(NSNotification*)note +{ + /* restore cached image, since it may not be current, post expose event too */ + [ self restoreCachedImage ]; + + _cocoa_video_data.active = true; +// DEBUG(driver, 1)("cocoa_v: appDidUnhide"); +} + + +- (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag +{ + /* Make our window subclass receive these application notifications */ + [ [ NSNotificationCenter defaultCenter ] addObserver:self + selector:@selector(appDidHide:) name:NSApplicationDidHideNotification object:NSApp ]; + + [ [ NSNotificationCenter defaultCenter ] addObserver:self + selector:@selector(appDidUnhide:) name:NSApplicationDidUnhideNotification object:NSApp ]; + + [ [ NSNotificationCenter defaultCenter ] addObserver:self + selector:@selector(appWillUnhide:) name:NSApplicationWillUnhideNotification object:NSApp ]; + + return [ super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag ]; +} + +@end + +@implementation OTTD_QuartzWindowDelegate +- (BOOL)windowShouldClose:(id)sender +{ + QZ_AskQuit(); + + return NO; +} + +- (void)windowDidBecomeKey:(NSNotification *)aNotification +{ + _cocoa_video_data.active = true; +// DEBUG(driver, 1)("cocoa_v: windowDidBecomeKey"); +} + +- (void)windowDidResignKey:(NSNotification *)aNotification +{ + _cocoa_video_data.active = false; +// DEBUG(driver, 1)("cocoa_v: windowDidResignKey"); +} + +- (void)windowDidBecomeMain:(NSNotification *)aNotification +{ + _cocoa_video_data.active = true; +// DEBUG(driver, 1)("cocoa_v: windowDidBecomeMain"); +} + +- (void)windowDidResignMain:(NSNotification *)aNotification +{ + _cocoa_video_data.active = false; +// DEBUG(driver, 1)("cocoa_v: windowDidResignMain"); +} + +@end + + +static void QZ_UpdateWindowPalette(uint start, uint count) +{ + uint i; + uint32 clr32; + uint16 clr16; + + switch(_cocoa_video_data.device_bpp) + { + case 32: + for (i = start; i < start + count; i++) { + clr32 = 0xff000000; + clr32|= ((uint32) _cur_palette[i].r) << 16; + clr32|= ((uint32) _cur_palette[i].g) << 8; + clr32|= (uint32) _cur_palette[i].b; + _cocoa_video_data.palette32[i] = clr32; + } + break; + case 16: + for (i = start; i < start + count; i++) { + clr16 = 0x0000; + clr16|= ((uint16) ((_cur_palette[i].r >> 3) & 0x1f)) << 10; + clr16|= ((uint16) ((_cur_palette[i].g >> 3) & 0x1f)) << 5; + clr16|= (uint16) ((_cur_palette[i].b >> 3) & 0x1f); + _cocoa_video_data.palette16[i] = clr16; + } + break; + } + + _cocoa_video_data.num_dirty_rects = MAX_DIRTY_RECTS; +} + +static inline void QZ_WindowBlitIndexedPixelsToView32(int left, int top, int right, int bottom) +{ + uint32 *trg; + uint8 *src; + int x, y; + + for(y = top; y < bottom; y++) { + trg = ((uint32 *) _cocoa_video_data.realpixels) + y * _cocoa_video_data.pitch / 4 + left; + src = _cocoa_video_data.pixels + y * _cocoa_video_data.width + left; + for(x = left; x < right; x++, trg++, src++) { + *trg = _cocoa_video_data.palette32[*src]; + } + } +} + +static inline void QZ_WindowBlitIndexedPixelsToView16(int left, int top, int right, int bottom) +{ + uint16 *trg; + uint8 *src; + int x, y; + + for(y = top; y < bottom; y++) { + trg = ((uint16 *) _cocoa_video_data.realpixels) + y * _cocoa_video_data.pitch / 2 + left; + src = _cocoa_video_data.pixels + y * _cocoa_video_data.width + left; + for(x = left; x < right; x++, trg++, src++) { + *trg = _cocoa_video_data.palette16[*src]; + } + } +} + +static inline void QZ_WindowBlitIndexedPixelsToView(int left, int top, int right, int bottom) +{ + switch(_cocoa_video_data.device_bpp) + { + case 32: + QZ_WindowBlitIndexedPixelsToView32(left, top, right, bottom); + break; + case 16: + QZ_WindowBlitIndexedPixelsToView16(left, top, right, bottom); + break; + } +} + +static bool _resize_icon[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, + 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, + 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, + 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, + 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, + 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, +}; + +static void QZ_DrawResizeIcon(void) { + int xoff, yoff, x, y; + uint16 *trg16; + uint32 *trg32; + + xoff = _cocoa_video_data.width - 16; + yoff = _cocoa_video_data.height - 16; + + for(y = 0; y < 16; y++) { + trg16 = ((uint16 *) _cocoa_video_data.realpixels) + (yoff + y) * _cocoa_video_data.pitch / 2 + xoff; + trg32 = ((uint32 *) _cocoa_video_data.realpixels) + (yoff + y) * _cocoa_video_data.pitch / 4 + xoff; + + for(x = 0; x < 16; x++, trg16++, trg32++) { + if(!_resize_icon[y * 16 + x]) + continue; + + switch(_cocoa_video_data.device_bpp) + { + case 32: + *trg32 = 0xff000000; + break; + case 16: + *trg16 = 0x0000; + break; + } + } + } +} + +static void QZ_DrawWindow (void) { + int i; + RgnHandle dirty, temp; + + /* Check if we need to do anything */ + if(_cocoa_video_data.num_dirty_rects == 0 || [ _cocoa_video_data.window isMiniaturized ]) + return; + + if(_cocoa_video_data.num_dirty_rects >= MAX_DIRTY_RECTS) { + _cocoa_video_data.num_dirty_rects = 1; + _cocoa_video_data.dirty_rects[0].left = 0; + _cocoa_video_data.dirty_rects[0].top = 0; + _cocoa_video_data.dirty_rects[0].right = _cocoa_video_data.width; + _cocoa_video_data.dirty_rects[0].bottom = _cocoa_video_data.height; + } + + dirty = NewRgn (); + temp = NewRgn (); + + SetEmptyRgn (dirty); + + /* Build the region of dirty rectangles */ + for (i = 0; i < _cocoa_video_data.num_dirty_rects; i++) { + QZ_WindowBlitIndexedPixelsToView(_cocoa_video_data.dirty_rects[i].left, + _cocoa_video_data.dirty_rects[i].top, + _cocoa_video_data.dirty_rects[i].right, + _cocoa_video_data.dirty_rects[i].bottom); + + MacSetRectRgn (temp, _cocoa_video_data.dirty_rects[i].left, _cocoa_video_data.dirty_rects[i].top, + _cocoa_video_data.dirty_rects[i].right, _cocoa_video_data.dirty_rects[i].bottom); + MacUnionRgn (dirty, temp, dirty); + } + + QZ_DrawResizeIcon(); + + /* Flush the dirty region */ + QDFlushPortBuffer ( [ _cocoa_video_data.qdview qdPort ], dirty ); + DisposeRgn (dirty); + DisposeRgn (temp); + + _cocoa_video_data.num_dirty_rects = 0; +} + + +extern const char _openttd_revision[]; + +static const char *QZ_SetVideoWindowed (uint width, uint height) { + char caption[50]; + NSString *nsscaption; + unsigned int style; + NSRect contentRect; + BOOL isCustom = NO; + + if(width > _cocoa_video_data.device_width) + width = _cocoa_video_data.device_width; + if(height > _cocoa_video_data.device_height) + height = _cocoa_video_data.device_height; + + _cocoa_video_data.width = width; + _cocoa_video_data.height = height; + + contentRect = NSMakeRect (0, 0, width, height); + + /* + Check if we should completely destroy the previous mode + - If it is fullscreen + */ + if (_cocoa_video_data.isset && _cocoa_video_data.fullscreen ) + QZ_UnsetVideoMode (); + + /* Check if we should recreate the window */ + if (_cocoa_video_data.window == nil) { + + /* Set the window style */ + style = NSTitledWindowMask; + style|= (NSMiniaturizableWindowMask | NSClosableWindowMask); + style|= NSResizableWindowMask; + + /* Manually create a window, avoids having a nib file resource */ + _cocoa_video_data.window = [ [ OTTD_QuartzWindow alloc ] + initWithContentRect:contentRect + styleMask:style + backing:NSBackingStoreBuffered + defer:NO ]; + + if (_cocoa_video_data.window == nil) + return "Could not create the Cocoa window"; + + snprintf(caption, sizeof(caption), "OpenTTD %s", _openttd_revision); + nsscaption = [ [ NSString alloc ] initWithCString:caption encoding:NSASCIIStringEncoding ]; + [ _cocoa_video_data.window setTitle:nsscaption ]; + [ _cocoa_video_data.window setMiniwindowTitle:nsscaption ]; + [ nsscaption release ]; + + [ _cocoa_video_data.window setAcceptsMouseMovedEvents:YES ]; + [ _cocoa_video_data.window setViewsNeedDisplay:NO ]; + + [ _cocoa_video_data.window setDelegate: [ [ [ OTTD_QuartzWindowDelegate alloc ] init ] autorelease ] ]; + } + /* We already have a window, just change its size */ + else { + + if (!isCustom) { + [ _cocoa_video_data.window setContentSize:contentRect.size ]; + [ _cocoa_video_data.qdview setFrameSize:contentRect.size ]; + } + } + + [ _cocoa_video_data.window center ]; + + /* Only recreate the view if it doesn't already exist */ + if (_cocoa_video_data.qdview == nil) { + + _cocoa_video_data.qdview = [ [ NSQuickDrawView alloc ] initWithFrame:contentRect ]; + [ _cocoa_video_data.qdview setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable ]; + [ [ _cocoa_video_data.window contentView ] addSubview:_cocoa_video_data.qdview ]; + [ _cocoa_video_data.qdview release ]; + [ _cocoa_video_data.window makeKeyAndOrderFront:nil ]; + } + + LockPortBits ( [ _cocoa_video_data.qdview qdPort ] ); + _cocoa_video_data.realpixels = GetPixBaseAddr ( GetPortPixMap ( [ _cocoa_video_data.qdview qdPort ] ) ); + _cocoa_video_data.pitch = GetPixRowBytes ( GetPortPixMap ( [ _cocoa_video_data.qdview qdPort ] ) ); + UnlockPortBits ( [ _cocoa_video_data.qdview qdPort ] ); + + /* + _cocoa_video_data.realpixels now points to the window's pixels + We want it to point to the *view's* pixels + */ + { + int vOffset = [ _cocoa_video_data.window frame ].size.height - [ _cocoa_video_data.qdview frame ].size.height - [ _cocoa_video_data.qdview frame ].origin.y; + + int hOffset = [ _cocoa_video_data.qdview frame ].origin.x; + + _cocoa_video_data.realpixels = (uint8 *) _cocoa_video_data.realpixels + (vOffset * _cocoa_video_data.pitch) + hOffset * (_cocoa_video_data.device_bpp / 8); + } + + if(_cocoa_video_data.pixels != NULL) + free(_cocoa_video_data.pixels); + _cocoa_video_data.pixels = (uint8 *) malloc(width * height); + if(_cocoa_video_data.pixels == NULL) + return "Failed to allocate 8-bit buffer"; + + _cocoa_video_data.fullscreen = false; + + return NULL; +} + + + + + + + + + + + + + +/****************************************************************************************** + * Fullscreen mode * + ******************************************************************************************/ + + +/* + Gamma functions to try to hide the flash from a rez switch + Fade the display from normal to black + Save gamma tables for fade back to normal +*/ +static uint32 QZ_FadeGammaOut (OTTD_QuartzGammaTable *table) { + + CGGammaValue redTable[QZ_GAMMA_TABLE_SIZE], + greenTable[QZ_GAMMA_TABLE_SIZE], + blueTable[QZ_GAMMA_TABLE_SIZE]; + + float percent; + int j; + unsigned int actual; + + if ( (CGGetDisplayTransferByTable(_cocoa_video_data.display_id, QZ_GAMMA_TABLE_SIZE, table->red, table->green, table->blue, &actual) != CGDisplayNoErr) || actual != QZ_GAMMA_TABLE_SIZE) { + return 1; + } + + memcpy (redTable, table->red, sizeof(redTable)); + memcpy (greenTable, table->green, sizeof(greenTable)); + memcpy (blueTable, table->blue, sizeof(greenTable)); + + for (percent = 1.0; percent >= 0.0; percent -= 0.01) { + for (j = 0; j < QZ_GAMMA_TABLE_SIZE; j++) { + redTable[j] = redTable[j] * percent; + greenTable[j] = greenTable[j] * percent; + blueTable[j] = blueTable[j] * percent; + } + + if (CGSetDisplayTransferByTable(_cocoa_video_data.display_id, QZ_GAMMA_TABLE_SIZE, redTable, greenTable, blueTable) != CGDisplayNoErr) { + CGDisplayRestoreColorSyncSettings(); + return 1; + } + + CSleep (10); + } + + return 0; +} + +/* + Fade the display from black to normal + Restore previously saved gamma values +*/ +static uint32 QZ_FadeGammaIn (OTTD_QuartzGammaTable *table) { + + CGGammaValue redTable[QZ_GAMMA_TABLE_SIZE], + greenTable[QZ_GAMMA_TABLE_SIZE], + blueTable[QZ_GAMMA_TABLE_SIZE]; + + float percent; + int j; + + memset (redTable, 0, sizeof(redTable)); + memset (greenTable, 0, sizeof(greenTable)); + memset (blueTable, 0, sizeof(greenTable)); + + for (percent = 0.0; percent <= 1.0; percent += 0.01) { + for (j = 0; j < QZ_GAMMA_TABLE_SIZE; j++) { + redTable[j] = table->red[j] * percent; + greenTable[j] = table->green[j] * percent; + blueTable[j] = table->blue[j] * percent; + } + + if (CGSetDisplayTransferByTable(_cocoa_video_data.display_id, QZ_GAMMA_TABLE_SIZE, redTable, greenTable, blueTable) != CGDisplayNoErr) { + CGDisplayRestoreColorSyncSettings(); + return 1; + } + + CSleep(10); + } + + return 0; +} + +static const char *QZ_SetVideoFullScreen (int width, int height) { + const char *errstr = "QZ_SetVideoFullScreen error"; + int exact_match; + CFNumberRef number; + int bpp; + int gamma_error; + OTTD_QuartzGammaTable gamma_table; + NSRect screen_rect; + CGError error; + NSPoint pt; + + /* Destroy any previous mode */ + if (_cocoa_video_data.isset) + QZ_UnsetVideoMode (); + + /* See if requested mode exists */ + _cocoa_video_data.mode = CGDisplayBestModeForParameters(_cocoa_video_data.display_id, 8, width, height, &exact_match); + + /* If the mode wasn't an exact match, check if it has the right bpp, and update width and height */ + if ( ! exact_match ) { + number = CFDictionaryGetValue (_cocoa_video_data.mode, kCGDisplayBitsPerPixel); + CFNumberGetValue (number, kCFNumberSInt32Type, &bpp); + if(bpp != 8) { + errstr = "Failed to find display resolution"; + goto ERR_NO_MATCH; + } + + number = CFDictionaryGetValue (_cocoa_video_data.mode, kCGDisplayWidth); + CFNumberGetValue (number, kCFNumberSInt32Type, &width); + + number = CFDictionaryGetValue (_cocoa_video_data.mode, kCGDisplayHeight); + CFNumberGetValue (number, kCFNumberSInt32Type, &height); + } + + /* Fade display to zero gamma */ + gamma_error = QZ_FadeGammaOut (&gamma_table); + + /* Put up the blanking window (a window above all other windows) */ + error = CGDisplayCapture (_cocoa_video_data.display_id); + + if ( CGDisplayNoErr != error ) { + errstr = "Failed capturing display"; + goto ERR_NO_CAPTURE; + } + + /* Do the physical switch */ + if (CGDisplaySwitchToMode (_cocoa_video_data.display_id, _cocoa_video_data.mode) != CGDisplayNoErr) { + errstr = "Failed switching display resolution"; + goto ERR_NO_SWITCH; + } + + _cocoa_video_data.realpixels = (uint8 *) CGDisplayBaseAddress (_cocoa_video_data.display_id); + _cocoa_video_data.pitch = CGDisplayBytesPerRow (_cocoa_video_data.display_id); + + _cocoa_video_data.width = CGDisplayPixelsWide (_cocoa_video_data.display_id); + _cocoa_video_data.height = CGDisplayPixelsHigh (_cocoa_video_data.display_id); + _cocoa_video_data.fullscreen = true; + + /* Setup double-buffer emulation */ + _cocoa_video_data.pixels = (uint8 *) malloc(width * height); + if(_cocoa_video_data.pixels == NULL) { + errstr = "Failed to allocate memory for double buffering"; + goto ERR_DOUBLEBUF; + } + + if (!CGDisplayCanSetPalette (_cocoa_video_data.display_id) ) { + errstr = "Not an indexed display mode."; + goto ERR_NOT_INDEXED; + } + + /* If we don't hide menu bar, it will get events and interrupt the program */ + HideMenuBar (); + + /* Fade the display to original gamma */ + if (! gamma_error ) + QZ_FadeGammaIn (&gamma_table); + + /* + There is a bug in Cocoa where NSScreen doesn't synchronize + with CGDirectDisplay, so the main screen's frame is wrong. + As a result, coordinate translation produces incorrect results. + We can hack around this bug by setting the screen rect + ourselves. This hack should be removed if/when the bug is fixed. + */ + screen_rect = NSMakeRect(0,0,width,height); + [ [ NSScreen mainScreen ] setFrame:screen_rect ]; + + /* we're fullscreen, so flag all input states... */ + _cocoa_video_data.active = true; + + + pt = [ NSEvent mouseLocation ]; + pt.y = CGDisplayPixelsHigh (_cocoa_video_data.display_id) - pt.y; + if(QZ_MouseIsInsideView(&pt)) + QZ_HideMouse(); + + return NULL; + +/* Since the blanking window covers *all* windows (even force quit) correct recovery is crucial */ +ERR_NOT_INDEXED: + free(_cocoa_video_data.pixels); + _cocoa_video_data.pixels = NULL; +ERR_DOUBLEBUF: + CGDisplaySwitchToMode (_cocoa_video_data.display_id, _cocoa_video_data.save_mode); +ERR_NO_SWITCH: + CGReleaseAllDisplays (); +ERR_NO_CAPTURE: + if (!gamma_error) { + QZ_FadeGammaIn (&gamma_table); + } +ERR_NO_MATCH: + return errstr; +} + + +static void QZ_UpdateFullscreenPalette (uint first_color, uint num_colors) { + + CGTableCount index; + CGDeviceColor color; + + for (index = first_color; index < first_color+num_colors; index++) { + /* Clamp colors between 0.0 and 1.0 */ + color.red = _cur_palette[index].r / 255.0; + color.blue = _cur_palette[index].b / 255.0; + color.green = _cur_palette[index].g / 255.0; + + CGPaletteSetColorAtIndex (_cocoa_video_data.palette, color, index); + } + + CGDisplaySetPalette (_cocoa_video_data.display_id, _cocoa_video_data.palette); +} + +/* Wait for the VBL to occur (estimated since we don't have a hardware interrupt) */ +static void QZ_WaitForVerticalBlank(void) +{ + /* The VBL delay is based on Ian Ollmann's RezLib <iano@cco.caltech.edu> */ + double refreshRate; + double linesPerSecond; + double target; + double position; + double adjustment; + CFNumberRef refreshRateCFNumber; + + refreshRateCFNumber = CFDictionaryGetValue (_cocoa_video_data.mode, kCGDisplayRefreshRate); + if (refreshRateCFNumber == NULL) + return; + + if (CFNumberGetValue (refreshRateCFNumber, kCFNumberDoubleType, &refreshRate) == 0) + return; + + if ( refreshRate == 0 ) + return; + + linesPerSecond = refreshRate * _cocoa_video_data.height; + target = _cocoa_video_data.height; + + /* Figure out the first delay so we start off about right */ + position = CGDisplayBeamPosition (_cocoa_video_data.display_id); + if (position > target) + position = 0; + + adjustment = (target - position) / linesPerSecond; + + CSleep((uint32) (adjustment * 1000)); +} + +static void QZ_DrawScreen(void) +{ + uint y; + uint8 *src, *dst; + + QZ_WaitForVerticalBlank(); + + for(y = 0; y < _cocoa_video_data.height; y++) { + src = _cocoa_video_data.pixels + y * _cocoa_video_data.width; + dst = ((uint8 *) _cocoa_video_data.realpixels) + y * _cocoa_video_data.pitch; + + memcpy(dst, src, _cocoa_video_data.width); + } +} + +static int QZ_ListFullscreenModes (OTTDPoint *mode_list, int max_modes) { + CFIndex num_modes; + CFIndex i; + int list_size = 0; + + num_modes = CFArrayGetCount (_cocoa_video_data.mode_list); + + /* Build list of modes with the requested bpp */ + for (i = 0; i < num_modes && list_size < max_modes; i++) { + CFDictionaryRef onemode; + CFNumberRef number; + int bpp; + int intvalue; + bool hasMode; + uint16 width, height; + + onemode = CFArrayGetValueAtIndex (_cocoa_video_data.mode_list, i); + number = CFDictionaryGetValue (onemode, kCGDisplayBitsPerPixel); + CFNumberGetValue (number, kCFNumberSInt32Type, &bpp); + + if (bpp != 8) + continue; + + number = CFDictionaryGetValue (onemode, kCGDisplayWidth); + CFNumberGetValue (number, kCFNumberSInt32Type, &intvalue); + width = (uint16) intvalue; + + number = CFDictionaryGetValue (onemode, kCGDisplayHeight); + CFNumberGetValue (number, kCFNumberSInt32Type, &intvalue); + height = (uint16) intvalue; + + /* Check if mode is already in the list */ + { + int i; + hasMode = false; + for (i = 0; i < list_size; i++) { + if (mode_list[i].x == width && mode_list[i].y == height) { + hasMode = true; + break; + } + } + } + + if ( hasMode ) + continue; + + /* Add mode to the list */ + mode_list[list_size].x = width; + mode_list[list_size].y = height; + list_size++; + } + + /* Sort list smallest to largest */ + { + int i, j; + for (i = 0; i < list_size; i++) { + for (j = 0; j < list_size-1; j++) { + if(mode_list[j].x > mode_list[j + 1].x || (mode_list[j].x == mode_list[j + 1].x && mode_list[j].y > mode_list[j + 1].y)) { + uint tmpw = mode_list[j].x; + uint tmph = mode_list[j].y; + + mode_list[j].x = mode_list[j+1].x; + mode_list[j].y = mode_list[j+1].y; + + mode_list[j+1].x = tmpw; + mode_list[j+1].y = tmph; + } + } + } + } + + return list_size; +} + + + + + + + + + + + + + + + +/****************************************************************************************** + * Windowed and fullscreen common code * + ******************************************************************************************/ + +static void QZ_UpdatePalette(uint start, uint count) +{ + if(_cocoa_video_data.fullscreen) { + QZ_UpdateFullscreenPalette(start, count); + } else { + QZ_UpdateWindowPalette(start, count); + } +} + +static void QZ_InitPalette(void) +{ + QZ_UpdatePalette(0, 256); +} + +static void QZ_Draw(void) +{ + if(_cocoa_video_data.fullscreen) { + QZ_DrawScreen(); + } else { + QZ_DrawWindow(); + } +} + + +static const OTTDPoint _default_resolutions[] = { + { 640, 480}, + { 800, 600}, + {1024, 768}, + {1152, 864}, + {1280, 800}, + {1280, 960}, + {1280, 1024}, + {1400, 1050}, + {1600, 1200}, + {1680, 1050}, + {1920, 1200} +}; + +static void QZ_UpdateVideoModes(void) +{ + uint i, j, count; + OTTDPoint modes[32]; + const OTTDPoint *current_modes; + + if(_cocoa_video_data.fullscreen) { + count = QZ_ListFullscreenModes(modes, 32); + current_modes = modes; + } else { + count = lengthof(_default_resolutions); + current_modes = _default_resolutions; + } + + for(i = 0, j = 0; j < lengthof(_resolutions) && i < count; i++) { + if (_cocoa_video_data.fullscreen || ((uint) current_modes[i].x <= _cocoa_video_data.device_width && + (uint) current_modes[i].y <= _cocoa_video_data.device_height)) { + _resolutions[j][0] = current_modes[i].x; + _resolutions[j][1] = current_modes[i].y; + j++; + } + } + + _num_resolutions = j; +} + +static void QZ_UnsetVideoMode (void) { + /* Release fullscreen resources */ + if ( _cocoa_video_data.fullscreen ) { + + OTTD_QuartzGammaTable gamma_table; + int gamma_error; + NSRect screen_rect; + + gamma_error = QZ_FadeGammaOut (&gamma_table); + + /* Restore original screen resolution/bpp */ + CGDisplaySwitchToMode (_cocoa_video_data.display_id, _cocoa_video_data.save_mode); + CGReleaseAllDisplays (); + ShowMenuBar (); + /* + Reset the main screen's rectangle + See comment in QZ_SetVideoFullscreen for why we do this + */ + screen_rect = NSMakeRect(0,0,_cocoa_video_data.device_width,_cocoa_video_data.device_height); + [ [ NSScreen mainScreen ] setFrame:screen_rect ]; + + if (! gamma_error) + QZ_FadeGammaIn (&gamma_table); + } + /* Release window mode resources */ + else { + + [ _cocoa_video_data.window close ]; + _cocoa_video_data.window = nil; + _cocoa_video_data.qdview = nil; + } + + free(_cocoa_video_data.pixels); + _cocoa_video_data.pixels = NULL; + + QZ_ShowMouse(); + + /* Signal successful teardown */ + _cocoa_video_data.isset = false; +} + + +static const char *QZ_SetVideoMode (uint width, uint height, bool fullscreen) { + const char *ret; + + /* Setup full screen video */ + if ( fullscreen ) { + _cocoa_video_data.issetting = true; + ret = QZ_SetVideoFullScreen (width, height); + _cocoa_video_data.issetting = false; + if (ret != NULL) + return ret; + } + /* Setup windowed video */ + else { + _cocoa_video_data.issetting = true; + ret = QZ_SetVideoWindowed (width, height); + _cocoa_video_data.issetting = false; + if (ret != NULL) + return ret; + } + + /* Signal successful completion (used internally) */ + _cocoa_video_data.isset = true; + + /* Tell the game that the resolution has changed */ + _screen.width = _cocoa_video_data.width; + _screen.height = _cocoa_video_data.height; + _screen.pitch = _cocoa_video_data.width; + + QZ_UpdateVideoModes(); + GameSizeChanged(); + + QZ_InitPalette(); + + return NULL; +} + +static const char *QZ_SetVideoModeAndRestoreOnFailure (uint width, uint height, bool fullscreen) +{ + bool wasset = _cocoa_video_data.isset; + uint32 oldwidth = _cocoa_video_data.width; + uint32 oldheight = _cocoa_video_data.height; + bool oldfullscreen = _cocoa_video_data.fullscreen; + const char *ret; + + ret = QZ_SetVideoMode(width, height, fullscreen); + if(ret != NULL && wasset) + QZ_SetVideoMode(oldwidth, oldheight, oldfullscreen); + + return ret; +} + +static void QZ_VideoInit (void) { + + memset(&_cocoa_video_data, 0, sizeof(_cocoa_video_data)); + + /* Initialize the video settings; this data persists between mode switches */ + _cocoa_video_data.display_id = kCGDirectMainDisplay; + _cocoa_video_data.save_mode = CGDisplayCurrentMode (_cocoa_video_data.display_id); + _cocoa_video_data.mode_list = CGDisplayAvailableModes (_cocoa_video_data.display_id); + _cocoa_video_data.palette = CGPaletteCreateDefaultColorPalette (); + + /* Gather some information that is useful to know about the display */ + /* Maybe this should be moved to QZ_SetVideoMode, in case this is changed after startup */ + CFNumberGetValue (CFDictionaryGetValue (_cocoa_video_data.save_mode, kCGDisplayBitsPerPixel), + kCFNumberSInt32Type, &_cocoa_video_data.device_bpp); + + CFNumberGetValue (CFDictionaryGetValue (_cocoa_video_data.save_mode, kCGDisplayWidth), + kCFNumberSInt32Type, &_cocoa_video_data.device_width); + + CFNumberGetValue (CFDictionaryGetValue (_cocoa_video_data.save_mode, kCGDisplayHeight), + kCFNumberSInt32Type, &_cocoa_video_data.device_height); + + _cocoa_video_data.cursor_visible = true; + + /* register for sleep notifications so wake from sleep generates SDL_VIDEOEXPOSE */ +// QZ_RegisterForSleepNotifications (); +} + + +/* Convert local coordinate to window server (CoreGraphics) coordinate */ +static CGPoint QZ_PrivateLocalToCG (NSPoint *p) { + CGPoint cgp; + + if ( ! _cocoa_video_data.fullscreen ) { + *p = [ _cocoa_video_data.qdview convertPoint:*p toView: nil ]; + *p = [ _cocoa_video_data.window convertBaseToScreen:*p ]; + p->y = _cocoa_video_data.device_height - p->y; + } + + cgp.x = p->x; + cgp.y = p->y; + + return cgp; +} + +static void QZ_WarpCursor (int x, int y) { + NSPoint p; + CGPoint cgp; + + /* Only allow warping when in foreground */ + if ( ! [ NSApp isActive ] ) + return; + + p = NSMakePoint (x, y); + cgp = QZ_PrivateLocalToCG (&p); + + /* this is the magic call that fixes cursor "freezing" after warp */ + CGSetLocalEventsSuppressionInterval (0.0); + /* Do the actual warp */ + CGWarpMouseCursorPosition (cgp); + + /* Generate the mouse moved event */ +// SDL_PrivateMouseMotion (0, 0, x, y); +} + +void QZ_ShowMouse (void) { + if (!_cocoa_video_data.cursor_visible) { + [ NSCursor unhide ]; + _cocoa_video_data.cursor_visible = true; + } +} + +void QZ_HideMouse (void) { + if (_cocoa_video_data.cursor_visible) { +#ifndef _DEBUG + [ NSCursor hide ]; +#endif + _cocoa_video_data.cursor_visible = false; + } +} + + + + + + + + + + + + + + + + +/****************************************************************************************** + * OS X application creation * + ******************************************************************************************/ + + + +/* The main class of the application, the application's delegate */ +@implementation OTTDMain +/* Called when the internal event loop has just started running */ +- (void) applicationDidFinishLaunching: (NSNotification *) note +{ + /* Hand off to main application code */ + QZ_GameLoop(); + + /* We're done, thank you for playing */ + [ NSApp stop:_ottd_main ]; +} + +/* Display the in game quit confirmation dialog */ +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *) sender +{ +// DEBUG(driver, 1)("cocoa_v: applicationShouldTerminate"); + + QZ_AskQuit(); + + return NSTerminateCancel; // NSTerminateLater ? +} +@end + +static void setApplicationMenu(void) +{ + /* warning: this code is very odd */ + NSMenu *appleMenu; + NSMenuItem *menuItem; + NSString *title; + NSString *appName; + + appName = @"OTTD"; + appleMenu = [[NSMenu alloc] initWithTitle:appName]; + + /* Add menu items */ + title = [@"About " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; + + [appleMenu addItem:[NSMenuItem separatorItem]]; + + title = [@"Hide " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"]; + + menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; + [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; + + [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; + + [appleMenu addItem:[NSMenuItem separatorItem]]; + + title = [@"Quit " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; + + + /* Put menu into the menubar */ + menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; + [menuItem setSubmenu:appleMenu]; + [[NSApp mainMenu] addItem:menuItem]; + + /* Tell the application object that this is now the application menu */ + [NSApp setAppleMenu:appleMenu]; + + /* Finally give up our references to the objects */ + [appleMenu release]; + [menuItem release]; +} + +/* Create a window menu */ +static void setupWindowMenu(void) +{ + NSMenu *windowMenu; + NSMenuItem *windowMenuItem; + NSMenuItem *menuItem; + + windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; + + /* "Minimize" item */ + menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; + [windowMenu addItem:menuItem]; + [menuItem release]; + + /* Put menu into the menubar */ + windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""]; + [windowMenuItem setSubmenu:windowMenu]; + [[NSApp mainMenu] addItem:windowMenuItem]; + + /* Tell the application object that this is now the window menu */ + [NSApp setWindowsMenu:windowMenu]; + + /* Finally give up our references to the objects */ + [windowMenu release]; + [windowMenuItem release]; +} + +static void setupApplication(void) +{ + CPSProcessSerNum PSN; + + /* Ensure the application object is initialised */ + [NSApplication sharedApplication]; + + /* Tell the dock about us */ + if (!CPSGetCurrentProcess(&PSN)) + if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103)) + if (!CPSSetFrontProcess(&PSN)) + [NSApplication sharedApplication]; + + /* Set up the menubar */ + [NSApp setMainMenu:[[NSMenu alloc] init]]; + setApplicationMenu(); + setupWindowMenu(); + + /* Create OTTDMain and make it the app delegate */ + _ottd_main = [[OTTDMain alloc] init]; + [NSApp setDelegate:_ottd_main]; +} + + + + + + + + + + + + + + + + +/****************************************************************************************** + * Video driver interface * + ******************************************************************************************/ + +static void CocoaVideoStop(void) +{ + DEBUG(driver, 1)("cocoa_v: CocoaVideoStop"); + + if(_cocoa_video_data.isset) + QZ_UnsetVideoMode(); + + [_ottd_main release]; +} + +static const char *CocoaVideoStart(const char * const *parm) +{ + const char *ret; + + DEBUG(driver, 1)("cocoa_v: CocoaVideoStart"); + + setupApplication(); + + QZ_VideoInit(); + + ret = QZ_SetVideoMode(_cur_resolution[0], _cur_resolution[1], _fullscreen); + if(ret != NULL) + CocoaVideoStop(); + + return ret; +} + +static void CocoaVideoMakeDirty(int left, int top, int width, int height) +{ + if (_cocoa_video_data.num_dirty_rects < MAX_DIRTY_RECTS) { + _cocoa_video_data.dirty_rects[_cocoa_video_data.num_dirty_rects].left = left; + _cocoa_video_data.dirty_rects[_cocoa_video_data.num_dirty_rects].top = top; + _cocoa_video_data.dirty_rects[_cocoa_video_data.num_dirty_rects].right = left + width; + _cocoa_video_data.dirty_rects[_cocoa_video_data.num_dirty_rects].bottom = top + height; + } + _cocoa_video_data.num_dirty_rects++; +} + +static void CocoaVideoMainLoop(void) +{ + DEBUG(driver, 1)("cocoa_v: CocoaVideoMainLoop"); + + /* Start the main event loop */ + [NSApp run]; +} + +static bool CocoaVideoChangeRes(int w, int h) +{ + const char *ret; + DEBUG(driver, 1)("cocoa_v: CocoaVideoChangeRes"); + + ret = QZ_SetVideoModeAndRestoreOnFailure((uint) w, (uint) h, _cocoa_video_data.fullscreen); + if(ret != NULL) { + DEBUG(driver, 1)("cocoa_v: failed with message: %s", ret); + } + + return ret == NULL; +} + +static void CocoaVideoFullScreen(bool full_screen) +{ + const char *ret; + + DEBUG(driver, 1)("cocoa_v: CocoaVideoFullScreen"); + + ret = QZ_SetVideoModeAndRestoreOnFailure(_cocoa_video_data.width, _cocoa_video_data.height, full_screen); + if(ret != NULL) { + DEBUG(driver, 1)("cocoa_v: failed with message: %s", ret); + } + + _fullscreen = _cocoa_video_data.fullscreen; +} + +const HalVideoDriver _cocoa_video_driver = { + CocoaVideoStart, + CocoaVideoStop, + CocoaVideoMakeDirty, + CocoaVideoMainLoop, + CocoaVideoChangeRes, + CocoaVideoFullScreen, +}; + + +/* This is needed since OS X applications are started with the working dir set to / when double-clicked */ +void cocoaSetWorkingDirectory(void) +{ + char parentdir[MAXPATHLEN]; + int chdir_ret; + CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle()); + CFURLRef url2 = CFURLCreateCopyDeletingLastPathComponent(0, url); + if (CFURLGetFileSystemRepresentation(url2, true, (unsigned char *) parentdir, MAXPATHLEN)) { + chdir_ret = chdir (parentdir); /* chdir to the binary app's parent */ + assert(chdir_ret == 0); + } + CFRelease(url); + CFRelease(url2); +} + +/* These are called from main() to prevent a _NSAutoreleaseNoPool error when + * exiting before the cocoa video driver has been loaded + */ +void cocoaSetupAutoreleasePool(void) +{ + _ottd_autorelease_pool = [[NSAutoreleasePool alloc] init]; +} + +void cocoaReleaseAutoreleasePool(void) +{ + [_ottd_autorelease_pool release]; +} + +#endif /* WITH_COCOA */ |