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 /* Missing define in MinGW headers. */
31 #ifndef MAPVK_VK_TO_CHAR
32 #define MAPVK_VK_TO_CHAR (2)
50 bool _force_full_redraw
;
51 bool _window_maximize
;
54 static Dimension _bck_resolution
;
55 #if !defined(WINCE) || _WIN32_WCE >= 0x400
59 /** Whether the drawing is/may be done in a separate thread. */
60 static bool _draw_threaded
;
61 /** Thread used to 'draw' to the screen, i.e. push data to the screen. */
62 static ThreadObject
*_draw_thread
= NULL
;
63 /** Mutex to keep the access to the shared memory controlled. */
64 static ThreadMutex
*_draw_mutex
= NULL
;
65 /** Should we keep continue drawing? */
66 static volatile bool _draw_continue
;
67 /** Local copy of the palette for use in the drawing thread. */
68 static Palette _local_palette
;
70 static void MakePalette()
72 LOGPALETTE
*pal
= (LOGPALETTE
*)alloca(sizeof(LOGPALETTE
) + (256 - 1) * sizeof(PALETTEENTRY
));
74 pal
->palVersion
= 0x300;
75 pal
->palNumEntries
= 256;
77 for (uint i
= 0; i
!= 256; i
++) {
78 pal
->palPalEntry
[i
].peRed
= _cur_palette
.palette
[i
].r
;
79 pal
->palPalEntry
[i
].peGreen
= _cur_palette
.palette
[i
].g
;
80 pal
->palPalEntry
[i
].peBlue
= _cur_palette
.palette
[i
].b
;
81 pal
->palPalEntry
[i
].peFlags
= 0;
84 _wnd
.gdi_palette
= CreatePalette(pal
);
85 if (_wnd
.gdi_palette
== NULL
) usererror("CreatePalette failed!\n");
87 _cur_palette
.first_dirty
= 0;
88 _cur_palette
.count_dirty
= 256;
89 _local_palette
= _cur_palette
;
92 static void UpdatePalette(HDC dc
, uint start
, uint count
)
97 for (i
= 0; i
!= count
; i
++) {
98 rgb
[i
].rgbRed
= _local_palette
.palette
[start
+ i
].r
;
99 rgb
[i
].rgbGreen
= _local_palette
.palette
[start
+ i
].g
;
100 rgb
[i
].rgbBlue
= _local_palette
.palette
[start
+ i
].b
;
101 rgb
[i
].rgbReserved
= 0;
104 SetDIBColorTable(dc
, start
, count
, rgb
);
107 bool VideoDriver_Win32::ClaimMousePointer()
109 MyShowCursor(false, true);
119 #define AS(x, z) {x, 0, z}
120 #define AM(x, y, z, w) {x, y - x, z}
122 static const VkMapping _vk_mapping
[] = {
123 /* Pageup stuff + up/down */
124 AM(VK_PRIOR
, VK_DOWN
, WKC_PAGEUP
, WKC_DOWN
),
125 /* Map letters & digits */
126 AM('A', 'Z', 'A', 'Z'),
127 AM('0', '9', '0', '9'),
129 AS(VK_ESCAPE
, WKC_ESC
),
130 AS(VK_PAUSE
, WKC_PAUSE
),
131 AS(VK_BACK
, WKC_BACKSPACE
),
132 AM(VK_INSERT
, VK_DELETE
, WKC_INSERT
, WKC_DELETE
),
134 AS(VK_SPACE
, WKC_SPACE
),
135 AS(VK_RETURN
, WKC_RETURN
),
139 AM(VK_F1
, VK_F12
, WKC_F1
, WKC_F12
),
142 AM(VK_NUMPAD0
, VK_NUMPAD9
, '0', '9'),
143 AS(VK_DIVIDE
, WKC_NUM_DIV
),
144 AS(VK_MULTIPLY
, WKC_NUM_MUL
),
145 AS(VK_SUBTRACT
, WKC_NUM_MINUS
),
146 AS(VK_ADD
, WKC_NUM_PLUS
),
147 AS(VK_DECIMAL
, WKC_NUM_DECIMAL
),
149 /* Other non-letter keys */
151 AS(0xBA, WKC_SEMICOLON
),
152 AS(0xBB, WKC_EQUALS
),
153 AS(0xDB, WKC_L_BRACKET
),
154 AS(0xDC, WKC_BACKSLASH
),
155 AS(0xDD, WKC_R_BRACKET
),
157 AS(0xDE, WKC_SINGLEQUOTE
),
163 static uint
MapWindowsKey(uint sym
)
165 const VkMapping
*map
;
168 for (map
= _vk_mapping
; map
!= endof(_vk_mapping
); ++map
) {
169 if ((uint
)(sym
- map
->vk_from
) <= map
->vk_count
) {
170 key
= sym
- map
->vk_from
+ map
->map_to
;
175 if (GetAsyncKeyState(VK_SHIFT
) < 0) key
|= WKC_SHIFT
;
176 if (GetAsyncKeyState(VK_CONTROL
) < 0) key
|= WKC_CTRL
;
177 if (GetAsyncKeyState(VK_MENU
) < 0) key
|= WKC_ALT
;
181 static bool AllocateDibSection(int w
, int h
, bool force
= false);
183 static void ClientSizeChanged(int w
, int h
)
185 /* allocate new dib section of the new size */
186 if (AllocateDibSection(w
, h
)) {
187 if (_draw_mutex
!= NULL
) _draw_mutex
->BeginCritical();
188 /* mark all palette colours dirty */
189 _cur_palette
.first_dirty
= 0;
190 _cur_palette
.count_dirty
= 256;
191 _local_palette
= _cur_palette
;
193 BlitterFactory::GetCurrentBlitter()->PostResize();
199 _screen
.dst_ptr
= _wnd
.buffer_bits
;
203 if (_draw_mutex
!= NULL
) _draw_mutex
->EndCritical();
208 /* Keep this function here..
209 * It allows you to redraw the screen from within the MSVC debugger */
210 int RedrawScreenDebug()
215 HPALETTE old_palette
;
217 _screen
.dst_ptr
= _wnd
.buffer_bits
;
220 dc
= GetDC(_wnd
.main_wnd
);
221 dc2
= CreateCompatibleDC(dc
);
223 old_bmp
= (HBITMAP
)SelectObject(dc2
, _wnd
.dib_sect
);
224 old_palette
= SelectPalette(dc
, _wnd
.gdi_palette
, FALSE
);
225 BitBlt(dc
, 0, 0, _wnd
.width
, _wnd
.height
, dc2
, 0, 0, SRCCOPY
);
226 SelectPalette(dc
, old_palette
, TRUE
);
227 SelectObject(dc2
, old_bmp
);
229 ReleaseDC(_wnd
.main_wnd
, dc
);
235 /* Windows 95 will not have a WM_MOUSELEAVE message, so define it if needed */
236 #if !defined(WM_MOUSELEAVE)
237 #define WM_MOUSELEAVE 0x02A3
239 #define TID_POLLMOUSE 1
240 #define MOUSE_POLL_DELAY 75
242 static void CALLBACK
TrackMouseTimerProc(HWND hwnd
, UINT msg
, UINT event
, DWORD time
)
247 /* Get the rectangle of our window and translate it to screen coordinates.
248 * Compare this with the current screen coordinates of the mouse and if it
249 * falls outside of the area or our window we have left the window. */
250 GetClientRect(hwnd
, &rc
);
251 MapWindowPoints(hwnd
, HWND_DESKTOP
, (LPPOINT
)(LPRECT
)&rc
, 2);
254 if (!PtInRect(&rc
, pt
) || (WindowFromPoint(pt
) != hwnd
)) {
255 KillTimer(hwnd
, event
);
256 PostMessage(hwnd
, WM_MOUSELEAVE
, 0, 0L);
261 * Instantiate a new window.
262 * @param full_screen Whether to make a full screen window or not.
263 * @return True if the window could be created.
265 bool VideoDriver_Win32::MakeWindow(bool full_screen
)
267 _fullscreen
= full_screen
;
269 /* recreate window? */
270 if ((full_screen
|| _wnd
.fullscreen
) && _wnd
.main_wnd
) {
271 DestroyWindow(_wnd
.main_wnd
);
276 /* WinCE is always fullscreen */
281 /* Make sure we are always at least the screen-depth of the blitter */
282 if (_fullscreen_bpp
< BlitterFactory::GetCurrentBlitter()->GetScreenDepth()) _fullscreen_bpp
= BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
284 memset(&settings
, 0, sizeof(settings
));
285 settings
.dmSize
= sizeof(settings
);
287 (_fullscreen_bpp
!= 0 ? DM_BITSPERPEL
: 0) |
290 (_display_hz
!= 0 ? DM_DISPLAYFREQUENCY
: 0);
291 settings
.dmBitsPerPel
= _fullscreen_bpp
;
292 settings
.dmPelsWidth
= _wnd
.width_org
;
293 settings
.dmPelsHeight
= _wnd
.height_org
;
294 settings
.dmDisplayFrequency
= _display_hz
;
296 /* Check for 8 bpp support. */
297 if (settings
.dmBitsPerPel
!= 32 && ChangeDisplaySettings(&settings
, CDS_FULLSCREEN
| CDS_TEST
) != DISP_CHANGE_SUCCESSFUL
) {
298 settings
.dmBitsPerPel
= 32;
301 /* Test fullscreen with current resolution, if it fails use desktop resolution. */
302 if (ChangeDisplaySettings(&settings
, CDS_FULLSCREEN
| CDS_TEST
) != DISP_CHANGE_SUCCESSFUL
) {
304 GetWindowRect(GetDesktopWindow(), &r
);
305 /* Guard against recursion. If we already failed here once, just fall through to
306 * the next ChangeDisplaySettings call which will fail and error out appropriately. */
307 if ((int)settings
.dmPelsWidth
!= r
.right
- r
.left
|| (int)settings
.dmPelsHeight
!= r
.bottom
- r
.top
) {
308 return this->ChangeResolution(r
.right
- r
.left
, r
.bottom
- r
.top
);
312 if (ChangeDisplaySettings(&settings
, CDS_FULLSCREEN
) != DISP_CHANGE_SUCCESSFUL
) {
313 this->MakeWindow(false); // don't care about the result
314 return false; // the request failed
316 } else if (_wnd
.fullscreen
) {
317 /* restore display? */
318 ChangeDisplaySettings(NULL
, 0);
319 /* restore the resolution */
320 _wnd
.width
= _bck_resolution
.width
;
321 _wnd
.height
= _bck_resolution
.height
;
327 DWORD style
, showstyle
;
330 showstyle
= SW_SHOWNORMAL
;
331 _wnd
.fullscreen
= full_screen
;
332 if (_wnd
.fullscreen
) {
334 SetRect(&r
, 0, 0, _wnd
.width_org
, _wnd
.height_org
);
336 style
= WS_OVERLAPPEDWINDOW
;
337 /* On window creation, check if we were in maximize mode before */
338 if (_window_maximize
) showstyle
= SW_SHOWMAXIMIZED
;
339 SetRect(&r
, 0, 0, _wnd
.width
, _wnd
.height
);
343 AdjustWindowRect(&r
, style
, FALSE
);
345 w
= r
.right
- r
.left
;
346 h
= r
.bottom
- r
.top
;
348 if (_wnd
.main_wnd
!= NULL
) {
349 if (!_window_maximize
) SetWindowPos(_wnd
.main_wnd
, 0, 0, 0, w
, h
, SWP_NOACTIVATE
| SWP_NOOWNERZORDER
| SWP_NOZORDER
| SWP_NOMOVE
);
351 TCHAR Windowtitle
[50];
352 int x
= (GetSystemMetrics(SM_CXSCREEN
) - w
) / 2;
353 int y
= (GetSystemMetrics(SM_CYSCREEN
) - h
) / 2;
355 _sntprintf(Windowtitle
, lengthof(Windowtitle
), _T("OpenTTD %s"), MB_TO_WIDE(_openttd_revision
));
357 _wnd
.main_wnd
= CreateWindow(_T("OTTD"), Windowtitle
, style
, x
, y
, w
, h
, 0, 0, GetModuleHandle(NULL
), 0);
358 if (_wnd
.main_wnd
== NULL
) usererror("CreateWindow failed");
359 ShowWindow(_wnd
.main_wnd
, showstyle
);
363 BlitterFactory::GetCurrentBlitter()->PostResize();
365 GameSizeChanged(); // invalidate all windows, force redraw
366 return true; // the request succeeded
369 /** Do palette animation and blit to the window. */
370 static void PaintWindow(HDC dc
)
372 HDC dc2
= CreateCompatibleDC(dc
);
373 HBITMAP old_bmp
= (HBITMAP
)SelectObject(dc2
, _wnd
.dib_sect
);
374 HPALETTE old_palette
= SelectPalette(dc
, _wnd
.gdi_palette
, FALSE
);
376 if (_cur_palette
.count_dirty
!= 0) {
377 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
379 switch (blitter
->UsePaletteAnimation()) {
380 case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND
:
381 UpdatePalette(dc2
, _local_palette
.first_dirty
, _local_palette
.count_dirty
);
384 case Blitter::PALETTE_ANIMATION_BLITTER
:
385 blitter
->PaletteAnimate(_local_palette
);
388 case Blitter::PALETTE_ANIMATION_NONE
:
394 _cur_palette
.count_dirty
= 0;
397 BitBlt(dc
, 0, 0, _wnd
.width
, _wnd
.height
, dc2
, 0, 0, SRCCOPY
);
398 SelectPalette(dc
, old_palette
, TRUE
);
399 SelectObject(dc2
, old_bmp
);
403 static void PaintWindowThread(void *)
405 /* First tell the main thread we're started */
406 _draw_mutex
->BeginCritical();
407 _draw_mutex
->SendSignal();
409 /* Do our best to make sure the main thread is the one that
410 * gets the signal, and not our wait below. */
413 /* Now wait for the first thing to draw! */
414 _draw_mutex
->WaitForSignal();
416 while (_draw_continue
) {
417 /* Convert update region from logical to device coordinates. */
419 ClientToScreen(_wnd
.main_wnd
, &pt
);
420 OffsetRect(&_wnd
.update_rect
, pt
.x
, pt
.y
);
422 /* Create a device context that is clipped to the region we need to draw.
423 * GetDCEx 'consumes' the update region, so we may not destroy it ourself. */
424 HRGN rgn
= CreateRectRgnIndirect(&_wnd
.update_rect
);
425 HDC dc
= GetDCEx(_wnd
.main_wnd
, rgn
, DCX_CLIPSIBLINGS
| DCX_CLIPCHILDREN
| DCX_INTERSECTRGN
);
429 /* Clear update rect. */
430 SetRectEmpty(&_wnd
.update_rect
);
431 ReleaseDC(_wnd
.main_wnd
, dc
);
433 /* Flush GDI buffer to ensure drawing here doesn't conflict with any GDI usage in the main WndProc. */
436 _draw_mutex
->WaitForSignal();
439 _draw_mutex
->EndCritical();
440 _draw_thread
->Exit();
443 /** Forward key presses to the window system. */
444 static LRESULT
HandleCharMsg(uint keycode
, WChar charcode
)
446 #if !defined(UNICODE)
447 static char prev_char
= 0;
449 char input
[2] = {(char)charcode
, 0};
452 if (prev_char
!= 0) {
453 /* We stored a lead byte previously, combine it with this byte. */
454 input
[0] = prev_char
;
455 input
[1] = (char)charcode
;
457 } else if (IsDBCSLeadByte(charcode
)) {
458 /* We got a lead byte, store and exit. */
459 prev_char
= charcode
;
464 wchar_t w
[2]; // Can get up to two code points as a result.
465 int len
= MultiByteToWideChar(CP_ACP
, 0, input
, input_len
, w
, 2);
467 case 1: // Normal unicode character.
471 case 2: // Got an UTF-16 surrogate pair back.
472 charcode
= Utf16DecodeSurrogate(w
[0], w
[1]);
475 default: // Some kind of error.
476 DEBUG(driver
, 1, "Invalid DBCS character sequence encountered, dropping input");
481 static WChar prev_char
= 0;
483 /* Did we get a lead surrogate? If yes, store and exit. */
484 if (Utf16IsLeadSurrogate(charcode
)) {
485 if (prev_char
!= 0) DEBUG(driver
, 1, "Got two UTF-16 lead surrogates, dropping the first one");
486 prev_char
= charcode
;
490 /* Stored lead surrogate and incoming trail surrogate? Combine and forward to input handling. */
491 if (prev_char
!= 0) {
492 if (Utf16IsTrailSurrogate(charcode
)) {
493 charcode
= Utf16DecodeSurrogate(prev_char
, charcode
);
495 DEBUG(driver
, 1, "Got an UTF-16 lead surrogate without a trail surrogate, dropping the lead surrogate");
501 HandleKeypress(keycode
, charcode
);
506 #if !defined(WINCE) || _WIN32_WCE >= 0x400
507 /** Should we draw the composition string ourself, i.e is this a normal IME? */
508 static bool DrawIMECompositionString()
510 return (_imm_props
& IME_PROP_AT_CARET
) && !(_imm_props
& IME_PROP_SPECIAL_UI
);
513 /** Set position of the composition window to the caret position. */
514 static void SetCompositionPos(HWND hwnd
)
516 HIMC hIMC
= ImmGetContext(hwnd
);
519 cf
.dwStyle
= CFS_POINT
;
521 if (EditBoxInGlobalFocus()) {
522 /* Get caret position. */
523 Point pt
= _focused_window
->GetCaretPosition();
524 cf
.ptCurrentPos
.x
= _focused_window
->left
+ pt
.x
;
525 cf
.ptCurrentPos
.y
= _focused_window
->top
+ pt
.y
;
527 cf
.ptCurrentPos
.x
= 0;
528 cf
.ptCurrentPos
.y
= 0;
530 ImmSetCompositionWindow(hIMC
, &cf
);
532 ImmReleaseContext(hwnd
, hIMC
);
535 /** Set the position of the candidate window. */
536 static void SetCandidatePos(HWND hwnd
)
538 HIMC hIMC
= ImmGetContext(hwnd
);
542 cf
.dwStyle
= CFS_EXCLUDE
;
544 if (EditBoxInGlobalFocus()) {
545 Point pt
= _focused_window
->GetCaretPosition();
546 cf
.ptCurrentPos
.x
= _focused_window
->left
+ pt
.x
;
547 cf
.ptCurrentPos
.y
= _focused_window
->top
+ pt
.y
;
548 if (_focused_window
->window_class
== WC_CONSOLE
) {
549 cf
.rcArea
.left
= _focused_window
->left
;
550 cf
.rcArea
.top
= _focused_window
->top
;
551 cf
.rcArea
.right
= _focused_window
->left
+ _focused_window
->width
;
552 cf
.rcArea
.bottom
= _focused_window
->top
+ _focused_window
->height
;
554 cf
.rcArea
.left
= _focused_window
->left
+ _focused_window
->nested_focus
->pos_x
;
555 cf
.rcArea
.top
= _focused_window
->top
+ _focused_window
->nested_focus
->pos_y
;
556 cf
.rcArea
.right
= cf
.rcArea
.left
+ _focused_window
->nested_focus
->current_x
;
557 cf
.rcArea
.bottom
= cf
.rcArea
.top
+ _focused_window
->nested_focus
->current_y
;
560 cf
.ptCurrentPos
.x
= 0;
561 cf
.ptCurrentPos
.y
= 0;
562 SetRectEmpty(&cf
.rcArea
);
564 ImmSetCandidateWindow(hIMC
, &cf
);
566 ImmReleaseContext(hwnd
, hIMC
);
569 /** Handle WM_IME_COMPOSITION messages. */
570 static LRESULT
HandleIMEComposition(HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
572 HIMC hIMC
= ImmGetContext(hwnd
);
575 if (lParam
& GCS_RESULTSTR
) {
576 /* Read result string from the IME. */
577 LONG len
= ImmGetCompositionString(hIMC
, GCS_RESULTSTR
, NULL
, 0); // Length is always in bytes, even in UNICODE build.
578 TCHAR
*str
= (TCHAR
*)_alloca(len
+ sizeof(TCHAR
));
579 len
= ImmGetCompositionString(hIMC
, GCS_RESULTSTR
, str
, len
);
580 str
[len
/ sizeof(TCHAR
)] = '\0';
582 /* Transmit text to windowing system. */
584 HandleTextInput(NULL
, true); // Clear marked string.
585 HandleTextInput(FS2OTTD(str
));
587 SetCompositionPos(hwnd
);
589 /* Don't pass the result string on to the default window proc. */
590 lParam
&= ~(GCS_RESULTSTR
| GCS_RESULTCLAUSE
| GCS_RESULTREADCLAUSE
| GCS_RESULTREADSTR
);
593 if ((lParam
& GCS_COMPSTR
) && DrawIMECompositionString()) {
594 /* Read composition string from the IME. */
595 LONG len
= ImmGetCompositionString(hIMC
, GCS_COMPSTR
, NULL
, 0); // Length is always in bytes, even in UNICODE build.
596 TCHAR
*str
= (TCHAR
*)_alloca(len
+ sizeof(TCHAR
));
597 len
= ImmGetCompositionString(hIMC
, GCS_COMPSTR
, str
, len
);
598 str
[len
/ sizeof(TCHAR
)] = '\0';
601 static char utf8_buf
[1024];
602 convert_from_fs(str
, utf8_buf
, lengthof(utf8_buf
));
604 /* Convert caret position from bytes in the input string to a position in the UTF-8 encoded string. */
605 LONG caret_bytes
= ImmGetCompositionString(hIMC
, GCS_CURSORPOS
, NULL
, 0);
606 const char *caret
= utf8_buf
;
607 for (const TCHAR
*c
= str
; *c
!= '\0' && *caret
!= '\0' && caret_bytes
> 0; c
++, caret_bytes
--) {
608 /* Skip DBCS lead bytes or leading surrogates. */
610 if (Utf16IsLeadSurrogate(*c
)) {
612 if (IsDBCSLeadByte(*c
)) {
620 HandleTextInput(utf8_buf
, true, caret
);
622 HandleTextInput(NULL
, true);
625 lParam
&= ~(GCS_COMPSTR
| GCS_COMPATTR
| GCS_COMPCLAUSE
| GCS_CURSORPOS
| GCS_DELTASTART
);
628 ImmReleaseContext(hwnd
, hIMC
);
630 return lParam
!= 0 ? DefWindowProc(hwnd
, WM_IME_COMPOSITION
, wParam
, lParam
) : 0;
633 /** Clear the current composition string. */
634 static void CancelIMEComposition(HWND hwnd
)
636 HIMC hIMC
= ImmGetContext(hwnd
);
637 if (hIMC
!= NULL
) ImmNotifyIME(hIMC
, NI_COMPOSITIONSTR
, CPS_CANCEL
, 0);
638 ImmReleaseContext(hwnd
, hIMC
);
639 /* Clear any marked string from the current edit box. */
640 HandleTextInput(NULL
, true);
645 static bool DrawIMECompositionString() { return false; }
646 static void SetCompositionPos(HWND hwnd
) {}
647 static void SetCandidatePos(HWND hwnd
) {}
648 static void CancelIMEComposition(HWND hwnd
) {}
650 #endif /* !defined(WINCE) || _WIN32_WCE >= 0x400 */
652 static LRESULT CALLBACK
WndProcGdi(HWND hwnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
654 static uint32 keycode
= 0;
655 static bool console
= false;
656 static bool in_sizemove
= false;
660 SetTimer(hwnd
, TID_POLLMOUSE
, MOUSE_POLL_DELAY
, (TIMERPROC
)TrackMouseTimerProc
);
661 SetCompositionPos(hwnd
);
662 #if !defined(WINCE) || _WIN32_WCE >= 0x400
663 _imm_props
= ImmGetProperty(GetKeyboardLayout(0), IGP_PROPERTY
);
667 case WM_ENTERSIZEMOVE
:
671 case WM_EXITSIZEMOVE
:
676 if (!in_sizemove
&& _draw_mutex
!= NULL
&& !HasModalProgress()) {
677 /* Get the union of the old update rect and the new update rect. */
679 GetUpdateRect(hwnd
, &r
, FALSE
);
680 UnionRect(&_wnd
.update_rect
, &_wnd
.update_rect
, &r
);
682 /* Mark the window as updated, otherwise Windows would send more WM_PAINT messages. */
683 ValidateRect(hwnd
, NULL
);
684 _draw_mutex
->SendSignal();
688 BeginPaint(hwnd
, &ps
);
694 case WM_PALETTECHANGED
:
695 if ((HWND
)wParam
== hwnd
) return 0;
698 case WM_QUERYNEWPALETTE
: {
699 HDC hDC
= GetWindowDC(hwnd
);
700 HPALETTE hOldPalette
= SelectPalette(hDC
, _wnd
.gdi_palette
, FALSE
);
701 UINT nChanged
= RealizePalette(hDC
);
703 SelectPalette(hDC
, hOldPalette
, TRUE
);
704 ReleaseDC(hwnd
, hDC
);
705 if (nChanged
!= 0) InvalidateRect(hwnd
, NULL
, FALSE
);
710 HandleExitGameRequest();
714 if (_window_maximize
) _cur_resolution
= _bck_resolution
;
719 _left_button_down
= true;
725 _left_button_down
= false;
726 _left_button_clicked
= false;
732 _right_button_down
= true;
733 _right_button_clicked
= true;
739 _right_button_down
= false;
745 _cursor
.in_window
= false;
747 if (!_left_button_down
&& !_right_button_down
) MyShowCursor(true);
751 int x
= (int16
)LOWORD(lParam
);
752 int y
= (int16
)HIWORD(lParam
);
755 /* If the mouse was not in the window and it has moved it means it has
756 * come into the window, so start drawing the mouse. Also start
757 * tracking the mouse for exiting the window */
758 if (!_cursor
.in_window
) {
759 _cursor
.in_window
= true;
760 SetTimer(hwnd
, TID_POLLMOUSE
, MOUSE_POLL_DELAY
, (TIMERPROC
)TrackMouseTimerProc
);
765 if (_cursor
.fix_at
) {
766 int dx
= x
- _cursor
.pos
.x
;
767 int dy
= y
- _cursor
.pos
.y
;
768 if (dx
!= 0 || dy
!= 0) {
769 _cursor
.delta
.x
= dx
;
770 _cursor
.delta
.y
= dy
;
772 pt
.x
= _cursor
.pos
.x
;
773 pt
.y
= _cursor
.pos
.y
;
775 ClientToScreen(hwnd
, &pt
);
776 SetCursorPos(pt
.x
, pt
.y
);
779 _cursor
.delta
.x
= x
- _cursor
.pos
.x
;
780 _cursor
.delta
.y
= y
- _cursor
.pos
.y
;
783 _cursor
.dirty
= true;
790 #if !defined(WINCE) || _WIN32_WCE >= 0x400
791 case WM_INPUTLANGCHANGE
:
792 _imm_props
= ImmGetProperty(GetKeyboardLayout(0), IGP_PROPERTY
);
795 case WM_IME_SETCONTEXT
:
796 /* Don't show the composition window if we draw the string ourself. */
797 if (DrawIMECompositionString()) lParam
&= ~ISC_SHOWUICOMPOSITIONWINDOW
;
800 case WM_IME_STARTCOMPOSITION
:
801 SetCompositionPos(hwnd
);
802 if (DrawIMECompositionString()) return 0;
805 case WM_IME_COMPOSITION
:
806 return HandleIMEComposition(hwnd
, wParam
, lParam
);
808 case WM_IME_ENDCOMPOSITION
:
809 /* Clear any pending composition string. */
810 HandleTextInput(NULL
, true);
811 if (DrawIMECompositionString()) return 0;
815 if (wParam
== IMN_OPENCANDIDATE
) SetCandidatePos(hwnd
);
818 #if !defined(UNICODE)
820 if (GB(wParam
, 8, 8) != 0) {
821 /* DBCS character, send lead byte first. */
822 HandleCharMsg(0, GB(wParam
, 8, 8));
824 HandleCharMsg(0, GB(wParam
, 0, 8));
830 console
= GB(lParam
, 16, 8) == 41;
834 uint scancode
= GB(lParam
, 16, 8);
835 uint charcode
= wParam
;
837 /* If the console key is a dead-key, we need to press it twice to get a WM_CHAR message.
838 * But we then get two WM_CHAR messages, so ignore the first one */
839 if (console
&& scancode
== 41) {
844 /* IMEs and other input methods sometimes send a WM_CHAR without a WM_KEYDOWN,
845 * clear the keycode so a previous WM_KEYDOWN doesn't become 'stuck'. */
846 uint cur_keycode
= keycode
;
849 return HandleCharMsg(cur_keycode
, charcode
);
853 /* No matter the keyboard layout, we will map the '~' to the console. */
854 uint scancode
= GB(lParam
, 16, 8);
855 keycode
= scancode
== 41 ? (uint
)WKC_BACKQUOTE
: MapWindowsKey(wParam
);
857 /* Silently drop all messages handled by WM_CHAR. */
859 if (PeekMessage(&msg
, NULL
, 0, 0, PM_NOREMOVE
)) {
860 if ((msg
.message
== WM_CHAR
|| msg
.message
== WM_DEADCHAR
) && GB(lParam
, 16, 8) == GB(msg
.lParam
, 16, 8)) {
865 uint charcode
= MapVirtualKey(wParam
, MAPVK_VK_TO_CHAR
);
867 /* No character translation? */
869 HandleKeypress(keycode
, 0);
873 /* Is the console key a dead key? If yes, ignore the first key down event. */
874 if (HasBit(charcode
, 31) && !console
) {
875 if (scancode
== 41) {
882 /* IMEs and other input methods sometimes send a WM_CHAR without a WM_KEYDOWN,
883 * clear the keycode so a previous WM_KEYDOWN doesn't become 'stuck'. */
884 uint cur_keycode
= keycode
;
887 return HandleCharMsg(cur_keycode
, LOWORD(charcode
));
890 case WM_SYSKEYDOWN
: // user presses F10 or Alt, both activating the title-menu
893 case 'F': // Full Screen on ALT + ENTER/F
894 ToggleFullScreen(!_wnd
.fullscreen
);
897 case VK_MENU
: // Just ALT
898 return 0; // do nothing
900 case VK_F10
: // F10, ignore activation of menu
901 HandleKeypress(MapWindowsKey(wParam
), 0);
904 default: // ALT in combination with something else
905 HandleKeypress(MapWindowsKey(wParam
), 0);
911 if (wParam
!= SIZE_MINIMIZED
) {
912 /* Set maximized flag when we maximize (obviously), but also when we
913 * switched to fullscreen from a maximized state */
914 _window_maximize
= (wParam
== SIZE_MAXIMIZED
|| (_window_maximize
&& _fullscreen
));
915 if (_window_maximize
|| _fullscreen
) _bck_resolution
= _cur_resolution
;
916 ClientSizeChanged(LOWORD(lParam
), HIWORD(lParam
));
922 RECT
*r
= (RECT
*)lParam
;
926 SetRect(&r2
, 0, 0, 0, 0);
927 AdjustWindowRect(&r2
, GetWindowLong(hwnd
, GWL_STYLE
), FALSE
);
929 w
= r
->right
- r
->left
- (r2
.right
- r2
.left
);
930 h
= r
->bottom
- r
->top
- (r2
.bottom
- r2
.top
);
933 SetRect(&r2
, 0, 0, w
, h
);
935 AdjustWindowRect(&r2
, GetWindowLong(hwnd
, GWL_STYLE
), FALSE
);
936 w
= r2
.right
- r2
.left
;
937 h
= r2
.bottom
- r2
.top
;
941 r
->bottom
= r
->top
+ h
;
944 case WMSZ_BOTTOMLEFT
:
945 r
->bottom
= r
->top
+ h
;
946 r
->left
= r
->right
- w
;
949 case WMSZ_BOTTOMRIGHT
:
950 r
->bottom
= r
->top
+ h
;
951 r
->right
= r
->left
+ w
;
955 r
->left
= r
->right
- w
;
959 r
->right
= r
->left
+ w
;
963 r
->top
= r
->bottom
- h
;
967 r
->top
= r
->bottom
- h
;
968 r
->left
= r
->right
- w
;
972 r
->top
= r
->bottom
- h
;
973 r
->right
= r
->left
+ w
;
980 /* needed for wheel */
981 #if !defined(WM_MOUSEWHEEL)
982 # define WM_MOUSEWHEEL 0x020A
983 #endif /* WM_MOUSEWHEEL */
984 #if !defined(GET_WHEEL_DELTA_WPARAM)
985 # define GET_WHEEL_DELTA_WPARAM(wparam) ((short)HIWORD(wparam))
986 #endif /* GET_WHEEL_DELTA_WPARAM */
988 case WM_MOUSEWHEEL
: {
989 int delta
= GET_WHEEL_DELTA_WPARAM(wParam
);
993 } else if (delta
> 0) {
1001 _wnd
.has_focus
= true;
1002 SetCompositionPos(hwnd
);
1006 _wnd
.has_focus
= false;
1011 /* Don't do anything if we are closing openttd */
1012 if (_exit_game
) break;
1014 bool active
= (LOWORD(wParam
) != WA_INACTIVE
);
1015 bool minimized
= (HIWORD(wParam
) != 0);
1016 if (_wnd
.fullscreen
) {
1017 if (active
&& minimized
) {
1018 /* Restore the game window */
1019 ShowWindow(hwnd
, SW_RESTORE
);
1020 static_cast<VideoDriver_Win32
*>(_video_driver
)->MakeWindow(true);
1021 } else if (!active
&& !minimized
) {
1022 /* Minimise the window and restore desktop */
1023 ShowWindow(hwnd
, SW_MINIMIZE
);
1024 ChangeDisplaySettings(NULL
, 0);
1032 return DefWindowProc(hwnd
, msg
, wParam
, lParam
);
1035 static void RegisterWndClass()
1037 static bool registered
= false;
1040 HINSTANCE hinst
= GetModuleHandle(NULL
);
1047 LoadIcon(hinst
, MAKEINTRESOURCE(100)),
1048 LoadCursor(NULL
, IDC_ARROW
),
1055 if (!RegisterClass(&wnd
)) usererror("RegisterClass failed");
1059 static bool AllocateDibSection(int w
, int h
, bool force
)
1063 int bpp
= BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
1068 if (bpp
== 0) usererror("Can't use a blitter that blits 0 bpp for normal visuals");
1070 if (!force
&& w
== _screen
.width
&& h
== _screen
.height
) return false;
1073 _screen
.pitch
= (bpp
== 8) ? Align(w
, 4) : w
;
1075 bi
= (BITMAPINFO
*)alloca(sizeof(BITMAPINFOHEADER
) + sizeof(RGBQUAD
) * 256);
1076 memset(bi
, 0, sizeof(BITMAPINFOHEADER
) + sizeof(RGBQUAD
) * 256);
1077 bi
->bmiHeader
.biSize
= sizeof(BITMAPINFOHEADER
);
1079 bi
->bmiHeader
.biWidth
= _wnd
.width
= w
;
1080 bi
->bmiHeader
.biHeight
= -(_wnd
.height
= h
);
1082 bi
->bmiHeader
.biPlanes
= 1;
1083 bi
->bmiHeader
.biBitCount
= BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
1084 bi
->bmiHeader
.biCompression
= BI_RGB
;
1086 if (_wnd
.dib_sect
) DeleteObject(_wnd
.dib_sect
);
1089 _wnd
.dib_sect
= CreateDIBSection(dc
, bi
, DIB_RGB_COLORS
, (VOID
**)&_wnd
.buffer_bits
, NULL
, 0);
1090 if (_wnd
.dib_sect
== NULL
) usererror("CreateDIBSection failed");
1096 static const Dimension default_resolutions
[] = {
1110 static void FindResolutions()
1114 /* EnumDisplaySettingsW is only supported in CE 4.2+
1115 * XXX -- One might argue that we assume 4.2+ on every system. Then we can use this function safely */
1120 /* XXX - EnumDisplaySettingsW crashes with unicows.dll on Windows95
1121 * Doesn't really matter since we don't pass a string anyways, but still
1123 for (i
= 0; EnumDisplaySettingsA(NULL
, i
, &dm
) != 0; i
++) {
1124 if (dm
.dmBitsPerPel
== BlitterFactory::GetCurrentBlitter()->GetScreenDepth() &&
1125 dm
.dmPelsWidth
>= 640 && dm
.dmPelsHeight
>= 480) {
1128 for (j
= 0; j
< n
; j
++) {
1129 if (_resolutions
[j
].width
== dm
.dmPelsWidth
&& _resolutions
[j
].height
== dm
.dmPelsHeight
) break;
1132 /* In the previous loop we have checked already existing/added resolutions if
1133 * they are the same as the new ones. If this is not the case (j == n); we have
1134 * looped all and found none, add the new one to the list. If we have reached the
1135 * maximum amount of resolutions, then quit querying the display */
1137 _resolutions
[j
].width
= dm
.dmPelsWidth
;
1138 _resolutions
[j
].height
= dm
.dmPelsHeight
;
1139 if (++n
== lengthof(_resolutions
)) break;
1145 /* We have found no resolutions, show the default list */
1147 memcpy(_resolutions
, default_resolutions
, sizeof(default_resolutions
));
1148 n
= lengthof(default_resolutions
);
1151 _num_resolutions
= n
;
1152 SortResolutions(_num_resolutions
);
1155 static FVideoDriver_Win32 iFVideoDriver_Win32
;
1157 const char *VideoDriver_Win32::Start(const char * const *parm
)
1159 memset(&_wnd
, 0, sizeof(_wnd
));
1167 DEBUG(driver
, 2, "Resolution for display: %ux%u", _cur_resolution
.width
, _cur_resolution
.height
);
1169 /* fullscreen uses those */
1170 _wnd
.width_org
= _cur_resolution
.width
;
1171 _wnd
.height_org
= _cur_resolution
.height
;
1173 AllocateDibSection(_cur_resolution
.width
, _cur_resolution
.height
);
1174 this->MakeWindow(_fullscreen
);
1176 MarkWholeScreenDirty();
1178 _draw_threaded
= GetDriverParam(parm
, "no_threads") == NULL
&& GetDriverParam(parm
, "no_thread") == NULL
&& GetCPUCoreCount() > 1;
1183 void VideoDriver_Win32::Stop()
1185 DeleteObject(_wnd
.gdi_palette
);
1186 DeleteObject(_wnd
.dib_sect
);
1187 DestroyWindow(_wnd
.main_wnd
);
1190 if (_wnd
.fullscreen
) ChangeDisplaySettings(NULL
, 0);
1195 void VideoDriver_Win32::MakeDirty(int left
, int top
, int width
, int height
)
1197 RECT r
= { left
, top
, left
+ width
, top
+ height
};
1199 InvalidateRect(_wnd
.main_wnd
, &r
, FALSE
);
1202 static void CheckPaletteAnim()
1204 if (_cur_palette
.count_dirty
== 0) return;
1206 _local_palette
= _cur_palette
;
1207 InvalidateRect(_wnd
.main_wnd
, NULL
, FALSE
);
1210 void VideoDriver_Win32::MainLoop()
1213 uint32 cur_ticks
= GetTickCount();
1214 uint32 last_cur_ticks
= cur_ticks
;
1215 uint32 next_tick
= cur_ticks
+ MILLISECONDS_PER_TICK
;
1217 if (_draw_threaded
) {
1218 /* Initialise the mutex first, because that's the thing we *need*
1219 * directly in the newly created thread. */
1220 _draw_mutex
= ThreadMutex::New();
1221 if (_draw_mutex
== NULL
) {
1222 _draw_threaded
= false;
1224 _draw_mutex
->BeginCritical();
1225 _draw_continue
= true;
1227 _draw_threaded
= ThreadObject::New(&PaintWindowThread
, NULL
, &_draw_thread
);
1229 /* Free the mutex if we won't be able to use it. */
1230 if (!_draw_threaded
) {
1231 _draw_mutex
->EndCritical();
1235 DEBUG(driver
, 1, "Threaded drawing enabled");
1237 /* Wait till the draw mutex has started itself. */
1238 _draw_mutex
->WaitForSignal();
1243 _wnd
.running
= true;
1247 uint32 prev_cur_ticks
= cur_ticks
; // to check for wrapping
1249 while (PeekMessage(&mesg
, NULL
, 0, 0, PM_REMOVE
)) {
1250 InteractiveRandom(); // randomness
1251 /* Convert key messages to char messages if we want text input. */
1252 if (EditBoxInGlobalFocus()) TranslateMessage(&mesg
);
1253 DispatchMessage(&mesg
);
1255 if (_exit_game
) return;
1258 if (_wnd
.has_focus
&& GetAsyncKeyState(VK_SHIFT
) < 0 &&
1260 /* Speed up using TAB, but disable for ALT+TAB of course */
1261 if (_wnd
.has_focus
&& GetAsyncKeyState(VK_TAB
) < 0 && GetAsyncKeyState(VK_MENU
) >= 0 &&
1263 !_networking
&& _game_mode
!= GM_MENU
) {
1265 } else if (_fast_forward
& 2) {
1269 cur_ticks
= GetTickCount();
1270 if (cur_ticks
>= next_tick
|| (_fast_forward
&& !_pause_mode
) || cur_ticks
< prev_cur_ticks
) {
1271 _realtime_tick
+= cur_ticks
- last_cur_ticks
;
1272 last_cur_ticks
= cur_ticks
;
1273 next_tick
= cur_ticks
+ MILLISECONDS_PER_TICK
;
1275 bool old_ctrl_pressed
= _ctrl_pressed
;
1277 _ctrl_pressed
= _wnd
.has_focus
&& GetAsyncKeyState(VK_CONTROL
)<0;
1278 _shift_pressed
= _wnd
.has_focus
&& GetAsyncKeyState(VK_SHIFT
)<0;
1280 /* determine which directional keys are down */
1281 if (_wnd
.has_focus
) {
1283 (GetAsyncKeyState(VK_LEFT
) < 0 ? 1 : 0) +
1284 (GetAsyncKeyState(VK_UP
) < 0 ? 2 : 0) +
1285 (GetAsyncKeyState(VK_RIGHT
) < 0 ? 4 : 0) +
1286 (GetAsyncKeyState(VK_DOWN
) < 0 ? 8 : 0);
1291 if (old_ctrl_pressed
!= _ctrl_pressed
) HandleCtrlChanged();
1294 /* Flush GDI buffer to ensure we don't conflict with the drawing thread. */
1298 /* The game loop is the part that can run asynchronously.
1299 * The rest except sleeping can't. */
1300 if (_draw_threaded
) _draw_mutex
->EndCritical();
1302 if (_draw_threaded
) _draw_mutex
->BeginCritical();
1304 if (_force_full_redraw
) MarkWholeScreenDirty();
1306 _screen
.dst_ptr
= _wnd
.buffer_bits
;
1311 /* Flush GDI buffer to ensure we don't conflict with the drawing thread. */
1315 /* Release the thread while sleeping */
1316 if (_draw_threaded
) _draw_mutex
->EndCritical();
1318 if (_draw_threaded
) _draw_mutex
->BeginCritical();
1320 _screen
.dst_ptr
= _wnd
.buffer_bits
;
1321 NetworkDrawChatMessage();
1326 if (_draw_threaded
) {
1327 _draw_continue
= false;
1328 /* Sending signal if there is no thread blocked
1329 * is very valid and results in noop */
1330 _draw_mutex
->SendSignal();
1331 _draw_mutex
->EndCritical();
1332 _draw_thread
->Join();
1335 delete _draw_thread
;
1339 bool VideoDriver_Win32::ChangeResolution(int w
, int h
)
1341 if (_window_maximize
) ShowWindow(_wnd
.main_wnd
, SW_SHOWNORMAL
);
1343 _wnd
.width
= _wnd
.width_org
= w
;
1344 _wnd
.height
= _wnd
.height_org
= h
;
1346 return this->MakeWindow(_fullscreen
); // _wnd.fullscreen screws up ingame resolution switching
1349 bool VideoDriver_Win32::ToggleFullscreen(bool full_screen
)
1351 return this->MakeWindow(full_screen
);
1354 bool VideoDriver_Win32::AfterBlitterChange()
1356 return AllocateDibSection(_screen
.width
, _screen
.height
, true) && this->MakeWindow(_fullscreen
);
1359 void VideoDriver_Win32::EditBoxLostFocus()
1361 CancelIMEComposition(_wnd
.main_wnd
);
1362 SetCompositionPos(_wnd
.main_wnd
);
1363 SetCandidatePos(_wnd
.main_wnd
);