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