10 #include "../stdafx.h"
11 #include "../openttd.h"
12 #include "../gfx_func.h"
13 #include "../os/windows/win32.h"
15 #include "../blitter/factory.hpp"
16 #include "../network/network.h"
17 #include "../core/math_func.hpp"
18 #include "../core/random_func.hpp"
19 #include "../texteff.hpp"
20 #include "../thread.h"
21 #include "../progress.h"
22 #include "../window_gui.h"
23 #include "../window_func.h"
24 #include "../framerate_type.h"
29 #include <condition_variable>
31 #include "../safeguards.h"
34 #ifndef MAPVK_VK_TO_CHAR
35 #define MAPVK_VK_TO_CHAR (2)
39 #define PM_QS_INPUT 0x20000
57 bool _force_full_redraw;
58 bool _window_maximize;
74 static void MakePalette()
80 LOGPALETTE *pal = (LOGPALETTE*)alloca(
sizeof(LOGPALETTE) + (256 - 1) *
sizeof(PALETTEENTRY));
82 pal->palVersion = 0x300;
83 pal->palNumEntries = 256;
85 for (uint i = 0; i != 256; i++) {
89 pal->palPalEntry[i].peFlags = 0;
92 _wnd.gdi_palette = CreatePalette(pal);
93 if (_wnd.gdi_palette ==
nullptr)
usererror(
"CreatePalette failed!\n");
96 static void UpdatePalette(HDC dc, uint start, uint count)
101 for (i = 0; i != count; i++) {
105 rgb[i].rgbReserved = 0;
108 SetDIBColorTable(dc, start, count, rgb);
111 bool VideoDriver_Win32::ClaimMousePointer()
113 MyShowCursor(
false,
true);
123 #define AS(x, z) {x, 0, z}
124 #define AM(x, y, z, w) {x, y - x, z}
128 AM(VK_PRIOR, VK_DOWN, WKC_PAGEUP, WKC_DOWN),
130 AM(
'A',
'Z',
'A',
'Z'),
131 AM(
'0',
'9',
'0',
'9'),
133 AS(VK_ESCAPE, WKC_ESC),
134 AS(VK_PAUSE, WKC_PAUSE),
135 AS(VK_BACK, WKC_BACKSPACE),
136 AM(VK_INSERT, VK_DELETE, WKC_INSERT, WKC_DELETE),
138 AS(VK_SPACE, WKC_SPACE),
139 AS(VK_RETURN, WKC_RETURN),
143 AM(VK_F1, VK_F12, WKC_F1, WKC_F12),
146 AM(VK_NUMPAD0, VK_NUMPAD9,
'0',
'9'),
147 AS(VK_DIVIDE, WKC_NUM_DIV),
148 AS(VK_MULTIPLY, WKC_NUM_MUL),
149 AS(VK_SUBTRACT, WKC_NUM_MINUS),
150 AS(VK_ADD, WKC_NUM_PLUS),
151 AS(VK_DECIMAL, WKC_NUM_DECIMAL),
167 static uint MapWindowsKey(uint sym)
172 for (map = _vk_mapping; map !=
endof(_vk_mapping); ++map) {
173 if ((uint)(sym - map->vk_from) <= map->vk_count) {
174 key = sym - map->vk_from + map->map_to;
179 if (GetAsyncKeyState(VK_SHIFT) < 0) key |= WKC_SHIFT;
180 if (GetAsyncKeyState(VK_CONTROL) < 0) key |= WKC_CTRL;
181 if (GetAsyncKeyState(VK_MENU) < 0) key |= WKC_ALT;
185 static bool AllocateDibSection(
int w,
int h,
bool force =
false);
187 static void ClientSizeChanged(
int w,
int h)
190 if (AllocateDibSection(w, h)) {
205 int RedrawScreenDebug()
210 HPALETTE old_palette;
214 dc = GetDC(_wnd.main_wnd);
215 dc2 = CreateCompatibleDC(dc);
217 old_bmp = (HBITMAP)SelectObject(dc2, _wnd.dib_sect);
218 old_palette = SelectPalette(dc, _wnd.gdi_palette, FALSE);
219 BitBlt(dc, 0, 0, _wnd.width, _wnd.height, dc2, 0, 0, SRCCOPY);
220 SelectPalette(dc, old_palette, TRUE);
221 SelectObject(dc2, old_bmp);
223 ReleaseDC(_wnd.main_wnd, dc);
238 _fullscreen = full_screen;
241 if ((full_screen || _wnd.fullscreen) && _wnd.main_wnd) {
242 DestroyWindow(_wnd.main_wnd);
255 (_display_hz != 0 ? DM_DISPLAYFREQUENCY : 0);
257 settings.dmPelsWidth = _wnd.width_org;
258 settings.dmPelsHeight = _wnd.height_org;
259 settings.dmDisplayFrequency = _display_hz;
263 (_support8bpp !=
S8BPP_HARDWARE || ChangeDisplaySettings(&
settings, CDS_FULLSCREEN | CDS_TEST) != DISP_CHANGE_SUCCESSFUL)) {
268 if (ChangeDisplaySettings(&
settings, CDS_FULLSCREEN | CDS_TEST) != DISP_CHANGE_SUCCESSFUL) {
270 GetWindowRect(GetDesktopWindow(), &r);
273 if ((
int)
settings.dmPelsWidth != r.right - r.left || (
int)
settings.dmPelsHeight != r.bottom - r.top) {
278 if (ChangeDisplaySettings(&
settings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL) {
282 }
else if (_wnd.fullscreen) {
284 ChangeDisplaySettings(
nullptr, 0);
286 _wnd.width = _bck_resolution.width;
287 _wnd.height = _bck_resolution.height;
292 DWORD style, showstyle;
295 showstyle = SW_SHOWNORMAL;
296 _wnd.fullscreen = full_screen;
297 if (_wnd.fullscreen) {
299 SetRect(&r, 0, 0, _wnd.width_org, _wnd.height_org);
301 style = WS_OVERLAPPEDWINDOW;
303 if (_window_maximize) showstyle = SW_SHOWMAXIMIZED;
304 SetRect(&r, 0, 0, _wnd.width, _wnd.height);
307 AdjustWindowRect(&r, style, FALSE);
308 w = r.right - r.left;
309 h = r.bottom - r.top;
311 if (_wnd.main_wnd !=
nullptr) {
312 if (!_window_maximize) SetWindowPos(_wnd.main_wnd, 0, 0, 0, w, h, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOMOVE);
314 int x = (GetSystemMetrics(SM_CXSCREEN) - w) / 2;
315 int y = (GetSystemMetrics(SM_CYSCREEN) - h) / 2;
317 char window_title[64];
318 seprintf(window_title,
lastof(window_title),
"OpenTTD %s", _openttd_revision);
320 _wnd.main_wnd = CreateWindow(_T(
"OTTD"), MB_TO_WIDE(window_title), style, x, y, w, h, 0, 0, GetModuleHandle(
nullptr), 0);
321 if (_wnd.main_wnd ==
nullptr)
usererror(
"CreateWindow failed");
322 ShowWindow(_wnd.main_wnd, showstyle);
337 HDC dc2 = CreateCompatibleDC(dc);
338 HBITMAP old_bmp = (HBITMAP)SelectObject(dc2, _wnd.dib_sect);
339 HPALETTE old_palette = SelectPalette(dc, _wnd.gdi_palette, FALSE);
362 BitBlt(dc, 0, 0, _wnd.width, _wnd.height, dc2, 0, 0, SRCCOPY);
363 SelectPalette(dc, old_palette, TRUE);
364 SelectObject(dc2, old_bmp);
368 static void PaintWindowThread()
380 ClientToScreen(_wnd.main_wnd, &pt);
381 OffsetRect(&_wnd.update_rect, pt.x, pt.y);
385 HRGN rgn = CreateRectRgnIndirect(&_wnd.update_rect);
386 HDC dc = GetDCEx(_wnd.main_wnd, rgn, DCX_CLIPSIBLINGS | DCX_CLIPCHILDREN | DCX_INTERSECTRGN);
391 SetRectEmpty(&_wnd.update_rect);
392 ReleaseDC(_wnd.main_wnd, dc);
404 #if !defined(UNICODE)
405 static char prev_char = 0;
407 char input[2] = {(char)charcode, 0};
410 if (prev_char != 0) {
412 input[0] = prev_char;
413 input[1] = (char)charcode;
415 }
else if (IsDBCSLeadByte(charcode)) {
417 prev_char = charcode;
423 int len = MultiByteToWideChar(CP_ACP, 0, input, input_len, w, 2);
434 DEBUG(driver, 1,
"Invalid DBCS character sequence encountered, dropping input");
439 static WChar prev_char = 0;
443 if (prev_char != 0)
DEBUG(driver, 1,
"Got two UTF-16 lead surrogates, dropping the first one");
444 prev_char = charcode;
449 if (prev_char != 0) {
453 DEBUG(driver, 1,
"Got an UTF-16 lead surrogate without a trail surrogate, dropping the lead surrogate");
467 return (_imm_props & IME_PROP_AT_CARET) && !(_imm_props & IME_PROP_SPECIAL_UI);
473 HIMC hIMC = ImmGetContext(hwnd);
476 cf.dwStyle = CFS_POINT;
481 cf.ptCurrentPos.x = _focused_window->
left + pt.x;
482 cf.ptCurrentPos.y = _focused_window->
top + pt.y;
484 cf.ptCurrentPos.x = 0;
485 cf.ptCurrentPos.y = 0;
487 ImmSetCompositionWindow(hIMC, &cf);
489 ImmReleaseContext(hwnd, hIMC);
495 HIMC hIMC = ImmGetContext(hwnd);
499 cf.dwStyle = CFS_EXCLUDE;
503 cf.ptCurrentPos.x = _focused_window->
left + pt.x;
504 cf.ptCurrentPos.y = _focused_window->
top + pt.y;
506 cf.rcArea.left = _focused_window->
left;
507 cf.rcArea.top = _focused_window->
top;
508 cf.rcArea.right = _focused_window->
left + _focused_window->
width;
509 cf.rcArea.bottom = _focused_window->
top + _focused_window->
height;
517 cf.ptCurrentPos.x = 0;
518 cf.ptCurrentPos.y = 0;
519 SetRectEmpty(&cf.rcArea);
521 ImmSetCandidateWindow(hIMC, &cf);
523 ImmReleaseContext(hwnd, hIMC);
529 HIMC hIMC = ImmGetContext(hwnd);
532 if (lParam & GCS_RESULTSTR) {
534 LONG len = ImmGetCompositionString(hIMC, GCS_RESULTSTR,
nullptr, 0);
535 TCHAR *str = (TCHAR *)_alloca(len +
sizeof(TCHAR));
536 len = ImmGetCompositionString(hIMC, GCS_RESULTSTR, str, len);
537 str[len /
sizeof(TCHAR)] =
'\0';
547 lParam &= ~(GCS_RESULTSTR | GCS_RESULTCLAUSE | GCS_RESULTREADCLAUSE | GCS_RESULTREADSTR);
552 LONG len = ImmGetCompositionString(hIMC, GCS_COMPSTR,
nullptr, 0);
553 TCHAR *str = (TCHAR *)_alloca(len +
sizeof(TCHAR));
554 len = ImmGetCompositionString(hIMC, GCS_COMPSTR, str, len);
555 str[len /
sizeof(TCHAR)] =
'\0';
558 static char utf8_buf[1024];
562 LONG caret_bytes = ImmGetCompositionString(hIMC, GCS_CURSORPOS,
nullptr, 0);
563 const char *caret = utf8_buf;
564 for (
const TCHAR *c = str; *c !=
'\0' && *caret !=
'\0' && caret_bytes > 0; c++, caret_bytes--) {
569 if (IsDBCSLeadByte(*c)) {
582 lParam &= ~(GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE | GCS_CURSORPOS | GCS_DELTASTART);
585 ImmReleaseContext(hwnd, hIMC);
587 return lParam != 0 ? DefWindowProc(hwnd, WM_IME_COMPOSITION, wParam, lParam) : 0;
593 HIMC hIMC = ImmGetContext(hwnd);
594 if (hIMC != NULL) ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
595 ImmReleaseContext(hwnd, hIMC);
600 static LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
602 static uint32 keycode = 0;
603 static bool console =
false;
604 static bool in_sizemove =
false;
613 case WM_ENTERSIZEMOVE:
617 case WM_EXITSIZEMOVE:
625 GetUpdateRect(hwnd, &r, FALSE);
626 UnionRect(&_wnd.update_rect, &_wnd.update_rect, &r);
629 ValidateRect(hwnd,
nullptr);
634 BeginPaint(hwnd, &ps);
640 case WM_PALETTECHANGED:
641 if ((HWND)wParam == hwnd)
return 0;
644 case WM_QUERYNEWPALETTE: {
645 HDC hDC = GetWindowDC(hwnd);
646 HPALETTE hOldPalette = SelectPalette(hDC, _wnd.gdi_palette, FALSE);
647 UINT nChanged = RealizePalette(hDC);
649 SelectPalette(hDC, hOldPalette, TRUE);
650 ReleaseDC(hwnd, hDC);
651 if (nChanged != 0) InvalidateRect(hwnd,
nullptr, FALSE);
656 HandleExitGameRequest();
697 int x = (int16)LOWORD(lParam);
698 int y = (int16)HIWORD(lParam);
706 tme.cbSize =
sizeof(tme);
707 tme.dwFlags = TME_LEAVE;
708 tme.hwndTrack = hwnd;
710 TrackMouseEvent(&tme);
717 while (PeekMessage(&m, hwnd, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_REMOVE | PM_NOYIELD | PM_QS_INPUT)) {
718 x = (int16)LOWORD(m.lParam);
719 y = (int16)HIWORD(m.lParam);
725 pt.x = _cursor.
pos.x;
726 pt.y = _cursor.
pos.y;
727 ClientToScreen(hwnd, &pt);
728 SetCursorPos(pt.x, pt.y);
735 case WM_INPUTLANGCHANGE:
739 case WM_IME_SETCONTEXT:
744 case WM_IME_STARTCOMPOSITION:
749 case WM_IME_COMPOSITION:
752 case WM_IME_ENDCOMPOSITION:
762 #if !defined(UNICODE)
764 if (
GB(wParam, 8, 8) != 0) {
773 console =
GB(lParam, 16, 8) == 41;
777 uint scancode =
GB(lParam, 16, 8);
778 uint charcode = wParam;
782 if (console && scancode == 41) {
789 uint cur_keycode = keycode;
797 uint scancode =
GB(lParam, 16, 8);
798 keycode = scancode == 41 ? (uint)WKC_BACKQUOTE : MapWindowsKey(wParam);
802 if (PeekMessage(&msg,
nullptr, 0, 0, PM_NOREMOVE)) {
803 if ((msg.message == WM_CHAR || msg.message == WM_DEADCHAR) &&
GB(lParam, 16, 8) ==
GB(msg.lParam, 16, 8)) {
808 uint charcode = MapVirtualKey(wParam, MAPVK_VK_TO_CHAR);
817 if (
HasBit(charcode, 31) && !console) {
818 if (scancode == 41) {
827 uint cur_keycode = keycode;
837 ToggleFullScreen(!_wnd.fullscreen);
854 if (wParam != SIZE_MINIMIZED) {
857 _window_maximize = (wParam == SIZE_MAXIMIZED || (_window_maximize && _fullscreen));
858 if (_window_maximize || _fullscreen) _bck_resolution =
_cur_resolution;
859 ClientSizeChanged(LOWORD(lParam), HIWORD(lParam));
864 RECT *r = (RECT*)lParam;
868 SetRect(&r2, 0, 0, 0, 0);
869 AdjustWindowRect(&r2, GetWindowLong(hwnd, GWL_STYLE), FALSE);
871 w = r->right - r->left - (r2.right - r2.left);
872 h = r->bottom - r->top - (r2.bottom - r2.top);
875 SetRect(&r2, 0, 0, w, h);
877 AdjustWindowRect(&r2, GetWindowLong(hwnd, GWL_STYLE), FALSE);
878 w = r2.right - r2.left;
879 h = r2.bottom - r2.top;
883 r->bottom = r->top + h;
886 case WMSZ_BOTTOMLEFT:
887 r->bottom = r->top + h;
888 r->left = r->right - w;
891 case WMSZ_BOTTOMRIGHT:
892 r->bottom = r->top + h;
893 r->right = r->left + w;
897 r->left = r->right - w;
901 r->right = r->left + w;
905 r->top = r->bottom - h;
909 r->top = r->bottom - h;
910 r->left = r->right - w;
914 r->top = r->bottom - h;
915 r->right = r->left + w;
922 #if !defined(WM_MOUSEWHEEL)
923 # define WM_MOUSEWHEEL 0x020A
925 #if !defined(GET_WHEEL_DELTA_WPARAM)
926 # define GET_WHEEL_DELTA_WPARAM(wparam) ((short)HIWORD(wparam))
929 case WM_MOUSEWHEEL: {
930 int delta = GET_WHEEL_DELTA_WPARAM(wParam);
934 }
else if (delta > 0) {
942 _wnd.has_focus =
true;
947 _wnd.has_focus =
false;
952 if (_exit_game)
break;
954 bool active = (LOWORD(wParam) != WA_INACTIVE);
955 bool minimized = (HIWORD(wParam) != 0);
956 if (_wnd.fullscreen) {
957 if (active && minimized) {
959 ShowWindow(hwnd, SW_RESTORE);
961 }
else if (!active && !minimized) {
963 ShowWindow(hwnd, SW_MINIMIZE);
964 ChangeDisplaySettings(
nullptr, 0);
971 return DefWindowProc(hwnd, msg, wParam, lParam);
974 static void RegisterWndClass()
976 static bool registered =
false;
978 if (registered)
return;
980 HINSTANCE hinst = GetModuleHandle(
nullptr);
987 LoadIcon(hinst, MAKEINTRESOURCE(100)),
988 LoadCursor(
nullptr, IDC_ARROW),
995 if (!RegisterClass(&wnd))
usererror(
"RegisterClass failed");
998 static bool AllocateDibSection(
int w,
int h,
bool force)
1004 w = std::max(w, 64);
1005 h = std::max(h, 64);
1007 if (!force && w == _screen.width && h == _screen.height)
return false;
1009 bi = (BITMAPINFO*)alloca(
sizeof(BITMAPINFOHEADER) +
sizeof(RGBQUAD) * 256);
1010 memset(bi, 0,
sizeof(BITMAPINFOHEADER) +
sizeof(RGBQUAD) * 256);
1011 bi->bmiHeader.biSize =
sizeof(BITMAPINFOHEADER);
1013 bi->bmiHeader.biWidth = _wnd.width = w;
1014 bi->bmiHeader.biHeight = -(_wnd.height = h);
1016 bi->bmiHeader.biPlanes = 1;
1017 bi->bmiHeader.biBitCount = bpp;
1018 bi->bmiHeader.biCompression = BI_RGB;
1020 if (_wnd.dib_sect) DeleteObject(_wnd.dib_sect);
1023 _wnd.dib_sect = CreateDIBSection(dc, bi, DIB_RGB_COLORS, (VOID**)&_wnd.buffer_bits,
nullptr, 0);
1024 if (_wnd.dib_sect ==
nullptr)
usererror(
"CreateDIBSection failed");
1028 _screen.pitch = (bpp == 8) ?
Align(w, 4) : w;
1030 _screen.dst_ptr = _wnd.buffer_bits;
1035 static const Dimension default_resolutions[] = {
1049 static void FindResolutions()
1062 for (i = 0; EnumDisplaySettingsA(
nullptr, i, &dm) != 0; i++) {
1063 if (dm.dmBitsPerPel != bpp || dm.dmPelsWidth < 640 || dm.dmPelsHeight < 480)
continue;
1065 _resolutions.emplace_back(dm.dmPelsWidth, dm.dmPelsHeight);
1070 _resolutions.assign(std::begin(default_resolutions), std::end(default_resolutions));
1084 memset(&_wnd, 0,
sizeof(_wnd));
1110 DeleteObject(_wnd.gdi_palette);
1111 DeleteObject(_wnd.dib_sect);
1112 DestroyWindow(_wnd.main_wnd);
1114 if (_wnd.fullscreen) ChangeDisplaySettings(
nullptr, 0);
1120 RECT r = { left, top, left +
width, top +
height };
1122 InvalidateRect(_wnd.main_wnd, &r, FALSE);
1125 static void CheckPaletteAnim()
1130 InvalidateRect(_wnd.main_wnd,
nullptr, FALSE);
1136 uint32 cur_ticks = GetTickCount();
1137 uint32 last_cur_ticks = cur_ticks;
1140 std::thread draw_thread;
1141 std::unique_lock<std::recursive_mutex> draw_lock;
1154 draw_lock = std::unique_lock<std::recursive_mutex>(*
_draw_mutex);
1162 draw_lock.release();
1168 DEBUG(driver, 1,
"Threaded drawing enabled");
1175 _wnd.running =
true;
1179 uint32 prev_cur_ticks = cur_ticks;
1181 while (PeekMessage(&mesg,
nullptr, 0, 0, PM_REMOVE)) {
1182 InteractiveRandom();
1185 DispatchMessage(&mesg);
1187 if (_exit_game)
break;
1190 if (_wnd.has_focus && GetAsyncKeyState(VK_SHIFT) < 0 &&
1193 if (_wnd.has_focus && GetAsyncKeyState(VK_TAB) < 0 && GetAsyncKeyState(VK_MENU) >= 0 &&
1197 }
else if (_fast_forward & 2) {
1201 cur_ticks = GetTickCount();
1202 if (cur_ticks >= next_tick || (_fast_forward && !
_pause_mode) || cur_ticks < prev_cur_ticks) {
1204 last_cur_ticks = cur_ticks;
1209 _ctrl_pressed = _wnd.has_focus && GetAsyncKeyState(VK_CONTROL)<0;
1213 if (_wnd.has_focus) {
1215 (GetAsyncKeyState(VK_LEFT) < 0 ? 1 : 0) +
1216 (GetAsyncKeyState(VK_UP) < 0 ? 2 : 0) +
1217 (GetAsyncKeyState(VK_RIGHT) < 0 ? 4 : 0) +
1218 (GetAsyncKeyState(VK_DOWN) < 0 ? 8 : 0);
1257 if (draw_lock.owns_lock()) draw_lock.unlock();
1258 draw_lock.release();
1270 std::unique_lock<std::recursive_mutex>
lock;
1273 if (_window_maximize) ShowWindow(_wnd.main_wnd, SW_SHOWNORMAL);
1275 _wnd.width = _wnd.width_org = w;
1276 _wnd.height = _wnd.height_org = h;
1283 std::unique_lock<std::recursive_mutex>
lock;
1292 return AllocateDibSection(_screen.width, _screen.height,
true) && this->
MakeWindow(_fullscreen);
1307 std::unique_lock<std::recursive_mutex>
lock;
1315 Dimension VideoDriver_Win32::GetScreenSize()
const
1317 return {
static_cast<uint
>(GetSystemMetrics(SM_CXSCREEN)),
static_cast<uint
>(GetSystemMetrics(SM_CYSCREEN)) };