summaryrefslogtreecommitdiff
path: root/src/video
diff options
context:
space:
mode:
authorrubidium <rubidium@openttd.org>2007-01-02 19:19:48 +0000
committerrubidium <rubidium@openttd.org>2007-01-02 19:19:48 +0000
commit66bbf336c6af7353ef0aeed58002c46543b30635 (patch)
treead4a63860df2626b22f77e7dac712e958bea54cb /src/video
parentccc0a3f4dbf58c005b22341ac8874252924690cd (diff)
downloadopenttd-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.h127
-rw-r--r--src/video/cocoa_v.h10
-rw-r--r--src/video/cocoa_v.m2064
-rw-r--r--src/video/dedicated_v.c298
-rw-r--r--src/video/dedicated_v.h10
-rw-r--r--src/video/null_v.c45
-rw-r--r--src/video/null_v.h10
-rw-r--r--src/video/sdl_v.c515
-rw-r--r--src/video/sdl_v.h10
-rw-r--r--src/video/win32_v.c876
-rw-r--r--src/video/win32_v.h10
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