Update readme.md
[openttd-joker.git] / src / video / win32_v.cpp
blob107c310200234d6432110ed5ea381fa6ea7b85b7
1 /* $Id: win32_v.cpp 26367 2014-02-23 16:08:50Z michi_cc $ */
3 /*
4 * This file is part of OpenTTD.
5 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 */
10 /** @file win32_v.cpp Implementation of the Windows (GDI) video driver. */
12 #include "../stdafx.h"
13 #include "../openttd.h"
14 #include "../gfx_func.h"
15 #include "../os/windows/win32.h"
16 #include "../rev.h"
17 #include "../blitter/factory.hpp"
18 #include "../network/network.h"
19 #include "../core/math_func.hpp"
20 #include "../core/random_func.hpp"
21 #include "../texteff.hpp"
22 #include "../thread/thread.h"
23 #include "../progress.h"
24 #include "../window_gui.h"
25 #include "../window_func.h"
26 #include "win32_v.h"
27 #include <windows.h>
28 #include <imm.h>
30 #include "../safeguards.h"
32 /* Missing define in MinGW headers. */
33 #ifndef MAPVK_VK_TO_CHAR
34 #define MAPVK_VK_TO_CHAR (2)
35 #endif
37 #ifndef PM_QS_INPUT
38 #define PM_QS_INPUT 0x20000
39 #endif
41 static struct {
42 HWND main_wnd;
43 HBITMAP dib_sect;
44 void *buffer_bits;
45 HPALETTE gdi_palette;
46 RECT update_rect;
47 int width;
48 int height;
49 int width_org;
50 int height_org;
51 bool fullscreen;
52 bool has_focus;
53 bool running;
54 } _wnd;
56 bool _force_full_redraw;
57 bool _window_maximize;
58 uint _display_hz;
59 static Dimension _bck_resolution;
60 #if !defined(WINCE) || _WIN32_WCE >= 0x400
61 DWORD _imm_props;
62 #endif
64 /** Whether the drawing is/may be done in a separate thread. */
65 static bool _draw_threaded;
66 /** Thread used to 'draw' to the screen, i.e. push data to the screen. */
67 static ThreadObject *_draw_thread = nullptr;
68 /** Mutex to keep the access to the shared memory controlled. */
69 static ThreadMutex *_draw_mutex = nullptr;
70 /** Event that is signaled when the drawing thread has finished initializing. */
71 static HANDLE _draw_thread_initialized = nullptr;
72 /** Should we keep continue drawing? */
73 static volatile bool _draw_continue;
74 /** Local copy of the palette for use in the drawing thread. */
75 static Palette _local_palette;
77 static void MakePalette()
79 LOGPALETTE *pal = (LOGPALETTE*)alloca(sizeof(LOGPALETTE) + (256 - 1) * sizeof(PALETTEENTRY));
81 pal->palVersion = 0x300;
82 pal->palNumEntries = 256;
84 for (uint i = 0; i != 256; i++) {
85 pal->palPalEntry[i].peRed = _cur_palette.palette[i].r;
86 pal->palPalEntry[i].peGreen = _cur_palette.palette[i].g;
87 pal->palPalEntry[i].peBlue = _cur_palette.palette[i].b;
88 pal->palPalEntry[i].peFlags = 0;
91 _wnd.gdi_palette = CreatePalette(pal);
92 if (_wnd.gdi_palette == nullptr) usererror("CreatePalette failed!\n");
94 _cur_palette.first_dirty = 0;
95 _cur_palette.count_dirty = 256;
96 _local_palette = _cur_palette;
99 static void UpdatePalette(HDC dc, uint start, uint count)
101 RGBQUAD rgb[256];
102 uint i;
104 for (i = 0; i != count; i++) {
105 rgb[i].rgbRed = _local_palette.palette[start + i].r;
106 rgb[i].rgbGreen = _local_palette.palette[start + i].g;
107 rgb[i].rgbBlue = _local_palette.palette[start + i].b;
108 rgb[i].rgbReserved = 0;
111 SetDIBColorTable(dc, start, count, rgb);
114 bool VideoDriver_Win32::ClaimMousePointer()
116 MyShowCursor(false, true);
117 return true;
120 struct VkMapping {
121 byte vk_from;
122 byte vk_count;
123 byte map_to;
126 #define AS(x, z) {x, 0, z}
127 #define AM(x, y, z, w) {x, y - x, z}
129 static const VkMapping _vk_mapping[] = {
130 /* Pageup stuff + up/down */
131 AM(VK_PRIOR, VK_DOWN, WKC_PAGEUP, WKC_DOWN),
132 /* Map letters & digits */
133 AM('A', 'Z', 'A', 'Z'),
134 AM('0', '9', '0', '9'),
136 AS(VK_ESCAPE, WKC_ESC),
137 AS(VK_PAUSE, WKC_PAUSE),
138 AS(VK_BACK, WKC_BACKSPACE),
139 AM(VK_INSERT, VK_DELETE, WKC_INSERT, WKC_DELETE),
141 AS(VK_SPACE, WKC_SPACE),
142 AS(VK_RETURN, WKC_RETURN),
143 AS(VK_TAB, WKC_TAB),
145 /* Function keys */
146 AM(VK_F1, VK_F12, WKC_F1, WKC_F12),
148 /* Numeric part */
149 AM(VK_NUMPAD0, VK_NUMPAD9, '0', '9'),
150 AS(VK_DIVIDE, WKC_NUM_DIV),
151 AS(VK_MULTIPLY, WKC_NUM_MUL),
152 AS(VK_SUBTRACT, WKC_NUM_MINUS),
153 AS(VK_ADD, WKC_NUM_PLUS),
154 AS(VK_DECIMAL, WKC_NUM_DECIMAL),
156 /* Other non-letter keys */
157 AS(0xBF, WKC_SLASH),
158 AS(0xBA, WKC_SEMICOLON),
159 AS(0xBB, WKC_EQUALS),
160 AS(0xDB, WKC_L_BRACKET),
161 AS(0xDC, WKC_BACKSLASH),
162 AS(0xDD, WKC_R_BRACKET),
164 AS(0xDE, WKC_SINGLEQUOTE),
165 AS(0xBC, WKC_COMMA),
166 AS(0xBD, WKC_MINUS),
167 AS(0xBE, WKC_PERIOD)
170 static uint MapWindowsKey(uint sym)
172 const VkMapping *map;
173 uint key = 0;
175 for (map = _vk_mapping; map != endof(_vk_mapping); ++map) {
176 if ((uint)(sym - map->vk_from) <= map->vk_count) {
177 key = sym - map->vk_from + map->map_to;
178 break;
182 if (GetAsyncKeyState(VK_SHIFT) < 0) key |= WKC_SHIFT;
183 if (GetAsyncKeyState(VK_CONTROL) < 0) key |= WKC_CTRL;
184 if (GetAsyncKeyState(VK_MENU) < 0) key |= WKC_ALT;
185 return key;
188 static bool AllocateDibSection(int w, int h, bool force = false);
190 static void ClientSizeChanged(int w, int h)
192 /* allocate new dib section of the new size */
193 if (AllocateDibSection(w, h)) {
194 /* mark all palette colours dirty */
195 _cur_palette.first_dirty = 0;
196 _cur_palette.count_dirty = 256;
197 _local_palette = _cur_palette;
199 BlitterFactory::GetCurrentBlitter()->PostResize();
201 GameSizeChanged();
205 #ifdef _DEBUG
206 /* Keep this function here..
207 * It allows you to redraw the screen from within the MSVC debugger */
208 int RedrawScreenDebug()
210 HDC dc, dc2;
211 static int _fooctr;
212 HBITMAP old_bmp;
213 HPALETTE old_palette;
215 UpdateWindows();
217 dc = GetDC(_wnd.main_wnd);
218 dc2 = CreateCompatibleDC(dc);
220 old_bmp = (HBITMAP)SelectObject(dc2, _wnd.dib_sect);
221 old_palette = SelectPalette(dc, _wnd.gdi_palette, FALSE);
222 BitBlt(dc, 0, 0, _wnd.width, _wnd.height, dc2, 0, 0, SRCCOPY);
223 SelectPalette(dc, old_palette, TRUE);
224 SelectObject(dc2, old_bmp);
225 DeleteDC(dc2);
226 ReleaseDC(_wnd.main_wnd, dc);
228 return _fooctr++;
230 #endif
232 /* Windows 95 will not have a WM_MOUSELEAVE message, so define it if needed */
233 #if !defined(WM_MOUSELEAVE)
234 #define WM_MOUSELEAVE 0x02A3
235 #endif
236 #define TID_POLLMOUSE 1
237 #define MOUSE_POLL_DELAY 75
239 static void CALLBACK TrackMouseTimerProc(HWND hwnd, UINT msg, UINT event, DWORD time)
241 RECT rc;
242 POINT pt;
244 /* Get the rectangle of our window and translate it to screen coordinates.
245 * Compare this with the current screen coordinates of the mouse and if it
246 * falls outside of the area or our window we have left the window. */
247 GetClientRect(hwnd, &rc);
248 MapWindowPoints(hwnd, HWND_DESKTOP, (LPPOINT)(LPRECT)&rc, 2);
249 GetCursorPos(&pt);
251 if (!PtInRect(&rc, pt) || (WindowFromPoint(pt) != hwnd)) {
252 KillTimer(hwnd, event);
253 PostMessage(hwnd, WM_MOUSELEAVE, 0, 0L);
258 * Instantiate a new window.
259 * @param full_screen Whether to make a full screen window or not.
260 * @return True if the window could be created.
262 bool VideoDriver_Win32::MakeWindow(bool full_screen)
264 _fullscreen = full_screen;
266 /* recreate window? */
267 if ((full_screen || _wnd.fullscreen) && _wnd.main_wnd) {
268 DestroyWindow(_wnd.main_wnd);
269 _wnd.main_wnd = 0;
272 #if defined(WINCE)
273 /* WinCE is always fullscreen */
274 #else
275 if (full_screen) {
276 DEVMODE settings;
278 memset(&settings, 0, sizeof(settings));
279 settings.dmSize = sizeof(settings);
280 settings.dmFields =
281 DM_BITSPERPEL |
282 DM_PELSWIDTH |
283 DM_PELSHEIGHT |
284 (_display_hz != 0 ? DM_DISPLAYFREQUENCY : 0);
285 settings.dmBitsPerPel = BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
286 settings.dmPelsWidth = _wnd.width_org;
287 settings.dmPelsHeight = _wnd.height_org;
288 settings.dmDisplayFrequency = _display_hz;
290 /* Check for 8 bpp support. */
291 if (settings.dmBitsPerPel == 8 &&
292 (_support8bpp != S8BPP_HARDWARE || ChangeDisplaySettings(&settings, CDS_FULLSCREEN | CDS_TEST) != DISP_CHANGE_SUCCESSFUL)) {
293 settings.dmBitsPerPel = 32;
296 /* Test fullscreen with current resolution, if it fails use desktop resolution. */
297 if (ChangeDisplaySettings(&settings, CDS_FULLSCREEN | CDS_TEST) != DISP_CHANGE_SUCCESSFUL) {
298 RECT r;
299 GetWindowRect(GetDesktopWindow(), &r);
300 /* Guard against recursion. If we already failed here once, just fall through to
301 * the next ChangeDisplaySettings call which will fail and error out appropriately. */
302 if ((int)settings.dmPelsWidth != r.right - r.left || (int)settings.dmPelsHeight != r.bottom - r.top) {
303 return this->ChangeResolution(r.right - r.left, r.bottom - r.top);
307 if (ChangeDisplaySettings(&settings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL) {
308 this->MakeWindow(false); // don't care about the result
309 return false; // the request failed
311 } else if (_wnd.fullscreen) {
312 /* restore display? */
313 ChangeDisplaySettings(nullptr, 0);
314 /* restore the resolution */
315 _wnd.width = _bck_resolution.width;
316 _wnd.height = _bck_resolution.height;
318 #endif
321 RECT r;
322 DWORD style, showstyle;
323 int w, h;
325 showstyle = SW_SHOWNORMAL;
326 _wnd.fullscreen = full_screen;
327 if (_wnd.fullscreen) {
328 style = WS_POPUP;
329 SetRect(&r, 0, 0, _wnd.width_org, _wnd.height_org);
330 } else {
331 style = WS_OVERLAPPEDWINDOW;
332 /* On window creation, check if we were in maximize mode before */
333 if (_window_maximize) showstyle = SW_SHOWMAXIMIZED;
334 SetRect(&r, 0, 0, _wnd.width, _wnd.height);
337 #if !defined(WINCE)
338 AdjustWindowRect(&r, style, FALSE);
339 #endif
340 w = r.right - r.left;
341 h = r.bottom - r.top;
343 if (_wnd.main_wnd != nullptr) {
344 if (!_window_maximize) SetWindowPos(_wnd.main_wnd, 0, 0, 0, w, h, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOMOVE);
345 } else {
346 TCHAR Windowtitle[50];
347 int x = (GetSystemMetrics(SM_CXSCREEN) - w) / 2;
348 int y = (GetSystemMetrics(SM_CYSCREEN) - h) / 2;
350 _sntprintf(Windowtitle, lengthof(Windowtitle), _T("OpenTTD %s"), MB_TO_WIDE(_openttd_revision));
352 _wnd.main_wnd = CreateWindow(_T("OTTD"), Windowtitle, style, x, y, w, h, 0, 0, GetModuleHandle(nullptr), 0);
353 if (_wnd.main_wnd == nullptr) usererror("CreateWindow failed");
354 ShowWindow(_wnd.main_wnd, showstyle);
358 BlitterFactory::GetCurrentBlitter()->PostResize();
360 GameSizeChanged(); // invalidate all windows, force redraw
361 return true; // the request succeeded
364 /** Do palette animation and blit to the window. */
365 static void PaintWindow(HDC dc)
367 HDC dc2 = CreateCompatibleDC(dc);
368 HBITMAP old_bmp = (HBITMAP)SelectObject(dc2, _wnd.dib_sect);
369 HPALETTE old_palette = SelectPalette(dc, _wnd.gdi_palette, FALSE);
371 if (_cur_palette.count_dirty != 0) {
372 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
374 switch (blitter->UsePaletteAnimation()) {
375 case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND:
376 UpdatePalette(dc2, _local_palette.first_dirty, _local_palette.count_dirty);
377 break;
379 case Blitter::PALETTE_ANIMATION_BLITTER:
380 blitter->PaletteAnimate(_local_palette);
381 break;
383 case Blitter::PALETTE_ANIMATION_NONE:
384 break;
386 default:
387 NOT_REACHED();
389 _cur_palette.count_dirty = 0;
392 BitBlt(dc, 0, 0, _wnd.width, _wnd.height, dc2, 0, 0, SRCCOPY);
393 SelectPalette(dc, old_palette, TRUE);
394 SelectObject(dc2, old_bmp);
395 DeleteDC(dc2);
398 static void PaintWindowThread(void *)
400 /* First tell the main thread we're started */
401 _draw_mutex->BeginCritical();
402 SetEvent(_draw_thread_initialized);
404 /* Now wait for the first thing to draw! */
405 _draw_mutex->WaitForSignal();
407 while (_draw_continue) {
408 /* Convert update region from logical to device coordinates. */
409 POINT pt = {0, 0};
410 ClientToScreen(_wnd.main_wnd, &pt);
411 OffsetRect(&_wnd.update_rect, pt.x, pt.y);
413 /* Create a device context that is clipped to the region we need to draw.
414 * GetDCEx 'consumes' the update region, so we may not destroy it ourself. */
415 HRGN rgn = CreateRectRgnIndirect(&_wnd.update_rect);
416 HDC dc = GetDCEx(_wnd.main_wnd, rgn, DCX_CLIPSIBLINGS | DCX_CLIPCHILDREN | DCX_INTERSECTRGN);
418 PaintWindow(dc);
420 /* Clear update rect. */
421 SetRectEmpty(&_wnd.update_rect);
422 ReleaseDC(_wnd.main_wnd, dc);
424 /* Flush GDI buffer to ensure drawing here doesn't conflict with any GDI usage in the main WndProc. */
425 GdiFlush();
427 _draw_mutex->WaitForSignal();
430 _draw_mutex->EndCritical();
431 _draw_thread->Exit();
434 /** Forward key presses to the window system. */
435 static LRESULT HandleCharMsg(uint keycode, WChar charcode)
437 #if !defined(UNICODE)
438 static char prev_char = 0;
440 char input[2] = {(char)charcode, 0};
441 int input_len = 1;
443 if (prev_char != 0) {
444 /* We stored a lead byte previously, combine it with this byte. */
445 input[0] = prev_char;
446 input[1] = (char)charcode;
447 input_len = 2;
448 } else if (IsDBCSLeadByte(charcode)) {
449 /* We got a lead byte, store and exit. */
450 prev_char = charcode;
451 return 0;
453 prev_char = 0;
455 wchar_t w[2]; // Can get up to two code points as a result.
456 int len = MultiByteToWideChar(CP_ACP, 0, input, input_len, w, 2);
457 switch (len) {
458 case 1: // Normal unicode character.
459 charcode = w[0];
460 break;
462 case 2: // Got an UTF-16 surrogate pair back.
463 charcode = Utf16DecodeSurrogate(w[0], w[1]);
464 break;
466 default: // Some kind of error.
467 DEBUG(driver, 1, "Invalid DBCS character sequence encountered, dropping input");
468 charcode = 0;
469 break;
471 #else
472 static WChar prev_char = 0;
474 /* Did we get a lead surrogate? If yes, store and exit. */
475 if (Utf16IsLeadSurrogate(charcode)) {
476 if (prev_char != 0) DEBUG(driver, 1, "Got two UTF-16 lead surrogates, dropping the first one");
477 prev_char = charcode;
478 return 0;
481 /* Stored lead surrogate and incoming trail surrogate? Combine and forward to input handling. */
482 if (prev_char != 0) {
483 if (Utf16IsTrailSurrogate(charcode)) {
484 charcode = Utf16DecodeSurrogate(prev_char, charcode);
485 } else {
486 DEBUG(driver, 1, "Got an UTF-16 lead surrogate without a trail surrogate, dropping the lead surrogate");
489 prev_char = 0;
490 #endif /* UNICODE */
492 HandleKeypress(keycode, charcode);
494 return 0;
497 #if !defined(WINCE) || _WIN32_WCE >= 0x400
498 /** Should we draw the composition string ourself, i.e is this a normal IME? */
499 static bool DrawIMECompositionString()
501 return (_imm_props & IME_PROP_AT_CARET) && !(_imm_props & IME_PROP_SPECIAL_UI);
504 /** Set position of the composition window to the caret position. */
505 static void SetCompositionPos(HWND hwnd)
507 HIMC hIMC = ImmGetContext(hwnd);
508 if (hIMC != NULL) {
509 COMPOSITIONFORM cf;
510 cf.dwStyle = CFS_POINT;
512 if (EditBoxInGlobalFocus()) {
513 /* Get caret position. */
514 Point pt = _focused_window->GetCaretPosition();
515 cf.ptCurrentPos.x = _focused_window->left + pt.x;
516 cf.ptCurrentPos.y = _focused_window->top + pt.y;
517 } else {
518 cf.ptCurrentPos.x = 0;
519 cf.ptCurrentPos.y = 0;
521 ImmSetCompositionWindow(hIMC, &cf);
523 ImmReleaseContext(hwnd, hIMC);
526 /** Set the position of the candidate window. */
527 static void SetCandidatePos(HWND hwnd)
529 HIMC hIMC = ImmGetContext(hwnd);
530 if (hIMC != NULL) {
531 CANDIDATEFORM cf;
532 cf.dwIndex = 0;
533 cf.dwStyle = CFS_EXCLUDE;
535 if (EditBoxInGlobalFocus()) {
536 Point pt = _focused_window->GetCaretPosition();
537 cf.ptCurrentPos.x = _focused_window->left + pt.x;
538 cf.ptCurrentPos.y = _focused_window->top + pt.y;
539 if (_focused_window->window_class == WC_CONSOLE) {
540 cf.rcArea.left = _focused_window->left;
541 cf.rcArea.top = _focused_window->top;
542 cf.rcArea.right = _focused_window->left + _focused_window->width;
543 cf.rcArea.bottom = _focused_window->top + _focused_window->height;
544 } else {
545 cf.rcArea.left = _focused_window->left + _focused_window->nested_focus->pos_x;
546 cf.rcArea.top = _focused_window->top + _focused_window->nested_focus->pos_y;
547 cf.rcArea.right = cf.rcArea.left + _focused_window->nested_focus->current_x;
548 cf.rcArea.bottom = cf.rcArea.top + _focused_window->nested_focus->current_y;
550 } else {
551 cf.ptCurrentPos.x = 0;
552 cf.ptCurrentPos.y = 0;
553 SetRectEmpty(&cf.rcArea);
555 ImmSetCandidateWindow(hIMC, &cf);
557 ImmReleaseContext(hwnd, hIMC);
560 /** Handle WM_IME_COMPOSITION messages. */
561 static LRESULT HandleIMEComposition(HWND hwnd, WPARAM wParam, LPARAM lParam)
563 HIMC hIMC = ImmGetContext(hwnd);
565 if (hIMC != NULL) {
566 if (lParam & GCS_RESULTSTR) {
567 /* Read result string from the IME. */
568 LONG len = ImmGetCompositionString(hIMC, GCS_RESULTSTR, nullptr, 0); // Length is always in bytes, even in UNICODE build.
569 TCHAR *str = (TCHAR *)_alloca(len + sizeof(TCHAR));
570 len = ImmGetCompositionString(hIMC, GCS_RESULTSTR, str, len);
571 str[len / sizeof(TCHAR)] = '\0';
573 /* Transmit text to windowing system. */
574 if (len > 0) {
575 HandleTextInput(nullptr, true); // Clear marked string.
576 HandleTextInput(FS2OTTD(str));
578 SetCompositionPos(hwnd);
580 /* Don't pass the result string on to the default window proc. */
581 lParam &= ~(GCS_RESULTSTR | GCS_RESULTCLAUSE | GCS_RESULTREADCLAUSE | GCS_RESULTREADSTR);
584 if ((lParam & GCS_COMPSTR) && DrawIMECompositionString()) {
585 /* Read composition string from the IME. */
586 LONG len = ImmGetCompositionString(hIMC, GCS_COMPSTR, nullptr, 0); // Length is always in bytes, even in UNICODE build.
587 TCHAR *str = (TCHAR *)_alloca(len + sizeof(TCHAR));
588 len = ImmGetCompositionString(hIMC, GCS_COMPSTR, str, len);
589 str[len / sizeof(TCHAR)] = '\0';
591 if (len > 0) {
592 static char utf8_buf[1024];
593 convert_from_fs(str, utf8_buf, lengthof(utf8_buf));
595 /* Convert caret position from bytes in the input string to a position in the UTF-8 encoded string. */
596 LONG caret_bytes = ImmGetCompositionString(hIMC, GCS_CURSORPOS, nullptr, 0);
597 const char *caret = utf8_buf;
598 for (const TCHAR *c = str; *c != '\0' && *caret != '\0' && caret_bytes > 0; c++, caret_bytes--) {
599 /* Skip DBCS lead bytes or leading surrogates. */
600 #ifdef UNICODE
601 if (Utf16IsLeadSurrogate(*c)) {
602 #else
603 if (IsDBCSLeadByte(*c)) {
604 #endif
605 c++;
606 caret_bytes--;
608 Utf8Consume(&caret);
611 HandleTextInput(utf8_buf, true, caret);
612 } else {
613 HandleTextInput(nullptr, true);
616 lParam &= ~(GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE | GCS_CURSORPOS | GCS_DELTASTART);
619 ImmReleaseContext(hwnd, hIMC);
621 return lParam != 0 ? DefWindowProc(hwnd, WM_IME_COMPOSITION, wParam, lParam) : 0;
624 /** Clear the current composition string. */
625 static void CancelIMEComposition(HWND hwnd)
627 HIMC hIMC = ImmGetContext(hwnd);
628 if (hIMC != NULL) ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
629 ImmReleaseContext(hwnd, hIMC);
630 /* Clear any marked string from the current edit box. */
631 HandleTextInput(nullptr, true);
634 #else
636 static bool DrawIMECompositionString() { return false; }
637 static void SetCompositionPos(HWND hwnd) {}
638 static void SetCandidatePos(HWND hwnd) {}
639 static void CancelIMEComposition(HWND hwnd) {}
641 #endif /* !defined(WINCE) || _WIN32_WCE >= 0x400 */
643 static LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
645 static uint32 keycode = 0;
646 static bool console = false;
647 static bool in_sizemove = false;
649 switch (msg) {
650 case WM_CREATE:
651 SetTimer(hwnd, TID_POLLMOUSE, MOUSE_POLL_DELAY, (TIMERPROC)TrackMouseTimerProc);
652 SetCompositionPos(hwnd);
653 #if !defined(WINCE) || _WIN32_WCE >= 0x400
654 _imm_props = ImmGetProperty(GetKeyboardLayout(0), IGP_PROPERTY);
655 #endif
656 break;
658 case WM_ENTERSIZEMOVE:
659 in_sizemove = true;
660 break;
662 case WM_EXITSIZEMOVE:
663 in_sizemove = false;
664 break;
666 case WM_PAINT:
667 if (!in_sizemove && _draw_mutex != nullptr && !HasModalProgress()) {
668 /* Get the union of the old update rect and the new update rect. */
669 RECT r;
670 GetUpdateRect(hwnd, &r, FALSE);
671 UnionRect(&_wnd.update_rect, &_wnd.update_rect, &r);
673 /* Mark the window as updated, otherwise Windows would send more WM_PAINT messages. */
674 ValidateRect(hwnd, nullptr);
675 _draw_mutex->SendSignal();
676 } else {
677 PAINTSTRUCT ps;
679 BeginPaint(hwnd, &ps);
680 PaintWindow(ps.hdc);
681 EndPaint(hwnd, &ps);
683 return 0;
685 case WM_PALETTECHANGED:
686 if ((HWND)wParam == hwnd) return 0;
687 FALLTHROUGH;
689 case WM_QUERYNEWPALETTE: {
690 HDC hDC = GetWindowDC(hwnd);
691 HPALETTE hOldPalette = SelectPalette(hDC, _wnd.gdi_palette, FALSE);
692 UINT nChanged = RealizePalette(hDC);
694 SelectPalette(hDC, hOldPalette, TRUE);
695 ReleaseDC(hwnd, hDC);
696 if (nChanged != 0) InvalidateRect(hwnd, nullptr, FALSE);
697 return 0;
700 case WM_CLOSE:
701 HandleExitGameRequest();
702 return 0;
704 case WM_DESTROY:
705 if (_window_maximize) _cur_resolution = _bck_resolution;
706 return 0;
708 case WM_LBUTTONDOWN:
709 SetCapture(hwnd);
710 _left_button_down = true;
711 HandleMouseEvents();
712 return 0;
714 case WM_LBUTTONUP:
715 ReleaseCapture();
716 _left_button_down = false;
717 _left_button_clicked = false;
718 HandleMouseEvents();
719 return 0;
721 case WM_RBUTTONDOWN:
722 SetCapture(hwnd);
723 _right_button_down = true;
724 _right_button_clicked = true;
725 HandleMouseEvents();
726 return 0;
728 case WM_RBUTTONUP:
729 ReleaseCapture();
730 _right_button_down = false;
731 HandleMouseEvents();
732 return 0;
734 case WM_MOUSELEAVE:
735 UndrawMouseCursor();
736 _cursor.in_window = false;
738 if (!_left_button_down && !_right_button_down) MyShowCursor(true);
739 return 0;
741 case WM_MOUSEMOVE: {
742 int x = (int16)LOWORD(lParam);
743 int y = (int16)HIWORD(lParam);
745 /* If the mouse was not in the window and it has moved it means it has
746 * come into the window, so start drawing the mouse. Also start
747 * tracking the mouse for exiting the window */
748 if (!_cursor.in_window) {
749 _cursor.in_window = true;
750 SetTimer(hwnd, TID_POLLMOUSE, MOUSE_POLL_DELAY, (TIMERPROC)TrackMouseTimerProc);
753 if (_cursor.fix_at) {
754 /* Get all queued mouse events now in case we have to warp the cursor. In the
755 * end, we only care about the current mouse position and not bygone events. */
756 MSG m;
757 while (PeekMessage(&m, hwnd, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_REMOVE | PM_NOYIELD | PM_QS_INPUT)) {
758 x = (int16)LOWORD(m.lParam);
759 y = (int16)HIWORD(m.lParam);
763 if (_cursor.UpdateCursorPosition(x, y, false)) {
764 POINT pt;
765 pt.x = _cursor.pos.x;
766 pt.y = _cursor.pos.y;
767 ClientToScreen(hwnd, &pt);
768 SetCursorPos(pt.x, pt.y);
770 MyShowCursor(false);
771 HandleMouseEvents();
772 return 0;
775 #if !defined(WINCE) || _WIN32_WCE >= 0x400
776 case WM_INPUTLANGCHANGE:
777 _imm_props = ImmGetProperty(GetKeyboardLayout(0), IGP_PROPERTY);
778 break;
780 case WM_IME_SETCONTEXT:
781 /* Don't show the composition window if we draw the string ourself. */
782 if (DrawIMECompositionString()) lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
783 break;
785 case WM_IME_STARTCOMPOSITION:
786 SetCompositionPos(hwnd);
787 if (DrawIMECompositionString()) return 0;
788 break;
790 case WM_IME_COMPOSITION:
791 return HandleIMEComposition(hwnd, wParam, lParam);
793 case WM_IME_ENDCOMPOSITION:
794 /* Clear any pending composition string. */
795 HandleTextInput(nullptr, true);
796 if (DrawIMECompositionString()) return 0;
797 break;
799 case WM_IME_NOTIFY:
800 if (wParam == IMN_OPENCANDIDATE) SetCandidatePos(hwnd);
801 break;
803 #if !defined(UNICODE)
804 case WM_IME_CHAR:
805 if (GB(wParam, 8, 8) != 0) {
806 /* DBCS character, send lead byte first. */
807 HandleCharMsg(0, GB(wParam, 8, 8));
809 HandleCharMsg(0, GB(wParam, 0, 8));
810 return 0;
811 #endif
812 #endif
814 case WM_DEADCHAR:
815 console = GB(lParam, 16, 8) == 41;
816 return 0;
818 case WM_CHAR: {
819 uint scancode = GB(lParam, 16, 8);
820 uint charcode = wParam;
822 /* If the console key is a dead-key, we need to press it twice to get a WM_CHAR message.
823 * But we then get two WM_CHAR messages, so ignore the first one */
824 if (console && scancode == 41) {
825 console = false;
826 return 0;
829 /* IMEs and other input methods sometimes send a WM_CHAR without a WM_KEYDOWN,
830 * clear the keycode so a previous WM_KEYDOWN doesn't become 'stuck'. */
831 uint cur_keycode = keycode;
832 keycode = 0;
834 return HandleCharMsg(cur_keycode, charcode);
837 case WM_KEYDOWN: {
838 /* No matter the keyboard layout, we will map the '~' to the console. */
839 uint scancode = GB(lParam, 16, 8);
840 keycode = scancode == 41 ? (uint)WKC_BACKQUOTE : MapWindowsKey(wParam);
842 /* Silently drop all messages handled by WM_CHAR. */
843 MSG msg;
844 if (PeekMessage(&msg, nullptr, 0, 0, PM_NOREMOVE)) {
845 if ((msg.message == WM_CHAR || msg.message == WM_DEADCHAR) && GB(lParam, 16, 8) == GB(msg.lParam, 16, 8)) {
846 return 0;
850 uint charcode = MapVirtualKey(wParam, MAPVK_VK_TO_CHAR);
852 /* No character translation? */
853 if (charcode == 0) {
854 HandleKeypress(keycode, 0);
855 return 0;
858 /* Is the console key a dead key? If yes, ignore the first key down event. */
859 if (HasBit(charcode, 31) && !console) {
860 if (scancode == 41) {
861 console = true;
862 return 0;
865 console = false;
867 /* IMEs and other input methods sometimes send a WM_CHAR without a WM_KEYDOWN,
868 * clear the keycode so a previous WM_KEYDOWN doesn't become 'stuck'. */
869 uint cur_keycode = keycode;
870 keycode = 0;
872 return HandleCharMsg(cur_keycode, LOWORD(charcode));
875 case WM_SYSKEYDOWN: // user presses F10 or Alt, both activating the title-menu
876 switch (wParam) {
877 case VK_RETURN:
878 case 'F': // Full Screen on ALT + ENTER/F
879 ToggleFullScreen(!_wnd.fullscreen);
880 return 0;
882 case VK_MENU: // Just ALT
883 return 0; // do nothing
885 case VK_F10: // F10, ignore activation of menu
886 HandleKeypress(MapWindowsKey(wParam), 0);
887 return 0;
889 default: // ALT in combination with something else
890 HandleKeypress(MapWindowsKey(wParam), 0);
891 break;
893 break;
895 case WM_SIZE:
896 if (wParam != SIZE_MINIMIZED) {
897 /* Set maximized flag when we maximize (obviously), but also when we
898 * switched to fullscreen from a maximized state */
899 _window_maximize = (wParam == SIZE_MAXIMIZED || (_window_maximize && _fullscreen));
900 if (_window_maximize || _fullscreen) _bck_resolution = _cur_resolution;
901 ClientSizeChanged(LOWORD(lParam), HIWORD(lParam));
903 return 0;
905 #if !defined(WINCE)
906 case WM_SIZING: {
907 RECT *r = (RECT*)lParam;
908 RECT r2;
909 int w, h;
911 SetRect(&r2, 0, 0, 0, 0);
912 AdjustWindowRect(&r2, GetWindowLong(hwnd, GWL_STYLE), FALSE);
914 w = r->right - r->left - (r2.right - r2.left);
915 h = r->bottom - r->top - (r2.bottom - r2.top);
916 w = max(w, 64);
917 h = max(h, 64);
918 SetRect(&r2, 0, 0, w, h);
920 AdjustWindowRect(&r2, GetWindowLong(hwnd, GWL_STYLE), FALSE);
921 w = r2.right - r2.left;
922 h = r2.bottom - r2.top;
924 switch (wParam) {
925 case WMSZ_BOTTOM:
926 r->bottom = r->top + h;
927 break;
929 case WMSZ_BOTTOMLEFT:
930 r->bottom = r->top + h;
931 r->left = r->right - w;
932 break;
934 case WMSZ_BOTTOMRIGHT:
935 r->bottom = r->top + h;
936 r->right = r->left + w;
937 break;
939 case WMSZ_LEFT:
940 r->left = r->right - w;
941 break;
943 case WMSZ_RIGHT:
944 r->right = r->left + w;
945 break;
947 case WMSZ_TOP:
948 r->top = r->bottom - h;
949 break;
951 case WMSZ_TOPLEFT:
952 r->top = r->bottom - h;
953 r->left = r->right - w;
954 break;
956 case WMSZ_TOPRIGHT:
957 r->top = r->bottom - h;
958 r->right = r->left + w;
959 break;
961 return TRUE;
963 #endif
965 /* needed for wheel */
966 #if !defined(WM_MOUSEWHEEL)
967 # define WM_MOUSEWHEEL 0x020A
968 #endif /* WM_MOUSEWHEEL */
969 #if !defined(GET_WHEEL_DELTA_WPARAM)
970 # define GET_WHEEL_DELTA_WPARAM(wparam) ((short)HIWORD(wparam))
971 #endif /* GET_WHEEL_DELTA_WPARAM */
973 case WM_MOUSEWHEEL: {
974 int delta = GET_WHEEL_DELTA_WPARAM(wParam);
976 if (delta < 0) {
977 _cursor.wheel++;
978 } else if (delta > 0) {
979 _cursor.wheel--;
981 HandleMouseEvents();
982 return 0;
985 case WM_SETFOCUS:
986 _wnd.has_focus = true;
987 SetCompositionPos(hwnd);
988 break;
990 case WM_KILLFOCUS:
991 _wnd.has_focus = false;
992 break;
994 #if !defined(WINCE)
995 case WM_ACTIVATE: {
996 /* Don't do anything if we are closing openttd */
997 if (_exit_game) break;
999 bool active = (LOWORD(wParam) != WA_INACTIVE);
1000 bool minimized = (HIWORD(wParam) != 0);
1001 if (_wnd.fullscreen) {
1002 if (active && minimized) {
1003 /* Restore the game window */
1004 ShowWindow(hwnd, SW_RESTORE);
1005 static_cast<VideoDriver_Win32 *>(VideoDriver::GetInstance())->MakeWindow(true);
1006 } else if (!active && !minimized) {
1007 /* Minimise the window and restore desktop */
1008 ShowWindow(hwnd, SW_MINIMIZE);
1009 ChangeDisplaySettings(nullptr, 0);
1012 break;
1014 #endif
1017 return DefWindowProc(hwnd, msg, wParam, lParam);
1020 static void RegisterWndClass()
1022 static bool registered = false;
1024 if (!registered) {
1025 HINSTANCE hinst = GetModuleHandle(nullptr);
1026 WNDCLASS wnd = {
1027 CS_OWNDC,
1028 WndProcGdi,
1031 hinst,
1032 LoadIcon(hinst, MAKEINTRESOURCE(100)),
1033 LoadCursor(nullptr, IDC_ARROW),
1036 _T("OTTD")
1039 registered = true;
1040 if (!RegisterClass(&wnd)) usererror("RegisterClass failed");
1044 static bool AllocateDibSection(int w, int h, bool force)
1046 BITMAPINFO *bi;
1047 HDC dc;
1048 uint bpp = BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
1050 w = max(w, 64);
1051 h = max(h, 64);
1053 if (bpp == 0) usererror("Can't use a blitter that blits 0 bpp for normal visuals");
1055 if (!force && w == _screen.width && h == _screen.height) return false;
1057 bi = (BITMAPINFO*)alloca(sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256);
1058 memset(bi, 0, sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256);
1059 bi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
1061 bi->bmiHeader.biWidth = _wnd.width = w;
1062 bi->bmiHeader.biHeight = -(_wnd.height = h);
1064 bi->bmiHeader.biPlanes = 1;
1065 bi->bmiHeader.biBitCount = BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
1066 bi->bmiHeader.biCompression = BI_RGB;
1068 if (_wnd.dib_sect) DeleteObject(_wnd.dib_sect);
1070 dc = GetDC(0);
1071 _wnd.dib_sect = CreateDIBSection(dc, bi, DIB_RGB_COLORS, (VOID**)&_wnd.buffer_bits, nullptr, 0);
1072 if (_wnd.dib_sect == nullptr) usererror("CreateDIBSection failed");
1073 ReleaseDC(0, dc);
1075 _screen.width = w;
1076 _screen.pitch = (bpp == 8) ? Align(w, 4) : w;
1077 _screen.height = h;
1078 _screen.dst_ptr = _wnd.buffer_bits;
1080 return true;
1083 static const Dimension default_resolutions[] = {
1084 { 640, 480 },
1085 { 800, 600 },
1086 { 1024, 768 },
1087 { 1152, 864 },
1088 { 1280, 800 },
1089 { 1280, 960 },
1090 { 1280, 1024 },
1091 { 1400, 1050 },
1092 { 1600, 1200 },
1093 { 1680, 1050 },
1094 { 1920, 1200 }
1097 static void FindResolutions()
1099 uint n = 0;
1100 #if defined(WINCE)
1101 /* EnumDisplaySettingsW is only supported in CE 4.2+
1102 * XXX -- One might argue that we assume 4.2+ on every system. Then we can use this function safely */
1103 #else
1104 uint i;
1105 DEVMODEA dm;
1107 /* Check modes for the relevant fullscreen bpp */
1108 uint bpp = _support8bpp != S8BPP_HARDWARE ? 32 : BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
1110 /* XXX - EnumDisplaySettingsW crashes with unicows.dll on Windows95
1111 * Doesn't really matter since we don't pass a string anyways, but still
1112 * a letdown */
1113 for (i = 0; EnumDisplaySettingsA(nullptr, i, &dm) != 0; i++) {
1114 if (dm.dmBitsPerPel == bpp &&
1115 dm.dmPelsWidth >= 640 && dm.dmPelsHeight >= 480) {
1116 uint j;
1118 for (j = 0; j < n; j++) {
1119 if (_resolutions[j].width == dm.dmPelsWidth && _resolutions[j].height == dm.dmPelsHeight) break;
1122 /* In the previous loop we have checked already existing/added resolutions if
1123 * they are the same as the new ones. If this is not the case (j == n); we have
1124 * looped all and found none, add the new one to the list. If we have reached the
1125 * maximum amount of resolutions, then quit querying the display */
1126 if (j == n) {
1127 _resolutions[j].width = dm.dmPelsWidth;
1128 _resolutions[j].height = dm.dmPelsHeight;
1129 if (++n == lengthof(_resolutions)) break;
1133 #endif
1135 /* We have found no resolutions, show the default list */
1136 if (n == 0) {
1137 memcpy(_resolutions, default_resolutions, sizeof(default_resolutions));
1138 n = lengthof(default_resolutions);
1141 _num_resolutions = n;
1142 SortResolutions(_num_resolutions);
1145 static FVideoDriver_Win32 iFVideoDriver_Win32;
1147 const char *VideoDriver_Win32::Start(const char * const *parm)
1149 memset(&_wnd, 0, sizeof(_wnd));
1151 RegisterWndClass();
1153 MakePalette();
1155 FindResolutions();
1157 DEBUG(driver, 2, "Resolution for display: %ux%u", _cur_resolution.width, _cur_resolution.height);
1159 /* fullscreen uses those */
1160 _wnd.width_org = _cur_resolution.width;
1161 _wnd.height_org = _cur_resolution.height;
1163 AllocateDibSection(_cur_resolution.width, _cur_resolution.height);
1164 this->MakeWindow(_fullscreen);
1166 MarkWholeScreenDirty();
1168 _draw_threaded = GetDriverParam(parm, "no_threads") == nullptr && GetDriverParam(parm, "no_thread") == nullptr && GetCPUCoreCount() > 1;
1170 return nullptr;
1173 void VideoDriver_Win32::Stop()
1175 DeleteObject(_wnd.gdi_palette);
1176 DeleteObject(_wnd.dib_sect);
1177 DestroyWindow(_wnd.main_wnd);
1179 #if !defined(WINCE)
1180 if (_wnd.fullscreen) ChangeDisplaySettings(nullptr, 0);
1181 #endif
1182 MyShowCursor(true);
1185 void VideoDriver_Win32::MakeDirty(int left, int top, int width, int height)
1187 RECT r = { left, top, left + width, top + height };
1189 InvalidateRect(_wnd.main_wnd, &r, FALSE);
1192 static void CheckPaletteAnim()
1194 if (_cur_palette.count_dirty == 0) return;
1196 _local_palette = _cur_palette;
1197 InvalidateRect(_wnd.main_wnd, nullptr, FALSE);
1200 void VideoDriver_Win32::MainLoop()
1202 MSG mesg;
1203 uint32 cur_ticks = GetTickCount();
1204 uint32 last_cur_ticks = cur_ticks;
1205 uint32 next_tick = cur_ticks + MILLISECONDS_PER_TICK;
1207 if (_draw_threaded) {
1208 /* Initialise the mutex first, because that's the thing we *need*
1209 * directly in the newly created thread. */
1210 _draw_mutex = ThreadMutex::New();
1211 _draw_thread_initialized = CreateEvent(nullptr, FALSE, FALSE, nullptr);
1212 if (_draw_mutex == nullptr || _draw_thread_initialized == nullptr) {
1213 _draw_threaded = false;
1214 } else {
1215 _draw_continue = true;
1216 _draw_threaded = ThreadObject::New(&PaintWindowThread, nullptr, &_draw_thread, "ottd:draw-win32");
1218 /* Free the mutex if we won't be able to use it. */
1219 if (!_draw_threaded) {
1220 delete _draw_mutex;
1221 _draw_mutex = nullptr;
1222 CloseHandle(_draw_thread_initialized);
1223 _draw_thread_initialized = nullptr;
1224 } else {
1225 DEBUG(driver, 1, "Threaded drawing enabled");
1226 /* Wait till the draw thread has started itself. */
1227 WaitForSingleObject(_draw_thread_initialized, INFINITE);
1228 _draw_mutex->BeginCritical();
1233 _wnd.running = true;
1235 CheckPaletteAnim();
1236 for (;;) {
1237 uint32 prev_cur_ticks = cur_ticks; // to check for wrapping
1239 while (PeekMessage(&mesg, nullptr, 0, 0, PM_REMOVE)) {
1240 InteractiveRandom(); // randomness
1241 /* Convert key messages to char messages if we want text input. */
1242 if (EditBoxInGlobalFocus()) TranslateMessage(&mesg);
1243 DispatchMessage(&mesg);
1245 if (_exit_game) return;
1247 #if defined(_DEBUG)
1248 if (_wnd.has_focus && GetAsyncKeyState(VK_SHIFT) < 0 &&
1249 #else
1250 /* Speed up using TAB, but disable for ALT+TAB of course */
1251 if (_wnd.has_focus && GetAsyncKeyState(VK_TAB) < 0 && GetAsyncKeyState(VK_MENU) >= 0 &&
1252 #endif
1253 !_networking && _game_mode != GM_MENU) {
1254 _fast_forward |= 2;
1255 } else if (_fast_forward & 2) {
1256 _fast_forward = 0;
1259 cur_ticks = GetTickCount();
1260 if (cur_ticks >= next_tick || (_fast_forward && !_pause_mode) || cur_ticks < prev_cur_ticks) {
1261 _realtime_tick += cur_ticks - last_cur_ticks;
1262 last_cur_ticks = cur_ticks;
1263 next_tick = cur_ticks + MILLISECONDS_PER_TICK;
1265 bool old_ctrl_pressed = _ctrl_pressed;
1267 _ctrl_pressed = _wnd.has_focus && GetAsyncKeyState(VK_CONTROL)<0;
1268 _shift_pressed = _wnd.has_focus && GetAsyncKeyState(VK_SHIFT)<0;
1270 /* determine which directional keys are down */
1271 if (_wnd.has_focus) {
1272 _dirkeys =
1273 (GetAsyncKeyState(VK_LEFT) < 0 ? 1 : 0) +
1274 (GetAsyncKeyState(VK_UP) < 0 ? 2 : 0) +
1275 (GetAsyncKeyState(VK_RIGHT) < 0 ? 4 : 0) +
1276 (GetAsyncKeyState(VK_DOWN) < 0 ? 8 : 0);
1277 } else {
1278 _dirkeys = 0;
1281 if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
1283 #if !defined(WINCE)
1284 /* Flush GDI buffer to ensure we don't conflict with the drawing thread. */
1285 GdiFlush();
1286 #endif
1288 /* The game loop is the part that can run asynchronously.
1289 * The rest except sleeping can't. */
1290 if (_draw_threaded) _draw_mutex->EndCritical();
1291 GameLoop();
1292 if (_draw_threaded) _draw_mutex->BeginCritical();
1294 if (_force_full_redraw) MarkWholeScreenDirty();
1296 UpdateWindows();
1297 CheckPaletteAnim();
1298 } else {
1299 #if !defined(WINCE)
1300 /* Flush GDI buffer to ensure we don't conflict with the drawing thread. */
1301 GdiFlush();
1302 #endif
1304 /* Release the thread while sleeping */
1305 if (_draw_threaded) _draw_mutex->EndCritical();
1306 Sleep(1);
1307 if (_draw_threaded) _draw_mutex->BeginCritical();
1309 NetworkDrawChatMessage();
1310 DrawMouseCursor();
1314 if (_draw_threaded) {
1315 _draw_continue = false;
1316 /* Sending signal if there is no thread blocked
1317 * is very valid and results in noop */
1318 _draw_mutex->SendSignal();
1319 _draw_mutex->EndCritical();
1320 _draw_thread->Join();
1322 CloseHandle(_draw_thread_initialized);
1323 delete _draw_mutex;
1324 delete _draw_thread;
1328 bool VideoDriver_Win32::ChangeResolution(int w, int h)
1330 if (_draw_mutex != nullptr) _draw_mutex->BeginCritical(true);
1331 if (_window_maximize) ShowWindow(_wnd.main_wnd, SW_SHOWNORMAL);
1333 _wnd.width = _wnd.width_org = w;
1334 _wnd.height = _wnd.height_org = h;
1336 bool ret = this->MakeWindow(_fullscreen); // _wnd.fullscreen screws up ingame resolution switching
1337 if (_draw_mutex != nullptr) _draw_mutex->EndCritical(true);
1338 return ret;
1341 bool VideoDriver_Win32::ToggleFullscreen(bool full_screen)
1343 if (_draw_mutex != nullptr) _draw_mutex->BeginCritical(true);
1344 bool ret = this->MakeWindow(full_screen);
1345 if (_draw_mutex != nullptr) _draw_mutex->EndCritical(true);
1346 return ret;
1349 bool VideoDriver_Win32::AfterBlitterChange()
1351 return AllocateDibSection(_screen.width, _screen.height, true) && this->MakeWindow(_fullscreen);
1354 void VideoDriver_Win32::AcquireBlitterLock()
1356 if (_draw_mutex != nullptr) _draw_mutex->BeginCritical(true);
1359 void VideoDriver_Win32::ReleaseBlitterLock()
1361 if (_draw_mutex != nullptr) _draw_mutex->EndCritical(true);
1364 void VideoDriver_Win32::EditBoxLostFocus()
1366 if (_draw_mutex != nullptr) _draw_mutex->BeginCritical(true);
1367 CancelIMEComposition(_wnd.main_wnd);
1368 SetCompositionPos(_wnd.main_wnd);
1369 SetCandidatePos(_wnd.main_wnd);
1370 if (_draw_mutex != nullptr) _draw_mutex->EndCritical(true);