Add: INR currency (#8136)
[openttd-github.git] / src / video / win32_v.cpp
blob3deb0beb064acd0d7aebf3ad8197d099f575571b
1 /*
2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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/>.
6 */
8 /** @file win32_v.cpp Implementation of the Windows (GDI) video driver. */
10 #include "../stdafx.h"
11 #include "../openttd.h"
12 #include "../gfx_func.h"
13 #include "../os/windows/win32.h"
14 #include "../rev.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"
25 #include "win32_v.h"
26 #include <windows.h>
27 #include <imm.h>
28 #include <mutex>
29 #include <condition_variable>
30 #include <algorithm>
32 #include "../safeguards.h"
34 /* Missing define in MinGW headers. */
35 #ifndef MAPVK_VK_TO_CHAR
36 #define MAPVK_VK_TO_CHAR (2)
37 #endif
39 #ifndef PM_QS_INPUT
40 #define PM_QS_INPUT 0x20000
41 #endif
43 typedef BOOL (WINAPI *PFNTRACKMOUSEEVENT)(LPTRACKMOUSEEVENT lpEventTrack);
44 static PFNTRACKMOUSEEVENT _pTrackMouseEvent = nullptr;
46 static struct {
47 HWND main_wnd;
48 HBITMAP dib_sect;
49 void *buffer_bits;
50 HPALETTE gdi_palette;
51 RECT update_rect;
52 int width;
53 int height;
54 int width_org;
55 int height_org;
56 bool fullscreen;
57 bool has_focus;
58 bool running;
59 } _wnd;
61 bool _force_full_redraw;
62 bool _window_maximize;
63 uint _display_hz;
64 static Dimension _bck_resolution;
65 DWORD _imm_props;
67 /** Whether the drawing is/may be done in a separate thread. */
68 static bool _draw_threaded;
69 /** Mutex to keep the access to the shared memory controlled. */
70 static std::recursive_mutex *_draw_mutex = nullptr;
71 /** Signal to draw the next frame. */
72 static std::condition_variable_any *_draw_signal = nullptr;
73 /** Should we keep continue drawing? */
74 static volatile bool _draw_continue;
75 /** Local copy of the palette for use in the drawing thread. */
76 static Palette _local_palette;
78 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++) {
86 pal->palPalEntry[i].peRed = _cur_palette.palette[i].r;
87 pal->palPalEntry[i].peGreen = _cur_palette.palette[i].g;
88 pal->palPalEntry[i].peBlue = _cur_palette.palette[i].b;
89 pal->palPalEntry[i].peFlags = 0;
92 _wnd.gdi_palette = CreatePalette(pal);
93 if (_wnd.gdi_palette == nullptr) usererror("CreatePalette failed!\n");
95 _cur_palette.first_dirty = 0;
96 _cur_palette.count_dirty = 256;
97 _local_palette = _cur_palette;
100 static void UpdatePalette(HDC dc, uint start, uint count)
102 RGBQUAD rgb[256];
103 uint i;
105 for (i = 0; i != count; i++) {
106 rgb[i].rgbRed = _local_palette.palette[start + i].r;
107 rgb[i].rgbGreen = _local_palette.palette[start + i].g;
108 rgb[i].rgbBlue = _local_palette.palette[start + i].b;
109 rgb[i].rgbReserved = 0;
112 SetDIBColorTable(dc, start, count, rgb);
115 bool VideoDriver_Win32::ClaimMousePointer()
117 MyShowCursor(false, true);
118 return true;
121 struct VkMapping {
122 byte vk_from;
123 byte vk_count;
124 byte map_to;
127 #define AS(x, z) {x, 0, z}
128 #define AM(x, y, z, w) {x, y - x, z}
130 static const VkMapping _vk_mapping[] = {
131 /* Pageup stuff + up/down */
132 AM(VK_PRIOR, VK_DOWN, WKC_PAGEUP, WKC_DOWN),
133 /* Map letters & digits */
134 AM('A', 'Z', 'A', 'Z'),
135 AM('0', '9', '0', '9'),
137 AS(VK_ESCAPE, WKC_ESC),
138 AS(VK_PAUSE, WKC_PAUSE),
139 AS(VK_BACK, WKC_BACKSPACE),
140 AM(VK_INSERT, VK_DELETE, WKC_INSERT, WKC_DELETE),
142 AS(VK_SPACE, WKC_SPACE),
143 AS(VK_RETURN, WKC_RETURN),
144 AS(VK_TAB, WKC_TAB),
146 /* Function keys */
147 AM(VK_F1, VK_F12, WKC_F1, WKC_F12),
149 /* Numeric part */
150 AM(VK_NUMPAD0, VK_NUMPAD9, '0', '9'),
151 AS(VK_DIVIDE, WKC_NUM_DIV),
152 AS(VK_MULTIPLY, WKC_NUM_MUL),
153 AS(VK_SUBTRACT, WKC_NUM_MINUS),
154 AS(VK_ADD, WKC_NUM_PLUS),
155 AS(VK_DECIMAL, WKC_NUM_DECIMAL),
157 /* Other non-letter keys */
158 AS(0xBF, WKC_SLASH),
159 AS(0xBA, WKC_SEMICOLON),
160 AS(0xBB, WKC_EQUALS),
161 AS(0xDB, WKC_L_BRACKET),
162 AS(0xDC, WKC_BACKSLASH),
163 AS(0xDD, WKC_R_BRACKET),
165 AS(0xDE, WKC_SINGLEQUOTE),
166 AS(0xBC, WKC_COMMA),
167 AS(0xBD, WKC_MINUS),
168 AS(0xBE, WKC_PERIOD)
171 static uint MapWindowsKey(uint sym)
173 const VkMapping *map;
174 uint key = 0;
176 for (map = _vk_mapping; map != endof(_vk_mapping); ++map) {
177 if ((uint)(sym - map->vk_from) <= map->vk_count) {
178 key = sym - map->vk_from + map->map_to;
179 break;
183 if (GetAsyncKeyState(VK_SHIFT) < 0) key |= WKC_SHIFT;
184 if (GetAsyncKeyState(VK_CONTROL) < 0) key |= WKC_CTRL;
185 if (GetAsyncKeyState(VK_MENU) < 0) key |= WKC_ALT;
186 return key;
189 static bool AllocateDibSection(int w, int h, bool force = false);
191 static void ClientSizeChanged(int w, int h)
193 /* allocate new dib section of the new size */
194 if (AllocateDibSection(w, h)) {
195 /* mark all palette colours dirty */
196 _cur_palette.first_dirty = 0;
197 _cur_palette.count_dirty = 256;
198 _local_palette = _cur_palette;
200 BlitterFactory::GetCurrentBlitter()->PostResize();
202 GameSizeChanged();
206 #ifdef _DEBUG
207 /* Keep this function here..
208 * It allows you to redraw the screen from within the MSVC debugger */
209 int RedrawScreenDebug()
211 HDC dc, dc2;
212 static int _fooctr;
213 HBITMAP old_bmp;
214 HPALETTE old_palette;
216 UpdateWindows();
218 dc = GetDC(_wnd.main_wnd);
219 dc2 = CreateCompatibleDC(dc);
221 old_bmp = (HBITMAP)SelectObject(dc2, _wnd.dib_sect);
222 old_palette = SelectPalette(dc, _wnd.gdi_palette, FALSE);
223 BitBlt(dc, 0, 0, _wnd.width, _wnd.height, dc2, 0, 0, SRCCOPY);
224 SelectPalette(dc, old_palette, TRUE);
225 SelectObject(dc2, old_bmp);
226 DeleteDC(dc2);
227 ReleaseDC(_wnd.main_wnd, dc);
229 return _fooctr++;
231 #endif
233 /* Windows 95 will not have a WM_MOUSELEAVE message, so define it if needed */
234 #if !defined(WM_MOUSELEAVE)
235 #define WM_MOUSELEAVE 0x02A3
236 #endif
237 #define TID_POLLMOUSE 1
238 #define MOUSE_POLL_DELAY 75
240 static void CALLBACK TrackMouseTimerProc(HWND hwnd, UINT msg, UINT_PTR event, DWORD time)
242 RECT rc;
243 POINT pt;
245 /* Get the rectangle of our window and translate it to screen coordinates.
246 * Compare this with the current screen coordinates of the mouse and if it
247 * falls outside of the area or our window we have left the window. */
248 GetClientRect(hwnd, &rc);
249 MapWindowPoints(hwnd, HWND_DESKTOP, (LPPOINT)(LPRECT)&rc, 2);
250 GetCursorPos(&pt);
252 if (!PtInRect(&rc, pt) || (WindowFromPoint(pt) != hwnd)) {
253 KillTimer(hwnd, event);
254 PostMessage(hwnd, WM_MOUSELEAVE, 0, 0L);
259 * Instantiate a new window.
260 * @param full_screen Whether to make a full screen window or not.
261 * @return True if the window could be created.
263 bool VideoDriver_Win32::MakeWindow(bool full_screen)
265 _fullscreen = full_screen;
267 /* recreate window? */
268 if ((full_screen || _wnd.fullscreen) && _wnd.main_wnd) {
269 DestroyWindow(_wnd.main_wnd);
270 _wnd.main_wnd = 0;
273 if (full_screen) {
274 DEVMODE settings;
276 memset(&settings, 0, sizeof(settings));
277 settings.dmSize = sizeof(settings);
278 settings.dmFields =
279 DM_BITSPERPEL |
280 DM_PELSWIDTH |
281 DM_PELSHEIGHT |
282 (_display_hz != 0 ? DM_DISPLAYFREQUENCY : 0);
283 settings.dmBitsPerPel = BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
284 settings.dmPelsWidth = _wnd.width_org;
285 settings.dmPelsHeight = _wnd.height_org;
286 settings.dmDisplayFrequency = _display_hz;
288 /* Check for 8 bpp support. */
289 if (settings.dmBitsPerPel == 8 &&
290 (_support8bpp != S8BPP_HARDWARE || ChangeDisplaySettings(&settings, CDS_FULLSCREEN | CDS_TEST) != DISP_CHANGE_SUCCESSFUL)) {
291 settings.dmBitsPerPel = 32;
294 /* Test fullscreen with current resolution, if it fails use desktop resolution. */
295 if (ChangeDisplaySettings(&settings, CDS_FULLSCREEN | CDS_TEST) != DISP_CHANGE_SUCCESSFUL) {
296 RECT r;
297 GetWindowRect(GetDesktopWindow(), &r);
298 /* Guard against recursion. If we already failed here once, just fall through to
299 * the next ChangeDisplaySettings call which will fail and error out appropriately. */
300 if ((int)settings.dmPelsWidth != r.right - r.left || (int)settings.dmPelsHeight != r.bottom - r.top) {
301 return this->ChangeResolution(r.right - r.left, r.bottom - r.top);
305 if (ChangeDisplaySettings(&settings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL) {
306 this->MakeWindow(false); // don't care about the result
307 return false; // the request failed
309 } else if (_wnd.fullscreen) {
310 /* restore display? */
311 ChangeDisplaySettings(nullptr, 0);
312 /* restore the resolution */
313 _wnd.width = _bck_resolution.width;
314 _wnd.height = _bck_resolution.height;
318 RECT r;
319 DWORD style, showstyle;
320 int w, h;
322 showstyle = SW_SHOWNORMAL;
323 _wnd.fullscreen = full_screen;
324 if (_wnd.fullscreen) {
325 style = WS_POPUP;
326 SetRect(&r, 0, 0, _wnd.width_org, _wnd.height_org);
327 } else {
328 style = WS_OVERLAPPEDWINDOW;
329 /* On window creation, check if we were in maximize mode before */
330 if (_window_maximize) showstyle = SW_SHOWMAXIMIZED;
331 SetRect(&r, 0, 0, _wnd.width, _wnd.height);
334 AdjustWindowRect(&r, style, FALSE);
335 w = r.right - r.left;
336 h = r.bottom - r.top;
338 if (_wnd.main_wnd != nullptr) {
339 if (!_window_maximize) SetWindowPos(_wnd.main_wnd, 0, 0, 0, w, h, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOMOVE);
340 } else {
341 int x = (GetSystemMetrics(SM_CXSCREEN) - w) / 2;
342 int y = (GetSystemMetrics(SM_CYSCREEN) - h) / 2;
344 char window_title[64];
345 seprintf(window_title, lastof(window_title), "OpenTTD %s", _openttd_revision);
347 _wnd.main_wnd = CreateWindow(_T("OTTD"), MB_TO_WIDE(window_title), style, x, y, w, h, 0, 0, GetModuleHandle(nullptr), 0);
348 if (_wnd.main_wnd == nullptr) usererror("CreateWindow failed");
349 ShowWindow(_wnd.main_wnd, showstyle);
353 BlitterFactory::GetCurrentBlitter()->PostResize();
355 GameSizeChanged(); // invalidate all windows, force redraw
356 return true; // the request succeeded
359 /** Do palette animation and blit to the window. */
360 static void PaintWindow(HDC dc)
362 PerformanceMeasurer framerate(PFE_VIDEO);
364 HDC dc2 = CreateCompatibleDC(dc);
365 HBITMAP old_bmp = (HBITMAP)SelectObject(dc2, _wnd.dib_sect);
366 HPALETTE old_palette = SelectPalette(dc, _wnd.gdi_palette, FALSE);
368 if (_cur_palette.count_dirty != 0) {
369 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
371 switch (blitter->UsePaletteAnimation()) {
372 case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND:
373 UpdatePalette(dc2, _local_palette.first_dirty, _local_palette.count_dirty);
374 break;
376 case Blitter::PALETTE_ANIMATION_BLITTER:
377 blitter->PaletteAnimate(_local_palette);
378 break;
380 case Blitter::PALETTE_ANIMATION_NONE:
381 break;
383 default:
384 NOT_REACHED();
386 _cur_palette.count_dirty = 0;
389 BitBlt(dc, 0, 0, _wnd.width, _wnd.height, dc2, 0, 0, SRCCOPY);
390 SelectPalette(dc, old_palette, TRUE);
391 SelectObject(dc2, old_bmp);
392 DeleteDC(dc2);
395 static void PaintWindowThread()
397 /* First tell the main thread we're started */
398 std::unique_lock<std::recursive_mutex> lock(*_draw_mutex);
399 _draw_signal->notify_one();
401 /* Now wait for the first thing to draw! */
402 _draw_signal->wait(*_draw_mutex);
404 while (_draw_continue) {
405 /* Convert update region from logical to device coordinates. */
406 POINT pt = {0, 0};
407 ClientToScreen(_wnd.main_wnd, &pt);
408 OffsetRect(&_wnd.update_rect, pt.x, pt.y);
410 /* Create a device context that is clipped to the region we need to draw.
411 * GetDCEx 'consumes' the update region, so we may not destroy it ourself. */
412 HRGN rgn = CreateRectRgnIndirect(&_wnd.update_rect);
413 HDC dc = GetDCEx(_wnd.main_wnd, rgn, DCX_CLIPSIBLINGS | DCX_CLIPCHILDREN | DCX_INTERSECTRGN);
415 PaintWindow(dc);
417 /* Clear update rect. */
418 SetRectEmpty(&_wnd.update_rect);
419 ReleaseDC(_wnd.main_wnd, dc);
421 /* Flush GDI buffer to ensure drawing here doesn't conflict with any GDI usage in the main WndProc. */
422 GdiFlush();
424 _draw_signal->wait(*_draw_mutex);
428 /** Forward key presses to the window system. */
429 static LRESULT HandleCharMsg(uint keycode, WChar charcode)
431 #if !defined(UNICODE)
432 static char prev_char = 0;
434 char input[2] = {(char)charcode, 0};
435 int input_len = 1;
437 if (prev_char != 0) {
438 /* We stored a lead byte previously, combine it with this byte. */
439 input[0] = prev_char;
440 input[1] = (char)charcode;
441 input_len = 2;
442 } else if (IsDBCSLeadByte(charcode)) {
443 /* We got a lead byte, store and exit. */
444 prev_char = charcode;
445 return 0;
447 prev_char = 0;
449 wchar_t w[2]; // Can get up to two code points as a result.
450 int len = MultiByteToWideChar(CP_ACP, 0, input, input_len, w, 2);
451 switch (len) {
452 case 1: // Normal unicode character.
453 charcode = w[0];
454 break;
456 case 2: // Got an UTF-16 surrogate pair back.
457 charcode = Utf16DecodeSurrogate(w[0], w[1]);
458 break;
460 default: // Some kind of error.
461 DEBUG(driver, 1, "Invalid DBCS character sequence encountered, dropping input");
462 charcode = 0;
463 break;
465 #else
466 static WChar prev_char = 0;
468 /* Did we get a lead surrogate? If yes, store and exit. */
469 if (Utf16IsLeadSurrogate(charcode)) {
470 if (prev_char != 0) DEBUG(driver, 1, "Got two UTF-16 lead surrogates, dropping the first one");
471 prev_char = charcode;
472 return 0;
475 /* Stored lead surrogate and incoming trail surrogate? Combine and forward to input handling. */
476 if (prev_char != 0) {
477 if (Utf16IsTrailSurrogate(charcode)) {
478 charcode = Utf16DecodeSurrogate(prev_char, charcode);
479 } else {
480 DEBUG(driver, 1, "Got an UTF-16 lead surrogate without a trail surrogate, dropping the lead surrogate");
483 prev_char = 0;
484 #endif /* UNICODE */
486 HandleKeypress(keycode, charcode);
488 return 0;
491 /** Should we draw the composition string ourself, i.e is this a normal IME? */
492 static bool DrawIMECompositionString()
494 return (_imm_props & IME_PROP_AT_CARET) && !(_imm_props & IME_PROP_SPECIAL_UI);
497 /** Set position of the composition window to the caret position. */
498 static void SetCompositionPos(HWND hwnd)
500 HIMC hIMC = ImmGetContext(hwnd);
501 if (hIMC != NULL) {
502 COMPOSITIONFORM cf;
503 cf.dwStyle = CFS_POINT;
505 if (EditBoxInGlobalFocus()) {
506 /* Get caret position. */
507 Point pt = _focused_window->GetCaretPosition();
508 cf.ptCurrentPos.x = _focused_window->left + pt.x;
509 cf.ptCurrentPos.y = _focused_window->top + pt.y;
510 } else {
511 cf.ptCurrentPos.x = 0;
512 cf.ptCurrentPos.y = 0;
514 ImmSetCompositionWindow(hIMC, &cf);
516 ImmReleaseContext(hwnd, hIMC);
519 /** Set the position of the candidate window. */
520 static void SetCandidatePos(HWND hwnd)
522 HIMC hIMC = ImmGetContext(hwnd);
523 if (hIMC != NULL) {
524 CANDIDATEFORM cf;
525 cf.dwIndex = 0;
526 cf.dwStyle = CFS_EXCLUDE;
528 if (EditBoxInGlobalFocus()) {
529 Point pt = _focused_window->GetCaretPosition();
530 cf.ptCurrentPos.x = _focused_window->left + pt.x;
531 cf.ptCurrentPos.y = _focused_window->top + pt.y;
532 if (_focused_window->window_class == WC_CONSOLE) {
533 cf.rcArea.left = _focused_window->left;
534 cf.rcArea.top = _focused_window->top;
535 cf.rcArea.right = _focused_window->left + _focused_window->width;
536 cf.rcArea.bottom = _focused_window->top + _focused_window->height;
537 } else {
538 cf.rcArea.left = _focused_window->left + _focused_window->nested_focus->pos_x;
539 cf.rcArea.top = _focused_window->top + _focused_window->nested_focus->pos_y;
540 cf.rcArea.right = cf.rcArea.left + _focused_window->nested_focus->current_x;
541 cf.rcArea.bottom = cf.rcArea.top + _focused_window->nested_focus->current_y;
543 } else {
544 cf.ptCurrentPos.x = 0;
545 cf.ptCurrentPos.y = 0;
546 SetRectEmpty(&cf.rcArea);
548 ImmSetCandidateWindow(hIMC, &cf);
550 ImmReleaseContext(hwnd, hIMC);
553 /** Handle WM_IME_COMPOSITION messages. */
554 static LRESULT HandleIMEComposition(HWND hwnd, WPARAM wParam, LPARAM lParam)
556 HIMC hIMC = ImmGetContext(hwnd);
558 if (hIMC != NULL) {
559 if (lParam & GCS_RESULTSTR) {
560 /* Read result string from the IME. */
561 LONG len = ImmGetCompositionString(hIMC, GCS_RESULTSTR, nullptr, 0); // Length is always in bytes, even in UNICODE build.
562 TCHAR *str = (TCHAR *)_alloca(len + sizeof(TCHAR));
563 len = ImmGetCompositionString(hIMC, GCS_RESULTSTR, str, len);
564 str[len / sizeof(TCHAR)] = '\0';
566 /* Transmit text to windowing system. */
567 if (len > 0) {
568 HandleTextInput(nullptr, true); // Clear marked string.
569 HandleTextInput(FS2OTTD(str));
571 SetCompositionPos(hwnd);
573 /* Don't pass the result string on to the default window proc. */
574 lParam &= ~(GCS_RESULTSTR | GCS_RESULTCLAUSE | GCS_RESULTREADCLAUSE | GCS_RESULTREADSTR);
577 if ((lParam & GCS_COMPSTR) && DrawIMECompositionString()) {
578 /* Read composition string from the IME. */
579 LONG len = ImmGetCompositionString(hIMC, GCS_COMPSTR, nullptr, 0); // Length is always in bytes, even in UNICODE build.
580 TCHAR *str = (TCHAR *)_alloca(len + sizeof(TCHAR));
581 len = ImmGetCompositionString(hIMC, GCS_COMPSTR, str, len);
582 str[len / sizeof(TCHAR)] = '\0';
584 if (len > 0) {
585 static char utf8_buf[1024];
586 convert_from_fs(str, utf8_buf, lengthof(utf8_buf));
588 /* Convert caret position from bytes in the input string to a position in the UTF-8 encoded string. */
589 LONG caret_bytes = ImmGetCompositionString(hIMC, GCS_CURSORPOS, nullptr, 0);
590 const char *caret = utf8_buf;
591 for (const TCHAR *c = str; *c != '\0' && *caret != '\0' && caret_bytes > 0; c++, caret_bytes--) {
592 /* Skip DBCS lead bytes or leading surrogates. */
593 #ifdef UNICODE
594 if (Utf16IsLeadSurrogate(*c)) {
595 #else
596 if (IsDBCSLeadByte(*c)) {
597 #endif
598 c++;
599 caret_bytes--;
601 Utf8Consume(&caret);
604 HandleTextInput(utf8_buf, true, caret);
605 } else {
606 HandleTextInput(nullptr, true);
609 lParam &= ~(GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE | GCS_CURSORPOS | GCS_DELTASTART);
612 ImmReleaseContext(hwnd, hIMC);
614 return lParam != 0 ? DefWindowProc(hwnd, WM_IME_COMPOSITION, wParam, lParam) : 0;
617 /** Clear the current composition string. */
618 static void CancelIMEComposition(HWND hwnd)
620 HIMC hIMC = ImmGetContext(hwnd);
621 if (hIMC != NULL) ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
622 ImmReleaseContext(hwnd, hIMC);
623 /* Clear any marked string from the current edit box. */
624 HandleTextInput(nullptr, true);
627 static LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
629 static uint32 keycode = 0;
630 static bool console = false;
631 static bool in_sizemove = false;
633 switch (msg) {
634 case WM_CREATE:
635 SetTimer(hwnd, TID_POLLMOUSE, MOUSE_POLL_DELAY, TrackMouseTimerProc);
636 SetCompositionPos(hwnd);
637 _imm_props = ImmGetProperty(GetKeyboardLayout(0), IGP_PROPERTY);
638 break;
640 case WM_ENTERSIZEMOVE:
641 in_sizemove = true;
642 break;
644 case WM_EXITSIZEMOVE:
645 in_sizemove = false;
646 break;
648 case WM_PAINT:
649 if (!in_sizemove && _draw_mutex != nullptr && !HasModalProgress()) {
650 /* Get the union of the old update rect and the new update rect. */
651 RECT r;
652 GetUpdateRect(hwnd, &r, FALSE);
653 UnionRect(&_wnd.update_rect, &_wnd.update_rect, &r);
655 /* Mark the window as updated, otherwise Windows would send more WM_PAINT messages. */
656 ValidateRect(hwnd, nullptr);
657 _draw_signal->notify_one();
658 } else {
659 PAINTSTRUCT ps;
661 BeginPaint(hwnd, &ps);
662 PaintWindow(ps.hdc);
663 EndPaint(hwnd, &ps);
665 return 0;
667 case WM_PALETTECHANGED:
668 if ((HWND)wParam == hwnd) return 0;
669 FALLTHROUGH;
671 case WM_QUERYNEWPALETTE: {
672 HDC hDC = GetWindowDC(hwnd);
673 HPALETTE hOldPalette = SelectPalette(hDC, _wnd.gdi_palette, FALSE);
674 UINT nChanged = RealizePalette(hDC);
676 SelectPalette(hDC, hOldPalette, TRUE);
677 ReleaseDC(hwnd, hDC);
678 if (nChanged != 0) InvalidateRect(hwnd, nullptr, FALSE);
679 return 0;
682 case WM_CLOSE:
683 HandleExitGameRequest();
684 return 0;
686 case WM_DESTROY:
687 if (_window_maximize) _cur_resolution = _bck_resolution;
688 return 0;
690 case WM_LBUTTONDOWN:
691 SetCapture(hwnd);
692 _left_button_down = true;
693 HandleMouseEvents();
694 return 0;
696 case WM_LBUTTONUP:
697 ReleaseCapture();
698 _left_button_down = false;
699 _left_button_clicked = false;
700 HandleMouseEvents();
701 return 0;
703 case WM_RBUTTONDOWN:
704 SetCapture(hwnd);
705 _right_button_down = true;
706 _right_button_clicked = true;
707 HandleMouseEvents();
708 return 0;
710 case WM_RBUTTONUP:
711 ReleaseCapture();
712 _right_button_down = false;
713 HandleMouseEvents();
714 return 0;
716 case WM_MOUSELEAVE:
717 UndrawMouseCursor();
718 _cursor.in_window = false;
720 if (!_left_button_down && !_right_button_down) MyShowCursor(true);
721 return 0;
723 case WM_MOUSEMOVE: {
724 int x = (int16)LOWORD(lParam);
725 int y = (int16)HIWORD(lParam);
727 /* If the mouse was not in the window and it has moved it means it has
728 * come into the window, so start drawing the mouse. Also start
729 * tracking the mouse for exiting the window */
730 if (!_cursor.in_window) {
731 _cursor.in_window = true;
732 if (_pTrackMouseEvent != nullptr) {
733 TRACKMOUSEEVENT tme;
734 tme.cbSize = sizeof(tme);
735 tme.dwFlags = TME_LEAVE;
736 tme.hwndTrack = hwnd;
738 _pTrackMouseEvent(&tme);
739 } else {
740 SetTimer(hwnd, TID_POLLMOUSE, MOUSE_POLL_DELAY, TrackMouseTimerProc);
744 if (_cursor.fix_at) {
745 /* Get all queued mouse events now in case we have to warp the cursor. In the
746 * end, we only care about the current mouse position and not bygone events. */
747 MSG m;
748 while (PeekMessage(&m, hwnd, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_REMOVE | PM_NOYIELD | PM_QS_INPUT)) {
749 x = (int16)LOWORD(m.lParam);
750 y = (int16)HIWORD(m.lParam);
754 if (_cursor.UpdateCursorPosition(x, y, false)) {
755 POINT pt;
756 pt.x = _cursor.pos.x;
757 pt.y = _cursor.pos.y;
758 ClientToScreen(hwnd, &pt);
759 SetCursorPos(pt.x, pt.y);
761 MyShowCursor(false);
762 HandleMouseEvents();
763 return 0;
766 case WM_INPUTLANGCHANGE:
767 _imm_props = ImmGetProperty(GetKeyboardLayout(0), IGP_PROPERTY);
768 break;
770 case WM_IME_SETCONTEXT:
771 /* Don't show the composition window if we draw the string ourself. */
772 if (DrawIMECompositionString()) lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
773 break;
775 case WM_IME_STARTCOMPOSITION:
776 SetCompositionPos(hwnd);
777 if (DrawIMECompositionString()) return 0;
778 break;
780 case WM_IME_COMPOSITION:
781 return HandleIMEComposition(hwnd, wParam, lParam);
783 case WM_IME_ENDCOMPOSITION:
784 /* Clear any pending composition string. */
785 HandleTextInput(nullptr, true);
786 if (DrawIMECompositionString()) return 0;
787 break;
789 case WM_IME_NOTIFY:
790 if (wParam == IMN_OPENCANDIDATE) SetCandidatePos(hwnd);
791 break;
793 #if !defined(UNICODE)
794 case WM_IME_CHAR:
795 if (GB(wParam, 8, 8) != 0) {
796 /* DBCS character, send lead byte first. */
797 HandleCharMsg(0, GB(wParam, 8, 8));
799 HandleCharMsg(0, GB(wParam, 0, 8));
800 return 0;
801 #endif
803 case WM_DEADCHAR:
804 console = GB(lParam, 16, 8) == 41;
805 return 0;
807 case WM_CHAR: {
808 uint scancode = GB(lParam, 16, 8);
809 uint charcode = wParam;
811 /* If the console key is a dead-key, we need to press it twice to get a WM_CHAR message.
812 * But we then get two WM_CHAR messages, so ignore the first one */
813 if (console && scancode == 41) {
814 console = false;
815 return 0;
818 /* IMEs and other input methods sometimes send a WM_CHAR without a WM_KEYDOWN,
819 * clear the keycode so a previous WM_KEYDOWN doesn't become 'stuck'. */
820 uint cur_keycode = keycode;
821 keycode = 0;
823 return HandleCharMsg(cur_keycode, charcode);
826 case WM_KEYDOWN: {
827 /* No matter the keyboard layout, we will map the '~' to the console. */
828 uint scancode = GB(lParam, 16, 8);
829 keycode = scancode == 41 ? (uint)WKC_BACKQUOTE : MapWindowsKey(wParam);
831 /* Silently drop all messages handled by WM_CHAR. */
832 MSG msg;
833 if (PeekMessage(&msg, nullptr, 0, 0, PM_NOREMOVE)) {
834 if ((msg.message == WM_CHAR || msg.message == WM_DEADCHAR) && GB(lParam, 16, 8) == GB(msg.lParam, 16, 8)) {
835 return 0;
839 uint charcode = MapVirtualKey(wParam, MAPVK_VK_TO_CHAR);
841 /* No character translation? */
842 if (charcode == 0) {
843 HandleKeypress(keycode, 0);
844 return 0;
847 /* Is the console key a dead key? If yes, ignore the first key down event. */
848 if (HasBit(charcode, 31) && !console) {
849 if (scancode == 41) {
850 console = true;
851 return 0;
854 console = false;
856 /* IMEs and other input methods sometimes send a WM_CHAR without a WM_KEYDOWN,
857 * clear the keycode so a previous WM_KEYDOWN doesn't become 'stuck'. */
858 uint cur_keycode = keycode;
859 keycode = 0;
861 return HandleCharMsg(cur_keycode, LOWORD(charcode));
864 case WM_SYSKEYDOWN: // user presses F10 or Alt, both activating the title-menu
865 switch (wParam) {
866 case VK_RETURN:
867 case 'F': // Full Screen on ALT + ENTER/F
868 ToggleFullScreen(!_wnd.fullscreen);
869 return 0;
871 case VK_MENU: // Just ALT
872 return 0; // do nothing
874 case VK_F10: // F10, ignore activation of menu
875 HandleKeypress(MapWindowsKey(wParam), 0);
876 return 0;
878 default: // ALT in combination with something else
879 HandleKeypress(MapWindowsKey(wParam), 0);
880 break;
882 break;
884 case WM_SIZE:
885 if (wParam != SIZE_MINIMIZED) {
886 /* Set maximized flag when we maximize (obviously), but also when we
887 * switched to fullscreen from a maximized state */
888 _window_maximize = (wParam == SIZE_MAXIMIZED || (_window_maximize && _fullscreen));
889 if (_window_maximize || _fullscreen) _bck_resolution = _cur_resolution;
890 ClientSizeChanged(LOWORD(lParam), HIWORD(lParam));
892 return 0;
894 case WM_SIZING: {
895 RECT *r = (RECT*)lParam;
896 RECT r2;
897 int w, h;
899 SetRect(&r2, 0, 0, 0, 0);
900 AdjustWindowRect(&r2, GetWindowLong(hwnd, GWL_STYLE), FALSE);
902 w = r->right - r->left - (r2.right - r2.left);
903 h = r->bottom - r->top - (r2.bottom - r2.top);
904 w = max(w, 64);
905 h = max(h, 64);
906 SetRect(&r2, 0, 0, w, h);
908 AdjustWindowRect(&r2, GetWindowLong(hwnd, GWL_STYLE), FALSE);
909 w = r2.right - r2.left;
910 h = r2.bottom - r2.top;
912 switch (wParam) {
913 case WMSZ_BOTTOM:
914 r->bottom = r->top + h;
915 break;
917 case WMSZ_BOTTOMLEFT:
918 r->bottom = r->top + h;
919 r->left = r->right - w;
920 break;
922 case WMSZ_BOTTOMRIGHT:
923 r->bottom = r->top + h;
924 r->right = r->left + w;
925 break;
927 case WMSZ_LEFT:
928 r->left = r->right - w;
929 break;
931 case WMSZ_RIGHT:
932 r->right = r->left + w;
933 break;
935 case WMSZ_TOP:
936 r->top = r->bottom - h;
937 break;
939 case WMSZ_TOPLEFT:
940 r->top = r->bottom - h;
941 r->left = r->right - w;
942 break;
944 case WMSZ_TOPRIGHT:
945 r->top = r->bottom - h;
946 r->right = r->left + w;
947 break;
949 return TRUE;
952 /* needed for wheel */
953 #if !defined(WM_MOUSEWHEEL)
954 # define WM_MOUSEWHEEL 0x020A
955 #endif /* WM_MOUSEWHEEL */
956 #if !defined(GET_WHEEL_DELTA_WPARAM)
957 # define GET_WHEEL_DELTA_WPARAM(wparam) ((short)HIWORD(wparam))
958 #endif /* GET_WHEEL_DELTA_WPARAM */
960 case WM_MOUSEWHEEL: {
961 int delta = GET_WHEEL_DELTA_WPARAM(wParam);
963 if (delta < 0) {
964 _cursor.wheel++;
965 } else if (delta > 0) {
966 _cursor.wheel--;
968 HandleMouseEvents();
969 return 0;
972 case WM_SETFOCUS:
973 _wnd.has_focus = true;
974 SetCompositionPos(hwnd);
975 break;
977 case WM_KILLFOCUS:
978 _wnd.has_focus = false;
979 break;
981 case WM_ACTIVATE: {
982 /* Don't do anything if we are closing openttd */
983 if (_exit_game) break;
985 bool active = (LOWORD(wParam) != WA_INACTIVE);
986 bool minimized = (HIWORD(wParam) != 0);
987 if (_wnd.fullscreen) {
988 if (active && minimized) {
989 /* Restore the game window */
990 ShowWindow(hwnd, SW_RESTORE);
991 static_cast<VideoDriver_Win32 *>(VideoDriver::GetInstance())->MakeWindow(true);
992 } else if (!active && !minimized) {
993 /* Minimise the window and restore desktop */
994 ShowWindow(hwnd, SW_MINIMIZE);
995 ChangeDisplaySettings(nullptr, 0);
998 break;
1002 return DefWindowProc(hwnd, msg, wParam, lParam);
1005 static void RegisterWndClass()
1007 static bool registered = false;
1009 if (!registered) {
1010 HINSTANCE hinst = GetModuleHandle(nullptr);
1011 WNDCLASS wnd = {
1012 CS_OWNDC,
1013 WndProcGdi,
1016 hinst,
1017 LoadIcon(hinst, MAKEINTRESOURCE(100)),
1018 LoadCursor(nullptr, IDC_ARROW),
1021 _T("OTTD")
1024 registered = true;
1025 if (!RegisterClass(&wnd)) usererror("RegisterClass failed");
1027 /* Dynamically load mouse tracking, as it doesn't exist on Windows 95. */
1028 _pTrackMouseEvent = (PFNTRACKMOUSEEVENT)GetProcAddress(GetModuleHandle(_T("User32")), "TrackMouseEvent");
1032 static bool AllocateDibSection(int w, int h, bool force)
1034 BITMAPINFO *bi;
1035 HDC dc;
1036 uint bpp = BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
1038 w = max(w, 64);
1039 h = max(h, 64);
1041 if (bpp == 0) usererror("Can't use a blitter that blits 0 bpp for normal visuals");
1043 if (!force && w == _screen.width && h == _screen.height) return false;
1045 bi = (BITMAPINFO*)alloca(sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256);
1046 memset(bi, 0, sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256);
1047 bi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
1049 bi->bmiHeader.biWidth = _wnd.width = w;
1050 bi->bmiHeader.biHeight = -(_wnd.height = h);
1052 bi->bmiHeader.biPlanes = 1;
1053 bi->bmiHeader.biBitCount = BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
1054 bi->bmiHeader.biCompression = BI_RGB;
1056 if (_wnd.dib_sect) DeleteObject(_wnd.dib_sect);
1058 dc = GetDC(0);
1059 _wnd.dib_sect = CreateDIBSection(dc, bi, DIB_RGB_COLORS, (VOID**)&_wnd.buffer_bits, nullptr, 0);
1060 if (_wnd.dib_sect == nullptr) usererror("CreateDIBSection failed");
1061 ReleaseDC(0, dc);
1063 _screen.width = w;
1064 _screen.pitch = (bpp == 8) ? Align(w, 4) : w;
1065 _screen.height = h;
1066 _screen.dst_ptr = _wnd.buffer_bits;
1068 return true;
1071 static const Dimension default_resolutions[] = {
1072 { 640, 480 },
1073 { 800, 600 },
1074 { 1024, 768 },
1075 { 1152, 864 },
1076 { 1280, 800 },
1077 { 1280, 960 },
1078 { 1280, 1024 },
1079 { 1400, 1050 },
1080 { 1600, 1200 },
1081 { 1680, 1050 },
1082 { 1920, 1200 }
1085 static void FindResolutions()
1087 uint i;
1088 DEVMODEA dm;
1090 /* Check modes for the relevant fullscreen bpp */
1091 uint bpp = _support8bpp != S8BPP_HARDWARE ? 32 : BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
1093 _resolutions.clear();
1095 /* XXX - EnumDisplaySettingsW crashes with unicows.dll on Windows95
1096 * Doesn't really matter since we don't pass a string anyways, but still
1097 * a letdown */
1098 for (i = 0; EnumDisplaySettingsA(nullptr, i, &dm) != 0; i++) {
1099 if (dm.dmBitsPerPel != bpp || dm.dmPelsWidth < 640 || dm.dmPelsHeight < 480) continue;
1100 if (std::find(_resolutions.begin(), _resolutions.end(), Dimension(dm.dmPelsWidth, dm.dmPelsHeight)) != _resolutions.end()) continue;
1101 _resolutions.emplace_back(dm.dmPelsWidth, dm.dmPelsHeight);
1104 /* We have found no resolutions, show the default list */
1105 if (_resolutions.empty()) {
1106 _resolutions.assign(std::begin(default_resolutions), std::end(default_resolutions));
1109 SortResolutions();
1112 static FVideoDriver_Win32 iFVideoDriver_Win32;
1114 const char *VideoDriver_Win32::Start(const char * const *parm)
1116 memset(&_wnd, 0, sizeof(_wnd));
1118 RegisterWndClass();
1120 MakePalette();
1122 FindResolutions();
1124 DEBUG(driver, 2, "Resolution for display: %ux%u", _cur_resolution.width, _cur_resolution.height);
1126 /* fullscreen uses those */
1127 _wnd.width_org = _cur_resolution.width;
1128 _wnd.height_org = _cur_resolution.height;
1130 AllocateDibSection(_cur_resolution.width, _cur_resolution.height);
1131 this->MakeWindow(_fullscreen);
1133 MarkWholeScreenDirty();
1135 _draw_threaded = GetDriverParam(parm, "no_threads") == nullptr && GetDriverParam(parm, "no_thread") == nullptr && std::thread::hardware_concurrency() > 1;
1137 return nullptr;
1140 void VideoDriver_Win32::Stop()
1142 DeleteObject(_wnd.gdi_palette);
1143 DeleteObject(_wnd.dib_sect);
1144 DestroyWindow(_wnd.main_wnd);
1146 if (_wnd.fullscreen) ChangeDisplaySettings(nullptr, 0);
1147 MyShowCursor(true);
1150 void VideoDriver_Win32::MakeDirty(int left, int top, int width, int height)
1152 RECT r = { left, top, left + width, top + height };
1154 InvalidateRect(_wnd.main_wnd, &r, FALSE);
1157 static void CheckPaletteAnim()
1159 if (_cur_palette.count_dirty == 0) return;
1161 _local_palette = _cur_palette;
1162 InvalidateRect(_wnd.main_wnd, nullptr, FALSE);
1165 void VideoDriver_Win32::MainLoop()
1167 MSG mesg;
1168 uint32 cur_ticks = GetTickCount();
1169 uint32 last_cur_ticks = cur_ticks;
1170 uint32 next_tick = cur_ticks + MILLISECONDS_PER_TICK;
1172 std::thread draw_thread;
1173 std::unique_lock<std::recursive_mutex> draw_lock;
1175 if (_draw_threaded) {
1176 /* Initialise the mutex first, because that's the thing we *need*
1177 * directly in the newly created thread. */
1178 try {
1179 _draw_signal = new std::condition_variable_any();
1180 _draw_mutex = new std::recursive_mutex();
1181 } catch (...) {
1182 _draw_threaded = false;
1185 if (_draw_threaded) {
1186 draw_lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
1188 _draw_continue = true;
1189 _draw_threaded = StartNewThread(&draw_thread, "ottd:draw-win32", &PaintWindowThread);
1191 /* Free the mutex if we won't be able to use it. */
1192 if (!_draw_threaded) {
1193 draw_lock.unlock();
1194 draw_lock.release();
1195 delete _draw_mutex;
1196 delete _draw_signal;
1197 _draw_mutex = nullptr;
1198 _draw_signal = nullptr;
1199 } else {
1200 DEBUG(driver, 1, "Threaded drawing enabled");
1201 /* Wait till the draw thread has started itself. */
1202 _draw_signal->wait(*_draw_mutex);
1207 _wnd.running = true;
1209 CheckPaletteAnim();
1210 for (;;) {
1211 uint32 prev_cur_ticks = cur_ticks; // to check for wrapping
1213 while (PeekMessage(&mesg, nullptr, 0, 0, PM_REMOVE)) {
1214 InteractiveRandom(); // randomness
1215 /* Convert key messages to char messages if we want text input. */
1216 if (EditBoxInGlobalFocus()) TranslateMessage(&mesg);
1217 DispatchMessage(&mesg);
1219 if (_exit_game) break;
1221 #if defined(_DEBUG)
1222 if (_wnd.has_focus && GetAsyncKeyState(VK_SHIFT) < 0 &&
1223 #else
1224 /* Speed up using TAB, but disable for ALT+TAB of course */
1225 if (_wnd.has_focus && GetAsyncKeyState(VK_TAB) < 0 && GetAsyncKeyState(VK_MENU) >= 0 &&
1226 #endif
1227 !_networking && _game_mode != GM_MENU) {
1228 _fast_forward |= 2;
1229 } else if (_fast_forward & 2) {
1230 _fast_forward = 0;
1233 cur_ticks = GetTickCount();
1234 if (cur_ticks >= next_tick || (_fast_forward && !_pause_mode) || cur_ticks < prev_cur_ticks) {
1235 _realtime_tick += cur_ticks - last_cur_ticks;
1236 last_cur_ticks = cur_ticks;
1237 next_tick = cur_ticks + MILLISECONDS_PER_TICK;
1239 bool old_ctrl_pressed = _ctrl_pressed;
1241 _ctrl_pressed = _wnd.has_focus && GetAsyncKeyState(VK_CONTROL)<0;
1242 _shift_pressed = _wnd.has_focus && GetAsyncKeyState(VK_SHIFT)<0;
1244 /* determine which directional keys are down */
1245 if (_wnd.has_focus) {
1246 _dirkeys =
1247 (GetAsyncKeyState(VK_LEFT) < 0 ? 1 : 0) +
1248 (GetAsyncKeyState(VK_UP) < 0 ? 2 : 0) +
1249 (GetAsyncKeyState(VK_RIGHT) < 0 ? 4 : 0) +
1250 (GetAsyncKeyState(VK_DOWN) < 0 ? 8 : 0);
1251 } else {
1252 _dirkeys = 0;
1255 if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
1257 /* Flush GDI buffer to ensure we don't conflict with the drawing thread. */
1258 GdiFlush();
1260 /* The game loop is the part that can run asynchronously.
1261 * The rest except sleeping can't. */
1262 if (_draw_threaded) draw_lock.unlock();
1263 GameLoop();
1264 if (_draw_threaded) draw_lock.lock();
1266 if (_force_full_redraw) MarkWholeScreenDirty();
1268 UpdateWindows();
1269 CheckPaletteAnim();
1270 } else {
1271 /* Flush GDI buffer to ensure we don't conflict with the drawing thread. */
1272 GdiFlush();
1274 /* Release the thread while sleeping */
1275 if (_draw_threaded) draw_lock.unlock();
1276 Sleep(1);
1277 if (_draw_threaded) draw_lock.lock();
1279 NetworkDrawChatMessage();
1280 DrawMouseCursor();
1284 if (_draw_threaded) {
1285 _draw_continue = false;
1286 /* Sending signal if there is no thread blocked
1287 * is very valid and results in noop */
1288 _draw_signal->notify_all();
1289 if (draw_lock.owns_lock()) draw_lock.unlock();
1290 draw_lock.release();
1291 draw_thread.join();
1293 delete _draw_mutex;
1294 delete _draw_signal;
1296 _draw_mutex = nullptr;
1300 bool VideoDriver_Win32::ChangeResolution(int w, int h)
1302 std::unique_lock<std::recursive_mutex> lock;
1303 if (_draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
1305 if (_window_maximize) ShowWindow(_wnd.main_wnd, SW_SHOWNORMAL);
1307 _wnd.width = _wnd.width_org = w;
1308 _wnd.height = _wnd.height_org = h;
1310 return this->MakeWindow(_fullscreen); // _wnd.fullscreen screws up ingame resolution switching
1313 bool VideoDriver_Win32::ToggleFullscreen(bool full_screen)
1315 std::unique_lock<std::recursive_mutex> lock;
1316 if (_draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
1318 return this->MakeWindow(full_screen);
1321 bool VideoDriver_Win32::AfterBlitterChange()
1323 return AllocateDibSection(_screen.width, _screen.height, true) && this->MakeWindow(_fullscreen);
1326 void VideoDriver_Win32::AcquireBlitterLock()
1328 if (_draw_mutex != nullptr) _draw_mutex->lock();
1331 void VideoDriver_Win32::ReleaseBlitterLock()
1333 if (_draw_mutex != nullptr) _draw_mutex->unlock();
1336 void VideoDriver_Win32::EditBoxLostFocus()
1338 std::unique_lock<std::recursive_mutex> lock;
1339 if (_draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
1341 CancelIMEComposition(_wnd.main_wnd);
1342 SetCompositionPos(_wnd.main_wnd);
1343 SetCandidatePos(_wnd.main_wnd);