video/win32_v.c
author rubidium
Mon, 04 Sep 2006 17:30:30 +0000
changeset 4548 23b56455df33
parent 4536 f13408cba18f
child 4840 176bc2a66322
permissions -rw-r--r--
(svn r6380) -Codechange: unify all ways to quit OTTD.
This means that in the intro menu the 'Quit' button immediatelly quits
and the 'Quit' in the menu of the normal game and scenario editor
immediatelly quits when the 'autosave_on_exit' patch is turned on.
This is the same way as the OS/window manager initiated quits, like
alt-F4 and the 'x' in the (OS/window manager drawn) title bar of OTTD.
/* $Id$ */

#include "../stdafx.h"
#include "../openttd.h"
#include "../functions.h"
#include "../gfx.h"
#include "../macros.h"
#include "../network.h"
#include "../variables.h"
#include "../win32.h"
#include "../window.h"
#include "win32_v.h"
#include <windows.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 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. There is no such event as WM_MOUSEENTER, we just made this up :) */
#define WM_MOUSEENTER WM_USER + 1
#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();
			_window_maximize = IsZoomed(_wnd.main_wnd);
			return 0;

		case WM_LBUTTONDOWN:
			SetCapture(hwnd);
			_left_button_down = true;
			return 0;

		case WM_LBUTTONUP:
			ReleaseCapture();
			_left_button_down = false;
			_left_button_clicked = false;
			return 0;

		case WM_RBUTTONDOWN:
			SetCapture(hwnd);
			_right_button_down = true;
			_right_button_clicked = true;
			return 0;

		case WM_RBUTTONUP:
			ReleaseCapture();
			_right_button_down = false;
			return 0;

		case WM_MOUSEENTER:
			_cursor.in_window = true;
			DrawMouseCursor();
			break;

		case WM_MOUSELEAVE:
			UndrawMouseCursor();
			_cursor.in_window = false;
			break;

		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 send a WM_MOUSEENTER message. 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);

				if (hwnd != GetCapture()) PostMessage(hwnd, WM_MOUSEENTER, 0, 0L);
			}

			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);
			return 0;
		}

		case WM_KEYDOWN: {
			// this is the rewritten ascii input function
			// it disables windows deadkey handling --> more linux like :D
			WORD w = 0;
			byte ks[256];
			uint scancode;

			GetKeyboardState(ks);
			if (ToAscii(wParam, 0, ks, &w, 0) == 0) {
				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 ((_pressed_key >> 16) == ('D' | WKC_CTRL) && !_wnd.fullscreen) {
				_double_size ^= 1;
				_wnd.double_size = _double_size;
				ClientSizeChanged(_wnd.width, _wnd.height);
				MarkWholeScreenDirty();
			}
			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 */
					_pressed_key = MapWindowsKey(wParam) << 16;
					return 0;

				default: /* ALT in combination with something else */
					_pressed_key = MapWindowsKey(wParam) << 16;
					break;
			}
			break;

		case WM_NCMOUSEMOVE:
			MyShowCursor(true);
			return 0;

		case WM_SIZE:
			if (wParam != SIZE_MINIMIZED) {
				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--;
			}
			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,
			"OTTD"
		};

		registered = true;
		if (!RegisterClass(&wnd)) error("RegisterClass failed");
	}
}

extern const char _openttd_revision[];

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;
		uint style;
		int x, y, w, h;

		_wnd.fullscreen = full_screen;
		if (_wnd.fullscreen) {
			style = WS_POPUP | WS_VISIBLE;
			SetRect(&r, 0, 0, _wnd.width_org, _wnd.height_org);
		} else {
			style = WS_OVERLAPPEDWINDOW | WS_VISIBLE;
			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 {
			char Windowtitle[50];

			snprintf(Windowtitle, lengthof(Windowtitle), "OpenTTD %s", _openttd_revision);

			_wnd.main_wnd = CreateWindow("OTTD", Windowtitle, style, x, y, w, h, 0, 0, GetModuleHandle(NULL), 0);
			if (_wnd.main_wnd == NULL) error("CreateWindow failed");

			/* On startup let's see if we quit maximized the last time, restore that */
			if (_window_maximize) {
				ShowWindow(_wnd.main_wnd, SW_MAXIMIZE);
				_window_maximize = false;
			}
		}
	}
	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;
	DEVMODE dm;

	for (i = 0; EnumDisplaySettings(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
			TranslateMessage(&mesg);
			DispatchMessage(&mesg);
		}
		if (_exit_game) return;

#if defined(_DEBUG)
		if (_wnd.has_focus && GetAsyncKeyState(VK_SHIFT) < 0) {
			if (
#else
		if (_wnd.has_focus && GetAsyncKeyState(VK_TAB) < 0) {
			/* Disable speeding up game with ALT+TAB (if syskey is pressed, the
			 * real key is in the upper 16 bits (see WM_SYSKEYDOWN in WndProcGdi()) */
			if ((_pressed_key >> 16) & WKC_TAB &&
#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,
};