1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
4 
5   This software is provided 'as-is', without any express or implied
6   warranty.  In no event will the authors be held liable for any damages
7   arising from the use of this software.
8 
9   Permission is granted to anyone to use this software for any purpose,
10   including commercial applications, and to alter it and redistribute it
11   freely, subject to the following restrictions:
12 
13   1. The origin of this software must not be misrepresented; you must not
14      claim that you wrote the original software. If you use this software
15      in a product, an acknowledgment in the product documentation would be
16      appreciated but is not required.
17   2. Altered source versions must be plainly marked as such, and must not be
18      misrepresented as being the original software.
19   3. This notice may not be removed or altered from any source distribution.
20 */
21 #include "../../SDL_internal.h"
22 
23 #if SDL_VIDEO_DRIVER_WINDOWS
24 
25 #include "SDL_windowsvideo.h"
26 #include "SDL_windowsshape.h"
27 #include "SDL_system.h"
28 #include "SDL_syswm.h"
29 #include "SDL_timer.h"
30 #include "SDL_vkeys.h"
31 #include "SDL_hints.h"
32 #include "../../events/SDL_events_c.h"
33 #include "../../events/SDL_touch_c.h"
34 #include "../../events/scancodes_windows.h"
35 #include "SDL_hints.h"
36 
37 /* Dropfile support */
38 #include <shellapi.h>
39 
40 /* For GET_X_LPARAM, GET_Y_LPARAM. */
41 #include <windowsx.h>
42 
43 /* #define WMMSG_DEBUG */
44 #ifdef WMMSG_DEBUG
45 #include <stdio.h>
46 #include "wmmsg.h"
47 #endif
48 
49 /* Masks for processing the windows KEYDOWN and KEYUP messages */
50 #define REPEATED_KEYMASK    (1<<30)
51 #define EXTENDED_KEYMASK    (1<<24)
52 
53 #define VK_ENTER    10          /* Keypad Enter ... no VKEY defined? */
54 #ifndef VK_OEM_NEC_EQUAL
55 #define VK_OEM_NEC_EQUAL 0x92
56 #endif
57 
58 /* Make sure XBUTTON stuff is defined that isn't in older Platform SDKs... */
59 #ifndef WM_XBUTTONDOWN
60 #define WM_XBUTTONDOWN 0x020B
61 #endif
62 #ifndef WM_XBUTTONUP
63 #define WM_XBUTTONUP 0x020C
64 #endif
65 #ifndef GET_XBUTTON_WPARAM
66 #define GET_XBUTTON_WPARAM(w) (HIWORD(w))
67 #endif
68 #ifndef WM_INPUT
69 #define WM_INPUT 0x00ff
70 #endif
71 #ifndef WM_TOUCH
72 #define WM_TOUCH 0x0240
73 #endif
74 #ifndef WM_MOUSEHWHEEL
75 #define WM_MOUSEHWHEEL 0x020E
76 #endif
77 #ifndef WM_POINTERUPDATE
78 #define WM_POINTERUPDATE 0x0245
79 #endif
80 #ifndef WM_UNICHAR
81 #define WM_UNICHAR 0x0109
82 #endif
83 
84 #ifndef IS_HIGH_SURROGATE
85 #define IS_HIGH_SURROGATE(x)   (((x) >= 0xd800) && ((x) <= 0xdbff))
86 #endif
87 #ifndef IS_LOW_SURROGATE
88 #define IS_LOW_SURROGATE(x)    (((x) >= 0xdc00) && ((x) <= 0xdfff))
89 #endif
90 #ifndef IS_SURROGATE_PAIR
91 #define IS_SURROGATE_PAIR(h,l) (IS_HIGH_SURROGATE(h) && IS_LOW_SURROGATE(l))
92 #endif
93 
94 static SDL_Scancode
VKeytoScancodeFallback(WPARAM vkey)95 VKeytoScancodeFallback(WPARAM vkey)
96 {
97     switch (vkey) {
98     case VK_LEFT: return SDL_SCANCODE_LEFT;
99     case VK_UP: return SDL_SCANCODE_UP;
100     case VK_RIGHT: return SDL_SCANCODE_RIGHT;
101     case VK_DOWN: return SDL_SCANCODE_DOWN;
102 
103     default: return SDL_SCANCODE_UNKNOWN;
104     }
105 }
106 
107 static SDL_Scancode
VKeytoScancode(WPARAM vkey)108 VKeytoScancode(WPARAM vkey)
109 {
110     switch (vkey) {
111     case VK_MODECHANGE: return SDL_SCANCODE_MODE;
112     case VK_SELECT: return SDL_SCANCODE_SELECT;
113     case VK_EXECUTE: return SDL_SCANCODE_EXECUTE;
114     case VK_HELP: return SDL_SCANCODE_HELP;
115     case VK_PAUSE: return SDL_SCANCODE_PAUSE;
116     case VK_NUMLOCK: return SDL_SCANCODE_NUMLOCKCLEAR;
117 
118     case VK_F13: return SDL_SCANCODE_F13;
119     case VK_F14: return SDL_SCANCODE_F14;
120     case VK_F15: return SDL_SCANCODE_F15;
121     case VK_F16: return SDL_SCANCODE_F16;
122     case VK_F17: return SDL_SCANCODE_F17;
123     case VK_F18: return SDL_SCANCODE_F18;
124     case VK_F19: return SDL_SCANCODE_F19;
125     case VK_F20: return SDL_SCANCODE_F20;
126     case VK_F21: return SDL_SCANCODE_F21;
127     case VK_F22: return SDL_SCANCODE_F22;
128     case VK_F23: return SDL_SCANCODE_F23;
129     case VK_F24: return SDL_SCANCODE_F24;
130 
131     case VK_OEM_NEC_EQUAL: return SDL_SCANCODE_KP_EQUALS;
132     case VK_BROWSER_BACK: return SDL_SCANCODE_AC_BACK;
133     case VK_BROWSER_FORWARD: return SDL_SCANCODE_AC_FORWARD;
134     case VK_BROWSER_REFRESH: return SDL_SCANCODE_AC_REFRESH;
135     case VK_BROWSER_STOP: return SDL_SCANCODE_AC_STOP;
136     case VK_BROWSER_SEARCH: return SDL_SCANCODE_AC_SEARCH;
137     case VK_BROWSER_FAVORITES: return SDL_SCANCODE_AC_BOOKMARKS;
138     case VK_BROWSER_HOME: return SDL_SCANCODE_AC_HOME;
139     case VK_VOLUME_MUTE: return SDL_SCANCODE_AUDIOMUTE;
140     case VK_VOLUME_DOWN: return SDL_SCANCODE_VOLUMEDOWN;
141     case VK_VOLUME_UP: return SDL_SCANCODE_VOLUMEUP;
142 
143     case VK_MEDIA_NEXT_TRACK: return SDL_SCANCODE_AUDIONEXT;
144     case VK_MEDIA_PREV_TRACK: return SDL_SCANCODE_AUDIOPREV;
145     case VK_MEDIA_STOP: return SDL_SCANCODE_AUDIOSTOP;
146     case VK_MEDIA_PLAY_PAUSE: return SDL_SCANCODE_AUDIOPLAY;
147     case VK_LAUNCH_MAIL: return SDL_SCANCODE_MAIL;
148     case VK_LAUNCH_MEDIA_SELECT: return SDL_SCANCODE_MEDIASELECT;
149 
150     case VK_OEM_102: return SDL_SCANCODE_NONUSBACKSLASH;
151 
152     case VK_ATTN: return SDL_SCANCODE_SYSREQ;
153     case VK_CRSEL: return SDL_SCANCODE_CRSEL;
154     case VK_EXSEL: return SDL_SCANCODE_EXSEL;
155     case VK_OEM_CLEAR: return SDL_SCANCODE_CLEAR;
156 
157     case VK_LAUNCH_APP1: return SDL_SCANCODE_APP1;
158     case VK_LAUNCH_APP2: return SDL_SCANCODE_APP2;
159 
160     default: return SDL_SCANCODE_UNKNOWN;
161     }
162 }
163 
164 static SDL_Scancode
WindowsScanCodeToSDLScanCode(LPARAM lParam,WPARAM wParam)165 WindowsScanCodeToSDLScanCode(LPARAM lParam, WPARAM wParam)
166 {
167     SDL_Scancode code;
168     int nScanCode = (lParam >> 16) & 0xFF;
169     SDL_bool bIsExtended = (lParam & (1 << 24)) != 0;
170 
171     code = VKeytoScancode(wParam);
172 
173     if (code == SDL_SCANCODE_UNKNOWN && nScanCode <= 127) {
174         code = windows_scancode_table[nScanCode];
175 
176         if (bIsExtended) {
177             switch (code) {
178             case SDL_SCANCODE_RETURN:
179                 code = SDL_SCANCODE_KP_ENTER;
180                 break;
181             case SDL_SCANCODE_LALT:
182                 code = SDL_SCANCODE_RALT;
183                 break;
184             case SDL_SCANCODE_LCTRL:
185                 code = SDL_SCANCODE_RCTRL;
186                 break;
187             case SDL_SCANCODE_SLASH:
188                 code = SDL_SCANCODE_KP_DIVIDE;
189                 break;
190             case SDL_SCANCODE_CAPSLOCK:
191                 code = SDL_SCANCODE_KP_PLUS;
192                 break;
193             default:
194                 break;
195             }
196         } else {
197             switch (code) {
198             case SDL_SCANCODE_HOME:
199                 code = SDL_SCANCODE_KP_7;
200                 break;
201             case SDL_SCANCODE_UP:
202                 code = SDL_SCANCODE_KP_8;
203                 break;
204             case SDL_SCANCODE_PAGEUP:
205                 code = SDL_SCANCODE_KP_9;
206                 break;
207             case SDL_SCANCODE_LEFT:
208                 code = SDL_SCANCODE_KP_4;
209                 break;
210             case SDL_SCANCODE_RIGHT:
211                 code = SDL_SCANCODE_KP_6;
212                 break;
213             case SDL_SCANCODE_END:
214                 code = SDL_SCANCODE_KP_1;
215                 break;
216             case SDL_SCANCODE_DOWN:
217                 code = SDL_SCANCODE_KP_2;
218                 break;
219             case SDL_SCANCODE_PAGEDOWN:
220                 code = SDL_SCANCODE_KP_3;
221                 break;
222             case SDL_SCANCODE_INSERT:
223                 code = SDL_SCANCODE_KP_0;
224                 break;
225             case SDL_SCANCODE_DELETE:
226                 code = SDL_SCANCODE_KP_PERIOD;
227                 break;
228             case SDL_SCANCODE_PRINTSCREEN:
229                 code = SDL_SCANCODE_KP_MULTIPLY;
230                 break;
231             default:
232                 break;
233             }
234         }
235     }
236 
237     /* The on-screen keyboard can generate VK_LEFT and VK_RIGHT events without a scancode
238      * value set, however we cannot simply map these in VKeytoScancode() or we will be
239      * incorrectly handling the arrow keys on the number pad when NumLock is disabled
240      * (which also generate VK_LEFT, VK_RIGHT, etc in that scenario). Instead, we'll only
241      * map them if none of the above special number pad mappings applied. */
242     if (code == SDL_SCANCODE_UNKNOWN) {
243         code = VKeytoScancodeFallback(wParam);
244     }
245 
246     return code;
247 }
248 
249 static SDL_bool
WIN_ShouldIgnoreFocusClick()250 WIN_ShouldIgnoreFocusClick()
251 {
252     return !SDL_GetHintBoolean(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, SDL_FALSE);
253 }
254 
255 static void
WIN_CheckWParamMouseButton(SDL_bool bwParamMousePressed,Uint32 mouseFlags,SDL_bool bSwapButtons,SDL_WindowData * data,Uint8 button,SDL_MouseID mouseID)256 WIN_CheckWParamMouseButton(SDL_bool bwParamMousePressed, Uint32 mouseFlags, SDL_bool bSwapButtons, SDL_WindowData *data, Uint8 button, SDL_MouseID mouseID)
257 {
258     if (bSwapButtons) {
259         if (button == SDL_BUTTON_LEFT) {
260             button = SDL_BUTTON_RIGHT;
261         }
262         else if (button == SDL_BUTTON_RIGHT) {
263             button = SDL_BUTTON_LEFT;
264         }
265     }
266 
267     if (data->focus_click_pending & SDL_BUTTON(button)) {
268         /* Ignore the button click for activation */
269         if (!bwParamMousePressed) {
270             data->focus_click_pending &= ~SDL_BUTTON(button);
271             WIN_UpdateClipCursor(data->window);
272         }
273         if (WIN_ShouldIgnoreFocusClick()) {
274             return;
275         }
276     }
277 
278     if (bwParamMousePressed && !(mouseFlags & SDL_BUTTON(button))) {
279         SDL_SendMouseButton(data->window, mouseID, SDL_PRESSED, button);
280     } else if (!bwParamMousePressed && (mouseFlags & SDL_BUTTON(button))) {
281         SDL_SendMouseButton(data->window, mouseID, SDL_RELEASED, button);
282     }
283 }
284 
285 /*
286 * Some windows systems fail to send a WM_LBUTTONDOWN sometimes, but each mouse move contains the current button state also
287 *  so this function reconciles our view of the world with the current buttons reported by windows
288 */
289 static void
WIN_CheckWParamMouseButtons(WPARAM wParam,SDL_WindowData * data,SDL_MouseID mouseID)290 WIN_CheckWParamMouseButtons(WPARAM wParam, SDL_WindowData *data, SDL_MouseID mouseID)
291 {
292     if (wParam != data->mouse_button_flags) {
293         Uint32 mouseFlags = SDL_GetMouseState(NULL, NULL);
294 
295         /* WM_LBUTTONDOWN and friends handle button swapping for us. No need to check SM_SWAPBUTTON here.  */
296         WIN_CheckWParamMouseButton((wParam & MK_LBUTTON), mouseFlags, SDL_FALSE, data, SDL_BUTTON_LEFT, mouseID);
297         WIN_CheckWParamMouseButton((wParam & MK_MBUTTON), mouseFlags, SDL_FALSE, data, SDL_BUTTON_MIDDLE, mouseID);
298         WIN_CheckWParamMouseButton((wParam & MK_RBUTTON), mouseFlags, SDL_FALSE, data, SDL_BUTTON_RIGHT, mouseID);
299         WIN_CheckWParamMouseButton((wParam & MK_XBUTTON1), mouseFlags, SDL_FALSE, data, SDL_BUTTON_X1, mouseID);
300         WIN_CheckWParamMouseButton((wParam & MK_XBUTTON2), mouseFlags, SDL_FALSE, data, SDL_BUTTON_X2, mouseID);
301 
302         data->mouse_button_flags = wParam;
303     }
304 }
305 
306 static void
WIN_CheckRawMouseButtons(ULONG rawButtons,SDL_WindowData * data,SDL_MouseID mouseID)307 WIN_CheckRawMouseButtons(ULONG rawButtons, SDL_WindowData *data, SDL_MouseID mouseID)
308 {
309     if (rawButtons != data->mouse_button_flags) {
310         Uint32 mouseFlags = SDL_GetMouseState(NULL, NULL);
311         SDL_bool swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0;
312         if ((rawButtons & RI_MOUSE_BUTTON_1_DOWN))
313             WIN_CheckWParamMouseButton((rawButtons & RI_MOUSE_BUTTON_1_DOWN), mouseFlags, swapButtons, data, SDL_BUTTON_LEFT, mouseID);
314         if ((rawButtons & RI_MOUSE_BUTTON_1_UP))
315             WIN_CheckWParamMouseButton(!(rawButtons & RI_MOUSE_BUTTON_1_UP), mouseFlags, swapButtons, data, SDL_BUTTON_LEFT, mouseID);
316         if ((rawButtons & RI_MOUSE_BUTTON_2_DOWN))
317             WIN_CheckWParamMouseButton((rawButtons & RI_MOUSE_BUTTON_2_DOWN), mouseFlags, swapButtons, data, SDL_BUTTON_RIGHT, mouseID);
318         if ((rawButtons & RI_MOUSE_BUTTON_2_UP))
319             WIN_CheckWParamMouseButton(!(rawButtons & RI_MOUSE_BUTTON_2_UP), mouseFlags, swapButtons, data, SDL_BUTTON_RIGHT, mouseID);
320         if ((rawButtons & RI_MOUSE_BUTTON_3_DOWN))
321             WIN_CheckWParamMouseButton((rawButtons & RI_MOUSE_BUTTON_3_DOWN), mouseFlags, swapButtons, data, SDL_BUTTON_MIDDLE, mouseID);
322         if ((rawButtons & RI_MOUSE_BUTTON_3_UP))
323             WIN_CheckWParamMouseButton(!(rawButtons & RI_MOUSE_BUTTON_3_UP), mouseFlags, swapButtons, data, SDL_BUTTON_MIDDLE, mouseID);
324         if ((rawButtons & RI_MOUSE_BUTTON_4_DOWN))
325             WIN_CheckWParamMouseButton((rawButtons & RI_MOUSE_BUTTON_4_DOWN), mouseFlags, swapButtons, data, SDL_BUTTON_X1, mouseID);
326         if ((rawButtons & RI_MOUSE_BUTTON_4_UP))
327             WIN_CheckWParamMouseButton(!(rawButtons & RI_MOUSE_BUTTON_4_UP), mouseFlags, swapButtons, data, SDL_BUTTON_X1, mouseID);
328         if ((rawButtons & RI_MOUSE_BUTTON_5_DOWN))
329             WIN_CheckWParamMouseButton((rawButtons & RI_MOUSE_BUTTON_5_DOWN), mouseFlags, swapButtons, data, SDL_BUTTON_X2, mouseID);
330         if ((rawButtons & RI_MOUSE_BUTTON_5_UP))
331             WIN_CheckWParamMouseButton(!(rawButtons & RI_MOUSE_BUTTON_5_UP), mouseFlags, swapButtons, data, SDL_BUTTON_X2, mouseID);
332         data->mouse_button_flags = rawButtons;
333     }
334 }
335 
336 static void
WIN_CheckAsyncMouseRelease(SDL_WindowData * data)337 WIN_CheckAsyncMouseRelease(SDL_WindowData *data)
338 {
339     Uint32 mouseFlags;
340     SHORT keyState;
341     SDL_bool swapButtons;
342 
343     /* mouse buttons may have changed state here, we need to resync them,
344        but we will get a WM_MOUSEMOVE right away which will fix things up if in non raw mode also
345     */
346     mouseFlags = SDL_GetMouseState(NULL, NULL);
347     swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0;
348 
349     keyState = GetAsyncKeyState(VK_LBUTTON);
350     if (!(keyState & 0x8000)) {
351         WIN_CheckWParamMouseButton(SDL_FALSE, mouseFlags, swapButtons, data, SDL_BUTTON_LEFT, 0);
352     }
353     keyState = GetAsyncKeyState(VK_RBUTTON);
354     if (!(keyState & 0x8000)) {
355         WIN_CheckWParamMouseButton(SDL_FALSE, mouseFlags, swapButtons, data, SDL_BUTTON_RIGHT, 0);
356     }
357     keyState = GetAsyncKeyState(VK_MBUTTON);
358     if (!(keyState & 0x8000)) {
359         WIN_CheckWParamMouseButton(SDL_FALSE, mouseFlags, swapButtons, data, SDL_BUTTON_MIDDLE, 0);
360     }
361     keyState = GetAsyncKeyState(VK_XBUTTON1);
362     if (!(keyState & 0x8000)) {
363         WIN_CheckWParamMouseButton(SDL_FALSE, mouseFlags, swapButtons, data, SDL_BUTTON_X1, 0);
364     }
365     keyState = GetAsyncKeyState(VK_XBUTTON2);
366     if (!(keyState & 0x8000)) {
367         WIN_CheckWParamMouseButton(SDL_FALSE, mouseFlags, swapButtons, data, SDL_BUTTON_X2, 0);
368     }
369     data->mouse_button_flags = 0;
370 }
371 
372 static void
WIN_UpdateFocus(SDL_Window * window)373 WIN_UpdateFocus(SDL_Window *window)
374 {
375     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
376     HWND hwnd = data->hwnd;
377     SDL_bool had_focus = (SDL_GetKeyboardFocus() == window) ? SDL_TRUE : SDL_FALSE;
378     SDL_bool has_focus = (GetForegroundWindow() == hwnd) ? SDL_TRUE : SDL_FALSE;
379 
380     if (had_focus == has_focus) {
381         return;
382     }
383 
384     if (has_focus) {
385         POINT cursorPos;
386 
387         SDL_bool swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0;
388         if (GetAsyncKeyState(VK_LBUTTON)) {
389             data->focus_click_pending |= !swapButtons ? SDL_BUTTON_LMASK : SDL_BUTTON_RMASK;
390         }
391         if (GetAsyncKeyState(VK_RBUTTON)) {
392             data->focus_click_pending |= !swapButtons ? SDL_BUTTON_RMASK : SDL_BUTTON_LMASK;
393         }
394         if (GetAsyncKeyState(VK_MBUTTON)) {
395             data->focus_click_pending |= SDL_BUTTON_MMASK;
396         }
397         if (GetAsyncKeyState(VK_XBUTTON1)) {
398             data->focus_click_pending |= SDL_BUTTON_X1MASK;
399         }
400         if (GetAsyncKeyState(VK_XBUTTON2)) {
401             data->focus_click_pending |= SDL_BUTTON_X2MASK;
402         }
403 
404         SDL_SetKeyboardFocus(window);
405 
406         /* In relative mode we are guaranteed to have mouse focus if we have keyboard focus */
407         if (!SDL_GetMouse()->relative_mode) {
408             GetCursorPos(&cursorPos);
409             ScreenToClient(hwnd, &cursorPos);
410             SDL_SendMouseMotion(window, 0, 0, cursorPos.x, cursorPos.y);
411         }
412 
413         WIN_CheckAsyncMouseRelease(data);
414         WIN_UpdateClipCursor(window);
415 
416         /*
417          * FIXME: Update keyboard state
418          */
419         WIN_CheckClipboardUpdate(data->videodata);
420 
421         SDL_ToggleModState(KMOD_CAPS, (GetKeyState(VK_CAPITAL) & 0x0001) != 0);
422         SDL_ToggleModState(KMOD_NUM, (GetKeyState(VK_NUMLOCK) & 0x0001) != 0);
423         SDL_ToggleModState(KMOD_SCROLL, (GetKeyState(VK_SCROLL) & 0x0001) != 0);
424 
425     } else {
426         RECT rect;
427 
428         data->in_window_deactivation = SDL_TRUE;
429 
430         SDL_SetKeyboardFocus(NULL);
431         WIN_ResetDeadKeys();
432 
433         if (GetClipCursor(&rect) && SDL_memcmp(&rect, &data->cursor_clipped_rect, sizeof(rect)) == 0) {
434             ClipCursor(NULL);
435             SDL_zero(data->cursor_clipped_rect);
436         }
437 
438         data->in_window_deactivation = SDL_FALSE;
439     }
440 }
441 
442 static BOOL
WIN_ConvertUTF32toUTF8(UINT32 codepoint,char * text)443 WIN_ConvertUTF32toUTF8(UINT32 codepoint, char * text)
444 {
445     if (codepoint <= 0x7F) {
446         text[0] = (char) codepoint;
447         text[1] = '\0';
448     } else if (codepoint <= 0x7FF) {
449         text[0] = 0xC0 | (char) ((codepoint >> 6) & 0x1F);
450         text[1] = 0x80 | (char) (codepoint & 0x3F);
451         text[2] = '\0';
452     } else if (codepoint <= 0xFFFF) {
453         text[0] = 0xE0 | (char) ((codepoint >> 12) & 0x0F);
454         text[1] = 0x80 | (char) ((codepoint >> 6) & 0x3F);
455         text[2] = 0x80 | (char) (codepoint & 0x3F);
456         text[3] = '\0';
457     } else if (codepoint <= 0x10FFFF) {
458         text[0] = 0xF0 | (char) ((codepoint >> 18) & 0x0F);
459         text[1] = 0x80 | (char) ((codepoint >> 12) & 0x3F);
460         text[2] = 0x80 | (char) ((codepoint >> 6) & 0x3F);
461         text[3] = 0x80 | (char) (codepoint & 0x3F);
462         text[4] = '\0';
463     } else {
464         return SDL_FALSE;
465     }
466     return SDL_TRUE;
467 }
468 
469 static BOOL
WIN_ConvertUTF16toUTF8(UINT32 high_surrogate,UINT32 low_surrogate,char * text)470 WIN_ConvertUTF16toUTF8(UINT32 high_surrogate, UINT32 low_surrogate, char * text)
471 {
472     const UINT32 SURROGATE_OFFSET = 0x10000 - (0xD800 << 10) - 0xDC00;
473     const UINT32 codepoint = (high_surrogate << 10) + low_surrogate + SURROGATE_OFFSET;
474     return WIN_ConvertUTF32toUTF8(codepoint, text);
475 }
476 
477 static SDL_bool
ShouldGenerateWindowCloseOnAltF4(void)478 ShouldGenerateWindowCloseOnAltF4(void)
479 {
480     return !SDL_GetHintBoolean(SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4, SDL_FALSE);
481 }
482 
483 /* We want to generate mouse events from mouse and pen, and touch events from touchscreens */
484 #define MI_WP_SIGNATURE         0xFF515700
485 #define MI_WP_SIGNATURE_MASK    0xFFFFFF00
486 #define IsTouchEvent(dw) ((dw) & MI_WP_SIGNATURE_MASK) == MI_WP_SIGNATURE
487 
488 typedef enum
489 {
490     SDL_MOUSE_EVENT_SOURCE_UNKNOWN,
491     SDL_MOUSE_EVENT_SOURCE_MOUSE,
492     SDL_MOUSE_EVENT_SOURCE_TOUCH,
493     SDL_MOUSE_EVENT_SOURCE_PEN,
494 } SDL_MOUSE_EVENT_SOURCE;
495 
GetMouseMessageSource()496 static SDL_MOUSE_EVENT_SOURCE GetMouseMessageSource()
497 {
498     LPARAM extrainfo = GetMessageExtraInfo();
499     /* Mouse data (ignoring synthetic mouse events generated for touchscreens) */
500     /* Versions below Vista will set the low 7 bits to the Mouse ID and don't use bit 7:
501        Check bits 8-32 for the signature (which will indicate a Tablet PC Pen or Touch Device).
502        Only check bit 7 when Vista and up(Cleared=Pen, Set=Touch(which we need to filter out)),
503        when the signature is set. The Mouse ID will be zero for an actual mouse. */
504     if (IsTouchEvent(extrainfo)) {
505         if (extrainfo & 0x80) {
506             return SDL_MOUSE_EVENT_SOURCE_TOUCH;
507         } else {
508             return SDL_MOUSE_EVENT_SOURCE_PEN;
509         }
510     }
511     return SDL_MOUSE_EVENT_SOURCE_MOUSE;
512 }
513 
514 static SDL_WindowData *
WIN_GetWindowDataFromHWND(HWND hwnd)515 WIN_GetWindowDataFromHWND(HWND hwnd)
516 {
517     SDL_VideoDevice *_this = SDL_GetVideoDevice();
518     SDL_Window *window;
519 
520     if (_this) {
521         for (window = _this->windows; window; window = window->next) {
522             SDL_WindowData *data = (SDL_WindowData *)window->driverdata;
523             if (data && data->hwnd == hwnd) {
524                 return data;
525             }
526         }
527     }
528     return NULL;
529 }
530 
531 LRESULT CALLBACK
WIN_KeyboardHookProc(int nCode,WPARAM wParam,LPARAM lParam)532 WIN_KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam)
533 {
534     KBDLLHOOKSTRUCT* hookData = (KBDLLHOOKSTRUCT*)lParam;
535     SDL_VideoData* data = SDL_GetVideoDevice()->driverdata;
536     SDL_Scancode scanCode;
537 
538     if (nCode < 0 || nCode != HC_ACTION) {
539         return CallNextHookEx(NULL, nCode, wParam, lParam);
540     }
541 
542     switch (hookData->vkCode) {
543     case VK_LWIN:
544         scanCode = SDL_SCANCODE_LGUI;
545         break;
546     case VK_RWIN:
547         scanCode = SDL_SCANCODE_RGUI;
548         break;
549     case VK_LMENU:
550         scanCode = SDL_SCANCODE_LALT;
551         break;
552     case VK_RMENU:
553         scanCode = SDL_SCANCODE_RALT;
554         break;
555     case VK_LCONTROL:
556         scanCode = SDL_SCANCODE_LCTRL;
557         break;
558     case VK_RCONTROL:
559         scanCode = SDL_SCANCODE_RCTRL;
560         break;
561 
562     /* These are required to intercept Alt+Tab and Alt+Esc on Windows 7 */
563     case VK_TAB:
564         scanCode = SDL_SCANCODE_TAB;
565         break;
566     case VK_ESCAPE:
567         scanCode = SDL_SCANCODE_ESCAPE;
568         break;
569 
570     default:
571         return CallNextHookEx(NULL, nCode, wParam, lParam);
572     }
573 
574     if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
575         SDL_SendKeyboardKey(SDL_PRESSED, scanCode);
576     } else {
577         SDL_SendKeyboardKey(SDL_RELEASED, scanCode);
578 
579         /* If the key was down prior to our hook being installed, allow the
580            key up message to pass normally the first time. This ensures other
581            windows have a consistent view of the key state, and avoids keys
582            being stuck down in those windows if they are down when the grab
583            happens and raised while grabbed. */
584         if (hookData->vkCode <= 0xFF && data->pre_hook_key_state[hookData->vkCode]) {
585             data->pre_hook_key_state[hookData->vkCode] = 0;
586             return CallNextHookEx(NULL, nCode, wParam, lParam);
587         }
588     }
589 
590     return 1;
591 }
592 
WIN_CheckICMProfileChanged(SDL_Window * window)593 static void WIN_CheckICMProfileChanged(SDL_Window* window)
594 {
595     SDL_VideoDisplay* display = SDL_GetDisplayForWindow(window);
596     SDL_DisplayData* data = (SDL_DisplayData*)display->driverdata;
597     static WCHAR currentIcmFileName[MAX_PATH] = { '\0' };
598     WCHAR icmFileName[MAX_PATH];
599     HDC hdc;
600     SDL_bool succeeded;
601     DWORD fileNameSize = MAX_PATH;
602 
603     hdc = CreateDCW(data->DeviceName, NULL, NULL, NULL);
604     if (hdc) {
605         succeeded = GetICMProfileW(hdc, &fileNameSize, icmFileName);
606         DeleteDC(hdc);
607         if (succeeded) {
608 
609             if (SDL_wcsncmp(currentIcmFileName, icmFileName, fileNameSize)) {
610                 SDL_wcslcpy(currentIcmFileName, icmFileName, fileNameSize);
611                 SDL_SendWindowEvent(window, SDL_WINDOWEVENT_ICCPROF_CHANGED, 0, 0);
612             }
613         }
614     }
615 }
616 
617 LRESULT CALLBACK
WIN_WindowProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)618 WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
619 {
620     SDL_WindowData *data;
621     LRESULT returnCode = -1;
622 
623     /* Send a SDL_SYSWMEVENT if the application wants them */
624     if (SDL_GetEventState(SDL_SYSWMEVENT) == SDL_ENABLE) {
625         SDL_SysWMmsg wmmsg;
626 
627         SDL_VERSION(&wmmsg.version);
628         wmmsg.subsystem = SDL_SYSWM_WINDOWS;
629         wmmsg.msg.win.hwnd = hwnd;
630         wmmsg.msg.win.msg = msg;
631         wmmsg.msg.win.wParam = wParam;
632         wmmsg.msg.win.lParam = lParam;
633         SDL_SendSysWMEvent(&wmmsg);
634     }
635 
636     /* Get the window data for the window */
637     data = WIN_GetWindowDataFromHWND(hwnd);
638     if (!data) {
639         /* Fallback */
640         data = (SDL_WindowData *) GetProp(hwnd, TEXT("SDL_WindowData"));
641     }
642     if (!data) {
643         return CallWindowProc(DefWindowProc, hwnd, msg, wParam, lParam);
644     }
645 
646 #ifdef WMMSG_DEBUG
647     {
648         char message[1024];
649         if (msg > MAX_WMMSG) {
650             SDL_snprintf(message, sizeof(message), "Received windows message: %p UNKNOWN (%d) -- 0x%X, 0x%X\n", hwnd, msg, wParam, lParam);
651         } else {
652             SDL_snprintf(message, sizeof(message), "Received windows message: %p %s -- 0x%X, 0x%X\n", hwnd, wmtab[msg], wParam, lParam);
653         }
654         OutputDebugStringA(message);
655     }
656 #endif /* WMMSG_DEBUG */
657 
658     if (IME_HandleMessage(hwnd, msg, wParam, &lParam, data->videodata))
659         return 0;
660 
661     switch (msg) {
662 
663     case WM_SHOWWINDOW:
664         {
665             if (wParam) {
666                 SDL_SendWindowEvent(data->window, SDL_WINDOWEVENT_SHOWN, 0, 0);
667             } else {
668                 SDL_SendWindowEvent(data->window, SDL_WINDOWEVENT_HIDDEN, 0, 0);
669             }
670         }
671         break;
672 
673     case WM_NCACTIVATE:
674         {
675             /* Don't immediately clip the cursor in case we're clicking minimize/maximize buttons */
676             data->skip_update_clipcursor = SDL_TRUE;
677 
678             /* Update the focus here, since it's possible to get WM_ACTIVATE and WM_SETFOCUS without
679                actually being the foreground window, but this appears to get called in all cases where
680                the global foreground window changes to and from this window. */
681             WIN_UpdateFocus(data->window);
682             WIN_CheckICMProfileChanged(data->window);
683         }
684         break;
685 
686     case WM_ACTIVATE:
687         {
688             /* Update the focus in case we changed focus to a child window and then away from the application */
689             WIN_UpdateFocus(data->window);
690         }
691         break;
692 
693     case WM_SETFOCUS:
694     case WM_KILLFOCUS:
695         {
696             /* Update the focus in case it's changing between top-level windows in the same application */
697             WIN_UpdateFocus(data->window);
698         }
699         break;
700 
701     case WM_POINTERUPDATE:
702         {
703             data->last_pointer_update = lParam;
704             break;
705         }
706 
707     case WM_MOUSEMOVE:
708         {
709             SDL_Mouse *mouse = SDL_GetMouse();
710 
711             if (!data->mouse_tracked) {
712                 TRACKMOUSEEVENT trackMouseEvent;
713 
714                 trackMouseEvent.cbSize = sizeof(TRACKMOUSEEVENT);
715                 trackMouseEvent.dwFlags = TME_LEAVE;
716                 trackMouseEvent.hwndTrack = data->hwnd;
717 
718                 if (TrackMouseEvent(&trackMouseEvent)) {
719                     data->mouse_tracked = SDL_TRUE;
720                 }
721             }
722 
723             if (!mouse->relative_mode || mouse->relative_mode_warp) {
724                 /* Only generate mouse events for real mouse */
725                 if (GetMouseMessageSource() != SDL_MOUSE_EVENT_SOURCE_TOUCH &&
726                     lParam != data->last_pointer_update) {
727                     SDL_SendMouseMotion(data->window, 0, 0, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
728                 }
729             }
730         }
731         /* don't break here, fall through to check the wParam like the button presses */
732         SDL_FALLTHROUGH;
733     case WM_LBUTTONUP:
734     case WM_RBUTTONUP:
735     case WM_MBUTTONUP:
736     case WM_XBUTTONUP:
737     case WM_LBUTTONDOWN:
738     case WM_LBUTTONDBLCLK:
739     case WM_RBUTTONDOWN:
740     case WM_RBUTTONDBLCLK:
741     case WM_MBUTTONDOWN:
742     case WM_MBUTTONDBLCLK:
743     case WM_XBUTTONDOWN:
744     case WM_XBUTTONDBLCLK:
745         {
746             SDL_Mouse *mouse = SDL_GetMouse();
747             if (!mouse->relative_mode || mouse->relative_mode_warp) {
748                 if (GetMouseMessageSource() != SDL_MOUSE_EVENT_SOURCE_TOUCH &&
749                     lParam != data->last_pointer_update) {
750                     WIN_CheckWParamMouseButtons(wParam, data, 0);
751                 }
752             }
753         }
754         break;
755 
756     case WM_INPUT:
757         {
758             SDL_Mouse *mouse = SDL_GetMouse();
759             HRAWINPUT hRawInput = (HRAWINPUT)lParam;
760             RAWINPUT inp;
761             UINT size = sizeof(inp);
762 
763             /* We only use raw mouse input in relative mode */
764             if (!mouse->relative_mode || mouse->relative_mode_warp) {
765                 break;
766             }
767 
768             /* Relative mouse motion is delivered to the window with keyboard focus */
769             if (data->window != SDL_GetKeyboardFocus()) {
770                 break;
771             }
772 
773             GetRawInputData(hRawInput, RID_INPUT, &inp, &size, sizeof(RAWINPUTHEADER));
774 
775             /* Mouse data (ignoring synthetic mouse events generated for touchscreens) */
776             if (inp.header.dwType == RIM_TYPEMOUSE) {
777                 SDL_MouseID mouseID;
778                 RAWMOUSE* rawmouse;
779                 if (SDL_GetNumTouchDevices() > 0 &&
780                     (GetMouseMessageSource() == SDL_MOUSE_EVENT_SOURCE_TOUCH || (GetMessageExtraInfo() & 0x82) == 0x82)) {
781                     break;
782                 }
783                 /* We do all of our mouse state checking against mouse ID 0
784                  * We would only use the actual hDevice if we were tracking
785                  * all mouse motion independently, and never using mouse ID 0.
786                  */
787                 mouseID = 0; /* (SDL_MouseID)(uintptr_t)inp.header.hDevice; */
788                 rawmouse = &inp.data.mouse;
789 
790                 if ((rawmouse->usFlags & 0x01) == MOUSE_MOVE_RELATIVE) {
791                     SDL_SendMouseMotion(data->window, mouseID, 1, (int)rawmouse->lLastX, (int)rawmouse->lLastY);
792                 } else if (rawmouse->lLastX || rawmouse->lLastY) {
793                     /* This is absolute motion, either using a tablet or mouse over RDP
794 
795                        Notes on how RDP appears to work, as of Windows 10 2004:
796                         - SetCursorPos() calls are cached, with multiple calls coalesced into a single call that's sent to the RDP client. If the last call to SetCursorPos() has the same value as the last one that was sent to the client, it appears to be ignored and not sent. This means that we need to jitter the SetCursorPos() position slightly in order for the recentering to work correctly.
797                         - User mouse motion is coalesced with SetCursorPos(), so the WM_INPUT positions we see will not necessarily match the positon we requested with SetCursorPos().
798                         - SetCursorPos() outside of the bounds of the focus window appears not to do anything.
799                         - SetCursorPos() while the cursor is NULL doesn't do anything
800 
801                        We handle this by creating a safe area within the application window, and when the mouse leaves that safe area, we warp back to the opposite side. Any single motion > 50% of the safe area is assumed to be a warp and ignored.
802                     */
803                     SDL_bool remote_desktop = GetSystemMetrics(SM_REMOTESESSION) ? SDL_TRUE : SDL_FALSE;
804                     SDL_bool virtual_desktop = (rawmouse->usFlags & MOUSE_VIRTUAL_DESKTOP) ? SDL_TRUE : SDL_FALSE;
805                     SDL_bool normalized_coordinates = ((rawmouse->usFlags & 0x40) == 0) ? SDL_TRUE : SDL_FALSE;
806                     int w = GetSystemMetrics(virtual_desktop ? SM_CXVIRTUALSCREEN : SM_CXSCREEN);
807                     int h = GetSystemMetrics(virtual_desktop ? SM_CYVIRTUALSCREEN : SM_CYSCREEN);
808                     int x = normalized_coordinates ? (int)(((float)rawmouse->lLastX / 65535.0f) * w) : (int)rawmouse->lLastX;
809                     int y = normalized_coordinates ? (int)(((float)rawmouse->lLastY / 65535.0f) * h) : (int)rawmouse->lLastY;
810                     int relX, relY;
811 
812                     /* Calculate relative motion */
813                     if (data->last_raw_mouse_position.x == 0 && data->last_raw_mouse_position.y == 0) {
814                         data->last_raw_mouse_position.x = x;
815                         data->last_raw_mouse_position.y = y;
816                     }
817                     relX = (int)(x - data->last_raw_mouse_position.x);
818                     relY = (int)(y - data->last_raw_mouse_position.y);
819 
820                     if (remote_desktop) {
821                         if (!data->in_title_click && !data->focus_click_pending) {
822                             static int wobble;
823                             float floatX = (float)x / w;
824                             float floatY = (float)y / h;
825 
826                             /* See if the mouse is at the edge of the screen, or in the RDP title bar area */
827                             if (floatX <= 0.01f || floatX >= 0.99f || floatY <= 0.01f || floatY >= 0.99f || y < 32) {
828                                 /* Wobble the cursor position so it's not ignored if the last warp didn't have any effect */
829                                 RECT rect = data->cursor_clipped_rect;
830                                 int warpX = rect.left + ((rect.right - rect.left) / 2) + wobble;
831                                 int warpY = rect.top + ((rect.bottom - rect.top) / 2);
832 
833                                 WIN_SetCursorPos(warpX, warpY);
834 
835                                 ++wobble;
836                                 if (wobble > 1) {
837                                     wobble = -1;
838                                 }
839                             } else {
840                                 /* Send relative motion if we didn't warp last frame (had good position data)
841                                    We also sometimes get large deltas due to coalesced mouse motion and warping,
842                                    so ignore those.
843                                  */
844                                 const int MAX_RELATIVE_MOTION = (h / 6);
845                                 if (SDL_abs(relX) < MAX_RELATIVE_MOTION &&
846                                     SDL_abs(relY) < MAX_RELATIVE_MOTION) {
847                                     SDL_SendMouseMotion(data->window, mouseID, 1, relX, relY);
848                                 }
849                             }
850                         }
851                     } else {
852                         const int MAXIMUM_TABLET_RELATIVE_MOTION = 32;
853                         if (SDL_abs(relX) > MAXIMUM_TABLET_RELATIVE_MOTION ||
854                             SDL_abs(relY) > MAXIMUM_TABLET_RELATIVE_MOTION) {
855                             /* Ignore this motion, probably a pen lift and drop */
856                         } else {
857                             SDL_SendMouseMotion(data->window, mouseID, 1, relX, relY);
858                         }
859                     }
860 
861                     data->last_raw_mouse_position.x = x;
862                     data->last_raw_mouse_position.y = y;
863                 }
864                 WIN_CheckRawMouseButtons(rawmouse->usButtonFlags, data, mouseID);
865             }
866         }
867         break;
868 
869     case WM_MOUSEWHEEL:
870     case WM_MOUSEHWHEEL:
871         {
872             short amount = GET_WHEEL_DELTA_WPARAM(wParam);
873             float fAmount = (float) amount / WHEEL_DELTA;
874             if (msg == WM_MOUSEWHEEL)
875                 SDL_SendMouseWheel(data->window, 0, 0.0f, fAmount, SDL_MOUSEWHEEL_NORMAL);
876             else
877                 SDL_SendMouseWheel(data->window, 0, fAmount, 0.0f, SDL_MOUSEWHEEL_NORMAL);
878         }
879         break;
880 
881     case WM_MOUSELEAVE:
882         if (!(data->window->flags & SDL_WINDOW_MOUSE_CAPTURE)) {
883             if (SDL_GetMouseFocus() == data->window && !SDL_GetMouse()->relative_mode && !IsIconic(hwnd)) {
884                 SDL_Mouse *mouse;
885                 POINT cursorPos;
886                 GetCursorPos(&cursorPos);
887                 ScreenToClient(hwnd, &cursorPos);
888                 mouse = SDL_GetMouse();
889                 if (!mouse->was_touch_mouse_events) { /* we're not a touch handler causing a mouse leave? */
890                     SDL_SendMouseMotion(data->window, 0, 0, cursorPos.x, cursorPos.y);
891                 } else { /* touch handling? */
892                     mouse->was_touch_mouse_events = SDL_FALSE; /* not anymore */
893                     if (mouse->touch_mouse_events) { /* convert touch to mouse events */
894                         SDL_SendMouseMotion(data->window, SDL_TOUCH_MOUSEID, 0, cursorPos.x, cursorPos.y);
895                     } else { /* normal handling */
896                         SDL_SendMouseMotion(data->window, 0, 0, cursorPos.x, cursorPos.y);
897                     }
898                }
899             }
900 
901             /* When WM_MOUSELEAVE is fired we can be assured that the cursor has left the window */
902             SDL_SetMouseFocus(NULL);
903         }
904 
905         /* Once we get WM_MOUSELEAVE we're guaranteed that the window is no longer tracked */
906         data->mouse_tracked = SDL_FALSE;
907 
908         returnCode = 0;
909         break;
910 
911     case WM_KEYDOWN:
912     case WM_SYSKEYDOWN:
913         {
914             SDL_Scancode code = WindowsScanCodeToSDLScanCode(lParam, wParam);
915             const Uint8 *keyboardState = SDL_GetKeyboardState(NULL);
916 
917             /* Detect relevant keyboard shortcuts */
918             if (keyboardState[SDL_SCANCODE_LALT] == SDL_PRESSED || keyboardState[SDL_SCANCODE_RALT] == SDL_PRESSED) {
919                 /* ALT+F4: Close window */
920                 if (code == SDL_SCANCODE_F4 && ShouldGenerateWindowCloseOnAltF4()) {
921                     SDL_SendWindowEvent(data->window, SDL_WINDOWEVENT_CLOSE, 0, 0);
922                 }
923             }
924 
925             if (code != SDL_SCANCODE_UNKNOWN) {
926                 SDL_SendKeyboardKey(SDL_PRESSED, code);
927             }
928         }
929 
930         returnCode = 0;
931         break;
932 
933     case WM_SYSKEYUP:
934     case WM_KEYUP:
935         {
936             SDL_Scancode code = WindowsScanCodeToSDLScanCode(lParam, wParam);
937             const Uint8 *keyboardState = SDL_GetKeyboardState(NULL);
938 
939             if (code != SDL_SCANCODE_UNKNOWN) {
940                 if (code == SDL_SCANCODE_PRINTSCREEN &&
941                     keyboardState[code] == SDL_RELEASED) {
942                     SDL_SendKeyboardKey(SDL_PRESSED, code);
943                 }
944                 SDL_SendKeyboardKey(SDL_RELEASED, code);
945             }
946         }
947         returnCode = 0;
948         break;
949 
950     case WM_UNICHAR:
951         if (wParam == UNICODE_NOCHAR) {
952             returnCode = 1;
953         } else {
954             char text[5];
955             if (WIN_ConvertUTF32toUTF8((UINT32)wParam, text)) {
956                 SDL_SendKeyboardText(text);
957             }
958             returnCode = 0;
959         }
960         break;
961 
962     case WM_CHAR:
963         /* When a user enters a Unicode code point defined in the Basic Multilingual Plane, Windows sends a WM_CHAR
964            message with the code point encoded as UTF-16. When a user enters a Unicode code point from a Supplementary
965            Plane, Windows sends the code point in two separate WM_CHAR messages: The first message includes the UTF-16
966            High Surrogate and the second the UTF-16 Low Surrogate. The High and Low Surrogates cannot be individually
967            converted to valid UTF-8, therefore, we must save the High Surrogate from the first WM_CHAR message and
968            concatenate it with the Low Surrogate from the second WM_CHAR message. At that point, we have a valid
969            UTF-16 surrogate pair ready to re-encode as UTF-8. */
970         if (IS_HIGH_SURROGATE(wParam)) {
971             data->high_surrogate = (WCHAR)wParam;
972         } else if (IS_SURROGATE_PAIR(data->high_surrogate, wParam)) {
973             /* The code point is in a Supplementary Plane.
974                Here wParam is the Low Surrogate. */
975             char text[5];
976             if (WIN_ConvertUTF16toUTF8((UINT32)data->high_surrogate, (UINT32)wParam, text)) {
977                 SDL_SendKeyboardText(text);
978             }
979             data->high_surrogate = 0;
980         } else {
981             /* The code point is in the Basic Multilingual Plane.
982                It's numerically equal to UTF-32. */
983             char text[5];
984             if (WIN_ConvertUTF32toUTF8((UINT32)wParam, text)) {
985                 SDL_SendKeyboardText(text);
986             }
987         }
988         returnCode = 0;
989         break;
990 
991 #ifdef WM_INPUTLANGCHANGE
992     case WM_INPUTLANGCHANGE:
993         {
994             WIN_UpdateKeymap();
995             SDL_SendKeymapChangedEvent();
996         }
997         returnCode = 1;
998         break;
999 #endif /* WM_INPUTLANGCHANGE */
1000 
1001     case WM_NCLBUTTONDOWN:
1002         {
1003             data->in_title_click = SDL_TRUE;
1004         }
1005         break;
1006 
1007     case WM_CAPTURECHANGED:
1008         {
1009             data->in_title_click = SDL_FALSE;
1010 
1011             /* The mouse may have been released during a modal loop */
1012             WIN_CheckAsyncMouseRelease(data);
1013         }
1014         break;
1015 
1016 #ifdef WM_GETMINMAXINFO
1017     case WM_GETMINMAXINFO:
1018         {
1019             MINMAXINFO *info;
1020             RECT size;
1021             int x, y;
1022             int w, h;
1023             int min_w, min_h;
1024             int max_w, max_h;
1025             BOOL constrain_max_size;
1026 
1027             if (SDL_IsShapedWindow(data->window)) {
1028                 Win32_ResizeWindowShape(data->window);
1029             }
1030 
1031             /* If this is an expected size change, allow it */
1032             if (data->expected_resize) {
1033                 break;
1034             }
1035 
1036             /* Get the current position of our window */
1037             GetWindowRect(hwnd, &size);
1038             x = size.left;
1039             y = size.top;
1040 
1041             /* Calculate current size of our window */
1042             SDL_GetWindowSize(data->window, &w, &h);
1043             SDL_GetWindowMinimumSize(data->window, &min_w, &min_h);
1044             SDL_GetWindowMaximumSize(data->window, &max_w, &max_h);
1045 
1046             /* Store in min_w and min_h difference between current size and minimal
1047                size so we don't need to call AdjustWindowRectEx twice */
1048             min_w -= w;
1049             min_h -= h;
1050             if (max_w && max_h) {
1051                 max_w -= w;
1052                 max_h -= h;
1053                 constrain_max_size = TRUE;
1054             } else {
1055                 constrain_max_size = FALSE;
1056             }
1057 
1058             if (!(SDL_GetWindowFlags(data->window) & SDL_WINDOW_BORDERLESS)) {
1059                 LONG style = GetWindowLong(hwnd, GWL_STYLE);
1060                 /* DJM - according to the docs for GetMenu(), the
1061                    return value is undefined if hwnd is a child window.
1062                    Apparently it's too difficult for MS to check
1063                    inside their function, so I have to do it here.
1064                  */
1065                 BOOL menu = (style & WS_CHILDWINDOW) ? FALSE : (GetMenu(hwnd) != NULL);
1066                 size.top = 0;
1067                 size.left = 0;
1068                 size.bottom = h;
1069                 size.right = w;
1070 
1071                 AdjustWindowRectEx(&size, style, menu, 0);
1072                 w = size.right - size.left;
1073                 h = size.bottom - size.top;
1074             }
1075 
1076             /* Fix our size to the current size */
1077             info = (MINMAXINFO *) lParam;
1078             if (SDL_GetWindowFlags(data->window) & SDL_WINDOW_RESIZABLE) {
1079                 if (SDL_GetWindowFlags(data->window) & SDL_WINDOW_BORDERLESS) {
1080                     int screenW = GetSystemMetrics(SM_CXSCREEN);
1081                     int screenH = GetSystemMetrics(SM_CYSCREEN);
1082                     info->ptMaxSize.x = SDL_max(w, screenW);
1083                     info->ptMaxSize.y = SDL_max(h, screenH);
1084                     info->ptMaxPosition.x = SDL_min(0, ((screenW - w) / 2));
1085                     info->ptMaxPosition.y = SDL_min(0, ((screenH - h) / 2));
1086                 }
1087                 info->ptMinTrackSize.x = w + min_w;
1088                 info->ptMinTrackSize.y = h + min_h;
1089                 if (constrain_max_size) {
1090                     info->ptMaxTrackSize.x = w + max_w;
1091                     info->ptMaxTrackSize.y = h + max_h;
1092                 }
1093             } else {
1094                 info->ptMaxSize.x = w;
1095                 info->ptMaxSize.y = h;
1096                 info->ptMaxPosition.x = x;
1097                 info->ptMaxPosition.y = y;
1098                 info->ptMinTrackSize.x = w;
1099                 info->ptMinTrackSize.y = h;
1100                 info->ptMaxTrackSize.x = w;
1101                 info->ptMaxTrackSize.y = h;
1102             }
1103         }
1104         returnCode = 0;
1105         break;
1106 #endif /* WM_GETMINMAXINFO */
1107 
1108     case WM_WINDOWPOSCHANGING:
1109 
1110         if (data->expected_resize) {
1111             returnCode = 0;
1112         }
1113         break;
1114 
1115     case WM_WINDOWPOSCHANGED:
1116         {
1117             RECT rect;
1118             int x, y;
1119             int w, h;
1120 
1121             if (data->initializing || data->in_border_change) {
1122                 break;
1123             }
1124 
1125             if (!GetClientRect(hwnd, &rect) || IsRectEmpty(&rect)) {
1126                 break;
1127             }
1128             ClientToScreen(hwnd, (LPPOINT) & rect);
1129             ClientToScreen(hwnd, (LPPOINT) & rect + 1);
1130 
1131             WIN_UpdateClipCursor(data->window);
1132 
1133             x = rect.left;
1134             y = rect.top;
1135             SDL_SendWindowEvent(data->window, SDL_WINDOWEVENT_MOVED, x, y);
1136 
1137             w = rect.right - rect.left;
1138             h = rect.bottom - rect.top;
1139             SDL_SendWindowEvent(data->window, SDL_WINDOWEVENT_RESIZED, w, h);
1140 
1141             /* Forces a WM_PAINT event */
1142             InvalidateRect(hwnd, NULL, FALSE);
1143 
1144             WIN_CheckICMProfileChanged(data->window);
1145         }
1146         break;
1147 
1148     case WM_SIZE:
1149         {
1150             switch (wParam) {
1151             case SIZE_MAXIMIZED:
1152                 SDL_SendWindowEvent(data->window,
1153                     SDL_WINDOWEVENT_RESTORED, 0, 0);
1154                 SDL_SendWindowEvent(data->window,
1155                     SDL_WINDOWEVENT_MAXIMIZED, 0, 0);
1156                 break;
1157             case SIZE_MINIMIZED:
1158                 SDL_SendWindowEvent(data->window,
1159                     SDL_WINDOWEVENT_MINIMIZED, 0, 0);
1160                 break;
1161             default:
1162                 SDL_SendWindowEvent(data->window,
1163                     SDL_WINDOWEVENT_RESTORED, 0, 0);
1164                 break;
1165             }
1166         }
1167         break;
1168 
1169     case WM_SETCURSOR:
1170         {
1171             Uint16 hittest;
1172 
1173             hittest = LOWORD(lParam);
1174             if (hittest == HTCLIENT) {
1175                 SetCursor(SDL_cursor);
1176                 returnCode = TRUE;
1177             } else if (!g_WindowFrameUsableWhileCursorHidden && !SDL_cursor) {
1178                 SetCursor(NULL);
1179                 returnCode = TRUE;
1180             }
1181         }
1182         break;
1183 
1184         /* We were occluded, refresh our display */
1185     case WM_PAINT:
1186         {
1187             RECT rect;
1188             if (GetUpdateRect(hwnd, &rect, FALSE)) {
1189                 ValidateRect(hwnd, NULL);
1190                 SDL_SendWindowEvent(data->window, SDL_WINDOWEVENT_EXPOSED, 0, 0);
1191             }
1192         }
1193         returnCode = 0;
1194         break;
1195 
1196         /* We'll do our own drawing, prevent flicker */
1197     case WM_ERASEBKGND:
1198         {
1199         }
1200         return (1);
1201 
1202     case WM_SYSCOMMAND:
1203         {
1204             if ((wParam & 0xFFF0) == SC_KEYMENU) {
1205                 return (0);
1206             }
1207 
1208 #if defined(SC_SCREENSAVE) || defined(SC_MONITORPOWER)
1209             /* Don't start the screensaver or blank the monitor in fullscreen apps */
1210             if ((wParam & 0xFFF0) == SC_SCREENSAVE ||
1211                 (wParam & 0xFFF0) == SC_MONITORPOWER) {
1212                 if (SDL_GetVideoDevice()->suspend_screensaver) {
1213                     return (0);
1214                 }
1215             }
1216 #endif /* System has screensaver support */
1217         }
1218         break;
1219 
1220     case WM_CLOSE:
1221         {
1222             SDL_SendWindowEvent(data->window, SDL_WINDOWEVENT_CLOSE, 0, 0);
1223         }
1224         returnCode = 0;
1225         break;
1226 
1227     case WM_TOUCH:
1228         if (data->videodata->GetTouchInputInfo && data->videodata->CloseTouchInputHandle) {
1229             UINT i, num_inputs = LOWORD(wParam);
1230             SDL_bool isstack;
1231             PTOUCHINPUT inputs = SDL_small_alloc(TOUCHINPUT, num_inputs, &isstack);
1232             if (data->videodata->GetTouchInputInfo((HTOUCHINPUT)lParam, num_inputs, inputs, sizeof(TOUCHINPUT))) {
1233                 RECT rect;
1234                 float x, y;
1235 
1236                 if (!GetClientRect(hwnd, &rect) ||
1237                     (rect.right == rect.left && rect.bottom == rect.top)) {
1238                     if (inputs) {
1239                         SDL_small_free(inputs, isstack);
1240                     }
1241                     break;
1242                 }
1243                 ClientToScreen(hwnd, (LPPOINT) & rect);
1244                 ClientToScreen(hwnd, (LPPOINT) & rect + 1);
1245                 rect.top *= 100;
1246                 rect.left *= 100;
1247                 rect.bottom *= 100;
1248                 rect.right *= 100;
1249 
1250                 for (i = 0; i < num_inputs; ++i) {
1251                     PTOUCHINPUT input = &inputs[i];
1252 
1253                     const SDL_TouchID touchId = (SDL_TouchID)((size_t)input->hSource);
1254 
1255                     /* TODO: Can we use GetRawInputDeviceInfo and HID info to
1256                        determine if this is a direct or indirect touch device?
1257                      */
1258                     if (SDL_AddTouch(touchId, SDL_TOUCH_DEVICE_DIRECT, "") < 0) {
1259                         continue;
1260                     }
1261 
1262                     /* Get the normalized coordinates for the window */
1263                     x = (float)(input->x - rect.left)/(rect.right - rect.left);
1264                     y = (float)(input->y - rect.top)/(rect.bottom - rect.top);
1265 
1266                     if (input->dwFlags & TOUCHEVENTF_DOWN) {
1267                         SDL_SendTouch(touchId, input->dwID, data->window, SDL_TRUE, x, y, 1.0f);
1268                     }
1269                     if (input->dwFlags & TOUCHEVENTF_MOVE) {
1270                         SDL_SendTouchMotion(touchId, input->dwID, data->window, x, y, 1.0f);
1271                     }
1272                     if (input->dwFlags & TOUCHEVENTF_UP) {
1273                         SDL_SendTouch(touchId, input->dwID, data->window, SDL_FALSE, x, y, 1.0f);
1274                     }
1275                 }
1276             }
1277             SDL_small_free(inputs, isstack);
1278 
1279             data->videodata->CloseTouchInputHandle((HTOUCHINPUT)lParam);
1280             return 0;
1281         }
1282         break;
1283 
1284     case WM_DROPFILES:
1285         {
1286             UINT i;
1287             HDROP drop = (HDROP) wParam;
1288             UINT count = DragQueryFile(drop, 0xFFFFFFFF, NULL, 0);
1289             for (i = 0; i < count; ++i) {
1290                 SDL_bool isstack;
1291                 UINT size = DragQueryFile(drop, i, NULL, 0) + 1;
1292                 LPTSTR buffer = SDL_small_alloc(TCHAR, size, &isstack);
1293                 if (buffer) {
1294                     if (DragQueryFile(drop, i, buffer, size)) {
1295                         char *file = WIN_StringToUTF8(buffer);
1296                         SDL_SendDropFile(data->window, file);
1297                         SDL_free(file);
1298                     }
1299                     SDL_small_free(buffer, isstack);
1300                 }
1301             }
1302             SDL_SendDropComplete(data->window);
1303             DragFinish(drop);
1304             return 0;
1305         }
1306         break;
1307 
1308     case WM_DISPLAYCHANGE:
1309         {
1310             // Reacquire displays if any were added or removed
1311             WIN_RefreshDisplays(SDL_GetVideoDevice());
1312         }
1313         break;
1314 
1315     case WM_NCCALCSIZE:
1316         {
1317             Uint32 window_flags = SDL_GetWindowFlags(data->window);
1318             if (wParam == TRUE && (window_flags & SDL_WINDOW_BORDERLESS) && !(window_flags & SDL_WINDOW_FULLSCREEN)) {
1319                 /* When borderless, need to tell windows that the size of the non-client area is 0 */
1320                 if (!(window_flags & SDL_WINDOW_RESIZABLE)) {
1321                     int w, h;
1322                     NCCALCSIZE_PARAMS *params = (NCCALCSIZE_PARAMS *)lParam;
1323                     w = data->window->windowed.w;
1324                     h = data->window->windowed.h;
1325                     params->rgrc[0].right = params->rgrc[0].left + w;
1326                     params->rgrc[0].bottom = params->rgrc[0].top + h;
1327                 }
1328                 return 0;
1329             }
1330         }
1331         break;
1332 
1333     case WM_NCHITTEST:
1334         {
1335             SDL_Window *window = data->window;
1336             if (window->hit_test) {
1337                 POINT winpoint = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
1338                 if (ScreenToClient(hwnd, &winpoint)) {
1339                     const SDL_Point point = { (int) winpoint.x, (int) winpoint.y };
1340                     const SDL_HitTestResult rc = window->hit_test(window, &point, window->hit_test_data);
1341                     switch (rc) {
1342                         #define POST_HIT_TEST(ret) { SDL_SendWindowEvent(data->window, SDL_WINDOWEVENT_HIT_TEST, 0, 0); return ret; }
1343                         case SDL_HITTEST_DRAGGABLE: POST_HIT_TEST(HTCAPTION);
1344                         case SDL_HITTEST_RESIZE_TOPLEFT: POST_HIT_TEST(HTTOPLEFT);
1345                         case SDL_HITTEST_RESIZE_TOP: POST_HIT_TEST(HTTOP);
1346                         case SDL_HITTEST_RESIZE_TOPRIGHT: POST_HIT_TEST(HTTOPRIGHT);
1347                         case SDL_HITTEST_RESIZE_RIGHT: POST_HIT_TEST(HTRIGHT);
1348                         case SDL_HITTEST_RESIZE_BOTTOMRIGHT: POST_HIT_TEST(HTBOTTOMRIGHT);
1349                         case SDL_HITTEST_RESIZE_BOTTOM: POST_HIT_TEST(HTBOTTOM);
1350                         case SDL_HITTEST_RESIZE_BOTTOMLEFT: POST_HIT_TEST(HTBOTTOMLEFT);
1351                         case SDL_HITTEST_RESIZE_LEFT: POST_HIT_TEST(HTLEFT);
1352                         #undef POST_HIT_TEST
1353                         case SDL_HITTEST_NORMAL: return HTCLIENT;
1354                     }
1355                 }
1356                 /* If we didn't return, this will call DefWindowProc below. */
1357             }
1358         }
1359         break;
1360     }
1361 
1362     /* If there's a window proc, assume it's going to handle messages */
1363     if (data->wndproc) {
1364         return CallWindowProc(data->wndproc, hwnd, msg, wParam, lParam);
1365     } else if (returnCode >= 0) {
1366         return returnCode;
1367     } else {
1368         return CallWindowProc(DefWindowProc, hwnd, msg, wParam, lParam);
1369     }
1370 }
1371 
WIN_UpdateClipCursorForWindows()1372 static void WIN_UpdateClipCursorForWindows()
1373 {
1374     SDL_VideoDevice *_this = SDL_GetVideoDevice();
1375     SDL_Window *window;
1376     Uint32 now = SDL_GetTicks();
1377     const Uint32 CLIPCURSOR_UPDATE_INTERVAL_MS = 3000;
1378 
1379     if (_this) {
1380         for (window = _this->windows; window; window = window->next) {
1381             SDL_WindowData *data = (SDL_WindowData *)window->driverdata;
1382             if (data) {
1383                 if (data->skip_update_clipcursor) {
1384                     data->skip_update_clipcursor = SDL_FALSE;
1385                     WIN_UpdateClipCursor(window);
1386                 } else if ((now - data->last_updated_clipcursor) >= CLIPCURSOR_UPDATE_INTERVAL_MS) {
1387                     WIN_UpdateClipCursor(window);
1388                 }
1389             }
1390         }
1391     }
1392 }
1393 
WIN_UpdateMouseCapture()1394 static void WIN_UpdateMouseCapture()
1395 {
1396     SDL_Window* focusWindow = SDL_GetKeyboardFocus();
1397 
1398     if (focusWindow && (focusWindow->flags & SDL_WINDOW_MOUSE_CAPTURE)) {
1399         SDL_WindowData *data = (SDL_WindowData *) focusWindow->driverdata;
1400 
1401         if (!data->mouse_tracked) {
1402             POINT pt;
1403 
1404             if (GetCursorPos(&pt) && ScreenToClient(data->hwnd, &pt)) {
1405                 SDL_bool swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0;
1406                 SDL_MouseID mouseID = SDL_GetMouse()->mouseID;
1407 
1408                 SDL_SendMouseMotion(data->window, mouseID, 0, (int)pt.x, (int)pt.y);
1409                 SDL_SendMouseButton(data->window, mouseID, GetAsyncKeyState(VK_LBUTTON) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, !swapButtons ? SDL_BUTTON_LEFT : SDL_BUTTON_RIGHT);
1410                 SDL_SendMouseButton(data->window, mouseID, GetAsyncKeyState(VK_RBUTTON) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, !swapButtons ? SDL_BUTTON_RIGHT : SDL_BUTTON_LEFT);
1411                 SDL_SendMouseButton(data->window, mouseID, GetAsyncKeyState(VK_MBUTTON) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_MIDDLE);
1412                 SDL_SendMouseButton(data->window, mouseID, GetAsyncKeyState(VK_XBUTTON1) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_X1);
1413                 SDL_SendMouseButton(data->window, mouseID, GetAsyncKeyState(VK_XBUTTON2) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_X2);
1414             }
1415         }
1416     }
1417 }
1418 
1419 /* A message hook called before TranslateMessage() */
1420 static SDL_WindowsMessageHook g_WindowsMessageHook = NULL;
1421 static void *g_WindowsMessageHookData = NULL;
1422 
SDL_SetWindowsMessageHook(SDL_WindowsMessageHook callback,void * userdata)1423 void SDL_SetWindowsMessageHook(SDL_WindowsMessageHook callback, void *userdata)
1424 {
1425     g_WindowsMessageHook = callback;
1426     g_WindowsMessageHookData = userdata;
1427 }
1428 
1429 int
WIN_WaitEventTimeout(_THIS,int timeout)1430 WIN_WaitEventTimeout(_THIS, int timeout)
1431 {
1432     MSG msg;
1433     if (g_WindowsEnableMessageLoop) {
1434         BOOL message_result;
1435         UINT_PTR timer_id = 0;
1436         if (timeout > 0) {
1437             timer_id = SetTimer(NULL, 0, timeout, NULL);
1438             message_result = GetMessage(&msg, 0, 0, 0);
1439             KillTimer(NULL, timer_id);
1440         } else if (timeout == 0) {
1441             message_result = PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
1442         } else {
1443             message_result = GetMessage(&msg, 0, 0, 0);
1444         }
1445         if (message_result) {
1446             if (msg.message == WM_TIMER && msg.hwnd == NULL && msg.wParam == timer_id) {
1447                 return 0;
1448             }
1449             if (g_WindowsMessageHook) {
1450                 g_WindowsMessageHook(g_WindowsMessageHookData, msg.hwnd, msg.message, msg.wParam, msg.lParam);
1451             }
1452             /* Always translate the message in case it's a non-SDL window (e.g. with Qt integration) */
1453             TranslateMessage(&msg);
1454             DispatchMessage(&msg);
1455             return 1;
1456         } else {
1457             return 0;
1458         }
1459     } else {
1460         /* Fail the wait so the caller falls back to polling */
1461         return -1;
1462     }
1463 }
1464 
1465 void
WIN_SendWakeupEvent(_THIS,SDL_Window * window)1466 WIN_SendWakeupEvent(_THIS, SDL_Window *window)
1467 {
1468     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
1469     PostMessage(data->hwnd, data->videodata->_SDL_WAKEUP, 0, 0);
1470 }
1471 
1472 void
WIN_PumpEvents(_THIS)1473 WIN_PumpEvents(_THIS)
1474 {
1475     const Uint8 *keystate;
1476     MSG msg;
1477     DWORD end_ticks = GetTickCount() + 1;
1478     int new_messages = 0;
1479     SDL_Window *focusWindow;
1480 
1481     if (g_WindowsEnableMessageLoop) {
1482         while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
1483             if (g_WindowsMessageHook) {
1484                 g_WindowsMessageHook(g_WindowsMessageHookData, msg.hwnd, msg.message, msg.wParam, msg.lParam);
1485             }
1486 
1487             /* Don't dispatch any mouse motion queued prior to or including the last mouse warp */
1488             if (msg.message == WM_MOUSEMOVE && SDL_last_warp_time) {
1489                 if (!SDL_TICKS_PASSED(msg.time, (SDL_last_warp_time + 1))) {
1490                     continue;
1491                 }
1492 
1493                 /* This mouse message happened after the warp */
1494                 SDL_last_warp_time = 0;
1495             }
1496 
1497             /* Always translate the message in case it's a non-SDL window (e.g. with Qt integration) */
1498             TranslateMessage(&msg);
1499             DispatchMessage(&msg);
1500 
1501             /* Make sure we don't busy loop here forever if there are lots of events coming in */
1502             if (SDL_TICKS_PASSED(msg.time, end_ticks)) {
1503                 /* We might get a few new messages generated by the Steam overlay or other application hooks
1504                    In this case those messages will be processed before any pending input, so we want to continue after those messages.
1505                    (thanks to Peter Deayton for his investigation here)
1506                  */
1507                 const int MAX_NEW_MESSAGES = 3;
1508                 ++new_messages;
1509                 if (new_messages > MAX_NEW_MESSAGES) {
1510                     break;
1511                 }
1512             }
1513         }
1514     }
1515 
1516     /* Windows loses a shift KEYUP event when you have both pressed at once and let go of one.
1517        You won't get a KEYUP until both are released, and that keyup will only be for the second
1518        key you released. Take heroic measures and check the keystate as of the last handled event,
1519        and if we think a key is pressed when Windows doesn't, unstick it in SDL's state. */
1520     keystate = SDL_GetKeyboardState(NULL);
1521     if ((keystate[SDL_SCANCODE_LSHIFT] == SDL_PRESSED) && !(GetKeyState(VK_LSHIFT) & 0x8000)) {
1522         SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_LSHIFT);
1523     }
1524     if ((keystate[SDL_SCANCODE_RSHIFT] == SDL_PRESSED) && !(GetKeyState(VK_RSHIFT) & 0x8000)) {
1525         SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_RSHIFT);
1526     }
1527 
1528     /* The Windows key state gets lost when using Windows+Space or Windows+G shortcuts and
1529        not grabbing the keyboard. Note: If we *are* grabbing the keyboard, GetKeyState()
1530        will return inaccurate results for VK_LWIN and VK_RWIN but we don't need it anyway. */
1531     focusWindow = SDL_GetKeyboardFocus();
1532     if (!focusWindow || !(focusWindow->flags & SDL_WINDOW_KEYBOARD_GRABBED)) {
1533         if ((keystate[SDL_SCANCODE_LGUI] == SDL_PRESSED) && !(GetKeyState(VK_LWIN) & 0x8000)) {
1534             SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_LGUI);
1535         }
1536         if ((keystate[SDL_SCANCODE_RGUI] == SDL_PRESSED) && !(GetKeyState(VK_RWIN) & 0x8000)) {
1537             SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_RGUI);
1538         }
1539     }
1540 
1541     /* Update the clipping rect in case someone else has stolen it */
1542     WIN_UpdateClipCursorForWindows();
1543 
1544     /* Update mouse capture */
1545     WIN_UpdateMouseCapture();
1546 }
1547 
1548 
1549 static int app_registered = 0;
1550 LPTSTR SDL_Appname = NULL;
1551 Uint32 SDL_Appstyle = 0;
1552 HINSTANCE SDL_Instance = NULL;
1553 
1554 /* Register the class for this application */
1555 int
SDL_RegisterApp(const char * name,Uint32 style,void * hInst)1556 SDL_RegisterApp(const char *name, Uint32 style, void *hInst)
1557 {
1558     const char *hint;
1559     WNDCLASSEX wcex;
1560     TCHAR path[MAX_PATH];
1561 
1562     /* Only do this once... */
1563     if (app_registered) {
1564         ++app_registered;
1565         return (0);
1566     }
1567     if (!name && !SDL_Appname) {
1568         name = "SDL_app";
1569 #if defined(CS_BYTEALIGNCLIENT) || defined(CS_OWNDC)
1570         SDL_Appstyle = (CS_BYTEALIGNCLIENT | CS_OWNDC);
1571 #endif
1572         SDL_Instance = hInst ? hInst : GetModuleHandle(NULL);
1573     }
1574 
1575     if (name) {
1576         SDL_Appname = WIN_UTF8ToString(name);
1577         SDL_Appstyle = style;
1578         SDL_Instance = hInst ? hInst : GetModuleHandle(NULL);
1579     }
1580 
1581     /* Register the application class */
1582     wcex.cbSize         = sizeof(WNDCLASSEX);
1583     wcex.hCursor        = NULL;
1584     wcex.hIcon          = NULL;
1585     wcex.hIconSm        = NULL;
1586     wcex.lpszMenuName   = NULL;
1587     wcex.lpszClassName  = SDL_Appname;
1588     wcex.style          = SDL_Appstyle;
1589     wcex.hbrBackground  = NULL;
1590     wcex.lpfnWndProc    = WIN_WindowProc;
1591     wcex.hInstance      = SDL_Instance;
1592     wcex.cbClsExtra     = 0;
1593     wcex.cbWndExtra     = 0;
1594 
1595     hint = SDL_GetHint(SDL_HINT_WINDOWS_INTRESOURCE_ICON);
1596     if (hint && *hint) {
1597         wcex.hIcon = LoadIcon(SDL_Instance, MAKEINTRESOURCE(SDL_atoi(hint)));
1598 
1599         hint = SDL_GetHint(SDL_HINT_WINDOWS_INTRESOURCE_ICON_SMALL);
1600         if (hint && *hint) {
1601             wcex.hIconSm = LoadIcon(SDL_Instance, MAKEINTRESOURCE(SDL_atoi(hint)));
1602         }
1603     } else {
1604         /* Use the first icon as a default icon, like in the Explorer */
1605         GetModuleFileName(SDL_Instance, path, MAX_PATH);
1606         ExtractIconEx(path, 0, &wcex.hIcon, &wcex.hIconSm, 1);
1607     }
1608 
1609     if (!RegisterClassEx(&wcex)) {
1610         return SDL_SetError("Couldn't register application class");
1611     }
1612 
1613     app_registered = 1;
1614     return 0;
1615 }
1616 
1617 /* Unregisters the windowclass registered in SDL_RegisterApp above. */
1618 void
SDL_UnregisterApp()1619 SDL_UnregisterApp()
1620 {
1621     WNDCLASSEX wcex;
1622 
1623     /* SDL_RegisterApp might not have been called before */
1624     if (!app_registered) {
1625         return;
1626     }
1627     --app_registered;
1628     if (app_registered == 0) {
1629         /* Check for any registered window classes. */
1630         if (GetClassInfoEx(SDL_Instance, SDL_Appname, &wcex)) {
1631             UnregisterClass(SDL_Appname, SDL_Instance);
1632             if (wcex.hIcon) DestroyIcon(wcex.hIcon);
1633             if (wcex.hIconSm) DestroyIcon(wcex.hIconSm);
1634         }
1635         SDL_free(SDL_Appname);
1636         SDL_Appname = NULL;
1637     }
1638 }
1639 
1640 #endif /* SDL_VIDEO_DRIVER_WINDOWS */
1641 
1642 /* vi: set ts=4 sw=4 expandtab: */
1643