1 /* $Id: win32_v.cpp 26367 2014-02-23 16:08:50Z michi_cc $ */
4 * This file is part of OpenTTD.
5 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
10 /** @file win32_v.cpp Implementation of the Windows (GDI) video driver. */
12 #include "../stdafx.h"
13 #include "../openttd.h"
14 #include "../gfx_func.h"
15 #include "../os/windows/win32.h"
17 #include "../blitter/factory.hpp"
18 #include "../network/network.h"
19 #include "../core/math_func.hpp"
20 #include "../core/random_func.hpp"
21 #include "../texteff.hpp"
22 #include "../thread/thread.h"
23 #include "../progress.h"
24 #include "../window_gui.h"
25 #include "../window_func.h"
30 #include "../safeguards.h"
32 /* Missing define in MinGW headers. */
33 #ifndef MAPVK_VK_TO_CHAR
34 #define MAPVK_VK_TO_CHAR (2)
38 #define PM_QS_INPUT 0x20000
56 bool _force_full_redraw
;
57 bool _window_maximize
;
59 static Dimension _bck_resolution
;
60 #if !defined(WINCE) || _WIN32_WCE >= 0x400
64 /** Whether the drawing is/may be done in a separate thread. */
65 static bool _draw_threaded
;
66 /** Thread used to 'draw' to the screen, i.e. push data to the screen. */
67 static ThreadObject
*_draw_thread
= nullptr;
68 /** Mutex to keep the access to the shared memory controlled. */
69 static ThreadMutex
*_draw_mutex
= nullptr;
70 /** Event that is signaled when the drawing thread has finished initializing. */
71 static HANDLE _draw_thread_initialized
= nullptr;
72 /** Should we keep continue drawing? */
73 static volatile bool _draw_continue
;
74 /** Local copy of the palette for use in the drawing thread. */
75 static Palette _local_palette
;
77 static void MakePalette()
79 LOGPALETTE
*pal
= (LOGPALETTE
*)alloca(sizeof(LOGPALETTE
) + (256 - 1) * sizeof(PALETTEENTRY
));
81 pal
->palVersion
= 0x300;
82 pal
->palNumEntries
= 256;
84 for (uint i
= 0; i
!= 256; i
++) {
85 pal
->palPalEntry
[i
].peRed
= _cur_palette
.palette
[i
].r
;
86 pal
->palPalEntry
[i
].peGreen
= _cur_palette
.palette
[i
].g
;
87 pal
->palPalEntry
[i
].peBlue
= _cur_palette
.palette
[i
].b
;
88 pal
->palPalEntry
[i
].peFlags
= 0;
91 _wnd
.gdi_palette
= CreatePalette(pal
);
92 if (_wnd
.gdi_palette
== nullptr) usererror("CreatePalette failed!\n");
94 _cur_palette
.first_dirty
= 0;
95 _cur_palette
.count_dirty
= 256;
96 _local_palette
= _cur_palette
;
99 static void UpdatePalette(HDC dc
, uint start
, uint count
)
104 for (i
= 0; i
!= count
; i
++) {
105 rgb
[i
].rgbRed
= _local_palette
.palette
[start
+ i
].r
;
106 rgb
[i
].rgbGreen
= _local_palette
.palette
[start
+ i
].g
;
107 rgb
[i
].rgbBlue
= _local_palette
.palette
[start
+ i
].b
;
108 rgb
[i
].rgbReserved
= 0;
111 SetDIBColorTable(dc
, start
, count
, rgb
);
114 bool VideoDriver_Win32::ClaimMousePointer()
116 MyShowCursor(false, true);
126 #define AS(x, z) {x, 0, z}
127 #define AM(x, y, z, w) {x, y - x, z}
129 static const VkMapping _vk_mapping
[] = {
130 /* Pageup stuff + up/down */
131 AM(VK_PRIOR
, VK_DOWN
, WKC_PAGEUP
, WKC_DOWN
),
132 /* Map letters & digits */
133 AM('A', 'Z', 'A', 'Z'),
134 AM('0', '9', '0', '9'),
136 AS(VK_ESCAPE
, WKC_ESC
),
137 AS(VK_PAUSE
, WKC_PAUSE
),
138 AS(VK_BACK
, WKC_BACKSPACE
),
139 AM(VK_INSERT
, VK_DELETE
, WKC_INSERT
, WKC_DELETE
),
141 AS(VK_SPACE
, WKC_SPACE
),
142 AS(VK_RETURN
, WKC_RETURN
),
146 AM(VK_F1
, VK_F12
, WKC_F1
, WKC_F12
),
149 AM(VK_NUMPAD0
, VK_NUMPAD9
, '0', '9'),
150 AS(VK_DIVIDE
, WKC_NUM_DIV
),
151 AS(VK_MULTIPLY
, WKC_NUM_MUL
),
152 AS(VK_SUBTRACT
, WKC_NUM_MINUS
),
153 AS(VK_ADD
, WKC_NUM_PLUS
),
154 AS(VK_DECIMAL
, WKC_NUM_DECIMAL
),
156 /* Other non-letter keys */
158 AS(0xBA, WKC_SEMICOLON
),
159 AS(0xBB, WKC_EQUALS
),
160 AS(0xDB, WKC_L_BRACKET
),
161 AS(0xDC, WKC_BACKSLASH
),
162 AS(0xDD, WKC_R_BRACKET
),
164 AS(0xDE, WKC_SINGLEQUOTE
),
170 static uint
MapWindowsKey(uint sym
)
172 const VkMapping
*map
;
175 for (map
= _vk_mapping
; map
!= endof(_vk_mapping
); ++map
) {
176 if ((uint
)(sym
- map
->vk_from
) <= map
->vk_count
) {
177 key
= sym
- map
->vk_from
+ map
->map_to
;
182 if (GetAsyncKeyState(VK_SHIFT
) < 0) key
|= WKC_SHIFT
;
183 if (GetAsyncKeyState(VK_CONTROL
) < 0) key
|= WKC_CTRL
;
184 if (GetAsyncKeyState(VK_MENU
) < 0) key
|= WKC_ALT
;
188 static bool AllocateDibSection(int w
, int h
, bool force
= false);
190 static void ClientSizeChanged(int w
, int h
)
192 /* allocate new dib section of the new size */
193 if (AllocateDibSection(w
, h
)) {
194 /* mark all palette colours dirty */
195 _cur_palette
.first_dirty
= 0;
196 _cur_palette
.count_dirty
= 256;
197 _local_palette
= _cur_palette
;
199 BlitterFactory::GetCurrentBlitter()->PostResize();
206 /* Keep this function here..
207 * It allows you to redraw the screen from within the MSVC debugger */
208 int RedrawScreenDebug()
213 HPALETTE old_palette
;
217 dc
= GetDC(_wnd
.main_wnd
);
218 dc2
= CreateCompatibleDC(dc
);
220 old_bmp
= (HBITMAP
)SelectObject(dc2
, _wnd
.dib_sect
);
221 old_palette
= SelectPalette(dc
, _wnd
.gdi_palette
, FALSE
);
222 BitBlt(dc
, 0, 0, _wnd
.width
, _wnd
.height
, dc2
, 0, 0, SRCCOPY
);
223 SelectPalette(dc
, old_palette
, TRUE
);
224 SelectObject(dc2
, old_bmp
);
226 ReleaseDC(_wnd
.main_wnd
, dc
);
232 /* Windows 95 will not have a WM_MOUSELEAVE message, so define it if needed */
233 #if !defined(WM_MOUSELEAVE)
234 #define WM_MOUSELEAVE 0x02A3
236 #define TID_POLLMOUSE 1
237 #define MOUSE_POLL_DELAY 75
239 static void CALLBACK
TrackMouseTimerProc(HWND hwnd
, UINT msg
, UINT event
, DWORD time
)
244 /* Get the rectangle of our window and translate it to screen coordinates.
245 * Compare this with the current screen coordinates of the mouse and if it
246 * falls outside of the area or our window we have left the window. */
247 GetClientRect(hwnd
, &rc
);
248 MapWindowPoints(hwnd
, HWND_DESKTOP
, (LPPOINT
)(LPRECT
)&rc
, 2);
251 if (!PtInRect(&rc
, pt
) || (WindowFromPoint(pt
) != hwnd
)) {
252 KillTimer(hwnd
, event
);
253 PostMessage(hwnd
, WM_MOUSELEAVE
, 0, 0L);
258 * Instantiate a new window.
259 * @param full_screen Whether to make a full screen window or not.
260 * @return True if the window could be created.
262 bool VideoDriver_Win32::MakeWindow(bool full_screen
)
264 _fullscreen
= full_screen
;
266 /* recreate window? */
267 if ((full_screen
|| _wnd
.fullscreen
) && _wnd
.main_wnd
) {
268 DestroyWindow(_wnd
.main_wnd
);
273 /* WinCE is always fullscreen */
278 memset(&settings
, 0, sizeof(settings
));
279 settings
.dmSize
= sizeof(settings
);
284 (_display_hz
!= 0 ? DM_DISPLAYFREQUENCY
: 0);
285 settings
.dmBitsPerPel
= BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
286 settings
.dmPelsWidth
= _wnd
.width_org
;
287 settings
.dmPelsHeight
= _wnd
.height_org
;
288 settings
.dmDisplayFrequency
= _display_hz
;
290 /* Check for 8 bpp support. */
291 if (settings
.dmBitsPerPel
== 8 &&
292 (_support8bpp
!= S8BPP_HARDWARE
|| ChangeDisplaySettings(&settings
, CDS_FULLSCREEN
| CDS_TEST
) != DISP_CHANGE_SUCCESSFUL
)) {
293 settings
.dmBitsPerPel
= 32;
296 /* Test fullscreen with current resolution, if it fails use desktop resolution. */
297 if (ChangeDisplaySettings(&settings
, CDS_FULLSCREEN
| CDS_TEST
) != DISP_CHANGE_SUCCESSFUL
) {
299 GetWindowRect(GetDesktopWindow(), &r
);
300 /* Guard against recursion. If we already failed here once, just fall through to
301 * the next ChangeDisplaySettings call which will fail and error out appropriately. */
302 if ((int)settings
.dmPelsWidth
!= r
.right
- r
.left
|| (int)settings
.dmPelsHeight
!= r
.bottom
- r
.top
) {
303 return this->ChangeResolution(r
.right
- r
.left
, r
.bottom
- r
.top
);
307 if (ChangeDisplaySettings(&settings
, CDS_FULLSCREEN
) != DISP_CHANGE_SUCCESSFUL
) {
308 this->MakeWindow(false); // don't care about the result
309 return false; // the request failed
311 } else if (_wnd
.fullscreen
) {
312 /* restore display? */
313 ChangeDisplaySettings(nullptr, 0);
314 /* restore the resolution */
315 _wnd
.width
= _bck_resolution
.width
;
316 _wnd
.height
= _bck_resolution
.height
;
322 DWORD style
, showstyle
;
325 showstyle
= SW_SHOWNORMAL
;
326 _wnd
.fullscreen
= full_screen
;
327 if (_wnd
.fullscreen
) {
329 SetRect(&r
, 0, 0, _wnd
.width_org
, _wnd
.height_org
);
331 style
= WS_OVERLAPPEDWINDOW
;
332 /* On window creation, check if we were in maximize mode before */
333 if (_window_maximize
) showstyle
= SW_SHOWMAXIMIZED
;
334 SetRect(&r
, 0, 0, _wnd
.width
, _wnd
.height
);
338 AdjustWindowRect(&r
, style
, FALSE
);
340 w
= r
.right
- r
.left
;
341 h
= r
.bottom
- r
.top
;
343 if (_wnd
.main_wnd
!= nullptr) {
344 if (!_window_maximize
) SetWindowPos(_wnd
.main_wnd
, 0, 0, 0, w
, h
, SWP_NOACTIVATE
| SWP_NOOWNERZORDER
| SWP_NOZORDER
| SWP_NOMOVE
);
346 TCHAR Windowtitle
[50];
347 int x
= (GetSystemMetrics(SM_CXSCREEN
) - w
) / 2;
348 int y
= (GetSystemMetrics(SM_CYSCREEN
) - h
) / 2;
350 _sntprintf(Windowtitle
, lengthof(Windowtitle
), _T("OpenTTD %s"), MB_TO_WIDE(_openttd_revision
));
352 _wnd
.main_wnd
= CreateWindow(_T("OTTD"), Windowtitle
, style
, x
, y
, w
, h
, 0, 0, GetModuleHandle(nullptr), 0);
353 if (_wnd
.main_wnd
== nullptr) usererror("CreateWindow failed");
354 ShowWindow(_wnd
.main_wnd
, showstyle
);
358 BlitterFactory::GetCurrentBlitter()->PostResize();
360 GameSizeChanged(); // invalidate all windows, force redraw
361 return true; // the request succeeded
364 /** Do palette animation and blit to the window. */
365 static void PaintWindow(HDC dc
)
367 HDC dc2
= CreateCompatibleDC(dc
);
368 HBITMAP old_bmp
= (HBITMAP
)SelectObject(dc2
, _wnd
.dib_sect
);
369 HPALETTE old_palette
= SelectPalette(dc
, _wnd
.gdi_palette
, FALSE
);
371 if (_cur_palette
.count_dirty
!= 0) {
372 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
374 switch (blitter
->UsePaletteAnimation()) {
375 case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND
:
376 UpdatePalette(dc2
, _local_palette
.first_dirty
, _local_palette
.count_dirty
);
379 case Blitter::PALETTE_ANIMATION_BLITTER
:
380 blitter
->PaletteAnimate(_local_palette
);
383 case Blitter::PALETTE_ANIMATION_NONE
:
389 _cur_palette
.count_dirty
= 0;
392 BitBlt(dc
, 0, 0, _wnd
.width
, _wnd
.height
, dc2
, 0, 0, SRCCOPY
);
393 SelectPalette(dc
, old_palette
, TRUE
);
394 SelectObject(dc2
, old_bmp
);
398 static void PaintWindowThread(void *)
400 /* First tell the main thread we're started */
401 _draw_mutex
->BeginCritical();
402 SetEvent(_draw_thread_initialized
);
404 /* Now wait for the first thing to draw! */
405 _draw_mutex
->WaitForSignal();
407 while (_draw_continue
) {
408 /* Convert update region from logical to device coordinates. */
410 ClientToScreen(_wnd
.main_wnd
, &pt
);
411 OffsetRect(&_wnd
.update_rect
, pt
.x
, pt
.y
);
413 /* Create a device context that is clipped to the region we need to draw.
414 * GetDCEx 'consumes' the update region, so we may not destroy it ourself. */
415 HRGN rgn
= CreateRectRgnIndirect(&_wnd
.update_rect
);
416 HDC dc
= GetDCEx(_wnd
.main_wnd
, rgn
, DCX_CLIPSIBLINGS
| DCX_CLIPCHILDREN
| DCX_INTERSECTRGN
);
420 /* Clear update rect. */
421 SetRectEmpty(&_wnd
.update_rect
);
422 ReleaseDC(_wnd
.main_wnd
, dc
);
424 /* Flush GDI buffer to ensure drawing here doesn't conflict with any GDI usage in the main WndProc. */
427 _draw_mutex
->WaitForSignal();
430 _draw_mutex
->EndCritical();
431 _draw_thread
->Exit();
434 /** Forward key presses to the window system. */
435 static LRESULT
HandleCharMsg(uint keycode
, WChar charcode
)
437 #if !defined(UNICODE)
438 static char prev_char
= 0;
440 char input
[2] = {(char)charcode
, 0};
443 if (prev_char
!= 0) {
444 /* We stored a lead byte previously, combine it with this byte. */
445 input
[0] = prev_char
;
446 input
[1] = (char)charcode
;
448 } else if (IsDBCSLeadByte(charcode
)) {
449 /* We got a lead byte, store and exit. */
450 prev_char
= charcode
;
455 wchar_t w
[2]; // Can get up to two code points as a result.
456 int len
= MultiByteToWideChar(CP_ACP
, 0, input
, input_len
, w
, 2);
458 case 1: // Normal unicode character.
462 case 2: // Got an UTF-16 surrogate pair back.
463 charcode
= Utf16DecodeSurrogate(w
[0], w
[1]);
466 default: // Some kind of error.
467 DEBUG(driver
, 1, "Invalid DBCS character sequence encountered, dropping input");
472 static WChar prev_char
= 0;
474 /* Did we get a lead surrogate? If yes, store and exit. */
475 if (Utf16IsLeadSurrogate(charcode
)) {
476 if (prev_char
!= 0) DEBUG(driver
, 1, "Got two UTF-16 lead surrogates, dropping the first one");
477 prev_char
= charcode
;
481 /* Stored lead surrogate and incoming trail surrogate? Combine and forward to input handling. */
482 if (prev_char
!= 0) {
483 if (Utf16IsTrailSurrogate(charcode
)) {
484 charcode
= Utf16DecodeSurrogate(prev_char
, charcode
);
486 DEBUG(driver
, 1, "Got an UTF-16 lead surrogate without a trail surrogate, dropping the lead surrogate");
492 HandleKeypress(keycode
, charcode
);
497 #if !defined(WINCE) || _WIN32_WCE >= 0x400
498 /** Should we draw the composition string ourself, i.e is this a normal IME? */
499 static bool DrawIMECompositionString()
501 return (_imm_props
& IME_PROP_AT_CARET
) && !(_imm_props
& IME_PROP_SPECIAL_UI
);
504 /** Set position of the composition window to the caret position. */
505 static void SetCompositionPos(HWND hwnd
)
507 HIMC hIMC
= ImmGetContext(hwnd
);
510 cf
.dwStyle
= CFS_POINT
;
512 if (EditBoxInGlobalFocus()) {
513 /* Get caret position. */
514 Point pt
= _focused_window
->GetCaretPosition();
515 cf
.ptCurrentPos
.x
= _focused_window
->left
+ pt
.x
;
516 cf
.ptCurrentPos
.y
= _focused_window
->top
+ pt
.y
;
518 cf
.ptCurrentPos
.x
= 0;
519 cf
.ptCurrentPos
.y
= 0;
521 ImmSetCompositionWindow(hIMC
, &cf
);
523 ImmReleaseContext(hwnd
, hIMC
);
526 /** Set the position of the candidate window. */
527 static void SetCandidatePos(HWND hwnd
)
529 HIMC hIMC
= ImmGetContext(hwnd
);
533 cf
.dwStyle
= CFS_EXCLUDE
;
535 if (EditBoxInGlobalFocus()) {
536 Point pt
= _focused_window
->GetCaretPosition();
537 cf
.ptCurrentPos
.x
= _focused_window
->left
+ pt
.x
;
538 cf
.ptCurrentPos
.y
= _focused_window
->top
+ pt
.y
;
539 if (_focused_window
->window_class
== WC_CONSOLE
) {
540 cf
.rcArea
.left
= _focused_window
->left
;
541 cf
.rcArea
.top
= _focused_window
->top
;
542 cf
.rcArea
.right
= _focused_window
->left
+ _focused_window
->width
;
543 cf
.rcArea
.bottom
= _focused_window
->top
+ _focused_window
->height
;
545 cf
.rcArea
.left
= _focused_window
->left
+ _focused_window
->nested_focus
->pos_x
;
546 cf
.rcArea
.top
= _focused_window
->top
+ _focused_window
->nested_focus
->pos_y
;
547 cf
.rcArea
.right
= cf
.rcArea
.left
+ _focused_window
->nested_focus
->current_x
;
548 cf
.rcArea
.bottom
= cf
.rcArea
.top
+ _focused_window
->nested_focus
->current_y
;
551 cf
.ptCurrentPos
.x
= 0;
552 cf
.ptCurrentPos
.y
= 0;
553 SetRectEmpty(&cf
.rcArea
);
555 ImmSetCandidateWindow(hIMC
, &cf
);
557 ImmReleaseContext(hwnd
, hIMC
);
560 /** Handle WM_IME_COMPOSITION messages. */
561 static LRESULT
HandleIMEComposition(HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
563 HIMC hIMC
= ImmGetContext(hwnd
);
566 if (lParam
& GCS_RESULTSTR
) {
567 /* Read result string from the IME. */
568 LONG len
= ImmGetCompositionString(hIMC
, GCS_RESULTSTR
, nullptr, 0); // Length is always in bytes, even in UNICODE build.
569 TCHAR
*str
= (TCHAR
*)_alloca(len
+ sizeof(TCHAR
));
570 len
= ImmGetCompositionString(hIMC
, GCS_RESULTSTR
, str
, len
);
571 str
[len
/ sizeof(TCHAR
)] = '\0';
573 /* Transmit text to windowing system. */
575 HandleTextInput(nullptr, true); // Clear marked string.
576 HandleTextInput(FS2OTTD(str
));
578 SetCompositionPos(hwnd
);
580 /* Don't pass the result string on to the default window proc. */
581 lParam
&= ~(GCS_RESULTSTR
| GCS_RESULTCLAUSE
| GCS_RESULTREADCLAUSE
| GCS_RESULTREADSTR
);
584 if ((lParam
& GCS_COMPSTR
) && DrawIMECompositionString()) {
585 /* Read composition string from the IME. */
586 LONG len
= ImmGetCompositionString(hIMC
, GCS_COMPSTR
, nullptr, 0); // Length is always in bytes, even in UNICODE build.
587 TCHAR
*str
= (TCHAR
*)_alloca(len
+ sizeof(TCHAR
));
588 len
= ImmGetCompositionString(hIMC
, GCS_COMPSTR
, str
, len
);
589 str
[len
/ sizeof(TCHAR
)] = '\0';
592 static char utf8_buf
[1024];
593 convert_from_fs(str
, utf8_buf
, lengthof(utf8_buf
));
595 /* Convert caret position from bytes in the input string to a position in the UTF-8 encoded string. */
596 LONG caret_bytes
= ImmGetCompositionString(hIMC
, GCS_CURSORPOS
, nullptr, 0);
597 const char *caret
= utf8_buf
;
598 for (const TCHAR
*c
= str
; *c
!= '\0' && *caret
!= '\0' && caret_bytes
> 0; c
++, caret_bytes
--) {
599 /* Skip DBCS lead bytes or leading surrogates. */
601 if (Utf16IsLeadSurrogate(*c
)) {
603 if (IsDBCSLeadByte(*c
)) {
611 HandleTextInput(utf8_buf
, true, caret
);
613 HandleTextInput(nullptr, true);
616 lParam
&= ~(GCS_COMPSTR
| GCS_COMPATTR
| GCS_COMPCLAUSE
| GCS_CURSORPOS
| GCS_DELTASTART
);
619 ImmReleaseContext(hwnd
, hIMC
);
621 return lParam
!= 0 ? DefWindowProc(hwnd
, WM_IME_COMPOSITION
, wParam
, lParam
) : 0;
624 /** Clear the current composition string. */
625 static void CancelIMEComposition(HWND hwnd
)
627 HIMC hIMC
= ImmGetContext(hwnd
);
628 if (hIMC
!= NULL
) ImmNotifyIME(hIMC
, NI_COMPOSITIONSTR
, CPS_CANCEL
, 0);
629 ImmReleaseContext(hwnd
, hIMC
);
630 /* Clear any marked string from the current edit box. */
631 HandleTextInput(nullptr, true);
636 static bool DrawIMECompositionString() { return false; }
637 static void SetCompositionPos(HWND hwnd
) {}
638 static void SetCandidatePos(HWND hwnd
) {}
639 static void CancelIMEComposition(HWND hwnd
) {}
641 #endif /* !defined(WINCE) || _WIN32_WCE >= 0x400 */
643 static LRESULT CALLBACK
WndProcGdi(HWND hwnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
645 static uint32 keycode
= 0;
646 static bool console
= false;
647 static bool in_sizemove
= false;
651 SetTimer(hwnd
, TID_POLLMOUSE
, MOUSE_POLL_DELAY
, (TIMERPROC
)TrackMouseTimerProc
);
652 SetCompositionPos(hwnd
);
653 #if !defined(WINCE) || _WIN32_WCE >= 0x400
654 _imm_props
= ImmGetProperty(GetKeyboardLayout(0), IGP_PROPERTY
);
658 case WM_ENTERSIZEMOVE
:
662 case WM_EXITSIZEMOVE
:
667 if (!in_sizemove
&& _draw_mutex
!= nullptr && !HasModalProgress()) {
668 /* Get the union of the old update rect and the new update rect. */
670 GetUpdateRect(hwnd
, &r
, FALSE
);
671 UnionRect(&_wnd
.update_rect
, &_wnd
.update_rect
, &r
);
673 /* Mark the window as updated, otherwise Windows would send more WM_PAINT messages. */
674 ValidateRect(hwnd
, nullptr);
675 _draw_mutex
->SendSignal();
679 BeginPaint(hwnd
, &ps
);
685 case WM_PALETTECHANGED
:
686 if ((HWND
)wParam
== hwnd
) return 0;
689 case WM_QUERYNEWPALETTE
: {
690 HDC hDC
= GetWindowDC(hwnd
);
691 HPALETTE hOldPalette
= SelectPalette(hDC
, _wnd
.gdi_palette
, FALSE
);
692 UINT nChanged
= RealizePalette(hDC
);
694 SelectPalette(hDC
, hOldPalette
, TRUE
);
695 ReleaseDC(hwnd
, hDC
);
696 if (nChanged
!= 0) InvalidateRect(hwnd
, nullptr, FALSE
);
701 HandleExitGameRequest();
705 if (_window_maximize
) _cur_resolution
= _bck_resolution
;
710 _left_button_down
= true;
716 _left_button_down
= false;
717 _left_button_clicked
= false;
723 _right_button_down
= true;
724 _right_button_clicked
= true;
730 _right_button_down
= false;
736 _cursor
.in_window
= false;
738 if (!_left_button_down
&& !_right_button_down
) MyShowCursor(true);
742 int x
= (int16
)LOWORD(lParam
);
743 int y
= (int16
)HIWORD(lParam
);
745 /* If the mouse was not in the window and it has moved it means it has
746 * come into the window, so start drawing the mouse. Also start
747 * tracking the mouse for exiting the window */
748 if (!_cursor
.in_window
) {
749 _cursor
.in_window
= true;
750 SetTimer(hwnd
, TID_POLLMOUSE
, MOUSE_POLL_DELAY
, (TIMERPROC
)TrackMouseTimerProc
);
753 if (_cursor
.fix_at
) {
754 /* Get all queued mouse events now in case we have to warp the cursor. In the
755 * end, we only care about the current mouse position and not bygone events. */
757 while (PeekMessage(&m
, hwnd
, WM_MOUSEMOVE
, WM_MOUSEMOVE
, PM_REMOVE
| PM_NOYIELD
| PM_QS_INPUT
)) {
758 x
= (int16
)LOWORD(m
.lParam
);
759 y
= (int16
)HIWORD(m
.lParam
);
763 if (_cursor
.UpdateCursorPosition(x
, y
, false)) {
765 pt
.x
= _cursor
.pos
.x
;
766 pt
.y
= _cursor
.pos
.y
;
767 ClientToScreen(hwnd
, &pt
);
768 SetCursorPos(pt
.x
, pt
.y
);
775 #if !defined(WINCE) || _WIN32_WCE >= 0x400
776 case WM_INPUTLANGCHANGE
:
777 _imm_props
= ImmGetProperty(GetKeyboardLayout(0), IGP_PROPERTY
);
780 case WM_IME_SETCONTEXT
:
781 /* Don't show the composition window if we draw the string ourself. */
782 if (DrawIMECompositionString()) lParam
&= ~ISC_SHOWUICOMPOSITIONWINDOW
;
785 case WM_IME_STARTCOMPOSITION
:
786 SetCompositionPos(hwnd
);
787 if (DrawIMECompositionString()) return 0;
790 case WM_IME_COMPOSITION
:
791 return HandleIMEComposition(hwnd
, wParam
, lParam
);
793 case WM_IME_ENDCOMPOSITION
:
794 /* Clear any pending composition string. */
795 HandleTextInput(nullptr, true);
796 if (DrawIMECompositionString()) return 0;
800 if (wParam
== IMN_OPENCANDIDATE
) SetCandidatePos(hwnd
);
803 #if !defined(UNICODE)
805 if (GB(wParam
, 8, 8) != 0) {
806 /* DBCS character, send lead byte first. */
807 HandleCharMsg(0, GB(wParam
, 8, 8));
809 HandleCharMsg(0, GB(wParam
, 0, 8));
815 console
= GB(lParam
, 16, 8) == 41;
819 uint scancode
= GB(lParam
, 16, 8);
820 uint charcode
= wParam
;
822 /* If the console key is a dead-key, we need to press it twice to get a WM_CHAR message.
823 * But we then get two WM_CHAR messages, so ignore the first one */
824 if (console
&& scancode
== 41) {
829 /* IMEs and other input methods sometimes send a WM_CHAR without a WM_KEYDOWN,
830 * clear the keycode so a previous WM_KEYDOWN doesn't become 'stuck'. */
831 uint cur_keycode
= keycode
;
834 return HandleCharMsg(cur_keycode
, charcode
);
838 /* No matter the keyboard layout, we will map the '~' to the console. */
839 uint scancode
= GB(lParam
, 16, 8);
840 keycode
= scancode
== 41 ? (uint
)WKC_BACKQUOTE
: MapWindowsKey(wParam
);
842 /* Silently drop all messages handled by WM_CHAR. */
844 if (PeekMessage(&msg
, nullptr, 0, 0, PM_NOREMOVE
)) {
845 if ((msg
.message
== WM_CHAR
|| msg
.message
== WM_DEADCHAR
) && GB(lParam
, 16, 8) == GB(msg
.lParam
, 16, 8)) {
850 uint charcode
= MapVirtualKey(wParam
, MAPVK_VK_TO_CHAR
);
852 /* No character translation? */
854 HandleKeypress(keycode
, 0);
858 /* Is the console key a dead key? If yes, ignore the first key down event. */
859 if (HasBit(charcode
, 31) && !console
) {
860 if (scancode
== 41) {
867 /* IMEs and other input methods sometimes send a WM_CHAR without a WM_KEYDOWN,
868 * clear the keycode so a previous WM_KEYDOWN doesn't become 'stuck'. */
869 uint cur_keycode
= keycode
;
872 return HandleCharMsg(cur_keycode
, LOWORD(charcode
));
875 case WM_SYSKEYDOWN
: // user presses F10 or Alt, both activating the title-menu
878 case 'F': // Full Screen on ALT + ENTER/F
879 ToggleFullScreen(!_wnd
.fullscreen
);
882 case VK_MENU
: // Just ALT
883 return 0; // do nothing
885 case VK_F10
: // F10, ignore activation of menu
886 HandleKeypress(MapWindowsKey(wParam
), 0);
889 default: // ALT in combination with something else
890 HandleKeypress(MapWindowsKey(wParam
), 0);
896 if (wParam
!= SIZE_MINIMIZED
) {
897 /* Set maximized flag when we maximize (obviously), but also when we
898 * switched to fullscreen from a maximized state */
899 _window_maximize
= (wParam
== SIZE_MAXIMIZED
|| (_window_maximize
&& _fullscreen
));
900 if (_window_maximize
|| _fullscreen
) _bck_resolution
= _cur_resolution
;
901 ClientSizeChanged(LOWORD(lParam
), HIWORD(lParam
));
907 RECT
*r
= (RECT
*)lParam
;
911 SetRect(&r2
, 0, 0, 0, 0);
912 AdjustWindowRect(&r2
, GetWindowLong(hwnd
, GWL_STYLE
), FALSE
);
914 w
= r
->right
- r
->left
- (r2
.right
- r2
.left
);
915 h
= r
->bottom
- r
->top
- (r2
.bottom
- r2
.top
);
918 SetRect(&r2
, 0, 0, w
, h
);
920 AdjustWindowRect(&r2
, GetWindowLong(hwnd
, GWL_STYLE
), FALSE
);
921 w
= r2
.right
- r2
.left
;
922 h
= r2
.bottom
- r2
.top
;
926 r
->bottom
= r
->top
+ h
;
929 case WMSZ_BOTTOMLEFT
:
930 r
->bottom
= r
->top
+ h
;
931 r
->left
= r
->right
- w
;
934 case WMSZ_BOTTOMRIGHT
:
935 r
->bottom
= r
->top
+ h
;
936 r
->right
= r
->left
+ w
;
940 r
->left
= r
->right
- w
;
944 r
->right
= r
->left
+ w
;
948 r
->top
= r
->bottom
- h
;
952 r
->top
= r
->bottom
- h
;
953 r
->left
= r
->right
- w
;
957 r
->top
= r
->bottom
- h
;
958 r
->right
= r
->left
+ w
;
965 /* needed for wheel */
966 #if !defined(WM_MOUSEWHEEL)
967 # define WM_MOUSEWHEEL 0x020A
968 #endif /* WM_MOUSEWHEEL */
969 #if !defined(GET_WHEEL_DELTA_WPARAM)
970 # define GET_WHEEL_DELTA_WPARAM(wparam) ((short)HIWORD(wparam))
971 #endif /* GET_WHEEL_DELTA_WPARAM */
973 case WM_MOUSEWHEEL
: {
974 int delta
= GET_WHEEL_DELTA_WPARAM(wParam
);
978 } else if (delta
> 0) {
986 _wnd
.has_focus
= true;
987 SetCompositionPos(hwnd
);
991 _wnd
.has_focus
= false;
996 /* Don't do anything if we are closing openttd */
997 if (_exit_game
) break;
999 bool active
= (LOWORD(wParam
) != WA_INACTIVE
);
1000 bool minimized
= (HIWORD(wParam
) != 0);
1001 if (_wnd
.fullscreen
) {
1002 if (active
&& minimized
) {
1003 /* Restore the game window */
1004 ShowWindow(hwnd
, SW_RESTORE
);
1005 static_cast<VideoDriver_Win32
*>(VideoDriver::GetInstance())->MakeWindow(true);
1006 } else if (!active
&& !minimized
) {
1007 /* Minimise the window and restore desktop */
1008 ShowWindow(hwnd
, SW_MINIMIZE
);
1009 ChangeDisplaySettings(nullptr, 0);
1017 return DefWindowProc(hwnd
, msg
, wParam
, lParam
);
1020 static void RegisterWndClass()
1022 static bool registered
= false;
1025 HINSTANCE hinst
= GetModuleHandle(nullptr);
1032 LoadIcon(hinst
, MAKEINTRESOURCE(100)),
1033 LoadCursor(nullptr, IDC_ARROW
),
1040 if (!RegisterClass(&wnd
)) usererror("RegisterClass failed");
1044 static bool AllocateDibSection(int w
, int h
, bool force
)
1048 uint bpp
= BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
1053 if (bpp
== 0) usererror("Can't use a blitter that blits 0 bpp for normal visuals");
1055 if (!force
&& w
== _screen
.width
&& h
== _screen
.height
) return false;
1057 bi
= (BITMAPINFO
*)alloca(sizeof(BITMAPINFOHEADER
) + sizeof(RGBQUAD
) * 256);
1058 memset(bi
, 0, sizeof(BITMAPINFOHEADER
) + sizeof(RGBQUAD
) * 256);
1059 bi
->bmiHeader
.biSize
= sizeof(BITMAPINFOHEADER
);
1061 bi
->bmiHeader
.biWidth
= _wnd
.width
= w
;
1062 bi
->bmiHeader
.biHeight
= -(_wnd
.height
= h
);
1064 bi
->bmiHeader
.biPlanes
= 1;
1065 bi
->bmiHeader
.biBitCount
= BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
1066 bi
->bmiHeader
.biCompression
= BI_RGB
;
1068 if (_wnd
.dib_sect
) DeleteObject(_wnd
.dib_sect
);
1071 _wnd
.dib_sect
= CreateDIBSection(dc
, bi
, DIB_RGB_COLORS
, (VOID
**)&_wnd
.buffer_bits
, nullptr, 0);
1072 if (_wnd
.dib_sect
== nullptr) usererror("CreateDIBSection failed");
1076 _screen
.pitch
= (bpp
== 8) ? Align(w
, 4) : w
;
1078 _screen
.dst_ptr
= _wnd
.buffer_bits
;
1083 static const Dimension default_resolutions
[] = {
1097 static void FindResolutions()
1101 /* EnumDisplaySettingsW is only supported in CE 4.2+
1102 * XXX -- One might argue that we assume 4.2+ on every system. Then we can use this function safely */
1107 /* Check modes for the relevant fullscreen bpp */
1108 uint bpp
= _support8bpp
!= S8BPP_HARDWARE
? 32 : BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
1110 /* XXX - EnumDisplaySettingsW crashes with unicows.dll on Windows95
1111 * Doesn't really matter since we don't pass a string anyways, but still
1113 for (i
= 0; EnumDisplaySettingsA(nullptr, i
, &dm
) != 0; i
++) {
1114 if (dm
.dmBitsPerPel
== bpp
&&
1115 dm
.dmPelsWidth
>= 640 && dm
.dmPelsHeight
>= 480) {
1118 for (j
= 0; j
< n
; j
++) {
1119 if (_resolutions
[j
].width
== dm
.dmPelsWidth
&& _resolutions
[j
].height
== dm
.dmPelsHeight
) break;
1122 /* In the previous loop we have checked already existing/added resolutions if
1123 * they are the same as the new ones. If this is not the case (j == n); we have
1124 * looped all and found none, add the new one to the list. If we have reached the
1125 * maximum amount of resolutions, then quit querying the display */
1127 _resolutions
[j
].width
= dm
.dmPelsWidth
;
1128 _resolutions
[j
].height
= dm
.dmPelsHeight
;
1129 if (++n
== lengthof(_resolutions
)) break;
1135 /* We have found no resolutions, show the default list */
1137 memcpy(_resolutions
, default_resolutions
, sizeof(default_resolutions
));
1138 n
= lengthof(default_resolutions
);
1141 _num_resolutions
= n
;
1142 SortResolutions(_num_resolutions
);
1145 static FVideoDriver_Win32 iFVideoDriver_Win32
;
1147 const char *VideoDriver_Win32::Start(const char * const *parm
)
1149 memset(&_wnd
, 0, sizeof(_wnd
));
1157 DEBUG(driver
, 2, "Resolution for display: %ux%u", _cur_resolution
.width
, _cur_resolution
.height
);
1159 /* fullscreen uses those */
1160 _wnd
.width_org
= _cur_resolution
.width
;
1161 _wnd
.height_org
= _cur_resolution
.height
;
1163 AllocateDibSection(_cur_resolution
.width
, _cur_resolution
.height
);
1164 this->MakeWindow(_fullscreen
);
1166 MarkWholeScreenDirty();
1168 _draw_threaded
= GetDriverParam(parm
, "no_threads") == nullptr && GetDriverParam(parm
, "no_thread") == nullptr && GetCPUCoreCount() > 1;
1173 void VideoDriver_Win32::Stop()
1175 DeleteObject(_wnd
.gdi_palette
);
1176 DeleteObject(_wnd
.dib_sect
);
1177 DestroyWindow(_wnd
.main_wnd
);
1180 if (_wnd
.fullscreen
) ChangeDisplaySettings(nullptr, 0);
1185 void VideoDriver_Win32::MakeDirty(int left
, int top
, int width
, int height
)
1187 RECT r
= { left
, top
, left
+ width
, top
+ height
};
1189 InvalidateRect(_wnd
.main_wnd
, &r
, FALSE
);
1192 static void CheckPaletteAnim()
1194 if (_cur_palette
.count_dirty
== 0) return;
1196 _local_palette
= _cur_palette
;
1197 InvalidateRect(_wnd
.main_wnd
, nullptr, FALSE
);
1200 void VideoDriver_Win32::MainLoop()
1203 uint32 cur_ticks
= GetTickCount();
1204 uint32 last_cur_ticks
= cur_ticks
;
1205 uint32 next_tick
= cur_ticks
+ MILLISECONDS_PER_TICK
;
1207 if (_draw_threaded
) {
1208 /* Initialise the mutex first, because that's the thing we *need*
1209 * directly in the newly created thread. */
1210 _draw_mutex
= ThreadMutex::New();
1211 _draw_thread_initialized
= CreateEvent(nullptr, FALSE
, FALSE
, nullptr);
1212 if (_draw_mutex
== nullptr || _draw_thread_initialized
== nullptr) {
1213 _draw_threaded
= false;
1215 _draw_continue
= true;
1216 _draw_threaded
= ThreadObject::New(&PaintWindowThread
, nullptr, &_draw_thread
, "ottd:draw-win32");
1218 /* Free the mutex if we won't be able to use it. */
1219 if (!_draw_threaded
) {
1221 _draw_mutex
= nullptr;
1222 CloseHandle(_draw_thread_initialized
);
1223 _draw_thread_initialized
= nullptr;
1225 DEBUG(driver
, 1, "Threaded drawing enabled");
1226 /* Wait till the draw thread has started itself. */
1227 WaitForSingleObject(_draw_thread_initialized
, INFINITE
);
1228 _draw_mutex
->BeginCritical();
1233 _wnd
.running
= true;
1237 uint32 prev_cur_ticks
= cur_ticks
; // to check for wrapping
1239 while (PeekMessage(&mesg
, nullptr, 0, 0, PM_REMOVE
)) {
1240 InteractiveRandom(); // randomness
1241 /* Convert key messages to char messages if we want text input. */
1242 if (EditBoxInGlobalFocus()) TranslateMessage(&mesg
);
1243 DispatchMessage(&mesg
);
1245 if (_exit_game
) return;
1248 if (_wnd
.has_focus
&& GetAsyncKeyState(VK_SHIFT
) < 0 &&
1250 /* Speed up using TAB, but disable for ALT+TAB of course */
1251 if (_wnd
.has_focus
&& GetAsyncKeyState(VK_TAB
) < 0 && GetAsyncKeyState(VK_MENU
) >= 0 &&
1253 !_networking
&& _game_mode
!= GM_MENU
) {
1255 } else if (_fast_forward
& 2) {
1259 cur_ticks
= GetTickCount();
1260 if (cur_ticks
>= next_tick
|| (_fast_forward
&& !_pause_mode
) || cur_ticks
< prev_cur_ticks
) {
1261 _realtime_tick
+= cur_ticks
- last_cur_ticks
;
1262 last_cur_ticks
= cur_ticks
;
1263 next_tick
= cur_ticks
+ MILLISECONDS_PER_TICK
;
1265 bool old_ctrl_pressed
= _ctrl_pressed
;
1267 _ctrl_pressed
= _wnd
.has_focus
&& GetAsyncKeyState(VK_CONTROL
)<0;
1268 _shift_pressed
= _wnd
.has_focus
&& GetAsyncKeyState(VK_SHIFT
)<0;
1270 /* determine which directional keys are down */
1271 if (_wnd
.has_focus
) {
1273 (GetAsyncKeyState(VK_LEFT
) < 0 ? 1 : 0) +
1274 (GetAsyncKeyState(VK_UP
) < 0 ? 2 : 0) +
1275 (GetAsyncKeyState(VK_RIGHT
) < 0 ? 4 : 0) +
1276 (GetAsyncKeyState(VK_DOWN
) < 0 ? 8 : 0);
1281 if (old_ctrl_pressed
!= _ctrl_pressed
) HandleCtrlChanged();
1284 /* Flush GDI buffer to ensure we don't conflict with the drawing thread. */
1288 /* The game loop is the part that can run asynchronously.
1289 * The rest except sleeping can't. */
1290 if (_draw_threaded
) _draw_mutex
->EndCritical();
1292 if (_draw_threaded
) _draw_mutex
->BeginCritical();
1294 if (_force_full_redraw
) MarkWholeScreenDirty();
1300 /* Flush GDI buffer to ensure we don't conflict with the drawing thread. */
1304 /* Release the thread while sleeping */
1305 if (_draw_threaded
) _draw_mutex
->EndCritical();
1307 if (_draw_threaded
) _draw_mutex
->BeginCritical();
1309 NetworkDrawChatMessage();
1314 if (_draw_threaded
) {
1315 _draw_continue
= false;
1316 /* Sending signal if there is no thread blocked
1317 * is very valid and results in noop */
1318 _draw_mutex
->SendSignal();
1319 _draw_mutex
->EndCritical();
1320 _draw_thread
->Join();
1322 CloseHandle(_draw_thread_initialized
);
1324 delete _draw_thread
;
1328 bool VideoDriver_Win32::ChangeResolution(int w
, int h
)
1330 if (_draw_mutex
!= nullptr) _draw_mutex
->BeginCritical(true);
1331 if (_window_maximize
) ShowWindow(_wnd
.main_wnd
, SW_SHOWNORMAL
);
1333 _wnd
.width
= _wnd
.width_org
= w
;
1334 _wnd
.height
= _wnd
.height_org
= h
;
1336 bool ret
= this->MakeWindow(_fullscreen
); // _wnd.fullscreen screws up ingame resolution switching
1337 if (_draw_mutex
!= nullptr) _draw_mutex
->EndCritical(true);
1341 bool VideoDriver_Win32::ToggleFullscreen(bool full_screen
)
1343 if (_draw_mutex
!= nullptr) _draw_mutex
->BeginCritical(true);
1344 bool ret
= this->MakeWindow(full_screen
);
1345 if (_draw_mutex
!= nullptr) _draw_mutex
->EndCritical(true);
1349 bool VideoDriver_Win32::AfterBlitterChange()
1351 return AllocateDibSection(_screen
.width
, _screen
.height
, true) && this->MakeWindow(_fullscreen
);
1354 void VideoDriver_Win32::AcquireBlitterLock()
1356 if (_draw_mutex
!= nullptr) _draw_mutex
->BeginCritical(true);
1359 void VideoDriver_Win32::ReleaseBlitterLock()
1361 if (_draw_mutex
!= nullptr) _draw_mutex
->EndCritical(true);
1364 void VideoDriver_Win32::EditBoxLostFocus()
1366 if (_draw_mutex
!= nullptr) _draw_mutex
->BeginCritical(true);
1367 CancelIMEComposition(_wnd
.main_wnd
);
1368 SetCompositionPos(_wnd
.main_wnd
);
1369 SetCandidatePos(_wnd
.main_wnd
);
1370 if (_draw_mutex
!= nullptr) _draw_mutex
->EndCritical(true);