diff options
author | rubidium <rubidium@openttd.org> | 2007-01-02 19:19:48 +0000 |
---|---|---|
committer | rubidium <rubidium@openttd.org> | 2007-01-02 19:19:48 +0000 |
commit | 66bbf336c6af7353ef0aeed58002c46543b30635 (patch) | |
tree | ad4a63860df2626b22f77e7dac712e958bea54cb /src/video | |
parent | ccc0a3f4dbf58c005b22341ac8874252924690cd (diff) | |
download | openttd-66bbf336c6af7353ef0aeed58002c46543b30635.tar.xz |
(svn r7759) -Merge: makefile rewrite. This merge features:
- A proper ./configure, so everything needs to be configured only once, not for every make.
- Usage of makedepend when available. This greatly reduces the time needed for generating the dependencies.
- A generator for all project files. There is a single file with sources, which is used to generate Makefiles and the project files for MSVC.
- Proper support for OSX universal binaries.
- Object files for non-MSVC compiles are also placed in separate directories, making is faster to switch between debug and release compiles and it does not touch the directory with the source files.
- Functionality to make a bundle of all needed files for for example a nightly or distribution of a binary with all needed GRFs and language files.
Note: as this merge moves almost all files, it is recommended to make a backup of your working copy before updating your working copy.
Diffstat (limited to 'src/video')
-rw-r--r-- | src/video/cocoa_keys.h | 127 | ||||
-rw-r--r-- | src/video/cocoa_v.h | 10 | ||||
-rw-r--r-- | src/video/cocoa_v.m | 2064 | ||||
-rw-r--r-- | src/video/dedicated_v.c | 298 | ||||
-rw-r--r-- | src/video/dedicated_v.h | 10 | ||||
-rw-r--r-- | src/video/null_v.c | 45 | ||||
-rw-r--r-- | src/video/null_v.h | 10 | ||||
-rw-r--r-- | src/video/sdl_v.c | 515 | ||||
-rw-r--r-- | src/video/sdl_v.h | 10 | ||||
-rw-r--r-- | src/video/win32_v.c | 876 | ||||
-rw-r--r-- | src/video/win32_v.h | 10 |
11 files changed, 3975 insertions, 0 deletions
diff --git a/src/video/cocoa_keys.h b/src/video/cocoa_keys.h new file mode 100644 index 000000000..1e69c1425 --- /dev/null +++ b/src/video/cocoa_keys.h @@ -0,0 +1,127 @@ +/* $Id$ */ + +#ifndef COCOA_KEYS_H +#define COCOA_KEYS_H + +/* 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 0x0A +#define QZ_BACKQUOTE2 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/src/video/cocoa_v.h b/src/video/cocoa_v.h new file mode 100644 index 000000000..93d7b2639 --- /dev/null +++ b/src/video/cocoa_v.h @@ -0,0 +1,10 @@ +/* $Id$ */ + +#ifndef VIDEO_COCOA_H +#define VIDEO_COCOA_H + +#include "../hal.h" + +extern const HalVideoDriver _cocoa_video_driver; + +#endif diff --git a/src/video/cocoa_v.m b/src/video/cocoa_v.m new file mode 100644 index 000000000..f93f86ca4 --- /dev/null +++ b/src/video/cocoa_v.m @@ -0,0 +1,2064 @@ +/* $Id$ */ + +/****************************************************************************** + * 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. */ +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4) +@interface NSApplication(NSAppleMenu) +- (void)setAppleMenu:(NSMenu *)menu; +@end +#endif + + +/* 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/network.h" +#include "../variables.h" +#include "../os/macosx/splash.h" + +#include "cocoa_v.h" +#include "cocoa_keys.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); +static void QZ_ShowMouse(void); +static 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; + +static bool _cocoa_video_started = false; +static bool _cocoa_video_dialog = false; + + + + +/****************************************************************************** + * 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; + } +} + + + +typedef struct VkMapping { + unsigned short vk_from; + byte map_to; +} VkMapping; + +#define AS(x, z) {x, z} + +static const VkMapping _vk_mapping[] = { + AS(QZ_BACKQUOTE, WKC_BACKQUOTE), // key left of '1' + AS(QZ_BACKQUOTE2, WKC_BACKQUOTE), // some keyboards have it on another scancode + + // 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); + } + break; + } + + if (down) { + uint32 pressed_key = QZ_MapKey(keycode) | unicode; + HandleKeypress(pressed_key); + 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) +{ + if (_cursor.fix_at) { + int dx = x - _cursor.pos.x; + int 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; + } + HandleMouseEvents(); +} + + +static 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; + } + HandleMouseEvents(); + break; + + case 1: + if (down) { + _right_button_down = true; + _right_button_clicked = true; + } else { + _right_button_down = false; + } + HandleMouseEvents(); + 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; + +#if 0 + /* 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; +#endif + + 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 deltaY ] > 0.0) { /* Scroll up */ + _cursor.wheel--; + } else if ([ event deltaY ] < 0.0) { /* Scroll down */ + _cursor.wheel++; + } /* else: deltaY was 0.0 and we don't want to do anything */ + 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; + +#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); + + for (;;) { + 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; + uint32 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 */ + 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; +} + + +- (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; +} + + +- (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 +{ + HandleExitGameRequest(); + + return NO; +} + +- (void)windowDidBecomeKey:(NSNotification*)aNotification +{ + _cocoa_video_data.active = true; +} + +- (void)windowDidResignKey:(NSNotification*)aNotification +{ + _cocoa_video_data.active = false; +} + +- (void)windowDidBecomeMain:(NSNotification*)aNotification +{ + _cocoa_video_data.active = true; +} + +- (void)windowDidResignMain:(NSNotification*)aNotification +{ + _cocoa_video_data.active = false; +} + +@end + + +static void QZ_UpdateWindowPalette(uint start, uint count) +{ + uint i; + + switch (_cocoa_video_data.device_bpp) { + case 32: + for (i = start; i < start + count; i++) { + uint32 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++) { + uint16 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(uint left, uint top, uint right, uint bottom) +{ + const uint32* pal = _cocoa_video_data.palette32; + const uint8* src = _cocoa_video_data.pixels; + uint32* dst = (uint32*)_cocoa_video_data.realpixels; + uint width = _cocoa_video_data.width; + uint pitch = _cocoa_video_data.pitch / 4; + uint x; + uint y; + + for (y = top; y < bottom; y++) { + for (x = left; x < right; x++) { + dst[y * pitch + x] = pal[src[y * width + x]]; + } + } +} + +static inline void QZ_WindowBlitIndexedPixelsToView16(uint left, uint top, uint right, uint bottom) +{ + const uint16* pal = _cocoa_video_data.palette16; + const uint8* src = _cocoa_video_data.pixels; + uint16* dst = (uint16*)_cocoa_video_data.realpixels; + uint width = _cocoa_video_data.width; + uint pitch = _cocoa_video_data.pitch / 2; + uint x; + uint y; + + for (y = top; y < bottom; y++) { + for (x = left; x < right; x++) { + dst[y * pitch + x] = pal[src[y * width + x]]; + } + } +} + +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 = _cocoa_video_data.width - 16; + int yoff = _cocoa_video_data.height - 16; + int x; + int y; + + for (y = 0; y < 16; y++) { + uint16* trg16 = (uint16*)_cocoa_video_data.realpixels + (yoff + y) * _cocoa_video_data.pitch / 2 + xoff; + uint32* 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 ]; + [ _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 ] ]; + } else { + /* We already have a window, just change its size */ + 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); + } + + 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]; + CGGammaValue greenTable[QZ_GAMMA_TABLE_SIZE]; + CGGammaValue 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(const OTTD_QuartzGammaTable* table) +{ + CGGammaValue redTable[QZ_GAMMA_TABLE_SIZE]; + CGGammaValue greenTable[QZ_GAMMA_TABLE_SIZE]; + CGGammaValue 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) +{ + const uint8* src = _cocoa_video_data.pixels; + uint8* dst = (uint8*)_cocoa_video_data.realpixels; + uint pitch = _cocoa_video_data.pitch; + uint width = _cocoa_video_data.width; + uint num_dirty = _cocoa_video_data.num_dirty_rects; + uint i; + + /* Check if we need to do anything */ + if (num_dirty == 0) return; + + if (num_dirty >= MAX_DIRTY_RECTS) { + num_dirty = 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; + } + + QZ_WaitForVerticalBlank(); + /* Build the region of dirty rectangles */ + for (i = 0; i < num_dirty; i++) { + uint y = _cocoa_video_data.dirty_rects[i].top; + uint left = _cocoa_video_data.dirty_rects[i].left; + uint length = _cocoa_video_data.dirty_rects[i].right - left; + uint bottom = _cocoa_video_data.dirty_rects[i].bottom; + + for (; y < bottom; y++) { + memcpy(dst + y * pitch + left, src + y * width + left, length); + } + } + + _cocoa_video_data.num_dirty_rects = 0; +} + + +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) +{ + if (_cocoa_video_data.fullscreen) { + /* Release fullscreen resources */ + 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); + } else { + /* Release window mode resources */ + [ _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; + + /* Signal successful teardown */ + _cocoa_video_data.isset = false; + + QZ_ShowMouse(); +} + + +static const char* QZ_SetVideoMode(uint width, uint height, bool fullscreen) +{ + const char *ret; + + _cocoa_video_data.issetting = true; + if (fullscreen) { + /* Setup full screen video */ + ret = QZ_SetVideoFullScreen(width, height); + } else { + /* Setup windowed video */ + 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 */ +} + +static void QZ_ShowMouse(void) +{ + if (!_cocoa_video_data.cursor_visible) { + [ NSCursor unhide ]; + _cocoa_video_data.cursor_visible = true; + + // Hide the openttd cursor when leaving the window + if (_cocoa_video_data.isset) + UndrawMouseCursor(); + _cursor.in_window = false; + } +} + +static void QZ_HideMouse(void) +{ + if (_cocoa_video_data.cursor_visible) { +#ifndef _DEBUG + [ NSCursor hide ]; +#endif + _cocoa_video_data.cursor_visible = false; + + // Show the openttd cursor again + _cursor.in_window = true; + } +} + + +/****************************************************************************** + * 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 +{ + + HandleExitGameRequest(); + + 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) && + !CPSEnableForegroundOperation(&PSN, 0x03, 0x3C, 0x2C, 0x1103) && + !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) +{ + if (!_cocoa_video_started) return; + + if (_cocoa_video_data.isset) QZ_UnsetVideoMode(); + + [_ottd_main release]; + + _cocoa_video_started = false; +} + +static const char *CocoaVideoStart(const char * const *parm) +{ + const char *ret; + + if (_cocoa_video_started) return "Already started"; + _cocoa_video_started = true; + + memset(&_cocoa_video_data, 0, sizeof(_cocoa_video_data)); + + setupApplication(); + + /* Don't create a window or enter fullscreen if we're just going to show a dialog. */ + if (_cocoa_video_dialog) return NULL; + + 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) +{ + /* Start the main event loop */ + [NSApp run]; +} + +static bool CocoaVideoChangeRes(int w, int h) +{ + const char *ret = QZ_SetVideoModeAndRestoreOnFailure((uint)w, (uint)h, _cocoa_video_data.fullscreen); + if (ret != NULL) { + DEBUG(driver, 0, "cocoa_v: CocoaVideoChangeRes failed with message: %s", ret); + } + + return ret == NULL; +} + +static void CocoaVideoFullScreen(bool full_screen) +{ + const char *ret = QZ_SetVideoModeAndRestoreOnFailure(_cocoa_video_data.width, _cocoa_video_data.height, full_screen); + if (ret != NULL) { + DEBUG(driver, 0, "cocoa_v: CocoaVideoFullScreen 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 sometimes assert is called before the videodriver is initialized */ +void CocoaDialog(const char* title, const char* message, const char* buttonLabel) +{ + bool wasstarted; + + _cocoa_video_dialog = true; + + wasstarted = _cocoa_video_started; + if (!_cocoa_video_started && CocoaVideoStart(NULL) != NULL) { + fprintf(stderr, "%s: %s\n", title, message); + return; + } + + NSRunAlertPanel([NSString stringWithCString: title], [NSString stringWithCString: message], [NSString stringWithCString: buttonLabel], nil, nil); + + if (!wasstarted) CocoaVideoStop(); + + _cocoa_video_dialog = false; +} + + +/* 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 */ diff --git a/src/video/dedicated_v.c b/src/video/dedicated_v.c new file mode 100644 index 000000000..caa1c8963 --- /dev/null +++ b/src/video/dedicated_v.c @@ -0,0 +1,298 @@ +/* $Id$ */ + +#include "../stdafx.h" + +#ifdef ENABLE_NETWORK + +#include "../openttd.h" +#include "../debug.h" +#include "../functions.h" +#include "../gfx.h" +#include "../network/network.h" +#include "../window.h" +#include "../console.h" +#include "../variables.h" +#include "../genworld.h" +#include "dedicated_v.h" + +#ifdef BEOS_NET_SERVER +#include <net/socket.h> +#endif + +#ifdef __OS2__ +# include <sys/time.h> /* gettimeofday */ +# include <sys/types.h> +# include <unistd.h> +# include <conio.h> + +# define INCL_DOS +# include <os2.h> + +# define STDIN 0 /* file descriptor for standard input */ + +/** + * Switches OpenTTD to a console app at run-time, instead of a PM app + * Necessary to see stdout, etc. */ +static void OS2_SwitchToConsoleMode(void) +{ + PPIB pib; + PTIB tib; + + DosGetInfoBlocks(&tib, &pib); + + // Change flag from PM to VIO + pib->pib_ultype = 3; +} +#endif + +#ifdef UNIX +# include <sys/time.h> /* gettimeofday */ +# include <sys/types.h> +# include <unistd.h> +# include <signal.h> +# define STDIN 0 /* file descriptor for standard input */ + +/* Signal handlers */ +static void DedicatedSignalHandler(int sig) +{ + _exit_game = true; + signal(sig, DedicatedSignalHandler); +} +#endif + +#ifdef WIN32 +#include <windows.h> /* GetTickCount */ +#include <conio.h> +#include <time.h> +#include <tchar.h> +static HANDLE _hInputReady, _hWaitForInputHandling; +static HANDLE _hThread; // Thread to close +static char _win_console_thread_buffer[200]; + +/* Windows Console thread. Just loop and signal when input has been received */ +static void WINAPI CheckForConsoleInput(void) +{ + while (true) { + fgets(_win_console_thread_buffer, lengthof(_win_console_thread_buffer), stdin); + /* Signal input waiting that input is read and wait for it being handled + * SignalObjectAndWait() should be used here, but it's unsupported in Win98< */ + SetEvent(_hInputReady); + WaitForSingleObject(_hWaitForInputHandling, INFINITE); + } +} + +static void CreateWindowsConsoleThread(void) +{ + DWORD dwThreadId; + /* Create event to signal when console input is ready */ + _hInputReady = CreateEvent(NULL, false, false, NULL); + _hWaitForInputHandling = CreateEvent(NULL, false, false, NULL); + if (_hInputReady == NULL || _hWaitForInputHandling == NULL) error("Cannot create console event!"); + + _hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CheckForConsoleInput, NULL, 0, &dwThreadId); + if (_hThread == NULL) error("Cannot create console thread!"); + + DEBUG(driver, 2, "Windows console thread started"); +} + +static void CloseWindowsConsoleThread(void) +{ + CloseHandle(_hThread); + CloseHandle(_hInputReady); + CloseHandle(_hWaitForInputHandling); + DEBUG(driver, 2, "Windows console thread shut down"); +} + +#endif + + +static void *_dedicated_video_mem; + +extern bool SafeSaveOrLoad(const char *filename, int mode, int newgm); +extern void SwitchMode(int new_mode); + + +static const char *DedicatedVideoStart(const char * const *parm) +{ + _screen.width = _screen.pitch = _cur_resolution[0]; + _screen.height = _cur_resolution[1]; + _dedicated_video_mem = malloc(_cur_resolution[0]*_cur_resolution[1]); + + SetDebugString("net=6"); + +#ifdef WIN32 + // For win32 we need to allocate a console (debug mode does the same) + CreateConsole(); + CreateWindowsConsoleThread(); + SetConsoleTitle(_T("OpenTTD Dedicated Server")); +#endif + +#ifdef __OS2__ + // For OS/2 we also need to switch to console mode instead of PM mode + OS2_SwitchToConsoleMode(); +#endif + + DEBUG(driver, 1, "Loading dedicated server"); + return NULL; +} + +static void DedicatedVideoStop(void) +{ +#ifdef WIN32 + CloseWindowsConsoleThread(); +#endif + free(_dedicated_video_mem); +} + +static void DedicatedVideoMakeDirty(int left, int top, int width, int height) {} +static bool DedicatedVideoChangeRes(int w, int h) { return false; } +static void DedicatedVideoFullScreen(bool fs) {} + +#if defined(UNIX) || defined(__OS2__) +static bool InputWaiting(void) +{ + struct timeval tv; + fd_set readfds; + + tv.tv_sec = 0; + tv.tv_usec = 1; + + FD_ZERO(&readfds); + FD_SET(STDIN, &readfds); + + /* don't care about writefds and exceptfds: */ + return select(STDIN + 1, &readfds, NULL, NULL, &tv) > 0; +} + +static uint32 GetTime(void) +{ + struct timeval tim; + + gettimeofday(&tim, NULL); + return tim.tv_usec / 1000 + tim.tv_sec * 1000; +} + +#else + +static bool InputWaiting(void) +{ + return WaitForSingleObject(_hInputReady, 1) == WAIT_OBJECT_0; +} + +static uint32 GetTime(void) +{ + return GetTickCount(); +} + +#endif + +static void DedicatedHandleKeyInput(void) +{ + static char input_line[200] = ""; + + if (!InputWaiting()) return; + + if (_exit_game) return; + +#if defined(UNIX) || defined(__OS2__) + if (fgets(input_line, lengthof(input_line), stdin) == NULL) return; +#else + /* Handle console input, and singal console thread, it can accept input again */ + strncpy(input_line, _win_console_thread_buffer, lengthof(input_line)); + SetEvent(_hWaitForInputHandling); +#endif + + /* XXX - strtok() does not 'forget' \n\r if it is the first character! */ + strtok(input_line, "\r\n"); // Forget about the final \n (or \r) + { /* Remove any special control characters */ + uint i; + for (i = 0; i < lengthof(input_line); i++) { + if (input_line[i] == '\n' || input_line[i] == '\r') // cut missed beginning '\0' + input_line[i] = '\0'; + + if (input_line[i] == '\0') + break; + + if (!IS_INT_INSIDE(input_line[i], ' ', 256)) + input_line[i] = ' '; + } + } + + IConsoleCmdExec(input_line); // execute command +} + +static void DedicatedVideoMainLoop(void) +{ + uint32 next_tick; + uint32 cur_ticks; + + next_tick = GetTime() + 30; + + /* Signal handlers */ +#ifdef UNIX + signal(SIGTERM, DedicatedSignalHandler); + signal(SIGINT, DedicatedSignalHandler); + signal(SIGQUIT, DedicatedSignalHandler); +#endif + + // Load the dedicated server stuff + _is_network_server = true; + _network_dedicated = true; + _network_playas = PLAYER_SPECTATOR; + _local_player = PLAYER_SPECTATOR; + + /* If SwitchMode is SM_LOAD, it means that the user used the '-g' options */ + if (_switch_mode != SM_LOAD) { + StartNewGameWithoutGUI(GENERATE_NEW_SEED); + SwitchMode(_switch_mode); + _switch_mode = SM_NONE; + } else { + _switch_mode = SM_NONE; + /* First we need to test if the savegame can be loaded, else we will end up playing the + * intro game... */ + if (!SafeSaveOrLoad(_file_to_saveload.name, _file_to_saveload.mode, GM_NORMAL)) { + /* Loading failed, pop out.. */ + DEBUG(net, 0, "Loading requested map failed, aborting"); + _networking = false; + } else { + /* We can load this game, so go ahead */ + SwitchMode(SM_LOAD); + } + } + + // Done loading, start game! + + if (!_networking) { + DEBUG(net, 0, "Dedicated server could not be started, aborting"); + return; + } + + while (!_exit_game) { + InteractiveRandom(); // randomness + + if (!_dedicated_forks) + DedicatedHandleKeyInput(); + + cur_ticks = GetTime(); + + if (cur_ticks >= next_tick) { + next_tick += 30; + + GameLoop(); + _screen.dst_ptr = _dedicated_video_mem; + UpdateWindows(); + } + CSleep(1); + } +} + +const HalVideoDriver _dedicated_video_driver = { + DedicatedVideoStart, + DedicatedVideoStop, + DedicatedVideoMakeDirty, + DedicatedVideoMainLoop, + DedicatedVideoChangeRes, + DedicatedVideoFullScreen, +}; + +#endif /* ENABLE_NETWORK */ diff --git a/src/video/dedicated_v.h b/src/video/dedicated_v.h new file mode 100644 index 000000000..9ba1223d3 --- /dev/null +++ b/src/video/dedicated_v.h @@ -0,0 +1,10 @@ +/* $Id$ */ + +#ifndef VIDEO_DEDICATED_H +#define VIDEO_DEDICATED_H + +#include "../hal.h" + +extern const HalVideoDriver _dedicated_video_driver; + +#endif diff --git a/src/video/null_v.c b/src/video/null_v.c new file mode 100644 index 000000000..ecdd3486c --- /dev/null +++ b/src/video/null_v.c @@ -0,0 +1,45 @@ +/* $Id$ */ + +#include "../stdafx.h" +#include "../openttd.h" +#include "../gfx.h" +#include "../variables.h" +#include "../window.h" +#include "null_v.h" + +static void* _null_video_mem = NULL; + +static const char* NullVideoStart(const char* const* parm) +{ + _screen.width = _screen.pitch = _cur_resolution[0]; + _screen.height = _cur_resolution[1]; + _null_video_mem = malloc(_cur_resolution[0] * _cur_resolution[1]); + return NULL; +} + +static void NullVideoStop(void) { free(_null_video_mem); } + +static void NullVideoMakeDirty(int left, int top, int width, int height) {} + +static void NullVideoMainLoop(void) +{ + uint i; + + for (i = 0; i < 1000; i++) { + GameLoop(); + _screen.dst_ptr = _null_video_mem; + UpdateWindows(); + } +} + +static bool NullVideoChangeRes(int w, int h) { return false; } +static void NullVideoFullScreen(bool fs) {} + +const HalVideoDriver _null_video_driver = { + NullVideoStart, + NullVideoStop, + NullVideoMakeDirty, + NullVideoMainLoop, + NullVideoChangeRes, + NullVideoFullScreen, +}; diff --git a/src/video/null_v.h b/src/video/null_v.h new file mode 100644 index 000000000..56922d9b3 --- /dev/null +++ b/src/video/null_v.h @@ -0,0 +1,10 @@ +/* $Id$ */ + +#ifndef VIDEO_NULL_H +#define VIDEO_NULL_H + +#include "../hal.h" + +extern const HalVideoDriver _null_video_driver; + +#endif diff --git a/src/video/sdl_v.c b/src/video/sdl_v.c new file mode 100644 index 000000000..e88de85a5 --- /dev/null +++ b/src/video/sdl_v.c @@ -0,0 +1,515 @@ +/* $Id$ */ + +#include "../stdafx.h" + +#ifdef WITH_SDL + +#include "../openttd.h" +#include "../debug.h" +#include "../functions.h" +#include "../gfx.h" +#include "../macros.h" +#include "../sdl.h" +#include "../window.h" +#include "../network/network.h" +#include "../variables.h" +#include "sdl_v.h" +#include <SDL.h> + +static SDL_Surface *_sdl_screen; +static bool _all_modes; + +#define MAX_DIRTY_RECTS 100 +static SDL_Rect _dirty_rects[MAX_DIRTY_RECTS]; +static int _num_dirty_rects; + +static void SdlVideoMakeDirty(int left, int top, int width, int height) +{ + if (_num_dirty_rects < MAX_DIRTY_RECTS) { + _dirty_rects[_num_dirty_rects].x = left; + _dirty_rects[_num_dirty_rects].y = top; + _dirty_rects[_num_dirty_rects].w = width; + _dirty_rects[_num_dirty_rects].h = height; + } + _num_dirty_rects++; +} + +static void UpdatePalette(uint start, uint count) +{ + SDL_Color pal[256]; + uint i; + + for (i = 0; i != count; i++) { + pal[i].r = _cur_palette[start + i].r; + pal[i].g = _cur_palette[start + i].g; + pal[i].b = _cur_palette[start + i].b; + pal[i].unused = 0; + } + + SDL_CALL SDL_SetColors(_sdl_screen, pal, start, count); +} + +static void InitPalette(void) +{ + UpdatePalette(0, 256); +} + +static void CheckPaletteAnim(void) +{ + if (_pal_last_dirty != -1) { + UpdatePalette(_pal_first_dirty, _pal_last_dirty - _pal_first_dirty + 1); + _pal_last_dirty = -1; + } +} + +static void DrawSurfaceToScreen(void) +{ + int n = _num_dirty_rects; + if (n != 0) { + _num_dirty_rects = 0; + if (n > MAX_DIRTY_RECTS) + SDL_CALL SDL_UpdateRect(_sdl_screen, 0, 0, 0, 0); + else + SDL_CALL SDL_UpdateRects(_sdl_screen, n, _dirty_rects); + } +} + +static const uint16 default_resolutions[][2] = { + { 640, 480}, + { 800, 600}, + {1024, 768}, + {1152, 864}, + {1280, 800}, + {1280, 960}, + {1280, 1024}, + {1400, 1050}, + {1600, 1200}, + {1680, 1050}, + {1920, 1200} +}; + +static void GetVideoModes(void) +{ + int i; + SDL_Rect **modes; + + modes = SDL_CALL SDL_ListModes(NULL, SDL_SWSURFACE + (_fullscreen ? SDL_FULLSCREEN : 0)); + + if (modes == NULL) + error("sdl: no modes available"); + + _all_modes = (modes == (void*)-1); + + if (_all_modes) { + // all modes available, put some default ones here + memcpy(_resolutions, default_resolutions, sizeof(default_resolutions)); + _num_resolutions = lengthof(default_resolutions); + } else { + int n = 0; + for (i = 0; modes[i]; i++) { + int w = modes[i]->w; + int h = modes[i]->h; + if (IS_INT_INSIDE(w, 640, MAX_SCREEN_WIDTH + 1) && + IS_INT_INSIDE(h, 480, MAX_SCREEN_HEIGHT + 1)) { + int j; + for (j = 0; j < n; j++) { + if (_resolutions[j][0] == w && _resolutions[j][1] == h) break; + } + + if (j == n) { + _resolutions[j][0] = w; + _resolutions[j][1] = h; + if (++n == lengthof(_resolutions)) break; + } + } + } + _num_resolutions = n; + SortResolutions(_num_resolutions); + } +} + +static void GetAvailableVideoMode(int *w, int *h) +{ + int i; + int best; + uint delta; + + // all modes available? + if (_all_modes) return; + + // is the wanted mode among the available modes? + for (i = 0; i != _num_resolutions; i++) { + if (*w == _resolutions[i][0] && *h == _resolutions[i][1]) return; + } + + // use the closest possible resolution + best = 0; + delta = abs((_resolutions[0][0] - *w) * (_resolutions[0][1] - *h)); + for (i = 1; i != _num_resolutions; ++i) { + uint newdelta = abs((_resolutions[i][0] - *w) * (_resolutions[i][1] - *h)); + if (newdelta < delta) { + best = i; + delta = newdelta; + } + } + *w = _resolutions[best][0]; + *h = _resolutions[best][1]; +} + +#ifndef ICON_DIR +#define ICON_DIR "media" +#endif + +#ifdef WIN32 +/* Let's redefine the LoadBMP macro with because we are dynamically + * loading SDL and need to 'SDL_CALL' all functions */ +#undef SDL_LoadBMP +#define SDL_LoadBMP(file) SDL_LoadBMP_RW(SDL_CALL SDL_RWFromFile(file, "rb"), 1) +#endif + +static bool CreateMainSurface(int w, int h) +{ + extern const char _openttd_revision[]; + SDL_Surface *newscreen, *icon; + char caption[50]; + + GetAvailableVideoMode(&w, &h); + + DEBUG(driver, 1, "SDL: using mode %dx%d", w, h); + + /* Give the application an icon */ + icon = SDL_CALL SDL_LoadBMP(ICON_DIR PATHSEP "openttd.32.bmp"); + if (icon != NULL) { + /* Get the colourkey, which will be magenta */ + uint32 rgbmap = SDL_CALL SDL_MapRGB(icon->format, 255, 0, 255); + + SDL_CALL SDL_SetColorKey(icon, SDL_SRCCOLORKEY, rgbmap); + SDL_CALL SDL_WM_SetIcon(icon, NULL); + SDL_CALL SDL_FreeSurface(icon); + } + + // DO NOT CHANGE TO HWSURFACE, IT DOES NOT WORK + newscreen = SDL_CALL SDL_SetVideoMode(w, h, 8, SDL_SWSURFACE | SDL_HWPALETTE | (_fullscreen ? SDL_FULLSCREEN : SDL_RESIZABLE)); + if (newscreen == NULL) + return false; + + _screen.width = newscreen->w; + _screen.height = newscreen->h; + _screen.pitch = newscreen->pitch; + + _sdl_screen = newscreen; + InitPalette(); + + snprintf(caption, sizeof(caption), "OpenTTD %s", _openttd_revision); + SDL_CALL SDL_WM_SetCaption(caption, caption); + SDL_CALL SDL_ShowCursor(0); + + GameSizeChanged(); + + return true; +} + +typedef struct VkMapping { + uint16 vk_from; + byte vk_count; + byte map_to; +} VkMapping; + +#define AS(x, z) {x, 0, z} +#define AM(x, y, z, w) {x, y - x, z} + +static const VkMapping _vk_mapping[] = { + // Pageup stuff + up/down + AM(SDLK_PAGEUP, SDLK_PAGEDOWN, WKC_PAGEUP, WKC_PAGEDOWN), + AS(SDLK_UP, WKC_UP), + AS(SDLK_DOWN, WKC_DOWN), + AS(SDLK_LEFT, WKC_LEFT), + AS(SDLK_RIGHT, WKC_RIGHT), + + AS(SDLK_HOME, WKC_HOME), + AS(SDLK_END, WKC_END), + + AS(SDLK_INSERT, WKC_INSERT), + AS(SDLK_DELETE, WKC_DELETE), + + // Map letters & digits + AM(SDLK_a, SDLK_z, 'A', 'Z'), + AM(SDLK_0, SDLK_9, '0', '9'), + + AS(SDLK_ESCAPE, WKC_ESC), + AS(SDLK_PAUSE, WKC_PAUSE), + AS(SDLK_BACKSPACE, WKC_BACKSPACE), + + AS(SDLK_SPACE, WKC_SPACE), + AS(SDLK_RETURN, WKC_RETURN), + AS(SDLK_TAB, WKC_TAB), + + // Function keys + AM(SDLK_F1, SDLK_F12, WKC_F1, WKC_F12), + + // Numeric part. + // What is the virtual keycode for numeric enter?? + AM(SDLK_KP0, SDLK_KP9, WKC_NUM_0, WKC_NUM_9), + AS(SDLK_KP_DIVIDE, WKC_NUM_DIV), + AS(SDLK_KP_MULTIPLY, WKC_NUM_MUL), + AS(SDLK_KP_MINUS, WKC_NUM_MINUS), + AS(SDLK_KP_PLUS, WKC_NUM_PLUS), + AS(SDLK_KP_ENTER, WKC_NUM_ENTER), + AS(SDLK_KP_PERIOD, WKC_NUM_DECIMAL) +}; + +static uint32 ConvertSdlKeyIntoMy(SDL_keysym *sym) +{ + const VkMapping *map; + uint key = 0; + + for (map = _vk_mapping; map != endof(_vk_mapping); ++map) { + if ((uint)(sym->sym - map->vk_from) <= map->vk_count) { + key = sym->sym - map->vk_from + map->map_to; + break; + } + } + + // check scancode for BACKQUOTE key, because we want the key left of "1", not anything else (on non-US keyboards) +#if defined(WIN32) || defined(__OS2__) + if (sym->scancode == 41) key = WKC_BACKQUOTE; +#elif defined(__APPLE__) + if (sym->scancode == 10) key = WKC_BACKQUOTE; +#elif defined(__MORPHOS__) + if (sym->scancode == 0) key = WKC_BACKQUOTE; // yes, that key is code '0' under MorphOS :) +#elif defined(__BEOS__) + if (sym->scancode == 17) key = WKC_BACKQUOTE; +#elif defined(__SVR4) && defined(__sun) + if (sym->scancode == 60) key = WKC_BACKQUOTE; + if (sym->scancode == 49) key = WKC_BACKSPACE; +#elif defined(__sgi__) + if (sym->scancode == 22) key = WKC_BACKQUOTE; +#else + if (sym->scancode == 49) key = WKC_BACKQUOTE; +#endif + + // META are the command keys on mac + if (sym->mod & KMOD_META) key |= WKC_META; + if (sym->mod & KMOD_SHIFT) key |= WKC_SHIFT; + if (sym->mod & KMOD_CTRL) key |= WKC_CTRL; + if (sym->mod & KMOD_ALT) key |= WKC_ALT; + // these two lines really help porting hotkey combos. Uncomment to use -- Bjarni +#if 0 + DEBUG(driver, 0, "Scancode character pressed %u", sym->scancode); + DEBUG(driver, 0, "Unicode character pressed %u", sym->unicode); +#endif + return (key << 16) + sym->unicode; +} + +static int PollEvent(void) +{ + SDL_Event ev; + + if (!SDL_CALL SDL_PollEvent(&ev)) return -2; + + switch (ev.type) { + case SDL_MOUSEMOTION: + if (_cursor.fix_at) { + int dx = ev.motion.x - _cursor.pos.x; + int dy = ev.motion.y - _cursor.pos.y; + if (dx != 0 || dy != 0) { + _cursor.delta.x += dx; + _cursor.delta.y += dy; + SDL_CALL SDL_WarpMouse(_cursor.pos.x, _cursor.pos.y); + } + } else { + _cursor.delta.x = ev.motion.x - _cursor.pos.x; + _cursor.delta.y = ev.motion.y - _cursor.pos.y; + _cursor.pos.x = ev.motion.x; + _cursor.pos.y = ev.motion.y; + _cursor.dirty = true; + } + HandleMouseEvents(); + break; + + case SDL_MOUSEBUTTONDOWN: + if (_rightclick_emulate && SDL_CALL SDL_GetModState() & KMOD_CTRL) { + ev.button.button = SDL_BUTTON_RIGHT; + } + + switch (ev.button.button) { + case SDL_BUTTON_LEFT: + _left_button_down = true; + break; + + case SDL_BUTTON_RIGHT: + _right_button_down = true; + _right_button_clicked = true; + break; + + case SDL_BUTTON_WHEELUP: _cursor.wheel--; break; + case SDL_BUTTON_WHEELDOWN: _cursor.wheel++; break; + + default: break; + } + HandleMouseEvents(); + break; + + case SDL_MOUSEBUTTONUP: + if (_rightclick_emulate) { + _right_button_down = false; + _left_button_down = false; + _left_button_clicked = false; + } else if (ev.button.button == SDL_BUTTON_LEFT) { + _left_button_down = false; + _left_button_clicked = false; + } else if (ev.button.button == SDL_BUTTON_RIGHT) { + _right_button_down = false; + } + HandleMouseEvents(); + break; + + case SDL_ACTIVEEVENT: + if (!(ev.active.state & SDL_APPMOUSEFOCUS)) break; + + if (ev.active.gain) { // mouse entered the window, enable cursor + _cursor.in_window = true; + } else { + UndrawMouseCursor(); // mouse left the window, undraw cursor + _cursor.in_window = false; + } + break; + + case SDL_QUIT: HandleExitGameRequest(); break; + + case SDL_KEYDOWN: /* Toggle full-screen on ALT + ENTER/F */ + if ((ev.key.keysym.mod & (KMOD_ALT | KMOD_META)) && + (ev.key.keysym.sym == SDLK_RETURN || ev.key.keysym.sym == SDLK_f)) { + ToggleFullScreen(!_fullscreen); + } else { + HandleKeypress(ConvertSdlKeyIntoMy(&ev.key.keysym)); + } + break; + + case SDL_VIDEORESIZE: { + int w = clamp(ev.resize.w, 64, MAX_SCREEN_WIDTH); + int h = clamp(ev.resize.h, 64, MAX_SCREEN_HEIGHT); + ChangeResInGame(w, h); + break; + } + } + return -1; +} + +static const char *SdlVideoStart(const char * const *parm) +{ + char buf[30]; + + const char *s = SdlOpen(SDL_INIT_VIDEO); + if (s != NULL) return s; + + SDL_CALL SDL_VideoDriverName(buf, 30); + DEBUG(driver, 1, "SDL: using driver '%s'", buf); + + GetVideoModes(); + CreateMainSurface(_cur_resolution[0], _cur_resolution[1]); + MarkWholeScreenDirty(); + + SDL_CALL SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); + SDL_CALL SDL_EnableUNICODE(1); + return NULL; +} + +static void SdlVideoStop(void) +{ + SdlClose(SDL_INIT_VIDEO); +} + +static void SdlVideoMainLoop(void) +{ + uint32 next_tick = SDL_CALL SDL_GetTicks() + 30; + uint32 cur_ticks; + uint32 pal_tick = 0; + uint32 mod; + int numkeys; + Uint8 *keys; + + for (;;) { + InteractiveRandom(); // randomness + + while (PollEvent() == -1) {} + if (_exit_game) return; + + mod = SDL_CALL SDL_GetModState(); + keys = SDL_CALL SDL_GetKeyState(&numkeys); +#if defined(_DEBUG) + if (_shift_pressed) +#else + /* Speedup when pressing tab, except when using ALT+TAB + * to switch to another application */ + if (keys[SDLK_TAB] && (mod & KMOD_ALT) == 0) +#endif + { + if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2; + } else if (_fast_forward & 2) { + _fast_forward = 0; + } + + cur_ticks = SDL_CALL SDL_GetTicks(); + if ((_fast_forward && !_pause) || cur_ticks > next_tick) + next_tick = cur_ticks; + + if (cur_ticks == next_tick) { + next_tick += 30; + + _ctrl_pressed = !!(mod & KMOD_CTRL); + _shift_pressed = !!(mod & KMOD_SHIFT); +#ifdef _DEBUG + _dbg_screen_rect = !!(mod & KMOD_CAPS); +#endif + + // determine which directional keys are down + _dirkeys = + (keys[SDLK_LEFT] ? 1 : 0) | + (keys[SDLK_UP] ? 2 : 0) | + (keys[SDLK_RIGHT] ? 4 : 0) | + (keys[SDLK_DOWN] ? 8 : 0); + GameLoop(); + + _screen.dst_ptr = _sdl_screen->pixels; + UpdateWindows(); + if (++pal_tick > 4) { + CheckPaletteAnim(); + pal_tick = 1; + } + DrawSurfaceToScreen(); + } else { + SDL_CALL SDL_Delay(1); + _screen.dst_ptr = _sdl_screen->pixels; + DrawTextMessage(); + DrawMouseCursor(); + DrawSurfaceToScreen(); + } + } +} + +static bool SdlVideoChangeRes(int w, int h) +{ + return CreateMainSurface(w, h); +} + +static void SdlVideoFullScreen(bool full_screen) +{ + _fullscreen = full_screen; + GetVideoModes(); // get the list of available video modes + if (_num_resolutions == 0 || !_video_driver->change_resolution(_cur_resolution[0], _cur_resolution[1])) { + // switching resolution failed, put back full_screen to original status + _fullscreen ^= true; + } +} + +const HalVideoDriver _sdl_video_driver = { + SdlVideoStart, + SdlVideoStop, + SdlVideoMakeDirty, + SdlVideoMainLoop, + SdlVideoChangeRes, + SdlVideoFullScreen, +}; + +#endif diff --git a/src/video/sdl_v.h b/src/video/sdl_v.h new file mode 100644 index 000000000..07ba50f3c --- /dev/null +++ b/src/video/sdl_v.h @@ -0,0 +1,10 @@ +/* $Id$ */ + +#ifndef VIDEO_SDL_H +#define VIDEO_SDL_H + +#include "../hal.h" + +extern const HalVideoDriver _sdl_video_driver; + +#endif diff --git a/src/video/win32_v.c b/src/video/win32_v.c new file mode 100644 index 000000000..7588653f0 --- /dev/null +++ b/src/video/win32_v.c @@ -0,0 +1,876 @@ +/* $Id$ */ + +#include "../stdafx.h" +#include "../openttd.h" +#include "../functions.h" +#include "../gfx.h" +#include "../macros.h" +#include "../network/network.h" +#include "../variables.h" +#include "../win32.h" +#include "../window.h" +#include "win32_v.h" +#include <windows.h> +#include <tchar.h> + +static struct { + HWND main_wnd; + HBITMAP dib_sect; + Pixel *bitmap_bits; + Pixel *buffer_bits; + Pixel *alloced_bits; + HPALETTE gdi_palette; + int width; + int height; + int width_org; + int height_org; + bool fullscreen; + bool double_size; + bool has_focus; + bool running; +} _wnd; + +bool _force_full_redraw; +bool _double_size; +bool _window_maximize; +uint _display_hz; +uint _fullscreen_bpp; +static uint16 _bck_resolution[2]; + +static void MakePalette(void) +{ + LOGPALETTE *pal; + uint i; + + pal = alloca(sizeof(LOGPALETTE) + (256-1) * sizeof(PALETTEENTRY)); + + pal->palVersion = 0x300; + pal->palNumEntries = 256; + + for (i = 0; i != 256; i++) { + pal->palPalEntry[i].peRed = _cur_palette[i].r; + pal->palPalEntry[i].peGreen = _cur_palette[i].g; + pal->palPalEntry[i].peBlue = _cur_palette[i].b; + pal->palPalEntry[i].peFlags = 0; + + } + _wnd.gdi_palette = CreatePalette(pal); + if (_wnd.gdi_palette == NULL) error("CreatePalette failed!\n"); +} + +static void UpdatePalette(HDC dc, uint start, uint count) +{ + RGBQUAD rgb[256]; + uint i; + + for (i = 0; i != count; i++) { + rgb[i].rgbRed = _cur_palette[start + i].r; + rgb[i].rgbGreen = _cur_palette[start + i].g; + rgb[i].rgbBlue = _cur_palette[start + i].b; + rgb[i].rgbReserved = 0; + } + + SetDIBColorTable(dc, start, count, rgb); +} + +typedef struct { + byte vk_from; + byte vk_count; + byte map_to; +} VkMapping; + +#define AS(x, z) {x, 0, z} +#define AM(x, y, z, w) {x, y - x, z} + +static const VkMapping _vk_mapping[] = { + // Pageup stuff + up/down + AM(VK_PRIOR,VK_DOWN, WKC_PAGEUP, WKC_DOWN), + // Map letters & digits + AM('A','Z','A','Z'), + AM('0','9','0','9'), + + AS(VK_ESCAPE, WKC_ESC), + AS(VK_PAUSE, WKC_PAUSE), + AS(VK_BACK, WKC_BACKSPACE), + AM(VK_INSERT, VK_DELETE, WKC_INSERT, WKC_DELETE), + + AS(VK_SPACE, WKC_SPACE), + AS(VK_RETURN, WKC_RETURN), + AS(VK_TAB, WKC_TAB), + + // Function keys + AM(VK_F1, VK_F12, WKC_F1, WKC_F12), + + // Numeric part. + // What is the virtual keycode for numeric enter?? + AM(VK_NUMPAD0, VK_NUMPAD9, WKC_NUM_0, WKC_NUM_9), + AS(VK_DIVIDE, WKC_NUM_DIV), + AS(VK_MULTIPLY, WKC_NUM_MUL), + AS(VK_SUBTRACT, WKC_NUM_MINUS), + AS(VK_ADD, WKC_NUM_PLUS), + AS(VK_DECIMAL, WKC_NUM_DECIMAL) +}; + +static uint MapWindowsKey(uint sym) +{ + const VkMapping *map; + uint key = 0; + + for (map = _vk_mapping; map != endof(_vk_mapping); ++map) { + if ((uint)(sym - map->vk_from) <= map->vk_count) { + key = sym - map->vk_from + map->map_to; + break; + } + } + + if (GetAsyncKeyState(VK_SHIFT) < 0) key |= WKC_SHIFT; + if (GetAsyncKeyState(VK_CONTROL) < 0) key |= WKC_CTRL; + if (GetAsyncKeyState(VK_MENU) < 0) key |= WKC_ALT; + return key; +} + +static bool AllocateDibSection(int w, int h); + +static void ClientSizeChanged(int w, int h) +{ + if (_wnd.double_size) { + w /= 2; + h /= 2; + } + + // allocate new dib section of the new size + if (AllocateDibSection(w, h)) { + // mark all palette colors dirty + _pal_first_dirty = 0; + _pal_last_dirty = 255; + GameSizeChanged(); + + // redraw screen + if (_wnd.running) { + _screen.dst_ptr = _wnd.buffer_bits; + UpdateWindows(); + } + } +} + +#ifdef _DEBUG +// Keep this function here.. +// It allows you to redraw the screen from within the MSVC debugger +int RedrawScreenDebug(void) +{ + HDC dc,dc2; + static int _fooctr; + HBITMAP old_bmp; + HPALETTE old_palette; + + _screen.dst_ptr = _wnd.buffer_bits; + UpdateWindows(); + + dc = GetDC(_wnd.main_wnd); + dc2 = CreateCompatibleDC(dc); + + old_bmp = SelectObject(dc2, _wnd.dib_sect); + old_palette = SelectPalette(dc, _wnd.gdi_palette, FALSE); + BitBlt(dc, 0, 0, _wnd.width, _wnd.height, dc2, 0, 0, SRCCOPY); + SelectPalette(dc, old_palette, TRUE); + SelectObject(dc2, old_bmp); + DeleteDC(dc2); + ReleaseDC(_wnd.main_wnd, dc); + + return _fooctr++; +} +#endif + +/* Windows 95 will not have a WM_MOUSELEAVE message, so define it if needed */ +#if !defined(WM_MOUSELEAVE) +#define WM_MOUSELEAVE 0x02A3 +#endif +#define TID_POLLMOUSE 1 +#define MOUSE_POLL_DELAY 75 + +static void CALLBACK TrackMouseTimerProc(HWND hwnd, UINT msg, UINT event, DWORD time) +{ + RECT rc; + POINT pt; + + /* Get the rectangle of our window and translate it to screen coordinates. + * Compare this with the current screen coordinates of the mouse and if it + * falls outside of the area or our window we have left the window. */ + GetClientRect(hwnd, &rc); + MapWindowPoints(hwnd, HWND_DESKTOP, (LPPOINT)(LPRECT)&rc, 2); + GetCursorPos(&pt); + + if (!PtInRect(&rc, pt) || (WindowFromPoint(pt) != hwnd)) { + KillTimer(hwnd, event); + PostMessage(hwnd, WM_MOUSELEAVE, 0, 0L); + } +} + +static LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_CREATE: + SetTimer(hwnd, TID_POLLMOUSE, MOUSE_POLL_DELAY, (TIMERPROC)TrackMouseTimerProc); + break; + + case WM_PAINT: { + PAINTSTRUCT ps; + HDC dc,dc2; + HBITMAP old_bmp; + HPALETTE old_palette; + + BeginPaint(hwnd, &ps); + dc = ps.hdc; + dc2 = CreateCompatibleDC(dc); + old_bmp = SelectObject(dc2, _wnd.dib_sect); + old_palette = SelectPalette(dc, _wnd.gdi_palette, FALSE); + + if (_pal_last_dirty != -1) { + UpdatePalette(dc2, _pal_first_dirty, _pal_last_dirty - _pal_first_dirty + 1); + _pal_last_dirty = -1; + } + + BitBlt(dc, 0, 0, _wnd.width, _wnd.height, dc2, 0, 0, SRCCOPY); + SelectPalette(dc, old_palette, TRUE); + SelectObject(dc2, old_bmp); + DeleteDC(dc2); + EndPaint(hwnd, &ps); + return 0; + } + + case WM_PALETTECHANGED: + if ((HWND)wParam == hwnd) return 0; + /* FALLTHROUGH */ + + case WM_QUERYNEWPALETTE: { + HDC hDC = GetWindowDC(hwnd); + HPALETTE hOldPalette = SelectPalette(hDC, _wnd.gdi_palette, FALSE); + UINT nChanged = RealizePalette(hDC); + + SelectPalette(hDC, hOldPalette, TRUE); + ReleaseDC(hwnd, hDC); + if (nChanged) InvalidateRect(hwnd, NULL, FALSE); + return 0; + } + + case WM_CLOSE: + HandleExitGameRequest(); + return 0; + + case WM_DESTROY: + if (_window_maximize) { + _cur_resolution[0] = _bck_resolution[0]; + _cur_resolution[1] = _bck_resolution[1]; + } + return 0; + + case WM_LBUTTONDOWN: + SetCapture(hwnd); + _left_button_down = true; + HandleMouseEvents(); + return 0; + + case WM_LBUTTONUP: + ReleaseCapture(); + _left_button_down = false; + _left_button_clicked = false; + HandleMouseEvents(); + return 0; + + case WM_RBUTTONDOWN: + SetCapture(hwnd); + _right_button_down = true; + _right_button_clicked = true; + HandleMouseEvents(); + return 0; + + case WM_RBUTTONUP: + ReleaseCapture(); + _right_button_down = false; + HandleMouseEvents(); + return 0; + + case WM_MOUSELEAVE: + UndrawMouseCursor(); + _cursor.in_window = false; + + if (!_left_button_down && !_right_button_down) MyShowCursor(true); + HandleMouseEvents(); + return 0; + + case WM_MOUSEMOVE: { + int x = (int16)LOWORD(lParam); + int y = (int16)HIWORD(lParam); + POINT pt; + + /* If the mouse was not in the window and it has moved it means it has + * come into the window, so start drawing the mouse. Also start + * tracking the mouse for exiting the window */ + if (!_cursor.in_window) { + _cursor.in_window = true; + SetTimer(hwnd, TID_POLLMOUSE, MOUSE_POLL_DELAY, (TIMERPROC)TrackMouseTimerProc); + + DrawMouseCursor(); + } + + if (_wnd.double_size) { + x /= 2; + y /= 2; + } + + if (_cursor.fix_at) { + int dx = x - _cursor.pos.x; + int dy = y - _cursor.pos.y; + if (dx != 0 || dy != 0) { + _cursor.delta.x += dx; + _cursor.delta.y += dy; + + pt.x = _cursor.pos.x; + pt.y = _cursor.pos.y; + + if (_wnd.double_size) { + pt.x *= 2; + pt.y *= 2; + } + ClientToScreen(hwnd, &pt); + SetCursorPos(pt.x, pt.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; + } + MyShowCursor(false); + HandleMouseEvents(); + return 0; + } + + case WM_KEYDOWN: { + // this is the rewritten ascii input function + // it disables windows deadkey handling --> more linux like :D + wchar_t w = 0; + byte ks[256]; + uint scancode; + uint32 pressed_key; + + GetKeyboardState(ks); + if (ToUnicode(wParam, 0, ks, &w, 1, 0) != 1) { + /* On win9x ToUnicode always fails, so fall back to ToAscii */ + if (ToAscii(wParam, 0, ks, &w, 0) != 1) w = 0; // no translation was possible + } + + pressed_key = w | MapWindowsKey(wParam) << 16; + + scancode = GB(lParam, 16, 8); + if (scancode == 41) pressed_key = w | WKC_BACKQUOTE << 16; + + if (GB(pressed_key, 16, 16) == ('D' | WKC_CTRL) && !_wnd.fullscreen) { + _double_size ^= 1; + _wnd.double_size = _double_size; + ClientSizeChanged(_wnd.width, _wnd.height); + MarkWholeScreenDirty(); + } + HandleKeypress(pressed_key); + break; + } + + case WM_SYSKEYDOWN: /* user presses F10 or Alt, both activating the title-menu */ + switch (wParam) { + case VK_RETURN: + case 'F': /* Full Screen on ALT + ENTER/F */ + ToggleFullScreen(!_wnd.fullscreen); + return 0; + + case VK_MENU: /* Just ALT */ + return 0; // do nothing + + case VK_F10: /* F10, ignore activation of menu */ + HandleKeypress(MapWindowsKey(wParam) << 16); + return 0; + + default: /* ALT in combination with something else */ + HandleKeypress(MapWindowsKey(wParam) << 16); + break; + } + break; + + case WM_SIZE: + if (wParam != SIZE_MINIMIZED) { + /* Set maximized flag when we maximize (obviously), but also when we + * switched to fullscreen from a maximized state */ + _window_maximize = (wParam == SIZE_MAXIMIZED || (_window_maximize && _fullscreen)); + if (_window_maximize) { + _bck_resolution[0] = _cur_resolution[0]; + _bck_resolution[1] = _cur_resolution[1]; + } + ClientSizeChanged(LOWORD(lParam), HIWORD(lParam)); + } + return 0; + + case WM_SIZING: { + RECT* r = (RECT*)lParam; + RECT r2; + int w, h; + + SetRect(&r2, 0, 0, 0, 0); + AdjustWindowRect(&r2, GetWindowLong(hwnd, GWL_STYLE), FALSE); + + w = r->right - r->left - (r2.right - r2.left); + h = r->bottom - r->top - (r2.bottom - r2.top); + if (_wnd.double_size) { + w /= 2; + h /= 2; + } + w = clamp(w, 64, MAX_SCREEN_WIDTH); + h = clamp(h, 64, MAX_SCREEN_HEIGHT); + if (_wnd.double_size) { + w *= 2; + h *= 2; + } + SetRect(&r2, 0, 0, w, h); + + AdjustWindowRect(&r2, GetWindowLong(hwnd, GWL_STYLE), FALSE); + w = r2.right - r2.left; + h = r2.bottom - r2.top; + + switch (wParam) { + case WMSZ_BOTTOM: + r->bottom = r->top + h; + break; + + case WMSZ_BOTTOMLEFT: + r->bottom = r->top + h; + r->left = r->right - w; + break; + + case WMSZ_BOTTOMRIGHT: + r->bottom = r->top + h; + r->right = r->left + w; + break; + + case WMSZ_LEFT: + r->left = r->right - w; + break; + + case WMSZ_RIGHT: + r->right = r->left + w; + break; + + case WMSZ_TOP: + r->top = r->bottom - h; + break; + + case WMSZ_TOPLEFT: + r->top = r->bottom - h; + r->left = r->right - w; + break; + + case WMSZ_TOPRIGHT: + r->top = r->bottom - h; + r->right = r->left + w; + break; + } + return TRUE; + } + +// needed for wheel +#if !defined(WM_MOUSEWHEEL) +# define WM_MOUSEWHEEL 0x020A +#endif //WM_MOUSEWHEEL +#if !defined(GET_WHEEL_DELTA_WPARAM) +# define GET_WHEEL_DELTA_WPARAM(wparam) ((short)HIWORD(wparam)) +#endif //GET_WHEEL_DELTA_WPARAM + + case WM_MOUSEWHEEL: { + int delta = GET_WHEEL_DELTA_WPARAM(wParam); + + if (delta < 0) { + _cursor.wheel++; + } else if (delta > 0) { + _cursor.wheel--; + } + HandleMouseEvents(); + return 0; + } + + case WM_ACTIVATEAPP: + _wnd.has_focus = (bool)wParam; + break; + } + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +static void RegisterWndClass(void) +{ + static bool registered = false; + + if (!registered) { + HINSTANCE hinst = GetModuleHandle(NULL); + WNDCLASS wnd = { + 0, + WndProcGdi, + 0, + 0, + hinst, + LoadIcon(hinst, MAKEINTRESOURCE(100)), + LoadCursor(NULL, IDC_ARROW), + 0, + 0, + _T("OTTD") + }; + + registered = true; + if (!RegisterClass(&wnd)) error("RegisterClass failed"); + } +} + +static void MakeWindow(bool full_screen) +{ + _fullscreen = full_screen; + + _wnd.double_size = _double_size && !full_screen; + + // recreate window? + if ((full_screen || _wnd.fullscreen) && _wnd.main_wnd) { + DestroyWindow(_wnd.main_wnd); + _wnd.main_wnd = 0; + } + + if (full_screen) { + DEVMODE settings; + + memset(&settings, 0, sizeof(settings)); + settings.dmSize = sizeof(settings); + settings.dmFields = + (_fullscreen_bpp != 0 ? DM_BITSPERPEL : 0) | + DM_PELSWIDTH | + DM_PELSHEIGHT | + (_display_hz != 0 ? DM_DISPLAYFREQUENCY : 0); + settings.dmBitsPerPel = _fullscreen_bpp; + settings.dmPelsWidth = _wnd.width_org; + settings.dmPelsHeight = _wnd.height_org; + settings.dmDisplayFrequency = _display_hz; + + if (ChangeDisplaySettings(&settings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL) { + MakeWindow(false); + return; + } + } else if (_wnd.fullscreen) { + // restore display? + ChangeDisplaySettings(NULL, 0); + } + + { + RECT r; + DWORD style, showstyle; + int x, y, w, h; + + showstyle = SW_SHOWNORMAL; + _wnd.fullscreen = full_screen; + if (_wnd.fullscreen) { + style = WS_POPUP; + SetRect(&r, 0, 0, _wnd.width_org, _wnd.height_org); + } else { + style = WS_OVERLAPPEDWINDOW; + /* On window creation, check if we were in maximize mode before */ + if (_window_maximize) showstyle = SW_SHOWMAXIMIZED; + SetRect(&r, 0, 0, _wnd.width, _wnd.height); + } + + AdjustWindowRect(&r, style, FALSE); + w = r.right - r.left; + h = r.bottom - r.top; + x = (GetSystemMetrics(SM_CXSCREEN) - w) / 2; + y = (GetSystemMetrics(SM_CYSCREEN) - h) / 2; + + if (_wnd.main_wnd) { + ShowWindow(_wnd.main_wnd, SW_SHOWNORMAL); // remove maximize-flag + SetWindowPos(_wnd.main_wnd, 0, x, y, w, h, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); + } else { + extern const char _openttd_revision[]; + TCHAR Windowtitle[50]; + + _sntprintf(Windowtitle, sizeof(Windowtitle), _T("OpenTTD %s"), MB_TO_WIDE(_openttd_revision)); + + _wnd.main_wnd = CreateWindow(_T("OTTD"), Windowtitle, style, x, y, w, h, 0, 0, GetModuleHandle(NULL), 0); + if (_wnd.main_wnd == NULL) error("CreateWindow failed"); + ShowWindow(_wnd.main_wnd, showstyle); + } + } + GameSizeChanged(); // invalidate all windows, force redraw +} + +static bool AllocateDibSection(int w, int h) +{ + BITMAPINFO *bi; + HDC dc; + + w = clamp(w, 64, MAX_SCREEN_WIDTH); + h = clamp(h, 64, MAX_SCREEN_HEIGHT); + + if (w == _screen.width && h == _screen.height) + return false; + + _screen.width = w; + _screen.pitch = ALIGN(w, 4); + _screen.height = h; + + if (_wnd.alloced_bits) { + free(_wnd.alloced_bits); + _wnd.alloced_bits = NULL; + } + + bi = alloca(sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD)*256); + memset(bi, 0, sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD)*256); + bi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + + if (_wnd.double_size) { + w = ALIGN(w, 4); + _wnd.alloced_bits = _wnd.buffer_bits = malloc(w * h); + w *= 2; + h *= 2; + } + + bi->bmiHeader.biWidth = _wnd.width = w; + bi->bmiHeader.biHeight = -(_wnd.height = h); + + bi->bmiHeader.biPlanes = 1; + bi->bmiHeader.biBitCount = 8; + bi->bmiHeader.biCompression = BI_RGB; + + if (_wnd.dib_sect) DeleteObject(_wnd.dib_sect); + + dc = GetDC(0); + _wnd.dib_sect = CreateDIBSection(dc, bi, DIB_RGB_COLORS, (VOID**)&_wnd.bitmap_bits, NULL, 0); + if (_wnd.dib_sect == NULL) error("CreateDIBSection failed"); + ReleaseDC(0, dc); + + if (!_wnd.double_size) _wnd.buffer_bits = _wnd.bitmap_bits; + + return true; +} + +static const uint16 default_resolutions[][2] = { + { 640, 480 }, + { 800, 600 }, + { 1024, 768 }, + { 1152, 864 }, + { 1280, 800 }, + { 1280, 960 }, + { 1280, 1024 }, + { 1400, 1050 }, + { 1600, 1200 }, + { 1680, 1050 }, + { 1920, 1200 } +}; + +static void FindResolutions(void) +{ + uint n = 0; + uint i; + DEVMODEA dm; + + /* XXX - EnumDisplaySettingsW crashes with unicows.dll on Windows95 + * Doesn't really matter since we don't pass a string anyways, but still + * a letdown */ + for (i = 0; EnumDisplaySettingsA(NULL, i, &dm) != 0; i++) { + if (dm.dmBitsPerPel == 8 && IS_INT_INSIDE(dm.dmPelsWidth, 640, MAX_SCREEN_WIDTH + 1) && + IS_INT_INSIDE(dm.dmPelsHeight, 480, MAX_SCREEN_HEIGHT + 1)) { + uint j; + + for (j = 0; j < n; j++) { + if (_resolutions[j][0] == dm.dmPelsWidth && _resolutions[j][1] == dm.dmPelsHeight) break; + } + + /* In the previous loop we have checked already existing/added resolutions if + * they are the same as the new ones. If this is not the case (j == n); we have + * looped all and found none, add the new one to the list. If we have reached the + * maximum amount of resolutions, then quit querying the display */ + if (j == n) { + _resolutions[j][0] = dm.dmPelsWidth; + _resolutions[j][1] = dm.dmPelsHeight; + if (++n == lengthof(_resolutions)) break; + } + } + } + + /* We have found no resolutions, show the default list */ + if (n == 0) { + memcpy(_resolutions, default_resolutions, sizeof(default_resolutions)); + n = lengthof(default_resolutions); + } + + _num_resolutions = n; + SortResolutions(_num_resolutions); +} + + +static const char *Win32GdiStart(const char * const *parm) +{ + memset(&_wnd, 0, sizeof(_wnd)); + + RegisterWndClass(); + + MakePalette(); + + FindResolutions(); + + // fullscreen uses those + _wnd.width_org = _cur_resolution[0]; + _wnd.height_org = _cur_resolution[1]; + + AllocateDibSection(_cur_resolution[0], _cur_resolution[1]); + MarkWholeScreenDirty(); + + MakeWindow(_fullscreen); + + return NULL; +} + +static void Win32GdiStop(void) +{ + DeleteObject(_wnd.gdi_palette); + DeleteObject(_wnd.dib_sect); + DestroyWindow(_wnd.main_wnd); + + if (_wnd.fullscreen) ChangeDisplaySettings(NULL, 0); + if (_double_size) { + _cur_resolution[0] *= 2; + _cur_resolution[1] *= 2; + } + + MyShowCursor(true); +} + +// simple upscaler by 2 +static void filter(int left, int top, int width, int height) +{ + uint p = _screen.pitch; + const Pixel *s = _wnd.buffer_bits + top * p + left; + Pixel *d = _wnd.bitmap_bits + top * p * 4 + left * 2; + + for (; height > 0; height--) { + int i; + + for (i = 0; i != width; i++) { + d[i * 2] = d[i * 2 + 1] = d[i * 2 + p * 2] = d[i * 2 + 1 + p * 2] = s[i]; + } + s += p; + d += p * 4; + } +} + +static void Win32GdiMakeDirty(int left, int top, int width, int height) +{ + RECT r = { left, top, left + width, top + height }; + + if (_wnd.double_size) { + filter(left, top, width, height); + r.left *= 2; + r.top *= 2; + r.right *= 2; + r.bottom *= 2; + } + InvalidateRect(_wnd.main_wnd, &r, FALSE); +} + +static void CheckPaletteAnim(void) +{ + if (_pal_last_dirty == -1) + return; + InvalidateRect(_wnd.main_wnd, NULL, FALSE); +} + +static void Win32GdiMainLoop(void) +{ + MSG mesg; + uint32 next_tick = GetTickCount() + 30, cur_ticks; + + _wnd.running = true; + + for (;;) { + while (PeekMessage(&mesg, NULL, 0, 0, PM_REMOVE)) { + InteractiveRandom(); // randomness + DispatchMessage(&mesg); + } + if (_exit_game) return; + +#if defined(_DEBUG) + if (_wnd.has_focus && GetAsyncKeyState(VK_SHIFT) < 0 && +#else + /* Speed up using TAB, but disable for ALT+TAB of course */ + if (_wnd.has_focus && GetAsyncKeyState(VK_TAB) < 0 && GetAsyncKeyState(VK_MENU) >= 0 && +#endif + !_networking && _game_mode != GM_MENU) { + _fast_forward |= 2; + } else if (_fast_forward & 2) { + _fast_forward = 0; + } + + cur_ticks = GetTickCount(); + if ((_fast_forward && !_pause) || cur_ticks > next_tick) + next_tick = cur_ticks; + + if (cur_ticks == next_tick) { + next_tick += 30; + _ctrl_pressed = _wnd.has_focus && GetAsyncKeyState(VK_CONTROL)<0; + _shift_pressed = _wnd.has_focus && GetAsyncKeyState(VK_SHIFT)<0; +#ifdef _DEBUG + _dbg_screen_rect = _wnd.has_focus && GetAsyncKeyState(VK_CAPITAL)<0; +#endif + + // determine which directional keys are down + if (_wnd.has_focus) { + _dirkeys = + (GetAsyncKeyState(VK_LEFT) < 0 ? 1 : 0) + + (GetAsyncKeyState(VK_UP) < 0 ? 2 : 0) + + (GetAsyncKeyState(VK_RIGHT) < 0 ? 4 : 0) + + (GetAsyncKeyState(VK_DOWN) < 0 ? 8 : 0); + } else { + _dirkeys = 0; + } + + GameLoop(); + _cursor.delta.x = _cursor.delta.y = 0; + + if (_force_full_redraw) MarkWholeScreenDirty(); + + GdiFlush(); + _screen.dst_ptr = _wnd.buffer_bits; + UpdateWindows(); + CheckPaletteAnim(); + } else { + Sleep(1); + GdiFlush(); + _screen.dst_ptr = _wnd.buffer_bits; + DrawTextMessage(); + DrawMouseCursor(); + } + } +} + +static bool Win32GdiChangeRes(int w, int h) +{ + _wnd.width = _wnd.width_org = w; + _wnd.height = _wnd.height_org = h; + + MakeWindow(_fullscreen); // _wnd.fullscreen screws up ingame resolution switching + + return true; +} + +static void Win32GdiFullScreen(bool full_screen) +{ + MakeWindow(full_screen); +} + +const HalVideoDriver _win32_video_driver = { + Win32GdiStart, + Win32GdiStop, + Win32GdiMakeDirty, + Win32GdiMainLoop, + Win32GdiChangeRes, + Win32GdiFullScreen, +}; diff --git a/src/video/win32_v.h b/src/video/win32_v.h new file mode 100644 index 000000000..c3b23a61a --- /dev/null +++ b/src/video/win32_v.h @@ -0,0 +1,10 @@ +/* $Id$ */ + +#ifndef VIDEO_WIN32_H +#define VIDEO_WIN32_H + +#include "../hal.h" + +extern const HalVideoDriver _win32_video_driver; + +#endif |