OpenTTD Source 20250205-master-gfd85ab1e2c
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 "../error_func.h"
13#include "../gfx_func.h"
14#include "../os/windows/win32.h"
15#include "../blitter/factory.hpp"
16#include "../core/geometry_func.hpp"
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 "../library_loader.h"
26#include "win32_v.h"
27#include <windows.h>
28#include <imm.h>
29#include <versionhelpers.h>
30#if defined(_MSC_VER) && defined(NTDDI_WIN10_RS4)
31#include <winrt/Windows.UI.ViewManagement.h>
32#endif
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#ifndef WM_DPICHANGED
46#define WM_DPICHANGED 0x02E0
47#endif
48
49bool _window_maximize;
50static Dimension _bck_resolution;
51DWORD _imm_props;
52
54
55bool VideoDriver_Win32Base::ClaimMousePointer()
56{
57 MyShowCursor(false, true);
58 return true;
59}
60
62 uint8_t vk_from;
63 uint8_t vk_count;
64 uint8_t map_to;
65};
66
67#define AS(x, z) {x, 1, z}
68#define AM(x, y, z, w) {x, y - x + 1, z}
69
70static const Win32VkMapping _vk_mapping[] = {
71 /* Pageup stuff + up/down */
72 AM(VK_PRIOR, VK_DOWN, WKC_PAGEUP, WKC_DOWN),
73 /* Map letters & digits */
74 AM('A', 'Z', 'A', 'Z'),
75 AM('0', '9', '0', '9'),
76
77 AS(VK_ESCAPE, WKC_ESC),
78 AS(VK_PAUSE, WKC_PAUSE),
79 AS(VK_BACK, WKC_BACKSPACE),
80 AM(VK_INSERT, VK_DELETE, WKC_INSERT, WKC_DELETE),
81
82 AS(VK_SPACE, WKC_SPACE),
83 AS(VK_RETURN, WKC_RETURN),
84 AS(VK_TAB, WKC_TAB),
85
86 /* Function keys */
87 AM(VK_F1, VK_F12, WKC_F1, WKC_F12),
88
89 /* Numeric part */
90 AM(VK_NUMPAD0, VK_NUMPAD9, '0', '9'),
91 AS(VK_DIVIDE, WKC_NUM_DIV),
92 AS(VK_MULTIPLY, WKC_NUM_MUL),
93 AS(VK_SUBTRACT, WKC_NUM_MINUS),
94 AS(VK_ADD, WKC_NUM_PLUS),
95 AS(VK_DECIMAL, WKC_NUM_DECIMAL),
96
97 /* Other non-letter keys */
98 AS(0xBF, WKC_SLASH),
99 AS(0xBA, WKC_SEMICOLON),
100 AS(0xBB, WKC_EQUALS),
101 AS(0xDB, WKC_L_BRACKET),
102 AS(0xDC, WKC_BACKSLASH),
103 AS(0xDD, WKC_R_BRACKET),
104
105 AS(0xDE, WKC_SINGLEQUOTE),
106 AS(0xBC, WKC_COMMA),
107 AS(0xBD, WKC_MINUS),
108 AS(0xBE, WKC_PERIOD)
109};
110
111static uint MapWindowsKey(uint sym)
112{
113 uint key = 0;
114
115 for (const auto &map : _vk_mapping) {
116 if (IsInsideBS(sym, map.vk_from, map.vk_count)) {
117 key = sym - map.vk_from + map.map_to;
118 break;
119 }
120 }
121
122 if (GetAsyncKeyState(VK_SHIFT) < 0) key |= WKC_SHIFT;
123 if (GetAsyncKeyState(VK_CONTROL) < 0) key |= WKC_CTRL;
124 if (GetAsyncKeyState(VK_MENU) < 0) key |= WKC_ALT;
125 return key;
126}
127
130{
131 /* Check modes for the relevant fullscreen bpp */
132 return _support8bpp != S8BPP_HARDWARE ? 32 : BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
133}
134
141bool VideoDriver_Win32Base::MakeWindow(bool full_screen, bool resize)
142{
143 /* full_screen is whether the new window should be fullscreen,
144 * _wnd.fullscreen is whether the current window is. */
145 _fullscreen = full_screen;
146
147 /* recreate window? */
148 if ((full_screen != this->fullscreen) && this->main_wnd) {
149 DestroyWindow(this->main_wnd);
150 this->main_wnd = 0;
151 }
152
153 if (full_screen) {
154 DEVMODE settings;
155
156 memset(&settings, 0, sizeof(settings));
157 settings.dmSize = sizeof(settings);
158 settings.dmFields =
159 DM_BITSPERPEL |
160 DM_PELSWIDTH |
161 DM_PELSHEIGHT;
162 settings.dmBitsPerPel = this->GetFullscreenBpp();
163 settings.dmPelsWidth = this->width_org;
164 settings.dmPelsHeight = this->height_org;
165
166 /* Check for 8 bpp support. */
167 if (settings.dmBitsPerPel == 8 && ChangeDisplaySettings(&settings, CDS_FULLSCREEN | CDS_TEST) != DISP_CHANGE_SUCCESSFUL) {
168 settings.dmBitsPerPel = 32;
169 }
170
171 /* Test fullscreen with current resolution, if it fails use desktop resolution. */
172 if (ChangeDisplaySettings(&settings, CDS_FULLSCREEN | CDS_TEST) != DISP_CHANGE_SUCCESSFUL) {
173 RECT r;
174 GetWindowRect(GetDesktopWindow(), &r);
175 /* Guard against recursion. If we already failed here once, just fall through to
176 * the next ChangeDisplaySettings call which will fail and error out appropriately. */
177 if ((int)settings.dmPelsWidth != r.right - r.left || (int)settings.dmPelsHeight != r.bottom - r.top) {
178 return this->ChangeResolution(r.right - r.left, r.bottom - r.top);
179 }
180 }
181
182 if (ChangeDisplaySettings(&settings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL) {
183 this->MakeWindow(false, resize); // don't care about the result
184 return false; // the request failed
185 }
186 } else if (this->fullscreen) {
187 /* restore display? */
188 ChangeDisplaySettings(nullptr, 0);
189 /* restore the resolution */
190 this->width = _bck_resolution.width;
191 this->height = _bck_resolution.height;
192 }
193
194 {
195 RECT r;
196 DWORD style, showstyle;
197 int w, h;
198
199 showstyle = SW_SHOWNORMAL;
200 this->fullscreen = full_screen;
201 if (this->fullscreen) {
202 style = WS_POPUP;
203 SetRect(&r, 0, 0, this->width_org, this->height_org);
204 } else {
205 style = WS_OVERLAPPEDWINDOW;
206 /* On window creation, check if we were in maximize mode before */
207 if (_window_maximize) showstyle = SW_SHOWMAXIMIZED;
208 SetRect(&r, 0, 0, this->width, this->height);
209 }
210
211 AdjustWindowRect(&r, style, FALSE);
212 w = r.right - r.left;
213 h = r.bottom - r.top;
214
215 if (this->main_wnd != nullptr) {
216 if (!_window_maximize && resize) SetWindowPos(this->main_wnd, 0, 0, 0, w, h, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOMOVE);
217 } else {
218 int x = 0;
219 int y = 0;
220
221 /* For windowed mode, center on the workspace of the primary display. */
222 if (!this->fullscreen) {
223 MONITORINFO mi;
224 mi.cbSize = sizeof(mi);
225 GetMonitorInfo(MonitorFromWindow(0, MONITOR_DEFAULTTOPRIMARY), &mi);
226
227 x = (mi.rcWork.right - mi.rcWork.left - w) / 2;
228 y = (mi.rcWork.bottom - mi.rcWork.top - h) / 2;
229 }
230
231 std::string caption = VideoDriver::GetCaption();
232 this->main_wnd = CreateWindow(L"OTTD", OTTD2FS(caption).c_str(), style, x, y, w, h, 0, 0, GetModuleHandle(nullptr), this);
233 if (this->main_wnd == nullptr) UserError("CreateWindow failed");
234 ShowWindow(this->main_wnd, showstyle);
235 }
236 }
237
239
241 return true;
242}
243
245static LRESULT HandleCharMsg(uint keycode, char32_t charcode)
246{
247 static char32_t prev_char = 0;
248
249 /* Did we get a lead surrogate? If yes, store and exit. */
250 if (Utf16IsLeadSurrogate(charcode)) {
251 if (prev_char != 0) Debug(driver, 1, "Got two UTF-16 lead surrogates, dropping the first one");
252 prev_char = charcode;
253 return 0;
254 }
255
256 /* Stored lead surrogate and incoming trail surrogate? Combine and forward to input handling. */
257 if (prev_char != 0) {
258 if (Utf16IsTrailSurrogate(charcode)) {
259 charcode = Utf16DecodeSurrogate(prev_char, charcode);
260 } else {
261 Debug(driver, 1, "Got an UTF-16 lead surrogate without a trail surrogate, dropping the lead surrogate");
262 }
263 }
264 prev_char = 0;
265
266 HandleKeypress(keycode, charcode);
267
268 return 0;
269}
270
273{
274 return (_imm_props & IME_PROP_AT_CARET) && !(_imm_props & IME_PROP_SPECIAL_UI);
275}
276
278static void SetCompositionPos(HWND hwnd)
279{
280 HIMC hIMC = ImmGetContext(hwnd);
281 if (hIMC != nullptr) {
282 COMPOSITIONFORM cf;
283 cf.dwStyle = CFS_POINT;
284
285 if (EditBoxInGlobalFocus()) {
286 /* Get caret position. */
287 Point pt = _focused_window->GetCaretPosition();
288 cf.ptCurrentPos.x = _focused_window->left + pt.x;
289 cf.ptCurrentPos.y = _focused_window->top + pt.y;
290 } else {
291 cf.ptCurrentPos.x = 0;
292 cf.ptCurrentPos.y = 0;
293 }
294 ImmSetCompositionWindow(hIMC, &cf);
295 }
296 ImmReleaseContext(hwnd, hIMC);
297}
298
300static void SetCandidatePos(HWND hwnd)
301{
302 HIMC hIMC = ImmGetContext(hwnd);
303 if (hIMC != nullptr) {
304 CANDIDATEFORM cf;
305 cf.dwIndex = 0;
306 cf.dwStyle = CFS_EXCLUDE;
307
308 if (EditBoxInGlobalFocus()) {
309 Point pt = _focused_window->GetCaretPosition();
310 cf.ptCurrentPos.x = _focused_window->left + pt.x;
311 cf.ptCurrentPos.y = _focused_window->top + pt.y;
312 if (_focused_window->window_class == WC_CONSOLE) {
313 cf.rcArea.left = _focused_window->left;
314 cf.rcArea.top = _focused_window->top;
315 cf.rcArea.right = _focused_window->left + _focused_window->width;
316 cf.rcArea.bottom = _focused_window->top + _focused_window->height;
317 } else {
318 cf.rcArea.left = _focused_window->left + _focused_window->nested_focus->pos_x;
319 cf.rcArea.top = _focused_window->top + _focused_window->nested_focus->pos_y;
320 cf.rcArea.right = cf.rcArea.left + _focused_window->nested_focus->current_x;
321 cf.rcArea.bottom = cf.rcArea.top + _focused_window->nested_focus->current_y;
322 }
323 } else {
324 cf.ptCurrentPos.x = 0;
325 cf.ptCurrentPos.y = 0;
326 SetRectEmpty(&cf.rcArea);
327 }
328 ImmSetCandidateWindow(hIMC, &cf);
329 }
330 ImmReleaseContext(hwnd, hIMC);
331}
332
334static LRESULT HandleIMEComposition(HWND hwnd, WPARAM wParam, LPARAM lParam)
335{
336 HIMC hIMC = ImmGetContext(hwnd);
337
338 if (hIMC != nullptr) {
339 if (lParam & GCS_RESULTSTR) {
340 /* Read result string from the IME. */
341 LONG len = ImmGetCompositionString(hIMC, GCS_RESULTSTR, nullptr, 0); // Length is always in bytes, even in UNICODE build.
342 std::wstring str(len + 1, L'\0');
343 len = ImmGetCompositionString(hIMC, GCS_RESULTSTR, str.data(), len);
344 str[len / sizeof(wchar_t)] = L'\0';
345
346 /* Transmit text to windowing system. */
347 if (len > 0) {
348 HandleTextInput(nullptr, true); // Clear marked string.
349 HandleTextInput(FS2OTTD(str).c_str());
350 }
351 SetCompositionPos(hwnd);
352
353 /* Don't pass the result string on to the default window proc. */
354 lParam &= ~(GCS_RESULTSTR | GCS_RESULTCLAUSE | GCS_RESULTREADCLAUSE | GCS_RESULTREADSTR);
355 }
356
357 if ((lParam & GCS_COMPSTR) && DrawIMECompositionString()) {
358 /* Read composition string from the IME. */
359 LONG len = ImmGetCompositionString(hIMC, GCS_COMPSTR, nullptr, 0); // Length is always in bytes, even in UNICODE build.
360 std::wstring str(len + 1, L'\0');
361 len = ImmGetCompositionString(hIMC, GCS_COMPSTR, str.data(), len);
362 str[len / sizeof(wchar_t)] = L'\0';
363
364 if (len > 0) {
365 static char utf8_buf[1024];
366 convert_from_fs(str.c_str(), utf8_buf);
367
368 /* Convert caret position from bytes in the input string to a position in the UTF-8 encoded string. */
369 LONG caret_bytes = ImmGetCompositionString(hIMC, GCS_CURSORPOS, nullptr, 0);
370 const char *caret = utf8_buf;
371 for (const wchar_t *c = str.c_str(); *c != '\0' && *caret != '\0' && caret_bytes > 0; c++, caret_bytes--) {
372 /* Skip DBCS lead bytes or leading surrogates. */
373 if (Utf16IsLeadSurrogate(*c)) {
374 c++;
375 caret_bytes--;
376 }
377 Utf8Consume(&caret);
378 }
379
380 HandleTextInput(utf8_buf, true, caret);
381 } else {
382 HandleTextInput(nullptr, true);
383 }
384
385 lParam &= ~(GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE | GCS_CURSORPOS | GCS_DELTASTART);
386 }
387 }
388 ImmReleaseContext(hwnd, hIMC);
389
390 return lParam != 0 ? DefWindowProc(hwnd, WM_IME_COMPOSITION, wParam, lParam) : 0;
391}
392
394static void CancelIMEComposition(HWND hwnd)
395{
396 HIMC hIMC = ImmGetContext(hwnd);
397 if (hIMC != nullptr) ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
398 ImmReleaseContext(hwnd, hIMC);
399 /* Clear any marked string from the current edit box. */
400 HandleTextInput(nullptr, true);
401}
402
403static bool IsDarkModeEnabled()
404{
405 /* Only build if SDK is Windows 10 1803 or later. */
406#if defined(_MSC_VER) && defined(NTDDI_WIN10_RS4)
407 if (IsWindows10OrGreater()) {
408 try {
409 /*
410 * The official documented way to find out if the system is running in dark mode is to
411 * check the brightness of the current theme's colour.
412 * See: https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/ui/apply-windows-themes#know-when-dark-mode-is-enabled
413 *
414 * There are other variants floating around on the Internet, but they all rely on internal,
415 * undocumented Windows functions that may or may not work in the future.
416 */
417 winrt::Windows::UI::ViewManagement::UISettings settings;
418 auto foreground = settings.GetColorValue(winrt::Windows::UI::ViewManagement::UIColorType::Foreground);
419
420 /* If the Foreground colour is a light colour, the system is running in dark mode. */
421 return ((5 * foreground.G) + (2 * foreground.R) + foreground.B) > (8 * 128);
422 } catch (...) {
423 /* Some kind of error, like a too old Windows version. Just return false. */
424 return false;
425 }
426 }
427#endif /* defined(_MSC_VER) && defined(NTDDI_WIN10_RS4) */
428
429 return false;
430}
431
432static void SetDarkModeForWindow(HWND hWnd, bool dark_mode)
433{
434 /* Only build if SDK is Windows 10+. */
435#if defined(NTDDI_WIN10)
436 if (!IsWindows10OrGreater()) return;
437
438 /* This function is documented, but not supported on all Windows 10/11 SDK builds. For this
439 * reason, the code uses dynamic loading and ignores any errors for a best-effort result. */
440 static LibraryLoader _dwmapi("dwmapi.dll");
441 typedef HRESULT(WINAPI *PFNDWMSETWINDOWATTRIBUTE)(HWND, DWORD, LPCVOID, DWORD);
442 static PFNDWMSETWINDOWATTRIBUTE DwmSetWindowAttribute = _dwmapi.GetFunction("DwmSetWindowAttribute");
443
444 if (DwmSetWindowAttribute != nullptr) {
445 /* Contrary to the published documentation, DWMWA_USE_IMMERSIVE_DARK_MODE does not change the
446 * window chrome according to the current theme, but forces it to either light or dark mode.
447 * As such, the set value has to depend on the current theming mode.*/
448 BOOL value = dark_mode ? TRUE : FALSE;
449 if (DwmSetWindowAttribute(hWnd, 20 /* DWMWA_USE_IMMERSIVE_DARK_MODE */, &value, sizeof(value)) != S_OK) {
450 DwmSetWindowAttribute(hWnd, 19 /* DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 */, &value, sizeof(value)); // Ignore errors. It works or it doesn't.
451 }
452 }
453#endif /* defined(NTDDI_WIN10) */
454}
455
456LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
457{
458 static uint32_t keycode = 0;
459 static bool console = false;
460
461 const float SCROLL_BUILTIN_MULTIPLIER = 14.0f / WHEEL_DELTA;
462
463 VideoDriver_Win32Base *video_driver = (VideoDriver_Win32Base *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
464
465 switch (msg) {
466 case WM_CREATE:
467 SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)((LPCREATESTRUCT)lParam)->lpCreateParams);
468 _cursor.in_window = false; // Win32 has mouse tracking.
469 SetCompositionPos(hwnd);
470 _imm_props = ImmGetProperty(GetKeyboardLayout(0), IGP_PROPERTY);
471
472 /* Enable dark mode theming for window chrome. */
473 SetDarkModeForWindow(hwnd, IsDarkModeEnabled());
474 break;
475
476 case WM_SETTINGCHANGE:
477 /* Synchronize dark mode theming state. */
478 SetDarkModeForWindow(hwnd, IsDarkModeEnabled());
479 break;
480
481 case WM_PAINT: {
482 RECT r;
483 GetUpdateRect(hwnd, &r, FALSE);
484 video_driver->MakeDirty(r.left, r.top, r.right - r.left, r.bottom - r.top);
485
486 ValidateRect(hwnd, nullptr);
487 return 0;
488 }
489
490 case WM_PALETTECHANGED:
491 if ((HWND)wParam == hwnd) return 0;
492 [[fallthrough]];
493
494 case WM_QUERYNEWPALETTE:
495 video_driver->PaletteChanged(hwnd);
496 return 0;
497
498 case WM_CLOSE:
499 HandleExitGameRequest();
500 return 0;
501
502 case WM_DESTROY:
503 if (_window_maximize) _cur_resolution = _bck_resolution;
504 return 0;
505
506 case WM_LBUTTONDOWN:
507 SetCapture(hwnd);
508 _left_button_down = true;
510 return 0;
511
512 case WM_LBUTTONUP:
513 ReleaseCapture();
514 _left_button_down = false;
515 _left_button_clicked = false;
517 return 0;
518
519 case WM_RBUTTONDOWN:
520 SetCapture(hwnd);
521 _right_button_down = true;
524 return 0;
525
526 case WM_RBUTTONUP:
527 ReleaseCapture();
528 _right_button_down = false;
530 return 0;
531
532 case WM_MOUSELEAVE:
533 UndrawMouseCursor();
534 _cursor.in_window = false;
535
536 if (!_left_button_down && !_right_button_down) MyShowCursor(true);
537 return 0;
538
539 case WM_MOUSEMOVE: {
540 int x = (int16_t)LOWORD(lParam);
541 int y = (int16_t)HIWORD(lParam);
542
543 /* If the mouse was not in the window and it has moved it means it has
544 * come into the window, so start drawing the mouse. Also start
545 * tracking the mouse for exiting the window */
546 if (!_cursor.in_window) {
547 _cursor.in_window = true;
548 TRACKMOUSEEVENT tme;
549 tme.cbSize = sizeof(tme);
550 tme.dwFlags = TME_LEAVE;
551 tme.hwndTrack = hwnd;
552
553 TrackMouseEvent(&tme);
554 }
555
556 if (_cursor.fix_at) {
557 /* Get all queued mouse events now in case we have to warp the cursor. In the
558 * end, we only care about the current mouse position and not bygone events. */
559 MSG m;
560 while (PeekMessage(&m, hwnd, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_REMOVE | PM_NOYIELD | PM_QS_INPUT)) {
561 x = (int16_t)LOWORD(m.lParam);
562 y = (int16_t)HIWORD(m.lParam);
563 }
564 }
565
566 if (_cursor.UpdateCursorPosition(x, y)) {
567 POINT pt;
568 pt.x = _cursor.pos.x;
569 pt.y = _cursor.pos.y;
570 ClientToScreen(hwnd, &pt);
571 SetCursorPos(pt.x, pt.y);
572 }
573 MyShowCursor(false);
575 return 0;
576 }
577
578 case WM_INPUTLANGCHANGE:
579 _imm_props = ImmGetProperty(GetKeyboardLayout(0), IGP_PROPERTY);
580 break;
581
582 case WM_IME_SETCONTEXT:
583 /* Don't show the composition window if we draw the string ourself. */
584 if (DrawIMECompositionString()) lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
585 break;
586
587 case WM_IME_STARTCOMPOSITION:
588 SetCompositionPos(hwnd);
589 if (DrawIMECompositionString()) return 0;
590 break;
591
592 case WM_IME_COMPOSITION:
593 return HandleIMEComposition(hwnd, wParam, lParam);
594
595 case WM_IME_ENDCOMPOSITION:
596 /* Clear any pending composition string. */
597 HandleTextInput(nullptr, true);
598 if (DrawIMECompositionString()) return 0;
599 break;
600
601 case WM_IME_NOTIFY:
602 if (wParam == IMN_OPENCANDIDATE) SetCandidatePos(hwnd);
603 break;
604
605 case WM_DEADCHAR:
606 console = GB(lParam, 16, 8) == 41;
607 return 0;
608
609 case WM_CHAR: {
610 uint scancode = GB(lParam, 16, 8);
611 uint charcode = wParam;
612
613 /* If the console key is a dead-key, we need to press it twice to get a WM_CHAR message.
614 * But we then get two WM_CHAR messages, so ignore the first one */
615 if (console && scancode == 41) {
616 console = false;
617 return 0;
618 }
619
620 /* IMEs and other input methods sometimes send a WM_CHAR without a WM_KEYDOWN,
621 * clear the keycode so a previous WM_KEYDOWN doesn't become 'stuck'. */
622 uint cur_keycode = keycode;
623 keycode = 0;
624
625 return HandleCharMsg(cur_keycode, charcode);
626 }
627
628 case WM_KEYDOWN: {
629 /* No matter the keyboard layout, we will map the '~' to the console. */
630 uint scancode = GB(lParam, 16, 8);
631 keycode = scancode == 41 ? (uint)WKC_BACKQUOTE : MapWindowsKey(wParam);
632
633 uint charcode = MapVirtualKey(wParam, MAPVK_VK_TO_CHAR);
634
635 /* No character translation? */
636 if (charcode == 0) {
637 HandleKeypress(keycode, 0);
638 return 0;
639 }
640
641 /* If an edit box is in focus, wait for the corresponding WM_CHAR message. */
642 if (!EditBoxInGlobalFocus()) {
643 /* Is the console key a dead key? If yes, ignore the first key down event. */
644 if (HasBit(charcode, 31) && !console) {
645 if (scancode == 41) {
646 console = true;
647 return 0;
648 }
649 }
650 console = false;
651
652 /* IMEs and other input methods sometimes send a WM_CHAR without a WM_KEYDOWN,
653 * clear the keycode so a previous WM_KEYDOWN doesn't become 'stuck'. */
654 uint cur_keycode = keycode;
655 keycode = 0;
656
657 return HandleCharMsg(cur_keycode, LOWORD(charcode));
658 }
659
660 return 0;
661 }
662
663 case WM_SYSKEYDOWN: // user presses F10 or Alt, both activating the title-menu
664 switch (wParam) {
665 case VK_RETURN:
666 case 'F': // Full Screen on ALT + ENTER/F
667 ToggleFullScreen(!video_driver->fullscreen);
668 return 0;
669
670 case VK_MENU: // Just ALT
671 return 0; // do nothing
672
673 case VK_F10: // F10, ignore activation of menu
674 HandleKeypress(MapWindowsKey(wParam), 0);
675 return 0;
676
677 default: // ALT in combination with something else
678 HandleKeypress(MapWindowsKey(wParam), 0);
679 break;
680 }
681 break;
682
683 case WM_SIZE:
684 if (wParam != SIZE_MINIMIZED) {
685 /* Set maximized flag when we maximize (obviously), but also when we
686 * switched to fullscreen from a maximized state */
687 _window_maximize = (wParam == SIZE_MAXIMIZED || (_window_maximize && _fullscreen));
688 if (_window_maximize || _fullscreen) _bck_resolution = _cur_resolution;
689 video_driver->ClientSizeChanged(LOWORD(lParam), HIWORD(lParam));
690 }
691 return 0;
692
693 case WM_SIZING: {
694 RECT *r = (RECT*)lParam;
695 RECT r2;
696 int w, h;
697
698 SetRect(&r2, 0, 0, 0, 0);
699 AdjustWindowRect(&r2, GetWindowLong(hwnd, GWL_STYLE), FALSE);
700
701 w = r->right - r->left - (r2.right - r2.left);
702 h = r->bottom - r->top - (r2.bottom - r2.top);
703 w = std::max(w, 64);
704 h = std::max(h, 64);
705 SetRect(&r2, 0, 0, w, h);
706
707 AdjustWindowRect(&r2, GetWindowLong(hwnd, GWL_STYLE), FALSE);
708 w = r2.right - r2.left;
709 h = r2.bottom - r2.top;
710
711 switch (wParam) {
712 case WMSZ_BOTTOM:
713 r->bottom = r->top + h;
714 break;
715
716 case WMSZ_BOTTOMLEFT:
717 r->bottom = r->top + h;
718 r->left = r->right - w;
719 break;
720
721 case WMSZ_BOTTOMRIGHT:
722 r->bottom = r->top + h;
723 r->right = r->left + w;
724 break;
725
726 case WMSZ_LEFT:
727 r->left = r->right - w;
728 break;
729
730 case WMSZ_RIGHT:
731 r->right = r->left + w;
732 break;
733
734 case WMSZ_TOP:
735 r->top = r->bottom - h;
736 break;
737
738 case WMSZ_TOPLEFT:
739 r->top = r->bottom - h;
740 r->left = r->right - w;
741 break;
742
743 case WMSZ_TOPRIGHT:
744 r->top = r->bottom - h;
745 r->right = r->left + w;
746 break;
747 }
748 return TRUE;
749 }
750
751 case WM_DPICHANGED: {
752 auto did_adjust = AdjustGUIZoom(true);
753
754 /* Resize the window to match the new DPI setting. */
755 RECT *prcNewWindow = (RECT *)lParam;
756 SetWindowPos(hwnd,
757 nullptr,
758 prcNewWindow->left,
759 prcNewWindow->top,
760 prcNewWindow->right - prcNewWindow->left,
761 prcNewWindow->bottom - prcNewWindow->top,
762 SWP_NOZORDER | SWP_NOACTIVATE);
763
764 if (did_adjust) ReInitAllWindows(true);
765
766 return 0;
767 }
768
769/* needed for wheel */
770#if !defined(WM_MOUSEWHEEL)
771# define WM_MOUSEWHEEL 0x020A
772#endif /* WM_MOUSEWHEEL */
773#if !defined(WM_MOUSEHWHEEL)
774# define WM_MOUSEHWHEEL 0x020E
775#endif /* WM_MOUSEHWHEEL */
776#if !defined(GET_WHEEL_DELTA_WPARAM)
777# define GET_WHEEL_DELTA_WPARAM(wparam) ((short)HIWORD(wparam))
778#endif /* GET_WHEEL_DELTA_WPARAM */
779
780 case WM_MOUSEWHEEL: {
781 int delta = GET_WHEEL_DELTA_WPARAM(wParam);
782
783 if (delta < 0) {
784 _cursor.wheel++;
785 } else if (delta > 0) {
786 _cursor.wheel--;
787 }
788
789 _cursor.v_wheel -= static_cast<float>(delta) * SCROLL_BUILTIN_MULTIPLIER * _settings_client.gui.scrollwheel_multiplier;
790 _cursor.wheel_moved = true;
792 return 0;
793 }
794
795 case WM_MOUSEHWHEEL: {
796 int delta = GET_WHEEL_DELTA_WPARAM(wParam);
797
798 _cursor.h_wheel += static_cast<float>(delta) * SCROLL_BUILTIN_MULTIPLIER * _settings_client.gui.scrollwheel_multiplier;
799 _cursor.wheel_moved = true;
801 return 0;
802 }
803
804 case WM_SETFOCUS:
805 video_driver->has_focus = true;
806 SetCompositionPos(hwnd);
807 break;
808
809 case WM_KILLFOCUS:
810 video_driver->has_focus = false;
811 break;
812
813 case WM_ACTIVATE: {
814 /* Don't do anything if we are closing openttd */
815 if (_exit_game) break;
816
817 bool active = (LOWORD(wParam) != WA_INACTIVE);
818 bool minimized = (HIWORD(wParam) != 0);
819 if (video_driver->fullscreen) {
820 if (active && minimized) {
821 /* Restore the game window */
822 Dimension d = _bck_resolution; // Save current non-fullscreen window size as it will be overwritten by ShowWindow.
823 ShowWindow(hwnd, SW_RESTORE);
824 _bck_resolution = d;
825 video_driver->MakeWindow(true);
826 } else if (!active && !minimized) {
827 /* Minimise the window and restore desktop */
828 ShowWindow(hwnd, SW_MINIMIZE);
829 ChangeDisplaySettings(nullptr, 0);
830 }
831 }
832 break;
833 }
834 }
835
836 return DefWindowProc(hwnd, msg, wParam, lParam);
837}
838
839static void RegisterWndClass()
840{
841 static bool registered = false;
842
843 if (registered) return;
844
845 HINSTANCE hinst = GetModuleHandle(nullptr);
846 WNDCLASS wnd = {
847 CS_OWNDC,
848 WndProcGdi,
849 0,
850 0,
851 hinst,
852 LoadIcon(hinst, MAKEINTRESOURCE(100)),
853 LoadCursor(nullptr, IDC_ARROW),
854 0,
855 0,
856 L"OTTD"
857 };
858
859 registered = true;
860 if (!RegisterClass(&wnd)) UserError("RegisterClass failed");
861}
862
863static const Dimension default_resolutions[] = {
864 { 640, 480 },
865 { 800, 600 },
866 { 1024, 768 },
867 { 1152, 864 },
868 { 1280, 800 },
869 { 1280, 960 },
870 { 1280, 1024 },
871 { 1400, 1050 },
872 { 1600, 1200 },
873 { 1680, 1050 },
874 { 1920, 1200 }
875};
876
877static void FindResolutions(uint8_t bpp)
878{
879 _resolutions.clear();
880
881 DEVMODE dm;
882 for (uint i = 0; EnumDisplaySettings(nullptr, i, &dm) != 0; i++) {
883 if (dm.dmBitsPerPel != bpp || dm.dmPelsWidth < 640 || dm.dmPelsHeight < 480) continue;
884 if (std::ranges::find(_resolutions, Dimension(dm.dmPelsWidth, dm.dmPelsHeight)) != _resolutions.end()) continue;
885 _resolutions.emplace_back(dm.dmPelsWidth, dm.dmPelsHeight);
886 }
887
888 /* We have found no resolutions, show the default list */
889 if (_resolutions.empty()) {
890 _resolutions.assign(std::begin(default_resolutions), std::end(default_resolutions));
891 }
892
893 SortResolutions();
894}
895
896void VideoDriver_Win32Base::Initialize()
897{
898 this->UpdateAutoResolution();
899
900 RegisterWndClass();
901 FindResolutions(this->GetFullscreenBpp());
902
903 /* fullscreen uses those */
904 this->width = this->width_org = _cur_resolution.width;
905 this->height = this->height_org = _cur_resolution.height;
906
907 Debug(driver, 2, "Resolution for display: {}x{}", _cur_resolution.width, _cur_resolution.height);
908}
909
911{
912 DestroyWindow(this->main_wnd);
913
914 if (this->fullscreen) ChangeDisplaySettings(nullptr, 0);
915 MyShowCursor(true);
916}
917void VideoDriver_Win32Base::MakeDirty(int left, int top, int width, int height)
918{
919 Rect r = {left, top, left + width, top + height};
920 this->dirty_rect = BoundingRect(this->dirty_rect, r);
921}
922
924{
925 if (!CopyPalette(_local_palette)) return;
926 this->MakeDirty(0, 0, _screen.width, _screen.height);
927}
928
930{
931 bool old_ctrl_pressed = _ctrl_pressed;
932
933 _ctrl_pressed = this->has_focus && GetAsyncKeyState(VK_CONTROL) < 0;
934 _shift_pressed = this->has_focus && GetAsyncKeyState(VK_SHIFT) < 0;
935
936 /* Speedup when pressing tab, except when using ALT+TAB
937 * to switch to another application. */
938 this->fast_forward_key_pressed = this->has_focus && GetAsyncKeyState(VK_TAB) < 0 && GetAsyncKeyState(VK_MENU) >= 0;
939
940 /* Determine which directional keys are down. */
941 if (this->has_focus) {
942 _dirkeys =
943 (GetAsyncKeyState(VK_LEFT) < 0 ? 1 : 0) +
944 (GetAsyncKeyState(VK_UP) < 0 ? 2 : 0) +
945 (GetAsyncKeyState(VK_RIGHT) < 0 ? 4 : 0) +
946 (GetAsyncKeyState(VK_DOWN) < 0 ? 8 : 0);
947 } else {
948 _dirkeys = 0;
949 }
950
951 if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
952}
953
955{
956 MSG mesg;
957
958 if (!PeekMessage(&mesg, nullptr, 0, 0, PM_REMOVE)) return false;
959
960 /* Convert key messages to char messages if we want text input. */
961 if (EditBoxInGlobalFocus()) TranslateMessage(&mesg);
962 DispatchMessage(&mesg);
963
964 return true;
965}
966
968{
969 this->StartGameThread();
970
971 for (;;) {
972 if (_exit_game) break;
973
974 this->Tick();
975 this->SleepTillNextTick();
976 }
977
978 this->StopGameThread();
979}
980
981void VideoDriver_Win32Base::ClientSizeChanged(int w, int h, bool force)
982{
983 /* Allocate backing store of the new size. */
984 if (this->AllocateBackingStore(w, h, force)) {
986
988
990 }
991}
992
994{
995 if (_window_maximize) ShowWindow(this->main_wnd, SW_SHOWNORMAL);
996
997 this->width = this->width_org = w;
998 this->height = this->height_org = h;
999
1000 return this->MakeWindow(_fullscreen); // _wnd.fullscreen screws up ingame resolution switching
1001}
1002
1004{
1005 bool res = this->MakeWindow(full_screen);
1006
1008 return res;
1009}
1010
1017
1018static BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor, HDC, LPRECT, LPARAM data)
1019{
1020 auto &list = *reinterpret_cast<std::vector<int>*>(data);
1021
1022 MONITORINFOEX monitorInfo = {};
1023 monitorInfo.cbSize = sizeof(MONITORINFOEX);
1024 GetMonitorInfo(hMonitor, &monitorInfo);
1025
1026 DEVMODE devMode = {};
1027 devMode.dmSize = sizeof(DEVMODE);
1028 devMode.dmDriverExtra = 0;
1029 EnumDisplaySettings(monitorInfo.szDevice, ENUM_CURRENT_SETTINGS, &devMode);
1030
1031 if (devMode.dmDisplayFrequency != 0) list.push_back(devMode.dmDisplayFrequency);
1032 return true;
1033}
1034
1036{
1037 std::vector<int> rates = {};
1038 EnumDisplayMonitors(nullptr, nullptr, MonitorEnumProc, reinterpret_cast<LPARAM>(&rates));
1039 return rates;
1040}
1041
1043{
1044 return { static_cast<uint>(GetSystemMetrics(SM_CXSCREEN)), static_cast<uint>(GetSystemMetrics(SM_CYSCREEN)) };
1045}
1046
1048{
1049 typedef UINT (WINAPI *PFNGETDPIFORWINDOW)(HWND hwnd);
1050 typedef UINT (WINAPI *PFNGETDPIFORSYSTEM)(VOID);
1051 typedef HRESULT (WINAPI *PFNGETDPIFORMONITOR)(HMONITOR hMonitor, int dpiType, UINT *dpiX, UINT *dpiY);
1052
1053 static PFNGETDPIFORWINDOW _GetDpiForWindow = nullptr;
1054 static PFNGETDPIFORSYSTEM _GetDpiForSystem = nullptr;
1055 static PFNGETDPIFORMONITOR _GetDpiForMonitor = nullptr;
1056
1057 static bool init_done = false;
1058 if (!init_done) {
1059 init_done = true;
1060 static LibraryLoader _user32("user32.dll");
1061 static LibraryLoader _shcore("shcore.dll");
1062 _GetDpiForWindow = _user32.GetFunction("GetDpiForWindow");
1063 _GetDpiForSystem = _user32.GetFunction("GetDpiForSystem");
1064 _GetDpiForMonitor = _shcore.GetFunction("GetDpiForMonitor");
1065 }
1066
1067 UINT cur_dpi = 0;
1068
1069 if (cur_dpi == 0 && _GetDpiForWindow != nullptr && this->main_wnd != nullptr) {
1070 /* Per window DPI is supported since Windows 10 Ver 1607. */
1071 cur_dpi = _GetDpiForWindow(this->main_wnd);
1072 }
1073 if (cur_dpi == 0 && _GetDpiForMonitor != nullptr && this->main_wnd != nullptr) {
1074 /* Per monitor is supported since Windows 8.1. */
1075 UINT dpiX, dpiY;
1076 if (SUCCEEDED(_GetDpiForMonitor(MonitorFromWindow(this->main_wnd, MONITOR_DEFAULTTOPRIMARY), 0 /* MDT_EFFECTIVE_DPI */, &dpiX, &dpiY))) {
1077 cur_dpi = dpiX; // X and Y are always identical.
1078 }
1079 }
1080 if (cur_dpi == 0 && _GetDpiForSystem != nullptr) {
1081 /* Fall back to system DPI. */
1082 cur_dpi = _GetDpiForSystem();
1083 }
1084
1085 return cur_dpi > 0 ? cur_dpi / 96.0f : 1.0f; // Default Windows DPI value is 96.
1086}
1087
1089{
1090 if (this->buffer_locked) return false;
1091 this->buffer_locked = true;
1092
1093 _screen.dst_ptr = this->GetVideoPointer();
1094 assert(_screen.dst_ptr != nullptr);
1095
1096 return true;
1097}
1098
1100{
1101 assert(_screen.dst_ptr != nullptr);
1102 if (_screen.dst_ptr != nullptr) {
1103 /* Hand video buffer back to the drawing backend. */
1104 this->ReleaseVideoPointer();
1105 _screen.dst_ptr = nullptr;
1106 }
1107
1108 this->buffer_locked = false;
1109}
1110
1111
1112static FVideoDriver_Win32GDI iFVideoDriver_Win32GDI;
1113
1114std::optional<std::string_view> VideoDriver_Win32GDI::Start(const StringList &param)
1115{
1116 if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 0) return "Only real blitters supported";
1117
1118 this->Initialize();
1119
1120 this->MakePalette();
1122 this->MakeWindow(_fullscreen);
1123
1125
1126 this->is_game_threaded = !GetDriverParamBool(param, "no_threads") && !GetDriverParamBool(param, "no_thread");
1127
1128 return std::nullopt;
1129}
1130
1132{
1133 DeleteObject(this->gdi_palette);
1134 DeleteObject(this->dib_sect);
1135
1137}
1138
1140{
1142
1143 w = std::max(w, 64);
1144 h = std::max(h, 64);
1145
1146 if (!force && w == _screen.width && h == _screen.height) return false;
1147
1148 BITMAPINFO *bi = (BITMAPINFO *)new char[sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256]();
1149 bi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
1150
1151 bi->bmiHeader.biWidth = this->width = w;
1152 bi->bmiHeader.biHeight = -(this->height = h);
1153
1154 bi->bmiHeader.biPlanes = 1;
1155 bi->bmiHeader.biBitCount = bpp;
1156 bi->bmiHeader.biCompression = BI_RGB;
1157
1158 if (this->dib_sect) DeleteObject(this->dib_sect);
1159
1160 HDC dc = GetDC(0);
1161 this->dib_sect = CreateDIBSection(dc, bi, DIB_RGB_COLORS, (VOID **)&this->buffer_bits, nullptr, 0);
1162 if (this->dib_sect == nullptr) {
1163 delete[] bi;
1164 UserError("CreateDIBSection failed");
1165 }
1166 ReleaseDC(0, dc);
1167
1168 _screen.width = w;
1169 _screen.pitch = (bpp == 8) ? Align(w, 4) : w;
1170 _screen.height = h;
1171 _screen.dst_ptr = this->GetVideoPointer();
1172
1173 delete[] bi;
1174 return true;
1175}
1176
1178{
1179 assert(BlitterFactory::GetCurrentBlitter()->GetScreenDepth() != 0);
1180 return this->AllocateBackingStore(_screen.width, _screen.height, true) && this->MakeWindow(_fullscreen, false);
1181}
1182
1183void VideoDriver_Win32GDI::MakePalette()
1184{
1186
1187 LOGPALETTE *pal = (LOGPALETTE *)new char[sizeof(LOGPALETTE) + (256 - 1) * sizeof(PALETTEENTRY)]();
1188
1189 pal->palVersion = 0x300;
1190 pal->palNumEntries = 256;
1191
1192 for (uint i = 0; i != 256; i++) {
1193 pal->palPalEntry[i].peRed = _local_palette.palette[i].r;
1194 pal->palPalEntry[i].peGreen = _local_palette.palette[i].g;
1195 pal->palPalEntry[i].peBlue = _local_palette.palette[i].b;
1196 pal->palPalEntry[i].peFlags = 0;
1197
1198 }
1199 this->gdi_palette = CreatePalette(pal);
1200 delete[] pal;
1201 if (this->gdi_palette == nullptr) UserError("CreatePalette failed!\n");
1202}
1203
1204void VideoDriver_Win32GDI::UpdatePalette(HDC dc, uint start, uint count)
1205{
1206 RGBQUAD rgb[256];
1207
1208 for (uint i = 0; i != count; i++) {
1209 rgb[i].rgbRed = _local_palette.palette[start + i].r;
1210 rgb[i].rgbGreen = _local_palette.palette[start + i].g;
1211 rgb[i].rgbBlue = _local_palette.palette[start + i].b;
1212 rgb[i].rgbReserved = 0;
1213 }
1214
1215 SetDIBColorTable(dc, start, count, rgb);
1216}
1217
1219{
1220 HDC hDC = GetWindowDC(hWnd);
1221 HPALETTE hOldPalette = SelectPalette(hDC, this->gdi_palette, FALSE);
1222 UINT nChanged = RealizePalette(hDC);
1223
1224 SelectPalette(hDC, hOldPalette, TRUE);
1225 ReleaseDC(hWnd, hDC);
1226 if (nChanged != 0) this->MakeDirty(0, 0, _screen.width, _screen.height);
1227}
1228
1230{
1231 PerformanceMeasurer framerate(PFE_VIDEO);
1232
1233 if (IsEmptyRect(this->dirty_rect)) return;
1234
1235 HDC dc = GetDC(this->main_wnd);
1236 HDC dc2 = CreateCompatibleDC(dc);
1237
1238 HBITMAP old_bmp = (HBITMAP)SelectObject(dc2, this->dib_sect);
1239 HPALETTE old_palette = SelectPalette(dc, this->gdi_palette, FALSE);
1240
1241 if (_local_palette.count_dirty != 0) {
1243
1244 switch (blitter->UsePaletteAnimation()) {
1246 this->UpdatePalette(dc2, _local_palette.first_dirty, _local_palette.count_dirty);
1247 break;
1248
1251 break;
1252 }
1253
1255 break;
1256
1257 default:
1258 NOT_REACHED();
1259 }
1261 }
1262
1263 BitBlt(dc, 0, 0, this->width, this->height, dc2, 0, 0, SRCCOPY);
1264 SelectPalette(dc, old_palette, TRUE);
1265 SelectObject(dc2, old_bmp);
1266 DeleteDC(dc2);
1267
1268 ReleaseDC(this->main_wnd, dc);
1269
1270 this->dirty_rect = {};
1271}
1272
1273#ifdef _DEBUG
1274/* Keep this function here..
1275 * It allows you to redraw the screen from within the MSVC debugger */
1276/* static */ int VideoDriver_Win32GDI::RedrawScreenDebug()
1277{
1278 static int _fooctr;
1279
1281
1282 _screen.dst_ptr = drv->GetVideoPointer();
1283 UpdateWindows();
1284
1285 drv->Paint();
1286 GdiFlush();
1287
1288 return _fooctr++;
1289}
1290#endif
1291
1292#ifdef WITH_OPENGL
1293
1294#include <GL/gl.h>
1295#include "../3rdparty/opengl/glext.h"
1296#include "../3rdparty/opengl/wglext.h"
1297#include "opengl.h"
1298
1299#ifndef PFD_SUPPORT_COMPOSITION
1300# define PFD_SUPPORT_COMPOSITION 0x00008000
1301#endif
1302
1303static PFNWGLCREATECONTEXTATTRIBSARBPROC _wglCreateContextAttribsARB = nullptr;
1304static PFNWGLSWAPINTERVALEXTPROC _wglSwapIntervalEXT = nullptr;
1305static bool _hasWGLARBCreateContextProfile = false;
1306
1308static OGLProc GetOGLProcAddressCallback(const char *proc)
1309{
1310 OGLProc ret = reinterpret_cast<OGLProc>(wglGetProcAddress(proc));
1311 if (ret == nullptr) {
1312 /* Non-extension GL function? Try normal loading. */
1313 ret = reinterpret_cast<OGLProc>(GetProcAddress(GetModuleHandle(L"opengl32"), proc));
1314 }
1315 return ret;
1316}
1317
1323static std::optional<std::string_view> SelectPixelFormat(HDC dc)
1324{
1325 PIXELFORMATDESCRIPTOR pfd = {
1326 sizeof(PIXELFORMATDESCRIPTOR), // Size of this struct.
1327 1, // Version of this struct.
1328 PFD_DRAW_TO_WINDOW | // Require window support.
1329 PFD_SUPPORT_OPENGL | // Require OpenGL support.
1330 PFD_DOUBLEBUFFER | // Use double buffering.
1331 PFD_DEPTH_DONTCARE,
1332 PFD_TYPE_RGBA, // Request RGBA format.
1333 24, // 24 bpp (excluding alpha).
1334 0, 0, 0, 0, 0, 0, 0, 0, // Colour bits and shift ignored.
1335 0, 0, 0, 0, 0, // No accumulation buffer.
1336 0, 0, // No depth/stencil buffer.
1337 0, // No aux buffers.
1338 PFD_MAIN_PLANE, // Main layer.
1339 0, 0, 0, 0 // Ignored/reserved.
1340 };
1341
1342 pfd.dwFlags |= PFD_SUPPORT_COMPOSITION; // Make OpenTTD compatible with Aero.
1343
1344 /* Choose a suitable pixel format. */
1345 int format = ChoosePixelFormat(dc, &pfd);
1346 if (format == 0) return "No suitable pixel format found";
1347 if (!SetPixelFormat(dc, format, &pfd)) return "Can't set pixel format";
1348
1349 return std::nullopt;
1350}
1351
1353static void LoadWGLExtensions()
1354{
1355 /* Querying the supported WGL extensions and loading the matching
1356 * functions requires a valid context, even for the extensions
1357 * regarding context creation. To get around this, we create
1358 * a dummy window with a dummy context. The extension functions
1359 * remain valid even after this context is destroyed. */
1360 HWND wnd = CreateWindow(L"STATIC", L"dummy", WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, nullptr, nullptr, GetModuleHandle(nullptr), nullptr);
1361 HDC dc = GetDC(wnd);
1362
1363 /* Set pixel format of the window. */
1364 if (SelectPixelFormat(dc) == std::nullopt) {
1365 /* Create rendering context. */
1366 HGLRC rc = wglCreateContext(dc);
1367 if (rc != nullptr) {
1368 wglMakeCurrent(dc, rc);
1369
1370#ifdef __MINGW32__
1371 /* GCC doesn't understand the expected usage of wglGetProcAddress(). */
1372#pragma GCC diagnostic push
1373#pragma GCC diagnostic ignored "-Wcast-function-type"
1374#endif /* __MINGW32__ */
1375
1376 /* Get list of WGL extensions. */
1377 PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)wglGetProcAddress("wglGetExtensionsStringARB");
1378 if (wglGetExtensionsStringARB != nullptr) {
1379 const char *wgl_exts = wglGetExtensionsStringARB(dc);
1380 /* Bind supported functions. */
1381 if (FindStringInExtensionList(wgl_exts, "WGL_ARB_create_context") != nullptr) {
1382 _wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB");
1383 }
1384 _hasWGLARBCreateContextProfile = FindStringInExtensionList(wgl_exts, "WGL_ARB_create_context_profile") != nullptr;
1385 if (FindStringInExtensionList(wgl_exts, "WGL_EXT_swap_control") != nullptr) {
1386 _wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT");
1387 }
1388 }
1389
1390#ifdef __MINGW32__
1391#pragma GCC diagnostic pop
1392#endif
1393 wglMakeCurrent(nullptr, nullptr);
1394 wglDeleteContext(rc);
1395 }
1396 }
1397
1398 ReleaseDC(wnd, dc);
1399 DestroyWindow(wnd);
1400}
1401
1402static FVideoDriver_Win32OpenGL iFVideoDriver_Win32OpenGL;
1403
1404std::optional<std::string_view> VideoDriver_Win32OpenGL::Start(const StringList &param)
1405{
1406 if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 0) return "Only real blitters supported";
1407
1408 Dimension old_res = _cur_resolution; // Save current screen resolution in case of errors, as MakeWindow invalidates it.
1409
1410 LoadWGLExtensions();
1411
1412 this->Initialize();
1413 this->MakeWindow(_fullscreen);
1414
1415 /* Create and initialize OpenGL context. */
1416 auto err = this->AllocateContext();
1417 if (err) {
1418 this->Stop();
1419 _cur_resolution = old_res;
1420 return err;
1421 }
1422
1423 this->driver_info = GetName();
1424 this->driver_info += " (";
1425 this->driver_info += OpenGLBackend::Get()->GetDriverName();
1426 this->driver_info += ")";
1427
1428 this->ClientSizeChanged(this->width, this->height, true);
1429 /* We should have a valid screen buffer now. If not, something went wrong and we should abort. */
1430 if (_screen.dst_ptr == nullptr) {
1431 this->Stop();
1432 _cur_resolution = old_res;
1433 return "Can't get pointer to screen buffer";
1434 }
1435 /* Main loop expects to start with the buffer unmapped. */
1436 this->ReleaseVideoPointer();
1437
1439
1440 this->is_game_threaded = !GetDriverParamBool(param, "no_threads") && !GetDriverParamBool(param, "no_thread");
1441
1442 return std::nullopt;
1443}
1444
1445void VideoDriver_Win32OpenGL::Stop()
1446{
1447 this->DestroyContext();
1449}
1450
1451void VideoDriver_Win32OpenGL::DestroyContext()
1452{
1454
1455 wglMakeCurrent(nullptr, nullptr);
1456 if (this->gl_rc != nullptr) {
1457 wglDeleteContext(this->gl_rc);
1458 this->gl_rc = nullptr;
1459 }
1460 if (this->dc != nullptr) {
1461 ReleaseDC(this->main_wnd, this->dc);
1462 this->dc = nullptr;
1463 }
1464}
1465
1466void VideoDriver_Win32OpenGL::ToggleVsync(bool vsync)
1467{
1468 if (_wglSwapIntervalEXT != nullptr) {
1469 _wglSwapIntervalEXT(vsync);
1470 } else if (vsync) {
1471 Debug(driver, 0, "OpenGL: Vsync requested, but not supported by driver");
1472 }
1473}
1474
1475std::optional<std::string_view> VideoDriver_Win32OpenGL::AllocateContext()
1476{
1477 this->dc = GetDC(this->main_wnd);
1478
1479 auto err = SelectPixelFormat(this->dc);
1480 if (err) return err;
1481
1482 HGLRC rc = nullptr;
1483
1484 /* Create OpenGL device context. Try to get an 3.2+ context if possible. */
1485 if (_wglCreateContextAttribsARB != nullptr) {
1486 /* Try for OpenGL 4.5 first. */
1487 int attribs[] = {
1488 WGL_CONTEXT_MAJOR_VERSION_ARB, 4,
1489 WGL_CONTEXT_MINOR_VERSION_ARB, 5,
1490 WGL_CONTEXT_FLAGS_ARB, _debug_driver_level >= 8 ? WGL_CONTEXT_DEBUG_BIT_ARB : 0,
1491 _hasWGLARBCreateContextProfile ? WGL_CONTEXT_PROFILE_MASK_ARB : 0, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, // Terminate list if WGL_ARB_create_context_profile isn't supported.
1492 0
1493 };
1494 rc = _wglCreateContextAttribsARB(this->dc, nullptr, attribs);
1495
1496 if (rc == nullptr) {
1497 /* Try again for a 3.2 context. */
1498 attribs[1] = 3;
1499 attribs[3] = 2;
1500 rc = _wglCreateContextAttribsARB(this->dc, nullptr, attribs);
1501 }
1502 }
1503
1504 if (rc == nullptr) {
1505 /* Old OpenGL or old driver, let's hope for the best. */
1506 rc = wglCreateContext(this->dc);
1507 if (rc == nullptr) return "Can't create OpenGL context";
1508 }
1509 if (!wglMakeCurrent(this->dc, rc)) return "Can't active GL context";
1510
1511 this->ToggleVsync(_video_vsync);
1512
1513 this->gl_rc = rc;
1514 return OpenGLBackend::Create(&GetOGLProcAddressCallback, this->GetScreenSize());
1515}
1516
1517bool VideoDriver_Win32OpenGL::ToggleFullscreen(bool full_screen)
1518{
1519 if (_screen.dst_ptr != nullptr) this->ReleaseVideoPointer();
1520 this->DestroyContext();
1521 bool res = this->VideoDriver_Win32Base::ToggleFullscreen(full_screen);
1522 res &= this->AllocateContext() == std::nullopt;
1523 this->ClientSizeChanged(this->width, this->height, true);
1524 return res;
1525}
1526
1527bool VideoDriver_Win32OpenGL::AfterBlitterChange()
1528{
1529 assert(BlitterFactory::GetCurrentBlitter()->GetScreenDepth() != 0);
1530 this->ClientSizeChanged(this->width, this->height, true);
1531 return true;
1532}
1533
1534void VideoDriver_Win32OpenGL::PopulateSystemSprites()
1535{
1536 OpenGLBackend::Get()->PopulateCursorCache();
1537}
1538
1539void VideoDriver_Win32OpenGL::ClearSystemSprites()
1540{
1542}
1543
1544bool VideoDriver_Win32OpenGL::AllocateBackingStore(int w, int h, bool force)
1545{
1546 if (!force && w == _screen.width && h == _screen.height) return false;
1547
1548 this->width = w = std::max(w, 64);
1549 this->height = h = std::max(h, 64);
1550
1551 if (this->gl_rc == nullptr) return false;
1552
1553 if (_screen.dst_ptr != nullptr) this->ReleaseVideoPointer();
1554
1555 this->dirty_rect = {};
1556 bool res = OpenGLBackend::Get()->Resize(w, h, force);
1557 SwapBuffers(this->dc);
1558 _screen.dst_ptr = this->GetVideoPointer();
1559
1560 return res;
1561}
1562
1563void *VideoDriver_Win32OpenGL::GetVideoPointer()
1564{
1565 if (BlitterFactory::GetCurrentBlitter()->NeedsAnimationBuffer()) {
1566 this->anim_buffer = OpenGLBackend::Get()->GetAnimBuffer();
1567 }
1569}
1570
1571void VideoDriver_Win32OpenGL::ReleaseVideoPointer()
1572{
1573 if (this->anim_buffer != nullptr) OpenGLBackend::Get()->ReleaseAnimBuffer(this->dirty_rect);
1574 OpenGLBackend::Get()->ReleaseVideoBuffer(this->dirty_rect);
1575 this->dirty_rect = {};
1576 _screen.dst_ptr = nullptr;
1577 this->anim_buffer = nullptr;
1578}
1579
1580void VideoDriver_Win32OpenGL::Paint()
1581{
1582 PerformanceMeasurer framerate(PFE_VIDEO);
1583
1584 if (_local_palette.count_dirty != 0) {
1586
1587 /* Always push a changed palette to OpenGL. */
1591 }
1592
1594 }
1595
1598
1599 SwapBuffers(this->dc);
1600}
1601
1602#endif /* WITH_OPENGL */
#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.
debug_inline constexpr bool HasBit(const T x, const uint8_t y)
Checks if a bit in a value is set.
debug_inline static constexpr uint GB(const T x, const uint8_t s, const uint8_t n)
Fetch n bits from x, started at bit s.
static Blitter * GetCurrentBlitter()
Get the current active blitter (always set by calling SelectBlitter).
Definition factory.hpp:138
How all blitters should look like.
Definition base.hpp:29
virtual uint8_t GetScreenDepth()=0
Get the screen depth this blitter works for.
virtual Blitter::PaletteAnimation UsePaletteAnimation()=0
Check if the blitter uses palette animation at all.
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...
@ None
No palette animation.
@ Blitter
The blitter takes care of the palette animation.
@ VideoBackend
Palette animation should be done by video backend (8bpp only!)
virtual void PostResize()
Post resize event.
Definition base.hpp:205
The factory for Windows' video driver.
Definition win32_v.h:110
Function GetFunction(const std::string &symbol_name)
Get a function from a loaded library.
uint current_x
Current horizontal size (after resizing).
int pos_y
Vertical position of top-left corner of the widget in the window.
int pos_x
Horizontal position of top-left corner of the widget in the window.
uint current_y
Current vertical size (after resizing).
void Paint()
Render video buffer to the screen.
Definition opengl.cpp:1039
uint8_t * GetAnimBuffer()
Get a pointer to the memory for the separate animation buffer.
Definition opengl.cpp:1170
void * GetVideoBuffer()
Get a pointer to the memory for the video driver to draw to.
Definition opengl.cpp:1148
bool Resize(int w, int h, bool force=false)
Change the size of the drawing window and allocate matching resources.
Definition opengl.cpp:913
static std::optional< std::string_view > Create(GetOGLProcAddressProc get_proc, const Dimension &screen_res)
Create and initialize the singleton back-end class.
Definition opengl.cpp:467
void UpdatePalette(const Colour *pal, uint first, uint length)
Update the stored palette.
Definition opengl.cpp:1025
void ReleaseAnimBuffer(const Rect &update_rect)
Update animation buffer texture after the animation buffer was filled.
Definition opengl.cpp:1231
void ClearCursorCache()
Queue a request for cursor cache clear.
Definition opengl.cpp:1135
static OpenGLBackend * Get()
Get singleton instance of this class.
Definition opengl.h:82
void DrawMouseCursor()
Draw mouse cursor on screen.
Definition opengl.cpp:1071
void ReleaseVideoBuffer(const Rect &update_rect)
Update video buffer texture after the video buffer was filled.
Definition opengl.cpp:1193
static void Destroy()
Free resources and destroy singleton back-end class.
Definition opengl.cpp:480
RAII class for measuring simple elements of performance.
Base class for Windows video drivers.
Definition win32_v.h:19
int height
Height in pixels of our display surface.
Definition win32_v.h:45
bool has_focus
Does our window have system focus?
Definition win32_v.h:42
void EditBoxLostFocus() override
An edit box lost the input focus.
Definition win32_v.cpp:1011
void CheckPaletteAnim() override
Process any pending palette animation.
Definition win32_v.cpp:923
void Stop() override
Stop this driver.
Definition win32_v.cpp:910
int height_org
Original monitor resolution height, before we changed it.
Definition win32_v.h:47
HWND main_wnd
Handle to system window.
Definition win32_v.h:40
bool fullscreen
Whether to use (true) fullscreen mode.
Definition win32_v.h:41
int width_org
Original monitor resolution width, before we changed it.
Definition win32_v.h:46
bool MakeWindow(bool full_screen, bool resize=true)
Instantiate a new window.
Definition win32_v.cpp:141
bool buffer_locked
Video buffer was locked by the main thread.
Definition win32_v.h:49
virtual void * GetVideoPointer()=0
Get a pointer to the video buffer.
void MakeDirty(int left, int top, int width, int height) override
Mark a particular area dirty.
Definition win32_v.cpp:917
bool PollEvent() override
Process a single system event.
Definition win32_v.cpp:954
virtual void PaletteChanged(HWND hWnd)=0
Palette of the window has changed.
bool LockVideoBuffer() override
Make sure the video buffer is ready for drawing.
Definition win32_v.cpp:1088
std::vector< int > GetListOfMonitorRefreshRates() override
Get a list of refresh rates of each available monitor.
Definition win32_v.cpp:1035
virtual bool AllocateBackingStore(int w, int h, bool force=false)=0
(Re-)create the backing store.
int width
Width in pixels of our display surface.
Definition win32_v.h:44
void InputLoop() override
Handle input logic, is CTRL pressed, should we fast-forward, etc.
Definition win32_v.cpp:929
virtual uint8_t GetFullscreenBpp()
Get screen depth to use for fullscreen mode.
Definition win32_v.cpp:129
Dimension GetScreenSize() const override
Get the resolution of the main screen.
Definition win32_v.cpp:1042
virtual void ReleaseVideoPointer()
Hand video buffer back to the painting backend.
Definition win32_v.h:70
void UnlockVideoBuffer() override
Unlock a previously locked video buffer.
Definition win32_v.cpp:1099
void MainLoop() override
Perform the actual drawing.
Definition win32_v.cpp:967
Rect dirty_rect
Region of the screen that needs redrawing.
Definition win32_v.h:43
float GetDPIScale() override
Get DPI scaling factor of the screen OTTD is displayed on.
Definition win32_v.cpp:1047
bool ToggleFullscreen(bool fullscreen) override
Change the full screen setting.
Definition win32_v.cpp:1003
bool ChangeResolution(int w, int h) override
Change the resolution of the window.
Definition win32_v.cpp:993
The GDI video driver for windows.
Definition win32_v.h:78
void * buffer_bits
Internal rendering buffer.
Definition win32_v.h:93
HBITMAP dib_sect
System bitmap object referencing our rendering buffer.
Definition win32_v.h:91
void PaletteChanged(HWND hWnd) override
Palette of the window has changed.
Definition win32_v.cpp:1218
std::optional< std::string_view > Start(const StringList &param) override
Start this driver.
Definition win32_v.cpp:1114
void * GetVideoPointer() override
Get a pointer to the video buffer.
Definition win32_v.h:96
HPALETTE gdi_palette
Palette object for 8bpp blitter.
Definition win32_v.h:92
bool AllocateBackingStore(int w, int h, bool force=false) override
(Re-)create the backing store.
Definition win32_v.cpp:1139
void Paint() override
Paint the window.
Definition win32_v.cpp:1229
bool AfterBlitterChange() override
Callback invoked after the blitter was changed.
Definition win32_v.cpp:1177
void Stop() override
Stop this driver.
Definition win32_v.cpp:1131
bool fast_forward_key_pressed
The fast-forward key is being pressed.
void Tick()
Give the video-driver a tick.
void SleepTillNextTick()
Sleep till the next tick is about to happen.
void StartGameThread()
Start the loop for game-tick.
static std::string GetCaption()
Get the caption to use for the game's title bar.
void StopGameThread()
Stop the loop for the game-tick.
static VideoDriver * GetInstance()
Get the currently active instance of the video driver.
void UpdateAutoResolution()
Apply resolution auto-detection and clamp to sensible defaults.
#define Debug(category, level, format_string,...)
Ouptut a line of debugging information.
Definition debug.h:37
std::vector< Dimension > _resolutions
List of resolutions.
Definition driver.cpp:25
Dimension _cur_resolution
The current resolution.
Definition driver.cpp:26
bool GetDriverParamBool(const StringList &parm, const char *name)
Get a boolean parameter the list of parameters.
Definition driver.cpp:64
fluid_settings_t * settings
FluidSynth settings handle.
@ PFE_VIDEO
Speed of painting drawn video buffer.
Rect BoundingRect(const Rect &r1, const Rect &r2)
Compute the bounding rectangle around two rectangles.
bool IsEmptyRect(const Rect &r)
Check if a rectangle is empty.
bool _shift_pressed
Is Shift pressed?
Definition gfx.cpp:39
bool _left_button_down
Is left mouse button pressed?
Definition gfx.cpp:41
bool _ctrl_pressed
Is Ctrl pressed?
Definition gfx.cpp:38
uint8_t _dirkeys
1 = left, 2 = up, 4 = right, 8 = down
Definition gfx.cpp:34
bool _left_button_clicked
Is left mouse button clicked?
Definition gfx.cpp:42
bool _right_button_clicked
Is right mouse button clicked?
Definition gfx.cpp:44
bool _right_button_down
Is right mouse button pressed?
Definition gfx.cpp:43
bool AdjustGUIZoom(bool automatic)
Resolve GUI zoom level and adjust GUI to new zoom, if auto-suggestion is requested.
Definition gfx.cpp:1793
void HandleCtrlChanged()
State of CONTROL key has changed.
Definition window.cpp:2623
void UpdateWindows()
Update the continuously changing contents of the windows, such as the viewports.
Definition window.cpp:3054
void GameSizeChanged()
Size of the application screen changed.
Definition main_gui.cpp:589
void HandleMouseEvents()
Handle a mouse event from the video driver.
Definition window.cpp:2883
void HandleKeypress(uint keycode, char32_t key)
Handle keyboard input.
Definition window.cpp:2567
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:2653
@ S8BPP_HARDWARE
Full 8bpp support by OS and hardware.
Definition gfx_type.h:370
@ WKC_BACKSLASH
\ Backslash
Definition gfx_type.h:99
@ WKC_MINUS
Definition gfx_type.h:104
@ WKC_COMMA
, Comma
Definition gfx_type.h:102
@ WKC_PERIOD
. Period
Definition gfx_type.h:103
@ WKC_EQUALS
= Equals
Definition gfx_type.h:97
@ WKC_SLASH
/ Forward slash
Definition gfx_type.h:95
@ WKC_SINGLEQUOTE
' Single quote
Definition gfx_type.h:101
@ WKC_R_BRACKET
] Right square bracket
Definition gfx_type.h:100
@ WKC_L_BRACKET
[ Left square bracket
Definition gfx_type.h:98
@ WKC_SEMICOLON
; Semicolon
Definition gfx_type.h:96
void MarkWholeScreenDirty()
This function mark the whole screen as dirty.
Definition gfx.cpp:1529
constexpr bool IsInsideBS(const T x, const size_t base, const size_t size)
Checks if a value is between a window started at some base point.
constexpr T Align(const T x, uint n)
Return the smallest multiple of n equal or greater than x.
Definition math_func.hpp:37
OpenGL video driver support.
void GetKeyboardLayout()
Retrieve keyboard layout from language string or (if set) config file.
Definition osk_gui.cpp:349
bool CopyPalette(Palette &local_palette, bool force_copy)
Copy the current palette if the palette was updated.
Definition palette.cpp:225
static OGLProc GetOGLProcAddressCallback(const char *proc)
Platform-specific callback to get an OpenGL funtion pointer.
ClientSettings _settings_client
The current settings for this game.
Definition settings.cpp:56
char32_t Utf16DecodeSurrogate(uint lead, uint trail)
Convert an UTF-16 surrogate pair to the corresponding Unicode character.
bool Utf16IsLeadSurrogate(uint c)
Is the given character a lead surrogate code point?
bool Utf16IsTrailSurrogate(uint c)
Is the given character a lead surrogate code point?
std::vector< std::string > StringList
Type for a list of strings.
Definition string_type.h:60
GUISettings gui
settings related to the GUI
bool UpdateCursorPosition(int x, int y)
Update cursor position on mouse movement.
Definition gfx.cpp:1728
bool fix_at
mouse is moving, but cursor is not (used for scrolling)
Definition gfx_type.h:127
Point pos
logical mouse position
Definition gfx_type.h:124
bool in_window
mouse inside this window, determines drawing logic
Definition gfx_type.h:146
int wheel
mouse wheel movement
Definition gfx_type.h:126
Dimensions (a width and height) of a rectangle in 2D.
uint8_t scrollwheel_multiplier
how much 'wheel' per incoming event from the OS?
Information about the currently used palette.
Definition gfx_type.h:360
int first_dirty
The first dirty element.
Definition gfx_type.h:362
int count_dirty
The number of dirty elements.
Definition gfx_type.h:363
Colour palette[256]
Current palette. Entry 0 has to be always fully transparent!
Definition gfx_type.h:361
Coordinates of a point in 2D.
Specification of a rectangle with absolute coordinates of all edges.
WindowClass window_class
Window class.
Definition window_gui.h:300
virtual Point GetCaretPosition() const
Get the current caret position if an edit box has the focus.
Definition window.cpp:375
int left
x position of left edge of the window
Definition window_gui.h:308
const NWidgetCore * nested_focus
Currently focused nested widget, or nullptr if no nested widget has focus.
Definition window_gui.h:318
int top
y position of top edge of the window
Definition window_gui.h:309
int height
Height of the window (number of pixels down in y direction)
Definition window_gui.h:311
int width
width of the window (number of pixels to the right in x direction)
Definition window_gui.h:310
bool _video_vsync
Whether we should use vsync (only if active video driver supports HW acceleration).
std::wstring OTTD2FS(const std::string &name)
Convert from OpenTTD's encoding to a wide string.
Definition win32.cpp:354
char * convert_from_fs(const std::wstring_view src, std::span< char > dst_buf)
Convert to OpenTTD's encoding from that of the environment in UNICODE.
Definition win32.cpp:372
std::string FS2OTTD(const std::wstring &name)
Convert to OpenTTD's encoding from a wide string.
Definition win32.cpp:337
static Palette _local_palette
Current palette to use for drawing.
Definition win32_v.cpp:53
static LRESULT HandleCharMsg(uint keycode, char32_t charcode)
Forward key presses to the window system.
Definition win32_v.cpp:245
static bool DrawIMECompositionString()
Should we draw the composition string ourself, i.e is this a normal IME?
Definition win32_v.cpp:272
static LRESULT HandleIMEComposition(HWND hwnd, WPARAM wParam, LPARAM lParam)
Handle WM_IME_COMPOSITION messages.
Definition win32_v.cpp:334
static void SetCandidatePos(HWND hwnd)
Set the position of the candidate window.
Definition win32_v.cpp:300
static void CancelIMEComposition(HWND hwnd)
Clear the current composition string.
Definition win32_v.cpp:394
static void SetCompositionPos(HWND hwnd)
Set position of the composition window to the caret position.
Definition win32_v.cpp:278
Base of the Windows video driver.
void ReInitAllWindows(bool zoom_changed)
Re-initialize all windows.
Definition window.cpp:3333
bool EditBoxInGlobalFocus()
Check if an edit box is in global focus.
Definition window.cpp:445
void InvalidateWindowClassesData(WindowClass cls, int data, bool gui_scope)
Mark window data of all windows of a given class as invalid (in need of re-computing) Note that by de...
Definition window.cpp:3234
@ WC_CONSOLE
Console; Window numbers:
@ WC_GAME_OPTIONS
Game options window; Window numbers: