OpenTTD
win32_v.cpp
Go to the documentation of this file.
1 /* $Id$ */
2 
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  */
9 
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.h"
23 #include "../progress.h"
24 #include "../window_gui.h"
25 #include "../window_func.h"
26 #include "../framerate_type.h"
27 #include "win32_v.h"
28 #include <windows.h>
29 #include <imm.h>
30 #include <mutex>
31 #include <condition_variable>
32 #include <algorithm>
33 
34 #include "../safeguards.h"
35 
36 /* Missing define in MinGW headers. */
37 #ifndef MAPVK_VK_TO_CHAR
38 #define MAPVK_VK_TO_CHAR (2)
39 #endif
40 
41 #ifndef PM_QS_INPUT
42 #define PM_QS_INPUT 0x20000
43 #endif
44 
45 typedef BOOL (WINAPI *PFNTRACKMOUSEEVENT)(LPTRACKMOUSEEVENT lpEventTrack);
46 static PFNTRACKMOUSEEVENT _pTrackMouseEvent = nullptr;
47 
48 static struct {
49  HWND main_wnd;
50  HBITMAP dib_sect;
51  void *buffer_bits;
52  HPALETTE gdi_palette;
53  RECT update_rect;
54  int width;
55  int height;
56  int width_org;
57  int height_org;
58  bool fullscreen;
59  bool has_focus;
60  bool running;
61 } _wnd;
62 
63 bool _force_full_redraw;
64 bool _window_maximize;
65 uint _display_hz;
66 static Dimension _bck_resolution;
67 DWORD _imm_props;
68 
70 static bool _draw_threaded;
72 static std::recursive_mutex *_draw_mutex = nullptr;
74 static std::condition_variable_any *_draw_signal = nullptr;
76 static volatile bool _draw_continue;
79 
80 static void MakePalette()
81 {
82  LOGPALETTE *pal = (LOGPALETTE*)alloca(sizeof(LOGPALETTE) + (256 - 1) * sizeof(PALETTEENTRY));
83 
84  pal->palVersion = 0x300;
85  pal->palNumEntries = 256;
86 
87  for (uint i = 0; i != 256; i++) {
88  pal->palPalEntry[i].peRed = _cur_palette.palette[i].r;
89  pal->palPalEntry[i].peGreen = _cur_palette.palette[i].g;
90  pal->palPalEntry[i].peBlue = _cur_palette.palette[i].b;
91  pal->palPalEntry[i].peFlags = 0;
92 
93  }
94  _wnd.gdi_palette = CreatePalette(pal);
95  if (_wnd.gdi_palette == nullptr) usererror("CreatePalette failed!\n");
96 
99  _local_palette = _cur_palette;
100 }
101 
102 static void UpdatePalette(HDC dc, uint start, uint count)
103 {
104  RGBQUAD rgb[256];
105  uint i;
106 
107  for (i = 0; i != count; i++) {
108  rgb[i].rgbRed = _local_palette.palette[start + i].r;
109  rgb[i].rgbGreen = _local_palette.palette[start + i].g;
110  rgb[i].rgbBlue = _local_palette.palette[start + i].b;
111  rgb[i].rgbReserved = 0;
112  }
113 
114  SetDIBColorTable(dc, start, count, rgb);
115 }
116 
117 bool VideoDriver_Win32::ClaimMousePointer()
118 {
119  MyShowCursor(false, true);
120  return true;
121 }
122 
123 struct VkMapping {
124  byte vk_from;
125  byte vk_count;
126  byte map_to;
127 };
128 
129 #define AS(x, z) {x, 0, z}
130 #define AM(x, y, z, w) {x, y - x, z}
131 
132 static const VkMapping _vk_mapping[] = {
133  /* Pageup stuff + up/down */
134  AM(VK_PRIOR, VK_DOWN, WKC_PAGEUP, WKC_DOWN),
135  /* Map letters & digits */
136  AM('A', 'Z', 'A', 'Z'),
137  AM('0', '9', '0', '9'),
138 
139  AS(VK_ESCAPE, WKC_ESC),
140  AS(VK_PAUSE, WKC_PAUSE),
141  AS(VK_BACK, WKC_BACKSPACE),
142  AM(VK_INSERT, VK_DELETE, WKC_INSERT, WKC_DELETE),
143 
144  AS(VK_SPACE, WKC_SPACE),
145  AS(VK_RETURN, WKC_RETURN),
146  AS(VK_TAB, WKC_TAB),
147 
148  /* Function keys */
149  AM(VK_F1, VK_F12, WKC_F1, WKC_F12),
150 
151  /* Numeric part */
152  AM(VK_NUMPAD0, VK_NUMPAD9, '0', '9'),
153  AS(VK_DIVIDE, WKC_NUM_DIV),
154  AS(VK_MULTIPLY, WKC_NUM_MUL),
155  AS(VK_SUBTRACT, WKC_NUM_MINUS),
156  AS(VK_ADD, WKC_NUM_PLUS),
157  AS(VK_DECIMAL, WKC_NUM_DECIMAL),
158 
159  /* Other non-letter keys */
160  AS(0xBF, WKC_SLASH),
161  AS(0xBA, WKC_SEMICOLON),
162  AS(0xBB, WKC_EQUALS),
163  AS(0xDB, WKC_L_BRACKET),
164  AS(0xDC, WKC_BACKSLASH),
165  AS(0xDD, WKC_R_BRACKET),
166 
167  AS(0xDE, WKC_SINGLEQUOTE),
168  AS(0xBC, WKC_COMMA),
169  AS(0xBD, WKC_MINUS),
170  AS(0xBE, WKC_PERIOD)
171 };
172 
173 static uint MapWindowsKey(uint sym)
174 {
175  const VkMapping *map;
176  uint key = 0;
177 
178  for (map = _vk_mapping; map != endof(_vk_mapping); ++map) {
179  if ((uint)(sym - map->vk_from) <= map->vk_count) {
180  key = sym - map->vk_from + map->map_to;
181  break;
182  }
183  }
184 
185  if (GetAsyncKeyState(VK_SHIFT) < 0) key |= WKC_SHIFT;
186  if (GetAsyncKeyState(VK_CONTROL) < 0) key |= WKC_CTRL;
187  if (GetAsyncKeyState(VK_MENU) < 0) key |= WKC_ALT;
188  return key;
189 }
190 
191 static bool AllocateDibSection(int w, int h, bool force = false);
192 
193 static void ClientSizeChanged(int w, int h)
194 {
195  /* allocate new dib section of the new size */
196  if (AllocateDibSection(w, h)) {
197  /* mark all palette colours dirty */
200  _local_palette = _cur_palette;
201 
203 
204  GameSizeChanged();
205  }
206 }
207 
208 #ifdef _DEBUG
209 /* Keep this function here..
210  * It allows you to redraw the screen from within the MSVC debugger */
211 int RedrawScreenDebug()
212 {
213  HDC dc, dc2;
214  static int _fooctr;
215  HBITMAP old_bmp;
216  HPALETTE old_palette;
217 
218  UpdateWindows();
219 
220  dc = GetDC(_wnd.main_wnd);
221  dc2 = CreateCompatibleDC(dc);
222 
223  old_bmp = (HBITMAP)SelectObject(dc2, _wnd.dib_sect);
224  old_palette = SelectPalette(dc, _wnd.gdi_palette, FALSE);
225  BitBlt(dc, 0, 0, _wnd.width, _wnd.height, dc2, 0, 0, SRCCOPY);
226  SelectPalette(dc, old_palette, TRUE);
227  SelectObject(dc2, old_bmp);
228  DeleteDC(dc2);
229  ReleaseDC(_wnd.main_wnd, dc);
230 
231  return _fooctr++;
232 }
233 #endif
234 
235 /* Windows 95 will not have a WM_MOUSELEAVE message, so define it if needed */
236 #if !defined(WM_MOUSELEAVE)
237 #define WM_MOUSELEAVE 0x02A3
238 #endif
239 #define TID_POLLMOUSE 1
240 #define MOUSE_POLL_DELAY 75
241 
242 static void CALLBACK TrackMouseTimerProc(HWND hwnd, UINT msg, UINT_PTR event, DWORD time)
243 {
244  RECT rc;
245  POINT pt;
246 
247  /* Get the rectangle of our window and translate it to screen coordinates.
248  * Compare this with the current screen coordinates of the mouse and if it
249  * falls outside of the area or our window we have left the window. */
250  GetClientRect(hwnd, &rc);
251  MapWindowPoints(hwnd, HWND_DESKTOP, (LPPOINT)(LPRECT)&rc, 2);
252  GetCursorPos(&pt);
253 
254  if (!PtInRect(&rc, pt) || (WindowFromPoint(pt) != hwnd)) {
255  KillTimer(hwnd, event);
256  PostMessage(hwnd, WM_MOUSELEAVE, 0, 0L);
257  }
258 }
259 
265 bool VideoDriver_Win32::MakeWindow(bool full_screen)
266 {
267  _fullscreen = full_screen;
268 
269  /* recreate window? */
270  if ((full_screen || _wnd.fullscreen) && _wnd.main_wnd) {
271  DestroyWindow(_wnd.main_wnd);
272  _wnd.main_wnd = 0;
273  }
274 
275  if (full_screen) {
276  DEVMODE settings;
277 
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;
289 
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;
294  }
295 
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);
304  }
305  }
306 
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
310  }
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;
317  }
318 
319  {
320  RECT r;
321  DWORD style, showstyle;
322  int w, h;
323 
324  showstyle = SW_SHOWNORMAL;
325  _wnd.fullscreen = full_screen;
326  if (_wnd.fullscreen) {
327  style = WS_POPUP;
328  SetRect(&r, 0, 0, _wnd.width_org, _wnd.height_org);
329  } else {
330  style = WS_OVERLAPPEDWINDOW;
331  /* On window creation, check if we were in maximize mode before */
332  if (_window_maximize) showstyle = SW_SHOWMAXIMIZED;
333  SetRect(&r, 0, 0, _wnd.width, _wnd.height);
334  }
335 
336  AdjustWindowRect(&r, style, FALSE);
337  w = r.right - r.left;
338  h = r.bottom - r.top;
339 
340  if (_wnd.main_wnd != nullptr) {
341  if (!_window_maximize) SetWindowPos(_wnd.main_wnd, 0, 0, 0, w, h, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOMOVE);
342  } else {
343  int x = (GetSystemMetrics(SM_CXSCREEN) - w) / 2;
344  int y = (GetSystemMetrics(SM_CYSCREEN) - h) / 2;
345 
346  char window_title[64];
347  seprintf(window_title, lastof(window_title), "OpenTTD %s", _openttd_revision);
348 
349  _wnd.main_wnd = CreateWindow(_T("OTTD"), MB_TO_WIDE(window_title), style, x, y, w, h, 0, 0, GetModuleHandle(nullptr), 0);
350  if (_wnd.main_wnd == nullptr) usererror("CreateWindow failed");
351  ShowWindow(_wnd.main_wnd, showstyle);
352  }
353  }
354 
356 
357  GameSizeChanged(); // invalidate all windows, force redraw
358  return true; // the request succeeded
359 }
360 
362 static void PaintWindow(HDC dc)
363 {
364  PerformanceMeasurer framerate(PFE_VIDEO);
365 
366  HDC dc2 = CreateCompatibleDC(dc);
367  HBITMAP old_bmp = (HBITMAP)SelectObject(dc2, _wnd.dib_sect);
368  HPALETTE old_palette = SelectPalette(dc, _wnd.gdi_palette, FALSE);
369 
370  if (_cur_palette.count_dirty != 0) {
372 
373  switch (blitter->UsePaletteAnimation()) {
375  UpdatePalette(dc2, _local_palette.first_dirty, _local_palette.count_dirty);
376  break;
377 
379  blitter->PaletteAnimate(_local_palette);
380  break;
381 
383  break;
384 
385  default:
386  NOT_REACHED();
387  }
389  }
390 
391  BitBlt(dc, 0, 0, _wnd.width, _wnd.height, dc2, 0, 0, SRCCOPY);
392  SelectPalette(dc, old_palette, TRUE);
393  SelectObject(dc2, old_bmp);
394  DeleteDC(dc2);
395 }
396 
397 static void PaintWindowThread()
398 {
399  /* First tell the main thread we're started */
400  std::unique_lock<std::recursive_mutex> lock(*_draw_mutex);
401  _draw_signal->notify_one();
402 
403  /* Now wait for the first thing to draw! */
404  _draw_signal->wait(*_draw_mutex);
405 
406  while (_draw_continue) {
407  /* Convert update region from logical to device coordinates. */
408  POINT pt = {0, 0};
409  ClientToScreen(_wnd.main_wnd, &pt);
410  OffsetRect(&_wnd.update_rect, pt.x, pt.y);
411 
412  /* Create a device context that is clipped to the region we need to draw.
413  * GetDCEx 'consumes' the update region, so we may not destroy it ourself. */
414  HRGN rgn = CreateRectRgnIndirect(&_wnd.update_rect);
415  HDC dc = GetDCEx(_wnd.main_wnd, rgn, DCX_CLIPSIBLINGS | DCX_CLIPCHILDREN | DCX_INTERSECTRGN);
416 
417  PaintWindow(dc);
418 
419  /* Clear update rect. */
420  SetRectEmpty(&_wnd.update_rect);
421  ReleaseDC(_wnd.main_wnd, dc);
422 
423  /* Flush GDI buffer to ensure drawing here doesn't conflict with any GDI usage in the main WndProc. */
424  GdiFlush();
425 
426  _draw_signal->wait(*_draw_mutex);
427  }
428 }
429 
431 static LRESULT HandleCharMsg(uint keycode, WChar charcode)
432 {
433 #if !defined(UNICODE)
434  static char prev_char = 0;
435 
436  char input[2] = {(char)charcode, 0};
437  int input_len = 1;
438 
439  if (prev_char != 0) {
440  /* We stored a lead byte previously, combine it with this byte. */
441  input[0] = prev_char;
442  input[1] = (char)charcode;
443  input_len = 2;
444  } else if (IsDBCSLeadByte(charcode)) {
445  /* We got a lead byte, store and exit. */
446  prev_char = charcode;
447  return 0;
448  }
449  prev_char = 0;
450 
451  wchar_t w[2]; // Can get up to two code points as a result.
452  int len = MultiByteToWideChar(CP_ACP, 0, input, input_len, w, 2);
453  switch (len) {
454  case 1: // Normal unicode character.
455  charcode = w[0];
456  break;
457 
458  case 2: // Got an UTF-16 surrogate pair back.
459  charcode = Utf16DecodeSurrogate(w[0], w[1]);
460  break;
461 
462  default: // Some kind of error.
463  DEBUG(driver, 1, "Invalid DBCS character sequence encountered, dropping input");
464  charcode = 0;
465  break;
466  }
467 #else
468  static WChar prev_char = 0;
469 
470  /* Did we get a lead surrogate? If yes, store and exit. */
471  if (Utf16IsLeadSurrogate(charcode)) {
472  if (prev_char != 0) DEBUG(driver, 1, "Got two UTF-16 lead surrogates, dropping the first one");
473  prev_char = charcode;
474  return 0;
475  }
476 
477  /* Stored lead surrogate and incoming trail surrogate? Combine and forward to input handling. */
478  if (prev_char != 0) {
479  if (Utf16IsTrailSurrogate(charcode)) {
480  charcode = Utf16DecodeSurrogate(prev_char, charcode);
481  } else {
482  DEBUG(driver, 1, "Got an UTF-16 lead surrogate without a trail surrogate, dropping the lead surrogate");
483  }
484  }
485  prev_char = 0;
486 #endif /* UNICODE */
487 
488  HandleKeypress(keycode, charcode);
489 
490  return 0;
491 }
492 
495 {
496  return (_imm_props & IME_PROP_AT_CARET) && !(_imm_props & IME_PROP_SPECIAL_UI);
497 }
498 
500 static void SetCompositionPos(HWND hwnd)
501 {
502  HIMC hIMC = ImmGetContext(hwnd);
503  if (hIMC != NULL) {
504  COMPOSITIONFORM cf;
505  cf.dwStyle = CFS_POINT;
506 
507  if (EditBoxInGlobalFocus()) {
508  /* Get caret position. */
509  Point pt = _focused_window->GetCaretPosition();
510  cf.ptCurrentPos.x = _focused_window->left + pt.x;
511  cf.ptCurrentPos.y = _focused_window->top + pt.y;
512  } else {
513  cf.ptCurrentPos.x = 0;
514  cf.ptCurrentPos.y = 0;
515  }
516  ImmSetCompositionWindow(hIMC, &cf);
517  }
518  ImmReleaseContext(hwnd, hIMC);
519 }
520 
522 static void SetCandidatePos(HWND hwnd)
523 {
524  HIMC hIMC = ImmGetContext(hwnd);
525  if (hIMC != NULL) {
526  CANDIDATEFORM cf;
527  cf.dwIndex = 0;
528  cf.dwStyle = CFS_EXCLUDE;
529 
530  if (EditBoxInGlobalFocus()) {
531  Point pt = _focused_window->GetCaretPosition();
532  cf.ptCurrentPos.x = _focused_window->left + pt.x;
533  cf.ptCurrentPos.y = _focused_window->top + pt.y;
534  if (_focused_window->window_class == WC_CONSOLE) {
535  cf.rcArea.left = _focused_window->left;
536  cf.rcArea.top = _focused_window->top;
537  cf.rcArea.right = _focused_window->left + _focused_window->width;
538  cf.rcArea.bottom = _focused_window->top + _focused_window->height;
539  } else {
540  cf.rcArea.left = _focused_window->left + _focused_window->nested_focus->pos_x;
541  cf.rcArea.top = _focused_window->top + _focused_window->nested_focus->pos_y;
542  cf.rcArea.right = cf.rcArea.left + _focused_window->nested_focus->current_x;
543  cf.rcArea.bottom = cf.rcArea.top + _focused_window->nested_focus->current_y;
544  }
545  } else {
546  cf.ptCurrentPos.x = 0;
547  cf.ptCurrentPos.y = 0;
548  SetRectEmpty(&cf.rcArea);
549  }
550  ImmSetCandidateWindow(hIMC, &cf);
551  }
552  ImmReleaseContext(hwnd, hIMC);
553 }
554 
556 static LRESULT HandleIMEComposition(HWND hwnd, WPARAM wParam, LPARAM lParam)
557 {
558  HIMC hIMC = ImmGetContext(hwnd);
559 
560  if (hIMC != NULL) {
561  if (lParam & GCS_RESULTSTR) {
562  /* Read result string from the IME. */
563  LONG len = ImmGetCompositionString(hIMC, GCS_RESULTSTR, nullptr, 0); // Length is always in bytes, even in UNICODE build.
564  TCHAR *str = (TCHAR *)_alloca(len + sizeof(TCHAR));
565  len = ImmGetCompositionString(hIMC, GCS_RESULTSTR, str, len);
566  str[len / sizeof(TCHAR)] = '\0';
567 
568  /* Transmit text to windowing system. */
569  if (len > 0) {
570  HandleTextInput(nullptr, true); // Clear marked string.
571  HandleTextInput(FS2OTTD(str));
572  }
573  SetCompositionPos(hwnd);
574 
575  /* Don't pass the result string on to the default window proc. */
576  lParam &= ~(GCS_RESULTSTR | GCS_RESULTCLAUSE | GCS_RESULTREADCLAUSE | GCS_RESULTREADSTR);
577  }
578 
579  if ((lParam & GCS_COMPSTR) && DrawIMECompositionString()) {
580  /* Read composition string from the IME. */
581  LONG len = ImmGetCompositionString(hIMC, GCS_COMPSTR, nullptr, 0); // Length is always in bytes, even in UNICODE build.
582  TCHAR *str = (TCHAR *)_alloca(len + sizeof(TCHAR));
583  len = ImmGetCompositionString(hIMC, GCS_COMPSTR, str, len);
584  str[len / sizeof(TCHAR)] = '\0';
585 
586  if (len > 0) {
587  static char utf8_buf[1024];
588  convert_from_fs(str, utf8_buf, lengthof(utf8_buf));
589 
590  /* Convert caret position from bytes in the input string to a position in the UTF-8 encoded string. */
591  LONG caret_bytes = ImmGetCompositionString(hIMC, GCS_CURSORPOS, nullptr, 0);
592  const char *caret = utf8_buf;
593  for (const TCHAR *c = str; *c != '\0' && *caret != '\0' && caret_bytes > 0; c++, caret_bytes--) {
594  /* Skip DBCS lead bytes or leading surrogates. */
595 #ifdef UNICODE
596  if (Utf16IsLeadSurrogate(*c)) {
597 #else
598  if (IsDBCSLeadByte(*c)) {
599 #endif
600  c++;
601  caret_bytes--;
602  }
603  Utf8Consume(&caret);
604  }
605 
606  HandleTextInput(utf8_buf, true, caret);
607  } else {
608  HandleTextInput(nullptr, true);
609  }
610 
611  lParam &= ~(GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE | GCS_CURSORPOS | GCS_DELTASTART);
612  }
613  }
614  ImmReleaseContext(hwnd, hIMC);
615 
616  return lParam != 0 ? DefWindowProc(hwnd, WM_IME_COMPOSITION, wParam, lParam) : 0;
617 }
618 
620 static void CancelIMEComposition(HWND hwnd)
621 {
622  HIMC hIMC = ImmGetContext(hwnd);
623  if (hIMC != NULL) ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
624  ImmReleaseContext(hwnd, hIMC);
625  /* Clear any marked string from the current edit box. */
626  HandleTextInput(nullptr, true);
627 }
628 
629 static LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
630 {
631  static uint32 keycode = 0;
632  static bool console = false;
633  static bool in_sizemove = false;
634 
635  switch (msg) {
636  case WM_CREATE:
637  SetTimer(hwnd, TID_POLLMOUSE, MOUSE_POLL_DELAY, TrackMouseTimerProc);
638  SetCompositionPos(hwnd);
639  _imm_props = ImmGetProperty(GetKeyboardLayout(0), IGP_PROPERTY);
640  break;
641 
642  case WM_ENTERSIZEMOVE:
643  in_sizemove = true;
644  break;
645 
646  case WM_EXITSIZEMOVE:
647  in_sizemove = false;
648  break;
649 
650  case WM_PAINT:
651  if (!in_sizemove && _draw_mutex != nullptr && !HasModalProgress()) {
652  /* Get the union of the old update rect and the new update rect. */
653  RECT r;
654  GetUpdateRect(hwnd, &r, FALSE);
655  UnionRect(&_wnd.update_rect, &_wnd.update_rect, &r);
656 
657  /* Mark the window as updated, otherwise Windows would send more WM_PAINT messages. */
658  ValidateRect(hwnd, nullptr);
659  _draw_signal->notify_one();
660  } else {
661  PAINTSTRUCT ps;
662 
663  BeginPaint(hwnd, &ps);
664  PaintWindow(ps.hdc);
665  EndPaint(hwnd, &ps);
666  }
667  return 0;
668 
669  case WM_PALETTECHANGED:
670  if ((HWND)wParam == hwnd) return 0;
671  FALLTHROUGH;
672 
673  case WM_QUERYNEWPALETTE: {
674  HDC hDC = GetWindowDC(hwnd);
675  HPALETTE hOldPalette = SelectPalette(hDC, _wnd.gdi_palette, FALSE);
676  UINT nChanged = RealizePalette(hDC);
677 
678  SelectPalette(hDC, hOldPalette, TRUE);
679  ReleaseDC(hwnd, hDC);
680  if (nChanged != 0) InvalidateRect(hwnd, nullptr, FALSE);
681  return 0;
682  }
683 
684  case WM_CLOSE:
685  HandleExitGameRequest();
686  return 0;
687 
688  case WM_DESTROY:
689  if (_window_maximize) _cur_resolution = _bck_resolution;
690  return 0;
691 
692  case WM_LBUTTONDOWN:
693  SetCapture(hwnd);
694  _left_button_down = true;
696  return 0;
697 
698  case WM_LBUTTONUP:
699  ReleaseCapture();
700  _left_button_down = false;
701  _left_button_clicked = false;
703  return 0;
704 
705  case WM_RBUTTONDOWN:
706  SetCapture(hwnd);
707  _right_button_down = true;
708  _right_button_clicked = true;
710  return 0;
711 
712  case WM_RBUTTONUP:
713  ReleaseCapture();
714  _right_button_down = false;
716  return 0;
717 
718  case WM_MOUSELEAVE:
719  UndrawMouseCursor();
720  _cursor.in_window = false;
721 
722  if (!_left_button_down && !_right_button_down) MyShowCursor(true);
723  return 0;
724 
725  case WM_MOUSEMOVE: {
726  int x = (int16)LOWORD(lParam);
727  int y = (int16)HIWORD(lParam);
728 
729  /* If the mouse was not in the window and it has moved it means it has
730  * come into the window, so start drawing the mouse. Also start
731  * tracking the mouse for exiting the window */
732  if (!_cursor.in_window) {
733  _cursor.in_window = true;
734  if (_pTrackMouseEvent != nullptr) {
735  TRACKMOUSEEVENT tme;
736  tme.cbSize = sizeof(tme);
737  tme.dwFlags = TME_LEAVE;
738  tme.hwndTrack = hwnd;
739 
740  _pTrackMouseEvent(&tme);
741  } else {
742  SetTimer(hwnd, TID_POLLMOUSE, MOUSE_POLL_DELAY, TrackMouseTimerProc);
743  }
744  }
745 
746  if (_cursor.fix_at) {
747  /* Get all queued mouse events now in case we have to warp the cursor. In the
748  * end, we only care about the current mouse position and not bygone events. */
749  MSG m;
750  while (PeekMessage(&m, hwnd, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_REMOVE | PM_NOYIELD | PM_QS_INPUT)) {
751  x = (int16)LOWORD(m.lParam);
752  y = (int16)HIWORD(m.lParam);
753  }
754  }
755 
756  if (_cursor.UpdateCursorPosition(x, y, false)) {
757  POINT pt;
758  pt.x = _cursor.pos.x;
759  pt.y = _cursor.pos.y;
760  ClientToScreen(hwnd, &pt);
761  SetCursorPos(pt.x, pt.y);
762  }
763  MyShowCursor(false);
765  return 0;
766  }
767 
768  case WM_INPUTLANGCHANGE:
769  _imm_props = ImmGetProperty(GetKeyboardLayout(0), IGP_PROPERTY);
770  break;
771 
772  case WM_IME_SETCONTEXT:
773  /* Don't show the composition window if we draw the string ourself. */
774  if (DrawIMECompositionString()) lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
775  break;
776 
777  case WM_IME_STARTCOMPOSITION:
778  SetCompositionPos(hwnd);
779  if (DrawIMECompositionString()) return 0;
780  break;
781 
782  case WM_IME_COMPOSITION:
783  return HandleIMEComposition(hwnd, wParam, lParam);
784 
785  case WM_IME_ENDCOMPOSITION:
786  /* Clear any pending composition string. */
787  HandleTextInput(nullptr, true);
788  if (DrawIMECompositionString()) return 0;
789  break;
790 
791  case WM_IME_NOTIFY:
792  if (wParam == IMN_OPENCANDIDATE) SetCandidatePos(hwnd);
793  break;
794 
795 #if !defined(UNICODE)
796  case WM_IME_CHAR:
797  if (GB(wParam, 8, 8) != 0) {
798  /* DBCS character, send lead byte first. */
799  HandleCharMsg(0, GB(wParam, 8, 8));
800  }
801  HandleCharMsg(0, GB(wParam, 0, 8));
802  return 0;
803 #endif
804 
805  case WM_DEADCHAR:
806  console = GB(lParam, 16, 8) == 41;
807  return 0;
808 
809  case WM_CHAR: {
810  uint scancode = GB(lParam, 16, 8);
811  uint charcode = wParam;
812 
813  /* If the console key is a dead-key, we need to press it twice to get a WM_CHAR message.
814  * But we then get two WM_CHAR messages, so ignore the first one */
815  if (console && scancode == 41) {
816  console = false;
817  return 0;
818  }
819 
820  /* IMEs and other input methods sometimes send a WM_CHAR without a WM_KEYDOWN,
821  * clear the keycode so a previous WM_KEYDOWN doesn't become 'stuck'. */
822  uint cur_keycode = keycode;
823  keycode = 0;
824 
825  return HandleCharMsg(cur_keycode, charcode);
826  }
827 
828  case WM_KEYDOWN: {
829  /* No matter the keyboard layout, we will map the '~' to the console. */
830  uint scancode = GB(lParam, 16, 8);
831  keycode = scancode == 41 ? (uint)WKC_BACKQUOTE : MapWindowsKey(wParam);
832 
833  /* Silently drop all messages handled by WM_CHAR. */
834  MSG msg;
835  if (PeekMessage(&msg, nullptr, 0, 0, PM_NOREMOVE)) {
836  if ((msg.message == WM_CHAR || msg.message == WM_DEADCHAR) && GB(lParam, 16, 8) == GB(msg.lParam, 16, 8)) {
837  return 0;
838  }
839  }
840 
841  uint charcode = MapVirtualKey(wParam, MAPVK_VK_TO_CHAR);
842 
843  /* No character translation? */
844  if (charcode == 0) {
845  HandleKeypress(keycode, 0);
846  return 0;
847  }
848 
849  /* Is the console key a dead key? If yes, ignore the first key down event. */
850  if (HasBit(charcode, 31) && !console) {
851  if (scancode == 41) {
852  console = true;
853  return 0;
854  }
855  }
856  console = false;
857 
858  /* IMEs and other input methods sometimes send a WM_CHAR without a WM_KEYDOWN,
859  * clear the keycode so a previous WM_KEYDOWN doesn't become 'stuck'. */
860  uint cur_keycode = keycode;
861  keycode = 0;
862 
863  return HandleCharMsg(cur_keycode, LOWORD(charcode));
864  }
865 
866  case WM_SYSKEYDOWN: // user presses F10 or Alt, both activating the title-menu
867  switch (wParam) {
868  case VK_RETURN:
869  case 'F': // Full Screen on ALT + ENTER/F
870  ToggleFullScreen(!_wnd.fullscreen);
871  return 0;
872 
873  case VK_MENU: // Just ALT
874  return 0; // do nothing
875 
876  case VK_F10: // F10, ignore activation of menu
877  HandleKeypress(MapWindowsKey(wParam), 0);
878  return 0;
879 
880  default: // ALT in combination with something else
881  HandleKeypress(MapWindowsKey(wParam), 0);
882  break;
883  }
884  break;
885 
886  case WM_SIZE:
887  if (wParam != SIZE_MINIMIZED) {
888  /* Set maximized flag when we maximize (obviously), but also when we
889  * switched to fullscreen from a maximized state */
890  _window_maximize = (wParam == SIZE_MAXIMIZED || (_window_maximize && _fullscreen));
891  if (_window_maximize || _fullscreen) _bck_resolution = _cur_resolution;
892  ClientSizeChanged(LOWORD(lParam), HIWORD(lParam));
893  }
894  return 0;
895 
896  case WM_SIZING: {
897  RECT *r = (RECT*)lParam;
898  RECT r2;
899  int w, h;
900 
901  SetRect(&r2, 0, 0, 0, 0);
902  AdjustWindowRect(&r2, GetWindowLong(hwnd, GWL_STYLE), FALSE);
903 
904  w = r->right - r->left - (r2.right - r2.left);
905  h = r->bottom - r->top - (r2.bottom - r2.top);
906  w = max(w, 64);
907  h = max(h, 64);
908  SetRect(&r2, 0, 0, w, h);
909 
910  AdjustWindowRect(&r2, GetWindowLong(hwnd, GWL_STYLE), FALSE);
911  w = r2.right - r2.left;
912  h = r2.bottom - r2.top;
913 
914  switch (wParam) {
915  case WMSZ_BOTTOM:
916  r->bottom = r->top + h;
917  break;
918 
919  case WMSZ_BOTTOMLEFT:
920  r->bottom = r->top + h;
921  r->left = r->right - w;
922  break;
923 
924  case WMSZ_BOTTOMRIGHT:
925  r->bottom = r->top + h;
926  r->right = r->left + w;
927  break;
928 
929  case WMSZ_LEFT:
930  r->left = r->right - w;
931  break;
932 
933  case WMSZ_RIGHT:
934  r->right = r->left + w;
935  break;
936 
937  case WMSZ_TOP:
938  r->top = r->bottom - h;
939  break;
940 
941  case WMSZ_TOPLEFT:
942  r->top = r->bottom - h;
943  r->left = r->right - w;
944  break;
945 
946  case WMSZ_TOPRIGHT:
947  r->top = r->bottom - h;
948  r->right = r->left + w;
949  break;
950  }
951  return TRUE;
952  }
953 
954 /* needed for wheel */
955 #if !defined(WM_MOUSEWHEEL)
956 # define WM_MOUSEWHEEL 0x020A
957 #endif /* WM_MOUSEWHEEL */
958 #if !defined(GET_WHEEL_DELTA_WPARAM)
959 # define GET_WHEEL_DELTA_WPARAM(wparam) ((short)HIWORD(wparam))
960 #endif /* GET_WHEEL_DELTA_WPARAM */
961 
962  case WM_MOUSEWHEEL: {
963  int delta = GET_WHEEL_DELTA_WPARAM(wParam);
964 
965  if (delta < 0) {
966  _cursor.wheel++;
967  } else if (delta > 0) {
968  _cursor.wheel--;
969  }
971  return 0;
972  }
973 
974  case WM_SETFOCUS:
975  _wnd.has_focus = true;
976  SetCompositionPos(hwnd);
977  break;
978 
979  case WM_KILLFOCUS:
980  _wnd.has_focus = false;
981  break;
982 
983  case WM_ACTIVATE: {
984  /* Don't do anything if we are closing openttd */
985  if (_exit_game) break;
986 
987  bool active = (LOWORD(wParam) != WA_INACTIVE);
988  bool minimized = (HIWORD(wParam) != 0);
989  if (_wnd.fullscreen) {
990  if (active && minimized) {
991  /* Restore the game window */
992  ShowWindow(hwnd, SW_RESTORE);
993  static_cast<VideoDriver_Win32 *>(VideoDriver::GetInstance())->MakeWindow(true);
994  } else if (!active && !minimized) {
995  /* Minimise the window and restore desktop */
996  ShowWindow(hwnd, SW_MINIMIZE);
997  ChangeDisplaySettings(nullptr, 0);
998  }
999  }
1000  break;
1001  }
1002  }
1003 
1004  return DefWindowProc(hwnd, msg, wParam, lParam);
1005 }
1006 
1007 static void RegisterWndClass()
1008 {
1009  static bool registered = false;
1010 
1011  if (!registered) {
1012  HINSTANCE hinst = GetModuleHandle(nullptr);
1013  WNDCLASS wnd = {
1014  CS_OWNDC,
1015  WndProcGdi,
1016  0,
1017  0,
1018  hinst,
1019  LoadIcon(hinst, MAKEINTRESOURCE(100)),
1020  LoadCursor(nullptr, IDC_ARROW),
1021  0,
1022  0,
1023  _T("OTTD")
1024  };
1025 
1026  registered = true;
1027  if (!RegisterClass(&wnd)) usererror("RegisterClass failed");
1028 
1029  /* Dynamically load mouse tracking, as it doesn't exist on Windows 95. */
1030  _pTrackMouseEvent = (PFNTRACKMOUSEEVENT)GetProcAddress(GetModuleHandle(_T("User32")), "TrackMouseEvent");
1031  }
1032 }
1033 
1034 static bool AllocateDibSection(int w, int h, bool force)
1035 {
1036  BITMAPINFO *bi;
1037  HDC dc;
1039 
1040  w = max(w, 64);
1041  h = max(h, 64);
1042 
1043  if (bpp == 0) usererror("Can't use a blitter that blits 0 bpp for normal visuals");
1044 
1045  if (!force && w == _screen.width && h == _screen.height) return false;
1046 
1047  bi = (BITMAPINFO*)alloca(sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256);
1048  memset(bi, 0, sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256);
1049  bi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
1050 
1051  bi->bmiHeader.biWidth = _wnd.width = w;
1052  bi->bmiHeader.biHeight = -(_wnd.height = h);
1053 
1054  bi->bmiHeader.biPlanes = 1;
1055  bi->bmiHeader.biBitCount = BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
1056  bi->bmiHeader.biCompression = BI_RGB;
1057 
1058  if (_wnd.dib_sect) DeleteObject(_wnd.dib_sect);
1059 
1060  dc = GetDC(0);
1061  _wnd.dib_sect = CreateDIBSection(dc, bi, DIB_RGB_COLORS, (VOID**)&_wnd.buffer_bits, nullptr, 0);
1062  if (_wnd.dib_sect == nullptr) usererror("CreateDIBSection failed");
1063  ReleaseDC(0, dc);
1064 
1065  _screen.width = w;
1066  _screen.pitch = (bpp == 8) ? Align(w, 4) : w;
1067  _screen.height = h;
1068  _screen.dst_ptr = _wnd.buffer_bits;
1069 
1070  return true;
1071 }
1072 
1073 static const Dimension default_resolutions[] = {
1074  { 640, 480 },
1075  { 800, 600 },
1076  { 1024, 768 },
1077  { 1152, 864 },
1078  { 1280, 800 },
1079  { 1280, 960 },
1080  { 1280, 1024 },
1081  { 1400, 1050 },
1082  { 1600, 1200 },
1083  { 1680, 1050 },
1084  { 1920, 1200 }
1085 };
1086 
1087 static void FindResolutions()
1088 {
1089  uint i;
1090  DEVMODEA dm;
1091 
1092  /* Check modes for the relevant fullscreen bpp */
1093  uint bpp = _support8bpp != S8BPP_HARDWARE ? 32 : BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
1094 
1095  _resolutions.clear();
1096 
1097  /* XXX - EnumDisplaySettingsW crashes with unicows.dll on Windows95
1098  * Doesn't really matter since we don't pass a string anyways, but still
1099  * a letdown */
1100  for (i = 0; EnumDisplaySettingsA(nullptr, i, &dm) != 0; i++) {
1101  if (dm.dmBitsPerPel != bpp || dm.dmPelsWidth < 640 || dm.dmPelsHeight < 480) continue;
1102  if (std::find(_resolutions.begin(), _resolutions.end(), Dimension(dm.dmPelsWidth, dm.dmPelsHeight)) != _resolutions.end()) continue;
1103  _resolutions.emplace_back(dm.dmPelsWidth, dm.dmPelsHeight);
1104  }
1105 
1106  /* We have found no resolutions, show the default list */
1107  if (_resolutions.empty()) {
1108  _resolutions.assign(std::begin(default_resolutions), std::end(default_resolutions));
1109  }
1110 
1111  SortResolutions();
1112 }
1113 
1114 static FVideoDriver_Win32 iFVideoDriver_Win32;
1115 
1116 const char *VideoDriver_Win32::Start(const char * const *parm)
1117 {
1118  memset(&_wnd, 0, sizeof(_wnd));
1119 
1120  RegisterWndClass();
1121 
1122  MakePalette();
1123 
1124  FindResolutions();
1125 
1126  DEBUG(driver, 2, "Resolution for display: %ux%u", _cur_resolution.width, _cur_resolution.height);
1127 
1128  /* fullscreen uses those */
1129  _wnd.width_org = _cur_resolution.width;
1130  _wnd.height_org = _cur_resolution.height;
1131 
1132  AllocateDibSection(_cur_resolution.width, _cur_resolution.height);
1133  this->MakeWindow(_fullscreen);
1134 
1136 
1137  _draw_threaded = GetDriverParam(parm, "no_threads") == nullptr && GetDriverParam(parm, "no_thread") == nullptr && std::thread::hardware_concurrency() > 1;
1138 
1139  return nullptr;
1140 }
1141 
1143 {
1144  DeleteObject(_wnd.gdi_palette);
1145  DeleteObject(_wnd.dib_sect);
1146  DestroyWindow(_wnd.main_wnd);
1147 
1148  if (_wnd.fullscreen) ChangeDisplaySettings(nullptr, 0);
1149  MyShowCursor(true);
1150 }
1151 
1152 void VideoDriver_Win32::MakeDirty(int left, int top, int width, int height)
1153 {
1154  RECT r = { left, top, left + width, top + height };
1155 
1156  InvalidateRect(_wnd.main_wnd, &r, FALSE);
1157 }
1158 
1159 static void CheckPaletteAnim()
1160 {
1161  if (_cur_palette.count_dirty == 0) return;
1162 
1163  _local_palette = _cur_palette;
1164  InvalidateRect(_wnd.main_wnd, nullptr, FALSE);
1165 }
1166 
1168 {
1169  MSG mesg;
1170  uint32 cur_ticks = GetTickCount();
1171  uint32 last_cur_ticks = cur_ticks;
1172  uint32 next_tick = cur_ticks + MILLISECONDS_PER_TICK;
1173 
1174  std::thread draw_thread;
1175  std::unique_lock<std::recursive_mutex> draw_lock;
1176 
1177  if (_draw_threaded) {
1178  /* Initialise the mutex first, because that's the thing we *need*
1179  * directly in the newly created thread. */
1180  try {
1181  _draw_signal = new std::condition_variable_any();
1182  _draw_mutex = new std::recursive_mutex();
1183  } catch (...) {
1184  _draw_threaded = false;
1185  }
1186 
1187  if (_draw_threaded) {
1188  draw_lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
1189 
1190  _draw_continue = true;
1191  _draw_threaded = StartNewThread(&draw_thread, "ottd:draw-win32", &PaintWindowThread);
1192 
1193  /* Free the mutex if we won't be able to use it. */
1194  if (!_draw_threaded) {
1195  draw_lock.unlock();
1196  draw_lock.release();
1197  delete _draw_mutex;
1198  delete _draw_signal;
1199  _draw_mutex = nullptr;
1200  _draw_signal = nullptr;
1201  } else {
1202  DEBUG(driver, 1, "Threaded drawing enabled");
1203  /* Wait till the draw thread has started itself. */
1204  _draw_signal->wait(*_draw_mutex);
1205  }
1206  }
1207  }
1208 
1209  _wnd.running = true;
1210 
1211  CheckPaletteAnim();
1212  for (;;) {
1213  uint32 prev_cur_ticks = cur_ticks; // to check for wrapping
1214 
1215  while (PeekMessage(&mesg, nullptr, 0, 0, PM_REMOVE)) {
1216  InteractiveRandom(); // randomness
1217  /* Convert key messages to char messages if we want text input. */
1218  if (EditBoxInGlobalFocus()) TranslateMessage(&mesg);
1219  DispatchMessage(&mesg);
1220  }
1221  if (_exit_game) break;
1222 
1223 #if defined(_DEBUG)
1224  if (_wnd.has_focus && GetAsyncKeyState(VK_SHIFT) < 0 &&
1225 #else
1226  /* Speed up using TAB, but disable for ALT+TAB of course */
1227  if (_wnd.has_focus && GetAsyncKeyState(VK_TAB) < 0 && GetAsyncKeyState(VK_MENU) >= 0 &&
1228 #endif
1229  !_networking && _game_mode != GM_MENU) {
1230  _fast_forward |= 2;
1231  } else if (_fast_forward & 2) {
1232  _fast_forward = 0;
1233  }
1234 
1235  cur_ticks = GetTickCount();
1236  if (cur_ticks >= next_tick || (_fast_forward && !_pause_mode) || cur_ticks < prev_cur_ticks) {
1237  _realtime_tick += cur_ticks - last_cur_ticks;
1238  last_cur_ticks = cur_ticks;
1239  next_tick = cur_ticks + MILLISECONDS_PER_TICK;
1240 
1241  bool old_ctrl_pressed = _ctrl_pressed;
1242 
1243  _ctrl_pressed = _wnd.has_focus && GetAsyncKeyState(VK_CONTROL)<0;
1244  _shift_pressed = _wnd.has_focus && GetAsyncKeyState(VK_SHIFT)<0;
1245 
1246  /* determine which directional keys are down */
1247  if (_wnd.has_focus) {
1248  _dirkeys =
1249  (GetAsyncKeyState(VK_LEFT) < 0 ? 1 : 0) +
1250  (GetAsyncKeyState(VK_UP) < 0 ? 2 : 0) +
1251  (GetAsyncKeyState(VK_RIGHT) < 0 ? 4 : 0) +
1252  (GetAsyncKeyState(VK_DOWN) < 0 ? 8 : 0);
1253  } else {
1254  _dirkeys = 0;
1255  }
1256 
1257  if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
1258 
1259  /* Flush GDI buffer to ensure we don't conflict with the drawing thread. */
1260  GdiFlush();
1261 
1262  /* The game loop is the part that can run asynchronously.
1263  * The rest except sleeping can't. */
1264  if (_draw_threaded) draw_lock.unlock();
1265  GameLoop();
1266  if (_draw_threaded) draw_lock.lock();
1267 
1268  if (_force_full_redraw) MarkWholeScreenDirty();
1269 
1270  UpdateWindows();
1271  CheckPaletteAnim();
1272  } else {
1273  /* Flush GDI buffer to ensure we don't conflict with the drawing thread. */
1274  GdiFlush();
1275 
1276  /* Release the thread while sleeping */
1277  if (_draw_threaded) draw_lock.unlock();
1278  Sleep(1);
1279  if (_draw_threaded) draw_lock.lock();
1280 
1282  DrawMouseCursor();
1283  }
1284  }
1285 
1286  if (_draw_threaded) {
1287  _draw_continue = false;
1288  /* Sending signal if there is no thread blocked
1289  * is very valid and results in noop */
1290  _draw_signal->notify_all();
1291  if (draw_lock.owns_lock()) draw_lock.unlock();
1292  draw_lock.release();
1293  draw_thread.join();
1294 
1295  delete _draw_mutex;
1296  delete _draw_signal;
1297 
1298  _draw_mutex = nullptr;
1299  }
1300 }
1301 
1303 {
1304  std::unique_lock<std::recursive_mutex> lock;
1305  if (_draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
1306 
1307  if (_window_maximize) ShowWindow(_wnd.main_wnd, SW_SHOWNORMAL);
1308 
1309  _wnd.width = _wnd.width_org = w;
1310  _wnd.height = _wnd.height_org = h;
1311 
1312  return this->MakeWindow(_fullscreen); // _wnd.fullscreen screws up ingame resolution switching
1313 }
1314 
1316 {
1317  std::unique_lock<std::recursive_mutex> lock;
1318  if (_draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
1319 
1320  return this->MakeWindow(full_screen);
1321 }
1322 
1324 {
1325  return AllocateDibSection(_screen.width, _screen.height, true) && this->MakeWindow(_fullscreen);
1326 }
1327 
1329 {
1330  if (_draw_mutex != nullptr) _draw_mutex->lock();
1331 }
1332 
1334 {
1335  if (_draw_mutex != nullptr) _draw_mutex->unlock();
1336 }
1337 
1339 {
1340  std::unique_lock<std::recursive_mutex> lock;
1341  if (_draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
1342 
1343  CancelIMEComposition(_wnd.main_wnd);
1344  SetCompositionPos(_wnd.main_wnd);
1345  SetCandidatePos(_wnd.main_wnd);
1346 }
static LRESULT HandleCharMsg(uint keycode, WChar charcode)
Forward key presses to the window system.
Definition: win32_v.cpp:431
const char * GetDriverParam(const char *const *parm, const char *name)
Get a string parameter the list of parameters.
Definition: driver.cpp:39
static bool _draw_threaded
Whether the drawing is/may be done in a separate thread.
Definition: win32_v.cpp:70
bool _networking
are we in networking mode?
Definition: network.cpp:54
uint32 _realtime_tick
The real time in the game.
Definition: debug.cpp:50
const NWidgetCore * nested_focus
Currently focused nested widget, or nullptr if no nested widget has focus.
Definition: window_gui.h:329
Point pos
logical mouse position
Definition: gfx_type.h:119
const char * FS2OTTD(const TCHAR *name)
Convert to OpenTTD&#39;s encoding from that of the local environment.
Definition: win32.cpp:560
Information about the currently used palette.
Definition: gfx_type.h:309
int left
x position of left edge of the window
Definition: window_gui.h:319
, Comma
Definition: gfx_type.h:104
void MainLoop() override
Perform the actual drawing.
Definition: win32_v.cpp:1167
The factory for Windows&#39; video driver.
Definition: win32_v.h:48
void EditBoxLostFocus() override
An edit box lost the input focus.
Definition: win32_v.cpp:1338
int CDECL seprintf(char *str, const char *last, const char *format,...)
Safer implementation of snprintf; same as snprintf except:
Definition: string.cpp:409
void Stop() override
Stop this driver.
Definition: win32_v.cpp:1142
bool _right_button_down
Is right mouse button pressed?
Definition: gfx.cpp:42
Colour palette[256]
Current palette. Entry 0 has to be always fully transparent!
Definition: gfx_type.h:310
fluid_settings_t * settings
FluidSynth settings handle.
Definition: fluidsynth.cpp:22
static std::recursive_mutex * _draw_mutex
Mutex to keep the access to the shared memory controlled.
Definition: win32_v.cpp:72
= Equals
Definition: gfx_type.h:99
int top
y position of top edge of the window
Definition: window_gui.h:320
Dimension _cur_resolution
The current resolution.
Definition: driver.cpp:23
static volatile bool _draw_continue
Should we keep continue drawing?
Definition: win32_v.cpp:76
#define lastof(x)
Get the last element of an fixed size array.
Definition: depend.cpp:50
No palette animation.
Definition: base.hpp:52
How all blitters should look like.
Definition: base.hpp:30
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:593
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:26
virtual void PostResize()
Post resize event.
Definition: base.hpp:203
void AcquireBlitterLock() override
Acquire any lock(s) required to be held when changing blitters.
Definition: win32_v.cpp:1328
Palette animation should be done by video backend (8bpp only!)
Definition: base.hpp:53
bool _left_button_clicked
Is left mouse button clicked?
Definition: gfx.cpp:41
void ReleaseBlitterLock() override
Release any lock(s) required to be held when changing blitters.
Definition: win32_v.cpp:1333
std::vector< Dimension > _resolutions
List of resolutions.
Definition: driver.cpp:22
Console; Window numbers:
Definition: window_type.h:633
bool _ctrl_pressed
Is Ctrl pressed?
Definition: gfx.cpp:37
bool StartNewThread(std::thread *thr, const char *name, TFn &&_Fx, TArgs &&... _Ax)
Start a new thread.
Definition: thread.h:50
&#39; Single quote
Definition: gfx_type.h:103
const char * Start(const char *const *param) override
Start this driver.
Definition: win32_v.cpp:1116
bool _right_button_clicked
Is right mouse button clicked?
Definition: gfx.cpp:43
static T Align(const T x, uint n)
Return the smallest multiple of n equal or greater than x.
Definition: math_func.hpp:97
The blitter takes care of the palette animation.
Definition: base.hpp:54
uint current_y
Current vertical size (after resizing).
Definition: widget_type.h:175
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:40
[ Left square bracket
Definition: gfx_type.h:100
] Right square bracket
Definition: gfx_type.h:102
std::mutex lock
synchronization for playback status fields
Definition: win32_m.cpp:36
\ Backslash
Definition: gfx_type.h:101
void GetKeyboardLayout()
Retrieve keyboard layout from language string or (if set) config file.
Definition: osk_gui.cpp:357
static bool DrawIMECompositionString()
Should we draw the composition string ourself, i.e is this a normal IME?
Definition: win32_v.cpp:494
void CDECL usererror(const char *s,...)
Error handling for fatal user errors.
Definition: openttd.cpp:94
int wheel
mouse wheel movement
Definition: gfx_type.h:121
bool UpdateCursorPosition(int x, int y, bool queued_warp)
Update cursor position on mouse movement.
Definition: gfx.cpp:1644
/ Forward slash
Definition: gfx_type.h:97
static const uint MILLISECONDS_PER_TICK
The number of milliseconds per game tick.
Definition: gfx_type.h:306
void HandleKeypress(uint keycode, WChar key)
Handle keyboard input.
Definition: window.cpp:2654
int pos_x
Horizontal position of top-left corner of the widget in the window.
Definition: widget_type.h:177
byte _dirkeys
1 = left, 2 = up, 4 = right, 8 = down
Definition: gfx.cpp:33
bool fix_at
mouse is moving, but cursor is not (used for scrolling)
Definition: gfx_type.h:122
bool ToggleFullscreen(bool fullscreen) override
Change the full screen setting.
Definition: win32_v.cpp:1315
void HandleMouseEvents()
Handle a mouse event from the video driver.
Definition: window.cpp:2961
#define lengthof(x)
Return the length of an fixed size array.
Definition: depend.cpp:42
static Blitter * GetCurrentBlitter()
Get the current active blitter (always set by calling SelectBlitter).
Definition: factory.hpp:147
int first_dirty
The first dirty element.
Definition: gfx_type.h:311
PauseMode _pause_mode
The current pause mode.
Definition: gfx.cpp:49
bool ChangeResolution(int w, int h) override
Change the resolution of the window.
Definition: win32_v.cpp:1302
static WChar Utf16DecodeSurrogate(uint lead, uint trail)
Convert an UTF-16 surrogate pair to the corresponding Unicode character.
Definition: string_func.h:185
; Semicolon
Definition: gfx_type.h:98
Palette _cur_palette
Current palette.
Definition: gfx.cpp:50
bool _shift_pressed
Is Shift pressed?
Definition: gfx.cpp:38
#define DEBUG(name, level,...)
Output a line of debugging information.
Definition: debug.h:37
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:362
The video driver for windows.
Definition: win32_v.h:18
static bool Utf16IsTrailSurrogate(uint c)
Is the given character a lead surrogate code point?
Definition: string_func.h:174
void HandleCtrlChanged()
State of CONTROL key has changed.
Definition: window.cpp:2711
bool EditBoxInGlobalFocus()
Check if an edit box is in global focus.
Definition: window.cpp:459
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:556
static void SetCandidatePos(HWND hwnd)
Set the position of the candidate window.
Definition: win32_v.cpp:522
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:1152
void NetworkDrawChatMessage()
Draw the chat message-box.
uint current_x
Current horizontal size (after resizing).
Definition: widget_type.h:174
static Palette _local_palette
Local copy of the palette for use in the drawing thread.
Definition: win32_v.cpp:78
#define endof(x)
Get the end element of an fixed size array.
Definition: stdafx.h:386
bool in_window
mouse inside this window, determines drawing logic
Definition: gfx_type.h:143
bool MakeWindow(bool full_screen)
Instantiate a new window.
Definition: win32_v.cpp:265
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:164
void GameSizeChanged()
Size of the application screen changed.
Definition: main_gui.cpp:596
int width
width of the window (number of pixels to the right in x direction)
Definition: window_gui.h:321
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:2742
. Period
Definition: gfx_type.h:105
static std::condition_variable_any * _draw_signal
Signal to draw the next frame.
Definition: win32_v.cpp:74
WindowClass window_class
Window class.
Definition: window_gui.h:313
bool AfterBlitterChange() override
Callback invoked after the blitter was changed.
Definition: win32_v.cpp:1323
int count_dirty
The number of dirty elements.
Definition: gfx_type.h:312
static void CancelIMEComposition(HWND hwnd)
Clear the current composition string.
Definition: win32_v.cpp:620
virtual Point GetCaretPosition() const
Get the current caret position if an edit box has the focus.
Definition: window.cpp:392
uint32 WChar
Type for wide characters, i.e.
Definition: string_type.h:37
static void SetCompositionPos(HWND hwnd)
Set position of the composition window to the caret position.
Definition: win32_v.cpp:500
Dimensions (a width and height) of a rectangle in 2D.
Full 8bpp support by OS and hardware.
Definition: gfx_type.h:319
int pos_y
Vertical position of top-left corner of the widget in the window.
Definition: widget_type.h:178
static bool HasModalProgress()
Check if we are currently in a modal progress state.
Definition: progress.h:23
int height
Height of the window (number of pixels down in y direction)
Definition: window_gui.h:322
void MarkWholeScreenDirty()
This function mark the whole screen as dirty.
Definition: gfx.cpp:1459
void UpdateWindows()
Update the continuously changing contents of the windows, such as the viewports.
Definition: window.cpp:3112