OpenTTD
win32_v.cpp
Go to the documentation of this file.
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  */
7 
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>
31 
32 #include "../safeguards.h"
33 
34 /* Missing define in MinGW headers. */
35 #ifndef MAPVK_VK_TO_CHAR
36 #define MAPVK_VK_TO_CHAR (2)
37 #endif
38 
39 #ifndef PM_QS_INPUT
40 #define PM_QS_INPUT 0x20000
41 #endif
42 
43 typedef BOOL (WINAPI *PFNTRACKMOUSEEVENT)(LPTRACKMOUSEEVENT lpEventTrack);
44 static PFNTRACKMOUSEEVENT _pTrackMouseEvent = nullptr;
45 
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;
60 
61 bool _force_full_redraw;
62 bool _window_maximize;
63 uint _display_hz;
64 static Dimension _bck_resolution;
65 DWORD _imm_props;
66 
68 static bool _draw_threaded;
70 static std::recursive_mutex *_draw_mutex = nullptr;
72 static std::condition_variable_any *_draw_signal = nullptr;
74 static volatile bool _draw_continue;
77 
78 static void MakePalette()
79 {
80  LOGPALETTE *pal = (LOGPALETTE*)alloca(sizeof(LOGPALETTE) + (256 - 1) * sizeof(PALETTEENTRY));
81 
82  pal->palVersion = 0x300;
83  pal->palNumEntries = 256;
84 
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;
90 
91  }
92  _wnd.gdi_palette = CreatePalette(pal);
93  if (_wnd.gdi_palette == nullptr) usererror("CreatePalette failed!\n");
94 
97  _local_palette = _cur_palette;
98 }
99 
100 static void UpdatePalette(HDC dc, uint start, uint count)
101 {
102  RGBQUAD rgb[256];
103  uint i;
104 
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;
110  }
111 
112  SetDIBColorTable(dc, start, count, rgb);
113 }
114 
115 bool VideoDriver_Win32::ClaimMousePointer()
116 {
117  MyShowCursor(false, true);
118  return true;
119 }
120 
121 struct VkMapping {
122  byte vk_from;
123  byte vk_count;
124  byte map_to;
125 };
126 
127 #define AS(x, z) {x, 0, z}
128 #define AM(x, y, z, w) {x, y - x, z}
129 
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'),
136 
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),
141 
142  AS(VK_SPACE, WKC_SPACE),
143  AS(VK_RETURN, WKC_RETURN),
144  AS(VK_TAB, WKC_TAB),
145 
146  /* Function keys */
147  AM(VK_F1, VK_F12, WKC_F1, WKC_F12),
148 
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),
156 
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),
164 
165  AS(0xDE, WKC_SINGLEQUOTE),
166  AS(0xBC, WKC_COMMA),
167  AS(0xBD, WKC_MINUS),
168  AS(0xBE, WKC_PERIOD)
169 };
170 
171 static uint MapWindowsKey(uint sym)
172 {
173  const VkMapping *map;
174  uint key = 0;
175 
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;
180  }
181  }
182 
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;
187 }
188 
189 static bool AllocateDibSection(int w, int h, bool force = false);
190 
191 static void ClientSizeChanged(int w, int h)
192 {
193  /* allocate new dib section of the new size */
194  if (AllocateDibSection(w, h)) {
195  /* mark all palette colours dirty */
198  _local_palette = _cur_palette;
199 
201 
202  GameSizeChanged();
203  }
204 }
205 
206 #ifdef _DEBUG
207 /* Keep this function here..
208  * It allows you to redraw the screen from within the MSVC debugger */
209 int RedrawScreenDebug()
210 {
211  HDC dc, dc2;
212  static int _fooctr;
213  HBITMAP old_bmp;
214  HPALETTE old_palette;
215 
216  UpdateWindows();
217 
218  dc = GetDC(_wnd.main_wnd);
219  dc2 = CreateCompatibleDC(dc);
220 
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);
228 
229  return _fooctr++;
230 }
231 #endif
232 
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
239 
240 static void CALLBACK TrackMouseTimerProc(HWND hwnd, UINT msg, UINT_PTR event, DWORD time)
241 {
242  RECT rc;
243  POINT pt;
244 
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);
251 
252  if (!PtInRect(&rc, pt) || (WindowFromPoint(pt) != hwnd)) {
253  KillTimer(hwnd, event);
254  PostMessage(hwnd, WM_MOUSELEAVE, 0, 0L);
255  }
256 }
257 
263 bool VideoDriver_Win32::MakeWindow(bool full_screen)
264 {
265  _fullscreen = full_screen;
266 
267  /* recreate window? */
268  if ((full_screen || _wnd.fullscreen) && _wnd.main_wnd) {
269  DestroyWindow(_wnd.main_wnd);
270  _wnd.main_wnd = 0;
271  }
272 
273  if (full_screen) {
274  DEVMODE settings;
275 
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;
287 
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;
292  }
293 
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);
302  }
303  }
304 
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
308  }
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;
315  }
316 
317  {
318  RECT r;
319  DWORD style, showstyle;
320  int w, h;
321 
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);
332  }
333 
334  AdjustWindowRect(&r, style, FALSE);
335  w = r.right - r.left;
336  h = r.bottom - r.top;
337 
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;
343 
344  char window_title[64];
345  seprintf(window_title, lastof(window_title), "OpenTTD %s", _openttd_revision);
346 
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);
350  }
351  }
352 
354 
355  GameSizeChanged(); // invalidate all windows, force redraw
356  return true; // the request succeeded
357 }
358 
360 static void PaintWindow(HDC dc)
361 {
362  PerformanceMeasurer framerate(PFE_VIDEO);
363 
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);
367 
368  if (_cur_palette.count_dirty != 0) {
370 
371  switch (blitter->UsePaletteAnimation()) {
373  UpdatePalette(dc2, _local_palette.first_dirty, _local_palette.count_dirty);
374  break;
375 
377  blitter->PaletteAnimate(_local_palette);
378  break;
379 
381  break;
382 
383  default:
384  NOT_REACHED();
385  }
387  }
388 
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);
393 }
394 
395 static void PaintWindowThread()
396 {
397  /* First tell the main thread we're started */
398  std::unique_lock<std::recursive_mutex> lock(*_draw_mutex);
399  _draw_signal->notify_one();
400 
401  /* Now wait for the first thing to draw! */
402  _draw_signal->wait(*_draw_mutex);
403 
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);
409 
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);
414 
415  PaintWindow(dc);
416 
417  /* Clear update rect. */
418  SetRectEmpty(&_wnd.update_rect);
419  ReleaseDC(_wnd.main_wnd, dc);
420 
421  /* Flush GDI buffer to ensure drawing here doesn't conflict with any GDI usage in the main WndProc. */
422  GdiFlush();
423 
424  _draw_signal->wait(*_draw_mutex);
425  }
426 }
427 
429 static LRESULT HandleCharMsg(uint keycode, WChar charcode)
430 {
431 #if !defined(UNICODE)
432  static char prev_char = 0;
433 
434  char input[2] = {(char)charcode, 0};
435  int input_len = 1;
436 
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;
446  }
447  prev_char = 0;
448 
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;
455 
456  case 2: // Got an UTF-16 surrogate pair back.
457  charcode = Utf16DecodeSurrogate(w[0], w[1]);
458  break;
459 
460  default: // Some kind of error.
461  DEBUG(driver, 1, "Invalid DBCS character sequence encountered, dropping input");
462  charcode = 0;
463  break;
464  }
465 #else
466  static WChar prev_char = 0;
467 
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;
473  }
474 
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");
481  }
482  }
483  prev_char = 0;
484 #endif /* UNICODE */
485 
486  HandleKeypress(keycode, charcode);
487 
488  return 0;
489 }
490 
493 {
494  return (_imm_props & IME_PROP_AT_CARET) && !(_imm_props & IME_PROP_SPECIAL_UI);
495 }
496 
498 static void SetCompositionPos(HWND hwnd)
499 {
500  HIMC hIMC = ImmGetContext(hwnd);
501  if (hIMC != NULL) {
502  COMPOSITIONFORM cf;
503  cf.dwStyle = CFS_POINT;
504 
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;
513  }
514  ImmSetCompositionWindow(hIMC, &cf);
515  }
516  ImmReleaseContext(hwnd, hIMC);
517 }
518 
520 static void SetCandidatePos(HWND hwnd)
521 {
522  HIMC hIMC = ImmGetContext(hwnd);
523  if (hIMC != NULL) {
524  CANDIDATEFORM cf;
525  cf.dwIndex = 0;
526  cf.dwStyle = CFS_EXCLUDE;
527 
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;
542  }
543  } else {
544  cf.ptCurrentPos.x = 0;
545  cf.ptCurrentPos.y = 0;
546  SetRectEmpty(&cf.rcArea);
547  }
548  ImmSetCandidateWindow(hIMC, &cf);
549  }
550  ImmReleaseContext(hwnd, hIMC);
551 }
552 
554 static LRESULT HandleIMEComposition(HWND hwnd, WPARAM wParam, LPARAM lParam)
555 {
556  HIMC hIMC = ImmGetContext(hwnd);
557 
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';
565 
566  /* Transmit text to windowing system. */
567  if (len > 0) {
568  HandleTextInput(nullptr, true); // Clear marked string.
569  HandleTextInput(FS2OTTD(str));
570  }
571  SetCompositionPos(hwnd);
572 
573  /* Don't pass the result string on to the default window proc. */
574  lParam &= ~(GCS_RESULTSTR | GCS_RESULTCLAUSE | GCS_RESULTREADCLAUSE | GCS_RESULTREADSTR);
575  }
576 
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';
583 
584  if (len > 0) {
585  static char utf8_buf[1024];
586  convert_from_fs(str, utf8_buf, lengthof(utf8_buf));
587 
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--;
600  }
601  Utf8Consume(&caret);
602  }
603 
604  HandleTextInput(utf8_buf, true, caret);
605  } else {
606  HandleTextInput(nullptr, true);
607  }
608 
609  lParam &= ~(GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE | GCS_CURSORPOS | GCS_DELTASTART);
610  }
611  }
612  ImmReleaseContext(hwnd, hIMC);
613 
614  return lParam != 0 ? DefWindowProc(hwnd, WM_IME_COMPOSITION, wParam, lParam) : 0;
615 }
616 
618 static void CancelIMEComposition(HWND hwnd)
619 {
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);
625 }
626 
627 static LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
628 {
629  static uint32 keycode = 0;
630  static bool console = false;
631  static bool in_sizemove = false;
632 
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;
639 
640  case WM_ENTERSIZEMOVE:
641  in_sizemove = true;
642  break;
643 
644  case WM_EXITSIZEMOVE:
645  in_sizemove = false;
646  break;
647 
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);
654 
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;
660 
661  BeginPaint(hwnd, &ps);
662  PaintWindow(ps.hdc);
663  EndPaint(hwnd, &ps);
664  }
665  return 0;
666 
667  case WM_PALETTECHANGED:
668  if ((HWND)wParam == hwnd) return 0;
669  FALLTHROUGH;
670 
671  case WM_QUERYNEWPALETTE: {
672  HDC hDC = GetWindowDC(hwnd);
673  HPALETTE hOldPalette = SelectPalette(hDC, _wnd.gdi_palette, FALSE);
674  UINT nChanged = RealizePalette(hDC);
675 
676  SelectPalette(hDC, hOldPalette, TRUE);
677  ReleaseDC(hwnd, hDC);
678  if (nChanged != 0) InvalidateRect(hwnd, nullptr, FALSE);
679  return 0;
680  }
681 
682  case WM_CLOSE:
683  HandleExitGameRequest();
684  return 0;
685 
686  case WM_DESTROY:
687  if (_window_maximize) _cur_resolution = _bck_resolution;
688  return 0;
689 
690  case WM_LBUTTONDOWN:
691  SetCapture(hwnd);
692  _left_button_down = true;
694  return 0;
695 
696  case WM_LBUTTONUP:
697  ReleaseCapture();
698  _left_button_down = false;
699  _left_button_clicked = false;
701  return 0;
702 
703  case WM_RBUTTONDOWN:
704  SetCapture(hwnd);
705  _right_button_down = true;
706  _right_button_clicked = true;
708  return 0;
709 
710  case WM_RBUTTONUP:
711  ReleaseCapture();
712  _right_button_down = false;
714  return 0;
715 
716  case WM_MOUSELEAVE:
717  UndrawMouseCursor();
718  _cursor.in_window = false;
719 
720  if (!_left_button_down && !_right_button_down) MyShowCursor(true);
721  return 0;
722 
723  case WM_MOUSEMOVE: {
724  int x = (int16)LOWORD(lParam);
725  int y = (int16)HIWORD(lParam);
726 
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;
737 
738  _pTrackMouseEvent(&tme);
739  } else {
740  SetTimer(hwnd, TID_POLLMOUSE, MOUSE_POLL_DELAY, TrackMouseTimerProc);
741  }
742  }
743 
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);
751  }
752  }
753 
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);
760  }
761  MyShowCursor(false);
763  return 0;
764  }
765 
766  case WM_INPUTLANGCHANGE:
767  _imm_props = ImmGetProperty(GetKeyboardLayout(0), IGP_PROPERTY);
768  break;
769 
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;
774 
775  case WM_IME_STARTCOMPOSITION:
776  SetCompositionPos(hwnd);
777  if (DrawIMECompositionString()) return 0;
778  break;
779 
780  case WM_IME_COMPOSITION:
781  return HandleIMEComposition(hwnd, wParam, lParam);
782 
783  case WM_IME_ENDCOMPOSITION:
784  /* Clear any pending composition string. */
785  HandleTextInput(nullptr, true);
786  if (DrawIMECompositionString()) return 0;
787  break;
788 
789  case WM_IME_NOTIFY:
790  if (wParam == IMN_OPENCANDIDATE) SetCandidatePos(hwnd);
791  break;
792 
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));
798  }
799  HandleCharMsg(0, GB(wParam, 0, 8));
800  return 0;
801 #endif
802 
803  case WM_DEADCHAR:
804  console = GB(lParam, 16, 8) == 41;
805  return 0;
806 
807  case WM_CHAR: {
808  uint scancode = GB(lParam, 16, 8);
809  uint charcode = wParam;
810 
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;
816  }
817 
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;
822 
823  return HandleCharMsg(cur_keycode, charcode);
824  }
825 
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);
830 
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;
836  }
837  }
838 
839  uint charcode = MapVirtualKey(wParam, MAPVK_VK_TO_CHAR);
840 
841  /* No character translation? */
842  if (charcode == 0) {
843  HandleKeypress(keycode, 0);
844  return 0;
845  }
846 
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;
852  }
853  }
854  console = false;
855 
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;
860 
861  return HandleCharMsg(cur_keycode, LOWORD(charcode));
862  }
863 
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;
870 
871  case VK_MENU: // Just ALT
872  return 0; // do nothing
873 
874  case VK_F10: // F10, ignore activation of menu
875  HandleKeypress(MapWindowsKey(wParam), 0);
876  return 0;
877 
878  default: // ALT in combination with something else
879  HandleKeypress(MapWindowsKey(wParam), 0);
880  break;
881  }
882  break;
883 
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));
891  }
892  return 0;
893 
894  case WM_SIZING: {
895  RECT *r = (RECT*)lParam;
896  RECT r2;
897  int w, h;
898 
899  SetRect(&r2, 0, 0, 0, 0);
900  AdjustWindowRect(&r2, GetWindowLong(hwnd, GWL_STYLE), FALSE);
901 
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);
907 
908  AdjustWindowRect(&r2, GetWindowLong(hwnd, GWL_STYLE), FALSE);
909  w = r2.right - r2.left;
910  h = r2.bottom - r2.top;
911 
912  switch (wParam) {
913  case WMSZ_BOTTOM:
914  r->bottom = r->top + h;
915  break;
916 
917  case WMSZ_BOTTOMLEFT:
918  r->bottom = r->top + h;
919  r->left = r->right - w;
920  break;
921 
922  case WMSZ_BOTTOMRIGHT:
923  r->bottom = r->top + h;
924  r->right = r->left + w;
925  break;
926 
927  case WMSZ_LEFT:
928  r->left = r->right - w;
929  break;
930 
931  case WMSZ_RIGHT:
932  r->right = r->left + w;
933  break;
934 
935  case WMSZ_TOP:
936  r->top = r->bottom - h;
937  break;
938 
939  case WMSZ_TOPLEFT:
940  r->top = r->bottom - h;
941  r->left = r->right - w;
942  break;
943 
944  case WMSZ_TOPRIGHT:
945  r->top = r->bottom - h;
946  r->right = r->left + w;
947  break;
948  }
949  return TRUE;
950  }
951 
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 */
959 
960  case WM_MOUSEWHEEL: {
961  int delta = GET_WHEEL_DELTA_WPARAM(wParam);
962 
963  if (delta < 0) {
964  _cursor.wheel++;
965  } else if (delta > 0) {
966  _cursor.wheel--;
967  }
969  return 0;
970  }
971 
972  case WM_SETFOCUS:
973  _wnd.has_focus = true;
974  SetCompositionPos(hwnd);
975  break;
976 
977  case WM_KILLFOCUS:
978  _wnd.has_focus = false;
979  break;
980 
981  case WM_ACTIVATE: {
982  /* Don't do anything if we are closing openttd */
983  if (_exit_game) break;
984 
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);
996  }
997  }
998  break;
999  }
1000  }
1001 
1002  return DefWindowProc(hwnd, msg, wParam, lParam);
1003 }
1004 
1005 static void RegisterWndClass()
1006 {
1007  static bool registered = false;
1008 
1009  if (!registered) {
1010  HINSTANCE hinst = GetModuleHandle(nullptr);
1011  WNDCLASS wnd = {
1012  CS_OWNDC,
1013  WndProcGdi,
1014  0,
1015  0,
1016  hinst,
1017  LoadIcon(hinst, MAKEINTRESOURCE(100)),
1018  LoadCursor(nullptr, IDC_ARROW),
1019  0,
1020  0,
1021  _T("OTTD")
1022  };
1023 
1024  registered = true;
1025  if (!RegisterClass(&wnd)) usererror("RegisterClass failed");
1026 
1027  /* Dynamically load mouse tracking, as it doesn't exist on Windows 95. */
1028  _pTrackMouseEvent = (PFNTRACKMOUSEEVENT)GetProcAddress(GetModuleHandle(_T("User32")), "TrackMouseEvent");
1029  }
1030 }
1031 
1032 static bool AllocateDibSection(int w, int h, bool force)
1033 {
1034  BITMAPINFO *bi;
1035  HDC dc;
1037 
1038  w = max(w, 64);
1039  h = max(h, 64);
1040 
1041  if (bpp == 0) usererror("Can't use a blitter that blits 0 bpp for normal visuals");
1042 
1043  if (!force && w == _screen.width && h == _screen.height) return false;
1044 
1045  bi = (BITMAPINFO*)alloca(sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256);
1046  memset(bi, 0, sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256);
1047  bi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
1048 
1049  bi->bmiHeader.biWidth = _wnd.width = w;
1050  bi->bmiHeader.biHeight = -(_wnd.height = h);
1051 
1052  bi->bmiHeader.biPlanes = 1;
1053  bi->bmiHeader.biBitCount = BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
1054  bi->bmiHeader.biCompression = BI_RGB;
1055 
1056  if (_wnd.dib_sect) DeleteObject(_wnd.dib_sect);
1057 
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);
1062 
1063  _screen.width = w;
1064  _screen.pitch = (bpp == 8) ? Align(w, 4) : w;
1065  _screen.height = h;
1066  _screen.dst_ptr = _wnd.buffer_bits;
1067 
1068  return true;
1069 }
1070 
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 }
1083 };
1084 
1085 static void FindResolutions()
1086 {
1087  uint i;
1088  DEVMODEA dm;
1089 
1090  /* Check modes for the relevant fullscreen bpp */
1091  uint bpp = _support8bpp != S8BPP_HARDWARE ? 32 : BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
1092 
1093  _resolutions.clear();
1094 
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);
1102  }
1103 
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));
1107  }
1108 
1109  SortResolutions();
1110 }
1111 
1112 static FVideoDriver_Win32 iFVideoDriver_Win32;
1113 
1114 const char *VideoDriver_Win32::Start(const char * const *parm)
1115 {
1116  memset(&_wnd, 0, sizeof(_wnd));
1117 
1118  RegisterWndClass();
1119 
1120  MakePalette();
1121 
1122  FindResolutions();
1123 
1124  DEBUG(driver, 2, "Resolution for display: %ux%u", _cur_resolution.width, _cur_resolution.height);
1125 
1126  /* fullscreen uses those */
1127  _wnd.width_org = _cur_resolution.width;
1128  _wnd.height_org = _cur_resolution.height;
1129 
1130  AllocateDibSection(_cur_resolution.width, _cur_resolution.height);
1131  this->MakeWindow(_fullscreen);
1132 
1134 
1135  _draw_threaded = GetDriverParam(parm, "no_threads") == nullptr && GetDriverParam(parm, "no_thread") == nullptr && std::thread::hardware_concurrency() > 1;
1136 
1137  return nullptr;
1138 }
1139 
1141 {
1142  DeleteObject(_wnd.gdi_palette);
1143  DeleteObject(_wnd.dib_sect);
1144  DestroyWindow(_wnd.main_wnd);
1145 
1146  if (_wnd.fullscreen) ChangeDisplaySettings(nullptr, 0);
1147  MyShowCursor(true);
1148 }
1149 
1150 void VideoDriver_Win32::MakeDirty(int left, int top, int width, int height)
1151 {
1152  RECT r = { left, top, left + width, top + height };
1153 
1154  InvalidateRect(_wnd.main_wnd, &r, FALSE);
1155 }
1156 
1157 static void CheckPaletteAnim()
1158 {
1159  if (_cur_palette.count_dirty == 0) return;
1160 
1161  _local_palette = _cur_palette;
1162  InvalidateRect(_wnd.main_wnd, nullptr, FALSE);
1163 }
1164 
1166 {
1167  MSG mesg;
1168  uint32 cur_ticks = GetTickCount();
1169  uint32 last_cur_ticks = cur_ticks;
1170  uint32 next_tick = cur_ticks + MILLISECONDS_PER_TICK;
1171 
1172  std::thread draw_thread;
1173  std::unique_lock<std::recursive_mutex> draw_lock;
1174 
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;
1183  }
1184 
1185  if (_draw_threaded) {
1186  draw_lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
1187 
1188  _draw_continue = true;
1189  _draw_threaded = StartNewThread(&draw_thread, "ottd:draw-win32", &PaintWindowThread);
1190 
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);
1203  }
1204  }
1205  }
1206 
1207  _wnd.running = true;
1208 
1209  CheckPaletteAnim();
1210  for (;;) {
1211  uint32 prev_cur_ticks = cur_ticks; // to check for wrapping
1212 
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);
1218  }
1219  if (_exit_game) break;
1220 
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;
1231  }
1232 
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;
1238 
1239  bool old_ctrl_pressed = _ctrl_pressed;
1240 
1241  _ctrl_pressed = _wnd.has_focus && GetAsyncKeyState(VK_CONTROL)<0;
1242  _shift_pressed = _wnd.has_focus && GetAsyncKeyState(VK_SHIFT)<0;
1243 
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;
1253  }
1254 
1255  if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
1256 
1257  /* Flush GDI buffer to ensure we don't conflict with the drawing thread. */
1258  GdiFlush();
1259 
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();
1265 
1266  if (_force_full_redraw) MarkWholeScreenDirty();
1267 
1268  UpdateWindows();
1269  CheckPaletteAnim();
1270  } else {
1271  /* Flush GDI buffer to ensure we don't conflict with the drawing thread. */
1272  GdiFlush();
1273 
1274  /* Release the thread while sleeping */
1275  if (_draw_threaded) draw_lock.unlock();
1276  Sleep(1);
1277  if (_draw_threaded) draw_lock.lock();
1278 
1280  DrawMouseCursor();
1281  }
1282  }
1283 
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();
1292 
1293  delete _draw_mutex;
1294  delete _draw_signal;
1295 
1296  _draw_mutex = nullptr;
1297  }
1298 }
1299 
1301 {
1302  std::unique_lock<std::recursive_mutex> lock;
1303  if (_draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
1304 
1305  if (_window_maximize) ShowWindow(_wnd.main_wnd, SW_SHOWNORMAL);
1306 
1307  _wnd.width = _wnd.width_org = w;
1308  _wnd.height = _wnd.height_org = h;
1309 
1310  return this->MakeWindow(_fullscreen); // _wnd.fullscreen screws up ingame resolution switching
1311 }
1312 
1314 {
1315  std::unique_lock<std::recursive_mutex> lock;
1316  if (_draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
1317 
1318  return this->MakeWindow(full_screen);
1319 }
1320 
1322 {
1323  return AllocateDibSection(_screen.width, _screen.height, true) && this->MakeWindow(_fullscreen);
1324 }
1325 
1327 {
1328  if (_draw_mutex != nullptr) _draw_mutex->lock();
1329 }
1330 
1332 {
1333  if (_draw_mutex != nullptr) _draw_mutex->unlock();
1334 }
1335 
1337 {
1338  std::unique_lock<std::recursive_mutex> lock;
1339  if (_draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
1340 
1341  CancelIMEComposition(_wnd.main_wnd);
1342  SetCompositionPos(_wnd.main_wnd);
1343  SetCandidatePos(_wnd.main_wnd);
1344 }
static LRESULT HandleCharMsg(uint keycode, WChar charcode)
Forward key presses to the window system.
Definition: win32_v.cpp:429
const char * GetDriverParam(const char *const *parm, const char *name)
Get a string parameter the list of parameters.
Definition: driver.cpp:37
static bool _draw_threaded
Whether the drawing is/may be done in a separate thread.
Definition: win32_v.cpp:68
bool _networking
are we in networking mode?
Definition: network.cpp:52
uint32 _realtime_tick
The real time in the game.
Definition: debug.cpp:48
const NWidgetCore * nested_focus
Currently focused nested widget, or nullptr if no nested widget has focus.
Definition: window_gui.h:327
Point pos
logical mouse position
Definition: gfx_type.h:117
const char * FS2OTTD(const TCHAR *name)
Convert to OpenTTD&#39;s encoding from that of the local environment.
Definition: win32.cpp:558
Information about the currently used palette.
Definition: gfx_type.h:308
int left
x position of left edge of the window
Definition: window_gui.h:317
, Comma
Definition: gfx_type.h:102
void MainLoop() override
Perform the actual drawing.
Definition: win32_v.cpp:1165
The factory for Windows&#39; video driver.
Definition: win32_v.h:46
void EditBoxLostFocus() override
An edit box lost the input focus.
Definition: win32_v.cpp:1336
int CDECL seprintf(char *str, const char *last, const char *format,...)
Safer implementation of snprintf; same as snprintf except:
Definition: string.cpp:407
void Stop() override
Stop this driver.
Definition: win32_v.cpp:1140
bool _right_button_down
Is right mouse button pressed?
Definition: gfx.cpp:40
Colour palette[256]
Current palette. Entry 0 has to be always fully transparent!
Definition: gfx_type.h:309
fluid_settings_t * settings
FluidSynth settings handle.
Definition: fluidsynth.cpp:20
static std::recursive_mutex * _draw_mutex
Mutex to keep the access to the shared memory controlled.
Definition: win32_v.cpp:70
= Equals
Definition: gfx_type.h:97
int top
y position of top edge of the window
Definition: window_gui.h:318
Dimension _cur_resolution
The current resolution.
Definition: driver.cpp:21
static volatile bool _draw_continue
Should we keep continue drawing?
Definition: win32_v.cpp:74
#define lastof(x)
Get the last element of an fixed size array.
Definition: depend.cpp:48
No palette animation.
Definition: base.hpp:50
How all blitters should look like.
Definition: base.hpp:28
char * convert_from_fs(const TCHAR *name, char *utf8_buf, size_t buflen)
Convert to OpenTTD&#39;s encoding from that of the environment in UNICODE.
Definition: win32.cpp:591
RAII class for measuring simple elements of performance.
static T max(const T a, const T b)
Returns the maximum of two values.
Definition: math_func.hpp:24
virtual void PostResize()
Post resize event.
Definition: base.hpp:201
void AcquireBlitterLock() override
Acquire any lock(s) required to be held when changing blitters.
Definition: win32_v.cpp:1326
Palette animation should be done by video backend (8bpp only!)
Definition: base.hpp:51
bool _left_button_clicked
Is left mouse button clicked?
Definition: gfx.cpp:39
void ReleaseBlitterLock() override
Release any lock(s) required to be held when changing blitters.
Definition: win32_v.cpp:1331
std::vector< Dimension > _resolutions
List of resolutions.
Definition: driver.cpp:20
Console; Window numbers:
Definition: window_type.h:631
bool _ctrl_pressed
Is Ctrl pressed?
Definition: gfx.cpp:35
bool StartNewThread(std::thread *thr, const char *name, TFn &&_Fx, TArgs &&... _Ax)
Start a new thread.
Definition: thread.h:48
&#39; Single quote
Definition: gfx_type.h:101
const char * Start(const char *const *param) override
Start this driver.
Definition: win32_v.cpp:1114
bool _right_button_clicked
Is right mouse button clicked?
Definition: gfx.cpp:41
static T Align(const T x, uint n)
Return the smallest multiple of n equal or greater than x.
Definition: math_func.hpp:95
The blitter takes care of the palette animation.
Definition: base.hpp:52
uint current_y
Current vertical size (after resizing).
Definition: widget_type.h:173
virtual void PaletteAnimate(const Palette &palette)=0
Called when the 8bpp palette is changed; you should redraw all pixels on the screen that are equal to...
bool _left_button_down
Is left mouse button pressed?
Definition: gfx.cpp:38
[ Left square bracket
Definition: gfx_type.h:98
] Right square bracket
Definition: gfx_type.h:100
std::mutex lock
synchronization for playback status fields
Definition: win32_m.cpp:34
\ Backslash
Definition: gfx_type.h:99
void GetKeyboardLayout()
Retrieve keyboard layout from language string or (if set) config file.
Definition: osk_gui.cpp:355
static bool DrawIMECompositionString()
Should we draw the composition string ourself, i.e is this a normal IME?
Definition: win32_v.cpp:492
void CDECL usererror(const char *s,...)
Error handling for fatal user errors.
Definition: openttd.cpp:92
int wheel
mouse wheel movement
Definition: gfx_type.h:119
bool UpdateCursorPosition(int x, int y, bool queued_warp)
Update cursor position on mouse movement.
Definition: gfx.cpp:1647
/ Forward slash
Definition: gfx_type.h:95
static const uint MILLISECONDS_PER_TICK
The number of milliseconds per game tick.
Definition: gfx_type.h:305
void HandleKeypress(uint keycode, WChar key)
Handle keyboard input.
Definition: window.cpp:2670
int pos_x
Horizontal position of top-left corner of the widget in the window.
Definition: widget_type.h:175
byte _dirkeys
1 = left, 2 = up, 4 = right, 8 = down
Definition: gfx.cpp:31
bool fix_at
mouse is moving, but cursor is not (used for scrolling)
Definition: gfx_type.h:120
bool ToggleFullscreen(bool fullscreen) override
Change the full screen setting.
Definition: win32_v.cpp:1313
void HandleMouseEvents()
Handle a mouse event from the video driver.
Definition: window.cpp:2977
#define lengthof(x)
Return the length of an fixed size array.
Definition: depend.cpp:40
static Blitter * GetCurrentBlitter()
Get the current active blitter (always set by calling SelectBlitter).
Definition: factory.hpp:145
int first_dirty
The first dirty element.
Definition: gfx_type.h:310
PauseMode _pause_mode
The current pause mode.
Definition: gfx.cpp:47
bool ChangeResolution(int w, int h) override
Change the resolution of the window.
Definition: win32_v.cpp:1300
static WChar Utf16DecodeSurrogate(uint lead, uint trail)
Convert an UTF-16 surrogate pair to the corresponding Unicode character.
Definition: string_func.h:183
; Semicolon
Definition: gfx_type.h:96
Palette _cur_palette
Current palette.
Definition: gfx.cpp:48
bool _shift_pressed
Is Shift pressed?
Definition: gfx.cpp:36
#define DEBUG(name, level,...)
Output a line of debugging information.
Definition: debug.h:35
virtual Blitter::PaletteAnimation UsePaletteAnimation()=0
Check if the blitter uses palette animation at all.
static void PaintWindow(HDC dc)
Do palette animation and blit to the window.
Definition: win32_v.cpp:360
The video driver for windows.
Definition: win32_v.h:16
static bool Utf16IsTrailSurrogate(uint c)
Is the given character a lead surrogate code point?
Definition: string_func.h:172
void HandleCtrlChanged()
State of CONTROL key has changed.
Definition: window.cpp:2727
bool EditBoxInGlobalFocus()
Check if an edit box is in global focus.
Definition: window.cpp:457
Base of the Windows video driver.
Speed of painting drawn video buffer.
static VideoDriver * GetInstance()
Get the currently active instance of the video driver.
static LRESULT HandleIMEComposition(HWND hwnd, WPARAM wParam, LPARAM lParam)
Handle WM_IME_COMPOSITION messages.
Definition: win32_v.cpp:554
static void SetCandidatePos(HWND hwnd)
Set the position of the candidate window.
Definition: win32_v.cpp:520
static uint GB(const T x, const uint8 s, const uint8 n)
Fetch n bits from x, started at bit s.
void MakeDirty(int left, int top, int width, int height) override
Mark a particular area dirty.
Definition: win32_v.cpp:1150
void NetworkDrawChatMessage()
Draw the chat message-box.
uint current_x
Current horizontal size (after resizing).
Definition: widget_type.h:172
static Palette _local_palette
Local copy of the palette for use in the drawing thread.
Definition: win32_v.cpp:76
#define endof(x)
Get the end element of an fixed size array.
Definition: stdafx.h:384
bool in_window
mouse inside this window, determines drawing logic
Definition: gfx_type.h:141
bool MakeWindow(bool full_screen)
Instantiate a new window.
Definition: win32_v.cpp:263
Coordinates of a point in 2D.
virtual uint8 GetScreenDepth()=0
Get the screen depth this blitter works for.
#define AS(ap_name, size_x, size_y, min_year, max_year, catchment, noise, maint_cost, ttdpatch_type, class_id, name, preview)
AirportSpec definition for airports with at least one depot.
static bool Utf16IsLeadSurrogate(uint c)
Is the given character a lead surrogate code point?
Definition: string_func.h:162
void GameSizeChanged()
Size of the application screen changed.
Definition: main_gui.cpp:594
int width
width of the window (number of pixels to the right in x direction)
Definition: window_gui.h:319
static bool HasBit(const T x, const uint8 y)
Checks if a bit in a value is set.
void HandleTextInput(const char *str, bool marked=false, const char *caret=nullptr, const char *insert_location=nullptr, const char *replacement_end=nullptr)
Handle text input.
Definition: window.cpp:2758
. Period
Definition: gfx_type.h:103
static std::condition_variable_any * _draw_signal
Signal to draw the next frame.
Definition: win32_v.cpp:72
WindowClass window_class
Window class.
Definition: window_gui.h:311
bool AfterBlitterChange() override
Callback invoked after the blitter was changed.
Definition: win32_v.cpp:1321
int count_dirty
The number of dirty elements.
Definition: gfx_type.h:311
static void CancelIMEComposition(HWND hwnd)
Clear the current composition string.
Definition: win32_v.cpp:618
virtual Point GetCaretPosition() const
Get the current caret position if an edit box has the focus.
Definition: window.cpp:390
uint32 WChar
Type for wide characters, i.e.
Definition: string_type.h:35
static void SetCompositionPos(HWND hwnd)
Set position of the composition window to the caret position.
Definition: win32_v.cpp:498
Dimensions (a width and height) of a rectangle in 2D.
Full 8bpp support by OS and hardware.
Definition: gfx_type.h:318
int pos_y
Vertical position of top-left corner of the widget in the window.
Definition: widget_type.h:176
static bool HasModalProgress()
Check if we are currently in a modal progress state.
Definition: progress.h:21
int height
Height of the window (number of pixels down in y direction)
Definition: window_gui.h:320
void MarkWholeScreenDirty()
This function mark the whole screen as dirty.
Definition: gfx.cpp:1462
void UpdateWindows()
Update the continuously changing contents of the windows, such as the viewports.
Definition: window.cpp:3128