mirror of
https://github.com/JGRennison/OpenTTD-patches.git
synced 2024-11-17 21:25:40 +00:00
ca9ec81fd1
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.
865 lines
20 KiB
C
865 lines
20 KiB
C
/* $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,
|
|
};
|