summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/video/windows/SDL_windowsevents.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/SDL-3.2.8/src/video/windows/SDL_windowsevents.c')
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsevents.c2734
1 files changed, 2734 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsevents.c b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsevents.c
new file mode 100644
index 0000000..f271db8
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsevents.c
@@ -0,0 +1,2734 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 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#ifdef SDL_VIDEO_DRIVER_WINDOWS
24
25#include "SDL_windowsvideo.h"
26#include "../../events/SDL_events_c.h"
27#include "../../events/SDL_touch_c.h"
28#include "../../events/scancodes_windows.h"
29#include "../../main/SDL_main_callbacks.h"
30#include "../../core/windows/SDL_hid.h"
31
32// Dropfile support
33#include <shellapi.h>
34
35// Device names
36#include <setupapi.h>
37
38// For GET_X_LPARAM, GET_Y_LPARAM.
39#include <windowsx.h>
40
41// For WM_TABLET_QUERYSYSTEMGESTURESTATUS et. al.
42#ifdef HAVE_TPCSHRD_H
43#include <tpcshrd.h>
44#endif // HAVE_TPCSHRD_H
45
46#if 0
47#define WMMSG_DEBUG
48#endif
49#ifdef WMMSG_DEBUG
50#include <stdio.h>
51#include "wmmsg.h"
52#endif
53
54#ifdef SDL_PLATFORM_GDK
55#include "../../core/gdk/SDL_gdk.h"
56#endif
57
58// #define HIGHDPI_DEBUG
59
60// Make sure XBUTTON stuff is defined that isn't in older Platform SDKs...
61#ifndef WM_XBUTTONDOWN
62#define WM_XBUTTONDOWN 0x020B
63#endif
64#ifndef WM_XBUTTONUP
65#define WM_XBUTTONUP 0x020C
66#endif
67#ifndef GET_XBUTTON_WPARAM
68#define GET_XBUTTON_WPARAM(w) (HIWORD(w))
69#endif
70#ifndef WM_INPUT
71#define WM_INPUT 0x00ff
72#endif
73#ifndef WM_TOUCH
74#define WM_TOUCH 0x0240
75#endif
76#ifndef WM_MOUSEHWHEEL
77#define WM_MOUSEHWHEEL 0x020E
78#endif
79#ifndef RI_MOUSE_HWHEEL
80#define RI_MOUSE_HWHEEL 0x0800
81#endif
82#ifndef WM_POINTERUPDATE
83#define WM_POINTERUPDATE 0x0245
84#endif
85#ifndef WM_POINTERDOWN
86#define WM_POINTERDOWN 0x0246
87#endif
88#ifndef WM_POINTERUP
89#define WM_POINTERUP 0x0247
90#endif
91#ifndef WM_POINTERENTER
92#define WM_POINTERENTER 0x0249
93#endif
94#ifndef WM_POINTERLEAVE
95#define WM_POINTERLEAVE 0x024A
96#endif
97#ifndef WM_POINTERCAPTURECHANGED
98#define WM_POINTERCAPTURECHANGED 0x024C
99#endif
100#ifndef WM_UNICHAR
101#define WM_UNICHAR 0x0109
102#endif
103#ifndef WM_DPICHANGED
104#define WM_DPICHANGED 0x02E0
105#endif
106#ifndef WM_GETDPISCALEDSIZE
107#define WM_GETDPISCALEDSIZE 0x02E4
108#endif
109#ifndef TOUCHEVENTF_PEN
110#define TOUCHEVENTF_PEN 0x0040
111#endif
112
113#ifndef MAPVK_VK_TO_VSC_EX
114#define MAPVK_VK_TO_VSC_EX 4
115#endif
116
117#ifndef WC_ERR_INVALID_CHARS
118#define WC_ERR_INVALID_CHARS 0x00000080
119#endif
120
121#ifndef IS_HIGH_SURROGATE
122#define IS_HIGH_SURROGATE(x) (((x) >= 0xd800) && ((x) <= 0xdbff))
123#endif
124
125#ifndef USER_TIMER_MINIMUM
126#define USER_TIMER_MINIMUM 0x0000000A
127#endif
128
129// Used to compare Windows message timestamps
130#define SDL_TICKS_PASSED(A, B) ((Sint32)((B) - (A)) <= 0)
131
132#ifdef _WIN64
133typedef Uint64 QWORD; // Needed for NEXTRAWINPUTBLOCK()
134#endif
135
136static bool SDL_processing_messages;
137static DWORD message_tick;
138static Uint64 timestamp_offset;
139
140static void WIN_SetMessageTick(DWORD tick)
141{
142 message_tick = tick;
143}
144
145static Uint64 WIN_GetEventTimestamp(void)
146{
147 const Uint64 TIMESTAMP_WRAP_OFFSET = SDL_MS_TO_NS(0x100000000LL);
148 Uint64 timestamp, now;
149
150 if (!SDL_processing_messages) {
151 // message_tick isn't valid, just use the current time
152 return 0;
153 }
154
155 now = SDL_GetTicksNS();
156 timestamp = SDL_MS_TO_NS(message_tick);
157 timestamp += timestamp_offset;
158 if (!timestamp_offset) {
159 // Initializing timestamp offset
160 //SDL_Log("Initializing timestamp offset");
161 timestamp_offset = (now - timestamp);
162 timestamp = now;
163 } else if ((Sint64)(now - timestamp - TIMESTAMP_WRAP_OFFSET) >= 0) {
164 // The windows message tick wrapped
165 //SDL_Log("Adjusting timestamp offset for wrapping tick");
166 timestamp_offset += TIMESTAMP_WRAP_OFFSET;
167 timestamp += TIMESTAMP_WRAP_OFFSET;
168 } else if (timestamp > now) {
169 // We got a newer timestamp, but it can't be newer than now, so adjust our offset
170 //SDL_Log("Adjusting timestamp offset, %.2f ms newer", (double)(timestamp - now) / SDL_NS_PER_MS);
171 timestamp_offset -= (timestamp - now);
172 timestamp = now;
173 }
174 return timestamp;
175}
176
177// A message hook called before TranslateMessage()
178static SDL_WindowsMessageHook g_WindowsMessageHook = NULL;
179static void *g_WindowsMessageHookData = NULL;
180
181void SDL_SetWindowsMessageHook(SDL_WindowsMessageHook callback, void *userdata)
182{
183 g_WindowsMessageHook = callback;
184 g_WindowsMessageHookData = userdata;
185}
186
187static SDL_Scancode WindowsScanCodeToSDLScanCode(LPARAM lParam, WPARAM wParam, Uint16 *rawcode, bool *virtual_key)
188{
189 SDL_Scancode code;
190 Uint8 index;
191 Uint16 keyFlags = HIWORD(lParam);
192 Uint16 scanCode = LOBYTE(keyFlags);
193
194 /* On-Screen Keyboard can send wrong scan codes with high-order bit set (key break code).
195 * Strip high-order bit. */
196 scanCode &= ~0x80;
197
198 *virtual_key = (scanCode == 0);
199
200 if (scanCode != 0) {
201 if ((keyFlags & KF_EXTENDED) == KF_EXTENDED) {
202 scanCode = MAKEWORD(scanCode, 0xe0);
203 } else if (scanCode == 0x45) {
204 // Pause
205 scanCode = 0xe046;
206 }
207 } else {
208 Uint16 vkCode = LOWORD(wParam);
209
210#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
211 /* Windows may not report scan codes for some buttons (multimedia buttons etc).
212 * Get scan code from the VK code.*/
213 scanCode = LOWORD(MapVirtualKey(vkCode, WIN_IsWindowsXP() ? MAPVK_VK_TO_VSC : MAPVK_VK_TO_VSC_EX));
214#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
215
216 /* Pause/Break key have a special scan code with 0xe1 prefix.
217 * Use Pause scan code that is used in Win32. */
218 if (scanCode == 0xe11d) {
219 scanCode = 0xe046;
220 }
221 }
222
223 // Pack scan code into one byte to make the index.
224 index = LOBYTE(scanCode) | (HIBYTE(scanCode) ? 0x80 : 0x00);
225 code = windows_scancode_table[index];
226 *rawcode = scanCode;
227
228 return code;
229}
230
231#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
232static bool WIN_ShouldIgnoreFocusClick(SDL_WindowData *data)
233{
234 return !SDL_WINDOW_IS_POPUP(data->window) &&
235 !SDL_GetHintBoolean(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, false);
236}
237
238static void WIN_CheckWParamMouseButton(Uint64 timestamp, bool bwParamMousePressed, Uint32 mouseFlags, bool bSwapButtons, SDL_WindowData *data, Uint8 button, SDL_MouseID mouseID)
239{
240 if (bSwapButtons) {
241 if (button == SDL_BUTTON_LEFT) {
242 button = SDL_BUTTON_RIGHT;
243 } else if (button == SDL_BUTTON_RIGHT) {
244 button = SDL_BUTTON_LEFT;
245 }
246 }
247
248 if (data->focus_click_pending & SDL_BUTTON_MASK(button)) {
249 // Ignore the button click for activation
250 if (!bwParamMousePressed) {
251 data->focus_click_pending &= ~SDL_BUTTON_MASK(button);
252 WIN_UpdateClipCursor(data->window);
253 }
254 return;
255 }
256
257 if (bwParamMousePressed && !(mouseFlags & SDL_BUTTON_MASK(button))) {
258 SDL_SendMouseButton(timestamp, data->window, mouseID, button, true);
259 } else if (!bwParamMousePressed && (mouseFlags & SDL_BUTTON_MASK(button))) {
260 SDL_SendMouseButton(timestamp, data->window, mouseID, button, false);
261 }
262}
263
264/*
265 * Some windows systems fail to send a WM_LBUTTONDOWN sometimes, but each mouse move contains the current button state also
266 * so this function reconciles our view of the world with the current buttons reported by windows
267 */
268static void WIN_CheckWParamMouseButtons(Uint64 timestamp, WPARAM wParam, SDL_WindowData *data, SDL_MouseID mouseID)
269{
270 if (wParam != data->mouse_button_flags) {
271 SDL_MouseButtonFlags mouseFlags = SDL_GetMouseState(NULL, NULL);
272
273 // WM_LBUTTONDOWN and friends handle button swapping for us. No need to check SM_SWAPBUTTON here.
274 WIN_CheckWParamMouseButton(timestamp, (wParam & MK_LBUTTON), mouseFlags, false, data, SDL_BUTTON_LEFT, mouseID);
275 WIN_CheckWParamMouseButton(timestamp, (wParam & MK_MBUTTON), mouseFlags, false, data, SDL_BUTTON_MIDDLE, mouseID);
276 WIN_CheckWParamMouseButton(timestamp, (wParam & MK_RBUTTON), mouseFlags, false, data, SDL_BUTTON_RIGHT, mouseID);
277 WIN_CheckWParamMouseButton(timestamp, (wParam & MK_XBUTTON1), mouseFlags, false, data, SDL_BUTTON_X1, mouseID);
278 WIN_CheckWParamMouseButton(timestamp, (wParam & MK_XBUTTON2), mouseFlags, false, data, SDL_BUTTON_X2, mouseID);
279
280 data->mouse_button_flags = wParam;
281 }
282}
283
284static void WIN_CheckAsyncMouseRelease(Uint64 timestamp, SDL_WindowData *data)
285{
286 SDL_MouseID mouseID = SDL_GLOBAL_MOUSE_ID;
287 Uint32 mouseFlags;
288 SHORT keyState;
289 bool swapButtons;
290
291 /* mouse buttons may have changed state here, we need to resync them,
292 but we will get a WM_MOUSEMOVE right away which will fix things up if in non raw mode also
293 */
294 mouseFlags = SDL_GetMouseState(NULL, NULL);
295 swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0;
296
297 keyState = GetAsyncKeyState(VK_LBUTTON);
298 if (!(keyState & 0x8000)) {
299 WIN_CheckWParamMouseButton(timestamp, false, mouseFlags, swapButtons, data, SDL_BUTTON_LEFT, mouseID);
300 }
301 keyState = GetAsyncKeyState(VK_RBUTTON);
302 if (!(keyState & 0x8000)) {
303 WIN_CheckWParamMouseButton(timestamp, false, mouseFlags, swapButtons, data, SDL_BUTTON_RIGHT, mouseID);
304 }
305 keyState = GetAsyncKeyState(VK_MBUTTON);
306 if (!(keyState & 0x8000)) {
307 WIN_CheckWParamMouseButton(timestamp, false, mouseFlags, swapButtons, data, SDL_BUTTON_MIDDLE, mouseID);
308 }
309 keyState = GetAsyncKeyState(VK_XBUTTON1);
310 if (!(keyState & 0x8000)) {
311 WIN_CheckWParamMouseButton(timestamp, false, mouseFlags, swapButtons, data, SDL_BUTTON_X1, mouseID);
312 }
313 keyState = GetAsyncKeyState(VK_XBUTTON2);
314 if (!(keyState & 0x8000)) {
315 WIN_CheckWParamMouseButton(timestamp, false, mouseFlags, swapButtons, data, SDL_BUTTON_X2, mouseID);
316 }
317 data->mouse_button_flags = (WPARAM)-1;
318}
319
320static void WIN_UpdateMouseCapture(void)
321{
322 SDL_Window *focusWindow = SDL_GetKeyboardFocus();
323
324 if (focusWindow && (focusWindow->flags & SDL_WINDOW_MOUSE_CAPTURE)) {
325 SDL_WindowData *data = focusWindow->internal;
326
327 if (!data->mouse_tracked) {
328 POINT cursorPos;
329
330 if (GetCursorPos(&cursorPos) && ScreenToClient(data->hwnd, &cursorPos)) {
331 bool swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0;
332 SDL_MouseID mouseID = SDL_GLOBAL_MOUSE_ID;
333
334 SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, mouseID, false, (float)cursorPos.x, (float)cursorPos.y);
335 SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID,
336 !swapButtons ? SDL_BUTTON_LEFT : SDL_BUTTON_RIGHT,
337 (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0);
338 SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID,
339 !swapButtons ? SDL_BUTTON_RIGHT : SDL_BUTTON_LEFT,
340 (GetAsyncKeyState(VK_RBUTTON) & 0x8000) != 0);
341 SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID,
342 SDL_BUTTON_MIDDLE,
343 (GetAsyncKeyState(VK_MBUTTON) & 0x8000) != 0);
344 SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID,
345 SDL_BUTTON_X1,
346 (GetAsyncKeyState(VK_XBUTTON1) & 0x8000) != 0);
347 SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID,
348 SDL_BUTTON_X2,
349 (GetAsyncKeyState(VK_XBUTTON2) & 0x8000) != 0);
350 }
351 }
352 }
353}
354
355static void WIN_UpdateFocus(SDL_Window *window, bool expect_focus)
356{
357 SDL_WindowData *data = window->internal;
358 HWND hwnd = data->hwnd;
359 bool had_focus = (SDL_GetKeyboardFocus() == window);
360 bool has_focus = (GetForegroundWindow() == hwnd);
361
362 if (had_focus == has_focus || has_focus != expect_focus) {
363 return;
364 }
365
366 if (has_focus) {
367 POINT cursorPos;
368
369 if (WIN_ShouldIgnoreFocusClick(data) && !(window->flags & SDL_WINDOW_MOUSE_CAPTURE)) {
370 bool swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0;
371 if (GetAsyncKeyState(VK_LBUTTON)) {
372 data->focus_click_pending |= !swapButtons ? SDL_BUTTON_LMASK : SDL_BUTTON_RMASK;
373 }
374 if (GetAsyncKeyState(VK_RBUTTON)) {
375 data->focus_click_pending |= !swapButtons ? SDL_BUTTON_RMASK : SDL_BUTTON_LMASK;
376 }
377 if (GetAsyncKeyState(VK_MBUTTON)) {
378 data->focus_click_pending |= SDL_BUTTON_MMASK;
379 }
380 if (GetAsyncKeyState(VK_XBUTTON1)) {
381 data->focus_click_pending |= SDL_BUTTON_X1MASK;
382 }
383 if (GetAsyncKeyState(VK_XBUTTON2)) {
384 data->focus_click_pending |= SDL_BUTTON_X2MASK;
385 }
386 }
387
388 SDL_SetKeyboardFocus(data->keyboard_focus ? data->keyboard_focus : window);
389
390 // In relative mode we are guaranteed to have mouse focus if we have keyboard focus
391 if (!SDL_GetMouse()->relative_mode) {
392 GetCursorPos(&cursorPos);
393 ScreenToClient(hwnd, &cursorPos);
394 SDL_SendMouseMotion(WIN_GetEventTimestamp(), window, SDL_GLOBAL_MOUSE_ID, false, (float)cursorPos.x, (float)cursorPos.y);
395 }
396
397 WIN_CheckAsyncMouseRelease(WIN_GetEventTimestamp(), data);
398 WIN_UpdateClipCursor(window);
399
400 /*
401 * FIXME: Update keyboard state
402 */
403 WIN_CheckClipboardUpdate(data->videodata);
404
405 SDL_ToggleModState(SDL_KMOD_CAPS, (GetKeyState(VK_CAPITAL) & 0x0001) ? true : false);
406 SDL_ToggleModState(SDL_KMOD_NUM, (GetKeyState(VK_NUMLOCK) & 0x0001) ? true : false);
407 SDL_ToggleModState(SDL_KMOD_SCROLL, (GetKeyState(VK_SCROLL) & 0x0001) ? true : false);
408
409 WIN_UpdateWindowICCProfile(data->window, true);
410 } else {
411 data->in_window_deactivation = true;
412
413 SDL_SetKeyboardFocus(NULL);
414 // In relative mode we are guaranteed to not have mouse focus if we don't have keyboard focus
415 if (SDL_GetMouse()->relative_mode) {
416 SDL_SetMouseFocus(NULL);
417 }
418 WIN_ResetDeadKeys();
419
420 WIN_UnclipCursorForWindow(window);
421
422 data->in_window_deactivation = false;
423 }
424}
425#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
426
427static bool ShouldGenerateWindowCloseOnAltF4(void)
428{
429 return SDL_GetHintBoolean(SDL_HINT_WINDOWS_CLOSE_ON_ALT_F4, true);
430}
431
432static bool ShouldClearWindowOnEraseBackground(SDL_WindowData *data)
433{
434 switch (data->hint_erase_background_mode) {
435 case SDL_ERASEBACKGROUNDMODE_NEVER:
436 return false;
437 case SDL_ERASEBACKGROUNDMODE_INITIAL:
438 return !data->videodata->cleared;
439 case SDL_ERASEBACKGROUNDMODE_ALWAYS:
440 return true;
441 default:
442 // Unexpected value, fallback to default behaviour
443 return !data->videodata->cleared;
444 }
445}
446
447#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
448// We want to generate mouse events from mouse and pen, and touch events from touchscreens
449#define MI_WP_SIGNATURE 0xFF515700
450#define MI_WP_SIGNATURE_MASK 0xFFFFFF00
451#define IsTouchEvent(dw) ((dw)&MI_WP_SIGNATURE_MASK) == MI_WP_SIGNATURE
452
453typedef enum
454{
455 SDL_MOUSE_EVENT_SOURCE_UNKNOWN,
456 SDL_MOUSE_EVENT_SOURCE_MOUSE,
457 SDL_MOUSE_EVENT_SOURCE_TOUCH,
458 SDL_MOUSE_EVENT_SOURCE_PEN,
459} SDL_MOUSE_EVENT_SOURCE;
460
461static SDL_MOUSE_EVENT_SOURCE GetMouseMessageSource(ULONG extrainfo)
462{
463 // Mouse data (ignoring synthetic mouse events generated for touchscreens)
464 /* Versions below Vista will set the low 7 bits to the Mouse ID and don't use bit 7:
465 Check bits 8-31 for the signature (which will indicate a Tablet PC Pen or Touch Device).
466 Only check bit 7 when Vista and up(Cleared=Pen, Set=Touch(which we need to filter out)),
467 when the signature is set. The Mouse ID will be zero for an actual mouse. */
468 if (IsTouchEvent(extrainfo)) {
469 if (extrainfo & 0x80) {
470 return SDL_MOUSE_EVENT_SOURCE_TOUCH;
471 } else {
472 return SDL_MOUSE_EVENT_SOURCE_PEN;
473 }
474 }
475 /* Sometimes WM_INPUT events won't have the correct touch signature,
476 so we have to rely purely on the touch bit being set. */
477 if (SDL_TouchDevicesAvailable() && extrainfo & 0x80) {
478 return SDL_MOUSE_EVENT_SOURCE_TOUCH;
479 }
480 return SDL_MOUSE_EVENT_SOURCE_MOUSE;
481}
482#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
483
484static SDL_WindowData *WIN_GetWindowDataFromHWND(HWND hwnd)
485{
486 SDL_VideoDevice *_this = SDL_GetVideoDevice();
487 SDL_Window *window;
488
489 if (_this) {
490 for (window = _this->windows; window; window = window->next) {
491 SDL_WindowData *data = window->internal;
492 if (data && data->hwnd == hwnd) {
493 return data;
494 }
495 }
496 }
497 return NULL;
498}
499
500#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
501LRESULT CALLBACK
502WIN_KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam)
503{
504 KBDLLHOOKSTRUCT *hookData = (KBDLLHOOKSTRUCT *)lParam;
505 SDL_VideoData *data = SDL_GetVideoDevice()->internal;
506 SDL_Scancode scanCode;
507
508 if (nCode < 0 || nCode != HC_ACTION) {
509 return CallNextHookEx(NULL, nCode, wParam, lParam);
510 }
511 if (hookData->scanCode == 0x21d) {
512 // Skip fake LCtrl when RAlt is pressed
513 return 1;
514 }
515
516 switch (hookData->vkCode) {
517 case VK_LWIN:
518 scanCode = SDL_SCANCODE_LGUI;
519 break;
520 case VK_RWIN:
521 scanCode = SDL_SCANCODE_RGUI;
522 break;
523 case VK_LMENU:
524 scanCode = SDL_SCANCODE_LALT;
525 break;
526 case VK_RMENU:
527 scanCode = SDL_SCANCODE_RALT;
528 break;
529 case VK_LCONTROL:
530 scanCode = SDL_SCANCODE_LCTRL;
531 break;
532 case VK_RCONTROL:
533 scanCode = SDL_SCANCODE_RCTRL;
534 break;
535
536 // These are required to intercept Alt+Tab and Alt+Esc on Windows 7
537 case VK_TAB:
538 scanCode = SDL_SCANCODE_TAB;
539 break;
540 case VK_ESCAPE:
541 scanCode = SDL_SCANCODE_ESCAPE;
542 break;
543
544 default:
545 return CallNextHookEx(NULL, nCode, wParam, lParam);
546 }
547
548 if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
549 if (!data->raw_keyboard_enabled) {
550 SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, hookData->scanCode, scanCode, true);
551 }
552 } else {
553 if (!data->raw_keyboard_enabled) {
554 SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, hookData->scanCode, scanCode, false);
555 }
556
557 /* If the key was down prior to our hook being installed, allow the
558 key up message to pass normally the first time. This ensures other
559 windows have a consistent view of the key state, and avoids keys
560 being stuck down in those windows if they are down when the grab
561 happens and raised while grabbed. */
562 if (hookData->vkCode <= 0xFF && data->pre_hook_key_state[hookData->vkCode]) {
563 data->pre_hook_key_state[hookData->vkCode] = 0;
564 return CallNextHookEx(NULL, nCode, wParam, lParam);
565 }
566 }
567
568 return 1;
569}
570
571static bool WIN_SwapButtons(HANDLE hDevice)
572{
573 if (hDevice == NULL) {
574 // Touchpad, already has buttons swapped
575 return false;
576 }
577 return GetSystemMetrics(SM_SWAPBUTTON) != 0;
578}
579
580static void WIN_HandleRawMouseInput(Uint64 timestamp, SDL_VideoData *data, HANDLE hDevice, RAWMOUSE *rawmouse)
581{
582 static struct {
583 USHORT usButtonFlags;
584 Uint8 button;
585 bool down;
586 } raw_buttons[] = {
587 { RI_MOUSE_LEFT_BUTTON_DOWN, SDL_BUTTON_LEFT, true },
588 { RI_MOUSE_LEFT_BUTTON_UP, SDL_BUTTON_LEFT, false },
589 { RI_MOUSE_RIGHT_BUTTON_DOWN, SDL_BUTTON_RIGHT, true },
590 { RI_MOUSE_RIGHT_BUTTON_UP, SDL_BUTTON_RIGHT, false },
591 { RI_MOUSE_MIDDLE_BUTTON_DOWN, SDL_BUTTON_MIDDLE, true },
592 { RI_MOUSE_MIDDLE_BUTTON_UP, SDL_BUTTON_MIDDLE, false },
593 { RI_MOUSE_BUTTON_4_DOWN, SDL_BUTTON_X1, true },
594 { RI_MOUSE_BUTTON_4_UP, SDL_BUTTON_X1, false },
595 { RI_MOUSE_BUTTON_5_DOWN, SDL_BUTTON_X2, true },
596 { RI_MOUSE_BUTTON_5_UP, SDL_BUTTON_X2, false }
597 };
598
599 int dx = (int)rawmouse->lLastX;
600 int dy = (int)rawmouse->lLastY;
601 bool haveMotion = (dx || dy);
602 bool haveButton = (rawmouse->usButtonFlags != 0);
603 bool isAbsolute = ((rawmouse->usFlags & MOUSE_MOVE_ABSOLUTE) != 0);
604 SDL_MouseID mouseID = (SDL_MouseID)(uintptr_t)hDevice;
605
606 // Check whether relative mode should also receive events from the rawinput stream
607 if (!data->raw_mouse_enabled) {
608 return;
609 }
610
611 // Relative mouse motion is delivered to the window with keyboard focus
612 SDL_Window *window = SDL_GetKeyboardFocus();
613 if (!window) {
614 return;
615 }
616
617 if (GetMouseMessageSource(rawmouse->ulExtraInformation) != SDL_MOUSE_EVENT_SOURCE_MOUSE) {
618 return;
619 }
620
621 SDL_WindowData *windowdata = window->internal;
622
623 if (haveMotion) {
624 if (!isAbsolute) {
625 SDL_SendMouseMotion(timestamp, window, mouseID, true, (float)dx, (float)dy);
626 } else {
627 /* This is absolute motion, either using a tablet or mouse over RDP
628
629 Notes on how RDP appears to work, as of Windows 10 2004:
630 - 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.
631 - User mouse motion is coalesced with SetCursorPos(), so the WM_INPUT positions we see will not necessarily match the position we requested with SetCursorPos().
632 - SetCursorPos() outside of the bounds of the focus window appears not to do anything.
633 - SetCursorPos() while the cursor is NULL doesn't do anything
634
635 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.
636 */
637 bool remote_desktop = (GetSystemMetrics(SM_REMOTESESSION) == TRUE);
638 bool virtual_desktop = ((rawmouse->usFlags & MOUSE_VIRTUAL_DESKTOP) != 0);
639 bool raw_coordinates = ((rawmouse->usFlags & 0x40) != 0);
640 int w = GetSystemMetrics(virtual_desktop ? SM_CXVIRTUALSCREEN : SM_CXSCREEN);
641 int h = GetSystemMetrics(virtual_desktop ? SM_CYVIRTUALSCREEN : SM_CYSCREEN);
642 int x = raw_coordinates ? dx : (int)(((float)dx / 65535.0f) * w);
643 int y = raw_coordinates ? dy : (int)(((float)dy / 65535.0f) * h);
644 int relX, relY;
645
646 /* Calculate relative motion */
647 if (data->last_raw_mouse_position.x == 0 && data->last_raw_mouse_position.y == 0) {
648 data->last_raw_mouse_position.x = x;
649 data->last_raw_mouse_position.y = y;
650 }
651 relX = x - data->last_raw_mouse_position.x;
652 relY = y - data->last_raw_mouse_position.y;
653
654 if (remote_desktop) {
655 if (!windowdata->in_title_click && !windowdata->focus_click_pending) {
656 static int wobble;
657 float floatX = (float)x / w;
658 float floatY = (float)y / h;
659
660 /* See if the mouse is at the edge of the screen, or in the RDP title bar area */
661 if (floatX <= 0.01f || floatX >= 0.99f || floatY <= 0.01f || floatY >= 0.99f || y < 32) {
662 /* Wobble the cursor position so it's not ignored if the last warp didn't have any effect */
663 RECT rect = windowdata->cursor_clipped_rect;
664 int warpX = rect.left + ((rect.right - rect.left) / 2) + wobble;
665 int warpY = rect.top + ((rect.bottom - rect.top) / 2);
666
667 WIN_SetCursorPos(warpX, warpY);
668
669 ++wobble;
670 if (wobble > 1) {
671 wobble = -1;
672 }
673 } else {
674 /* Send relative motion if we didn't warp last frame (had good position data)
675 We also sometimes get large deltas due to coalesced mouse motion and warping,
676 so ignore those.
677 */
678 const int MAX_RELATIVE_MOTION = (h / 6);
679 if (SDL_abs(relX) < MAX_RELATIVE_MOTION &&
680 SDL_abs(relY) < MAX_RELATIVE_MOTION) {
681 SDL_SendMouseMotion(timestamp, window, mouseID, true, (float)relX, (float)relY);
682 }
683 }
684 }
685 } else {
686 const int MAXIMUM_TABLET_RELATIVE_MOTION = 32;
687 if (SDL_abs(relX) > MAXIMUM_TABLET_RELATIVE_MOTION ||
688 SDL_abs(relY) > MAXIMUM_TABLET_RELATIVE_MOTION) {
689 /* Ignore this motion, probably a pen lift and drop */
690 } else {
691 SDL_SendMouseMotion(timestamp, window, mouseID, true, (float)relX, (float)relY);
692 }
693 }
694
695 data->last_raw_mouse_position.x = x;
696 data->last_raw_mouse_position.y = y;
697 }
698 }
699
700 if (haveButton) {
701 for (int i = 0; i < SDL_arraysize(raw_buttons); ++i) {
702 if (rawmouse->usButtonFlags & raw_buttons[i].usButtonFlags) {
703 Uint8 button = raw_buttons[i].button;
704 bool down = raw_buttons[i].down;
705
706 if (button == SDL_BUTTON_LEFT) {
707 if (WIN_SwapButtons(hDevice)) {
708 button = SDL_BUTTON_RIGHT;
709 }
710 } else if (button == SDL_BUTTON_RIGHT) {
711 if (WIN_SwapButtons(hDevice)) {
712 button = SDL_BUTTON_LEFT;
713 }
714 }
715
716 if (windowdata->focus_click_pending & SDL_BUTTON_MASK(button)) {
717 // Ignore the button click for activation
718 if (!down) {
719 windowdata->focus_click_pending &= ~SDL_BUTTON_MASK(button);
720 WIN_UpdateClipCursor(window);
721 }
722 continue;
723 }
724
725 SDL_SendMouseButton(timestamp, window, mouseID, button, down);
726 }
727 }
728
729 if (rawmouse->usButtonFlags & RI_MOUSE_WHEEL) {
730 SHORT amount = (SHORT)rawmouse->usButtonData;
731 float fAmount = (float)amount / WHEEL_DELTA;
732 SDL_SendMouseWheel(WIN_GetEventTimestamp(), window, mouseID, 0.0f, fAmount, SDL_MOUSEWHEEL_NORMAL);
733 } else if (rawmouse->usButtonFlags & RI_MOUSE_HWHEEL) {
734 SHORT amount = (SHORT)rawmouse->usButtonData;
735 float fAmount = (float)amount / WHEEL_DELTA;
736 SDL_SendMouseWheel(WIN_GetEventTimestamp(), window, mouseID, fAmount, 0.0f, SDL_MOUSEWHEEL_NORMAL);
737 }
738 }
739}
740
741static void WIN_HandleRawKeyboardInput(Uint64 timestamp, SDL_VideoData *data, HANDLE hDevice, RAWKEYBOARD *rawkeyboard)
742{
743 SDL_KeyboardID keyboardID = (SDL_KeyboardID)(uintptr_t)hDevice;
744
745 if (!data->raw_keyboard_enabled) {
746 return;
747 }
748
749 if (rawkeyboard->Flags & RI_KEY_E1) {
750 // First key in a Ctrl+{key} sequence
751 data->pending_E1_key_sequence = true;
752 return;
753 }
754
755 if ((rawkeyboard->Flags & RI_KEY_E0) && rawkeyboard->MakeCode == 0x2A) {
756 // 0xE02A make code prefix, ignored
757 return;
758 }
759
760#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
761 if (!rawkeyboard->MakeCode) {
762 rawkeyboard->MakeCode = LOWORD(MapVirtualKey(rawkeyboard->VKey, WIN_IsWindowsXP() ? MAPVK_VK_TO_VSC : MAPVK_VK_TO_VSC_EX));
763 }
764#endif
765 if (!rawkeyboard->MakeCode) {
766 return;
767 }
768
769 bool down = !(rawkeyboard->Flags & RI_KEY_BREAK);
770 SDL_Scancode code;
771 USHORT rawcode = rawkeyboard->MakeCode;
772 if (data->pending_E1_key_sequence) {
773 rawcode |= 0xE100;
774 if (rawkeyboard->MakeCode == 0x45) {
775 // Ctrl+NumLock == Pause
776 code = SDL_SCANCODE_PAUSE;
777 } else {
778 // Ctrl+ScrollLock == Break (no SDL scancode?)
779 code = SDL_SCANCODE_UNKNOWN;
780 }
781 data->pending_E1_key_sequence = false;
782 } else {
783 // The code is in the lower 7 bits, the high bit is set for the E0 prefix
784 Uint8 index = (Uint8)rawkeyboard->MakeCode;
785 if (rawkeyboard->Flags & RI_KEY_E0) {
786 rawcode |= 0xE000;
787 index |= 0x80;
788 }
789 code = windows_scancode_table[index];
790 }
791
792 if (down) {
793 SDL_Window *focus = SDL_GetKeyboardFocus();
794 if (!focus || focus->text_input_active) {
795 return;
796 }
797 }
798
799 SDL_SendKeyboardKey(timestamp, keyboardID, rawcode, code, down);
800}
801
802void WIN_PollRawInput(SDL_VideoDevice *_this, Uint64 poll_start)
803{
804 SDL_VideoData *data = _this->internal;
805 UINT size, i, count, total = 0;
806 RAWINPUT *input;
807 Uint64 poll_finish;
808
809 if (data->rawinput_offset == 0) {
810 BOOL isWow64;
811
812 data->rawinput_offset = sizeof(RAWINPUTHEADER);
813 if (IsWow64Process(GetCurrentProcess(), &isWow64) && isWow64) {
814 // We're going to get 64-bit data, so use the 64-bit RAWINPUTHEADER size
815 data->rawinput_offset += 8;
816 }
817 }
818
819 // Get all available events
820 input = (RAWINPUT *)data->rawinput;
821 for (;;) {
822 size = data->rawinput_size - (UINT)((BYTE *)input - data->rawinput);
823 count = GetRawInputBuffer(input, &size, sizeof(RAWINPUTHEADER));
824 poll_finish = SDL_GetTicksNS();
825 if (count == 0 || count == (UINT)-1) {
826 if (!data->rawinput || (count == (UINT)-1 && GetLastError() == ERROR_INSUFFICIENT_BUFFER)) {
827 const UINT RAWINPUT_BUFFER_SIZE_INCREMENT = 96; // 2 64-bit raw mouse packets
828 BYTE *rawinput = (BYTE *)SDL_realloc(data->rawinput, data->rawinput_size + RAWINPUT_BUFFER_SIZE_INCREMENT);
829 if (!rawinput) {
830 break;
831 }
832 input = (RAWINPUT *)(rawinput + ((BYTE *)input - data->rawinput));
833 data->rawinput = rawinput;
834 data->rawinput_size += RAWINPUT_BUFFER_SIZE_INCREMENT;
835 } else {
836 break;
837 }
838 } else {
839 total += count;
840
841 // Advance input to the end of the buffer
842 while (count--) {
843 input = NEXTRAWINPUTBLOCK(input);
844 }
845 }
846 }
847
848 if (total > 0) {
849 Uint64 delta = poll_finish - poll_start;
850 UINT mouse_total = 0;
851 for (i = 0, input = (RAWINPUT *)data->rawinput; i < total; ++i, input = NEXTRAWINPUTBLOCK(input)) {
852 if (input->header.dwType == RIM_TYPEMOUSE) {
853 mouse_total += 1;
854 }
855 }
856 int mouse_index = 0;
857 for (i = 0, input = (RAWINPUT *)data->rawinput; i < total; ++i, input = NEXTRAWINPUTBLOCK(input)) {
858 if (input->header.dwType == RIM_TYPEMOUSE) {
859 mouse_index += 1; // increment first so that it starts at one
860 RAWMOUSE *rawmouse = (RAWMOUSE *)((BYTE *)input + data->rawinput_offset);
861 Uint64 time = poll_finish - (delta * (mouse_total - mouse_index)) / mouse_total;
862 WIN_HandleRawMouseInput(time, data, input->header.hDevice, rawmouse);
863 } else if (input->header.dwType == RIM_TYPEKEYBOARD) {
864 RAWKEYBOARD *rawkeyboard = (RAWKEYBOARD *)((BYTE *)input + data->rawinput_offset);
865 WIN_HandleRawKeyboardInput(poll_finish, data, input->header.hDevice, rawkeyboard);
866 }
867 }
868 }
869 data->last_rawinput_poll = poll_finish;
870}
871
872#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
873
874static void AddDeviceID(Uint32 deviceID, Uint32 **list, int *count)
875{
876 int new_count = (*count + 1);
877 Uint32 *new_list = (Uint32 *)SDL_realloc(*list, new_count * sizeof(*new_list));
878 if (!new_list) {
879 // Oh well, we'll drop this one
880 return;
881 }
882 new_list[new_count - 1] = deviceID;
883
884 *count = new_count;
885 *list = new_list;
886}
887
888static bool HasDeviceID(Uint32 deviceID, const Uint32 *list, int count)
889{
890 for (int i = 0; i < count; ++i) {
891 if (deviceID == list[i]) {
892 return true;
893 }
894 }
895 return false;
896}
897
898#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
899static char *GetDeviceName(HANDLE hDevice, HDEVINFO devinfo, const char *instance, const char *default_name, bool hid_loaded)
900{
901 char *vendor_name = NULL;
902 char *product_name = NULL;
903 char *name = NULL;
904
905 // These are 126 for USB, but can be longer for Bluetooth devices
906 WCHAR vend[256], prod[256];
907 vend[0] = 0;
908 prod[0] = 0;
909
910
911 HIDD_ATTRIBUTES attr;
912 attr.VendorID = 0;
913 attr.ProductID = 0;
914 attr.Size = sizeof(attr);
915
916 if (hid_loaded) {
917 char devName[MAX_PATH + 1];
918 UINT cap = sizeof(devName) - 1;
919 UINT len = GetRawInputDeviceInfoA(hDevice, RIDI_DEVICENAME, devName, &cap);
920 if (len != (UINT)-1) {
921 devName[len] = '\0';
922
923 // important: for devices with exclusive access mode as per
924 // https://learn.microsoft.com/en-us/windows-hardware/drivers/hid/top-level-collections-opened-by-windows-for-system-use
925 // they can only be opened with a desired access of none instead of generic read.
926 HANDLE hFile = CreateFileA(devName, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
927 if (hFile != INVALID_HANDLE_VALUE) {
928 SDL_HidD_GetAttributes(hFile, &attr);
929 SDL_HidD_GetManufacturerString(hFile, vend, sizeof(vend));
930 SDL_HidD_GetProductString(hFile, prod, sizeof(prod));
931 CloseHandle(hFile);
932 }
933 }
934 }
935
936 if (vend[0]) {
937 vendor_name = WIN_StringToUTF8W(vend);
938 }
939
940 if (prod[0]) {
941 product_name = WIN_StringToUTF8W(prod);
942 } else {
943 SP_DEVINFO_DATA data;
944 SDL_zero(data);
945 data.cbSize = sizeof(data);
946 for (DWORD i = 0;; ++i) {
947 if (!SetupDiEnumDeviceInfo(devinfo, i, &data)) {
948 if (GetLastError() == ERROR_NO_MORE_ITEMS) {
949 break;
950 } else {
951 continue;
952 }
953 }
954
955 char DeviceInstanceId[64];
956 if (!SetupDiGetDeviceInstanceIdA(devinfo, &data, DeviceInstanceId, sizeof(DeviceInstanceId), NULL))
957 continue;
958
959 if (SDL_strcasecmp(instance, DeviceInstanceId) == 0) {
960 DWORD size = 0;
961 if (SetupDiGetDeviceRegistryPropertyW(devinfo, &data, SPDRP_DEVICEDESC, NULL, (PBYTE)prod, sizeof(prod), &size)) {
962 // Make sure the device description is null terminated
963 size /= sizeof(*prod);
964 if (size >= SDL_arraysize(prod)) {
965 // Truncated description...
966 size = (SDL_arraysize(prod) - 1);
967 }
968 prod[size] = 0;
969
970 if (attr.VendorID || attr.ProductID) {
971 SDL_asprintf(&product_name, "%S (0x%.4x/0x%.4x)", prod, attr.VendorID, attr.ProductID);
972 } else {
973 product_name = WIN_StringToUTF8W(prod);
974 }
975 }
976 break;
977 }
978 }
979 }
980
981 if (!product_name && (attr.VendorID || attr.ProductID)) {
982 SDL_asprintf(&product_name, "%s (0x%.4x/0x%.4x)", default_name, attr.VendorID, attr.ProductID);
983 }
984 name = SDL_CreateDeviceName(attr.VendorID, attr.ProductID, vendor_name, product_name, default_name);
985 SDL_free(vendor_name);
986 SDL_free(product_name);
987
988 return name;
989}
990
991void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, bool initial_check)
992{
993 PRAWINPUTDEVICELIST raw_devices = NULL;
994 UINT raw_device_count = 0;
995 int old_keyboard_count = 0;
996 SDL_KeyboardID *old_keyboards = NULL;
997 int new_keyboard_count = 0;
998 SDL_KeyboardID *new_keyboards = NULL;
999 int old_mouse_count = 0;
1000 SDL_MouseID *old_mice = NULL;
1001 int new_mouse_count = 0;
1002 SDL_MouseID *new_mice = NULL;
1003 bool send_event = !initial_check;
1004
1005 // Check to see if anything has changed
1006 static Uint64 s_last_device_change;
1007 Uint64 last_device_change = WIN_GetLastDeviceNotification();
1008 if (!initial_check && last_device_change == s_last_device_change) {
1009 return;
1010 }
1011 s_last_device_change = last_device_change;
1012
1013 if ((GetRawInputDeviceList(NULL, &raw_device_count, sizeof(RAWINPUTDEVICELIST)) == -1) || (!raw_device_count)) {
1014 return; // oh well.
1015 }
1016
1017 raw_devices = (PRAWINPUTDEVICELIST)SDL_malloc(sizeof(RAWINPUTDEVICELIST) * raw_device_count);
1018 if (!raw_devices) {
1019 return; // oh well.
1020 }
1021
1022 raw_device_count = GetRawInputDeviceList(raw_devices, &raw_device_count, sizeof(RAWINPUTDEVICELIST));
1023 if (raw_device_count == (UINT)-1) {
1024 SDL_free(raw_devices);
1025 raw_devices = NULL;
1026 return; // oh well.
1027 }
1028
1029 HDEVINFO devinfo = SetupDiGetClassDevsA(NULL, NULL, NULL, (DIGCF_ALLCLASSES | DIGCF_PRESENT));
1030
1031 old_keyboards = SDL_GetKeyboards(&old_keyboard_count);
1032 old_mice = SDL_GetMice(&old_mouse_count);
1033
1034 bool hid_loaded = WIN_LoadHIDDLL();
1035 for (UINT i = 0; i < raw_device_count; i++) {
1036 RID_DEVICE_INFO rdi;
1037 char devName[MAX_PATH] = { 0 };
1038 UINT rdiSize = sizeof(rdi);
1039 UINT nameSize = SDL_arraysize(devName);
1040 int vendor = 0, product = 0;
1041 DWORD dwType = raw_devices[i].dwType;
1042 char *instance, *ptr, *name;
1043
1044 if (dwType != RIM_TYPEKEYBOARD && dwType != RIM_TYPEMOUSE) {
1045 continue;
1046 }
1047
1048 rdi.cbSize = sizeof(rdi);
1049 if (GetRawInputDeviceInfoA(raw_devices[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) == ((UINT)-1) ||
1050 GetRawInputDeviceInfoA(raw_devices[i].hDevice, RIDI_DEVICENAME, devName, &nameSize) == ((UINT)-1)) {
1051 continue;
1052 }
1053
1054 // Extract the device instance
1055 instance = devName;
1056 while (*instance == '\\' || *instance == '?') {
1057 ++instance;
1058 }
1059 for (ptr = instance; *ptr; ++ptr) {
1060 if (*ptr == '#') {
1061 *ptr = '\\';
1062 }
1063 if (*ptr == '{') {
1064 if (ptr > instance && ptr[-1] == '\\') {
1065 --ptr;
1066 }
1067 break;
1068 }
1069 }
1070 *ptr = '\0';
1071
1072 SDL_sscanf(instance, "HID\\VID_%X&PID_%X&", &vendor, &product);
1073
1074 switch (dwType) {
1075 case RIM_TYPEKEYBOARD:
1076 if (SDL_IsKeyboard((Uint16)vendor, (Uint16)product, rdi.keyboard.dwNumberOfKeysTotal)) {
1077 SDL_KeyboardID keyboardID = (Uint32)(uintptr_t)raw_devices[i].hDevice;
1078 AddDeviceID(keyboardID, &new_keyboards, &new_keyboard_count);
1079 if (!HasDeviceID(keyboardID, old_keyboards, old_keyboard_count)) {
1080 name = GetDeviceName(raw_devices[i].hDevice, devinfo, instance, "Keyboard", hid_loaded);
1081 SDL_AddKeyboard(keyboardID, name, send_event);
1082 SDL_free(name);
1083 }
1084 }
1085 break;
1086 case RIM_TYPEMOUSE:
1087 if (SDL_IsMouse((Uint16)vendor, (Uint16)product)) {
1088 SDL_MouseID mouseID = (Uint32)(uintptr_t)raw_devices[i].hDevice;
1089 AddDeviceID(mouseID, &new_mice, &new_mouse_count);
1090 if (!HasDeviceID(mouseID, old_mice, old_mouse_count)) {
1091 name = GetDeviceName(raw_devices[i].hDevice, devinfo, instance, "Mouse", hid_loaded);
1092 SDL_AddMouse(mouseID, name, send_event);
1093 SDL_free(name);
1094 }
1095 }
1096 break;
1097 default:
1098 break;
1099 }
1100 }
1101 if (hid_loaded) {
1102 WIN_UnloadHIDDLL();
1103 }
1104
1105 for (int i = old_keyboard_count; i--;) {
1106 if (!HasDeviceID(old_keyboards[i], new_keyboards, new_keyboard_count)) {
1107 SDL_RemoveKeyboard(old_keyboards[i], send_event);
1108 }
1109 }
1110
1111 for (int i = old_mouse_count; i--;) {
1112 if (!HasDeviceID(old_mice[i], new_mice, new_mouse_count)) {
1113 SDL_RemoveMouse(old_mice[i], send_event);
1114 }
1115 }
1116
1117 SDL_free(old_keyboards);
1118 SDL_free(new_keyboards);
1119 SDL_free(old_mice);
1120 SDL_free(new_mice);
1121
1122 SetupDiDestroyDeviceInfoList(devinfo);
1123
1124 SDL_free(raw_devices);
1125}
1126#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1127
1128// Return true if spurious LCtrl is pressed
1129// LCtrl is sent when RAltGR is pressed
1130static bool SkipAltGrLeftControl(WPARAM wParam, LPARAM lParam)
1131{
1132 if (wParam != VK_CONTROL) {
1133 return false;
1134 }
1135
1136 // Is this an extended key (i.e. right key)?
1137 if (lParam & 0x01000000) {
1138 return false;
1139 }
1140
1141#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1142 // Here is a trick: "Alt Gr" sends LCTRL, then RALT. We only
1143 // want the RALT message, so we try to see if the next message
1144 // is a RALT message. In that case, this is a false LCTRL!
1145 MSG next_msg;
1146 DWORD msg_time = GetMessageTime();
1147 if (PeekMessage(&next_msg, NULL, 0, 0, PM_NOREMOVE)) {
1148 if (next_msg.message == WM_KEYDOWN ||
1149 next_msg.message == WM_SYSKEYDOWN) {
1150 if (next_msg.wParam == VK_MENU && (next_msg.lParam & 0x01000000) && next_msg.time == msg_time) {
1151 // Next message is a RALT down message, which means that this is NOT a proper LCTRL message!
1152 return true;
1153 }
1154 }
1155 }
1156#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1157
1158 return false;
1159}
1160
1161static bool DispatchModalLoopMessageHook(HWND *hwnd, UINT *msg, WPARAM *wParam, LPARAM *lParam)
1162{
1163 MSG dummy;
1164
1165 SDL_zero(dummy);
1166 dummy.hwnd = *hwnd;
1167 dummy.message = *msg;
1168 dummy.wParam = *wParam;
1169 dummy.lParam = *lParam;
1170 if (g_WindowsMessageHook(g_WindowsMessageHookData, &dummy)) {
1171 // Can't modify the hwnd, but everything else is fair game
1172 *msg = dummy.message;
1173 *wParam = dummy.wParam;
1174 *lParam = dummy.lParam;
1175 return true;
1176 }
1177 return false;
1178}
1179
1180LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
1181{
1182 SDL_WindowData *data;
1183 LRESULT returnCode = -1;
1184
1185 // Get the window data for the window
1186 data = WIN_GetWindowDataFromHWND(hwnd);
1187#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1188 if (!data) {
1189 // Fallback
1190 data = (SDL_WindowData *)GetProp(hwnd, TEXT("SDL_WindowData"));
1191 }
1192#endif
1193 if (!data) {
1194 return CallWindowProc(DefWindowProc, hwnd, msg, wParam, lParam);
1195 }
1196
1197#ifdef WMMSG_DEBUG
1198 {
1199 char message[1024];
1200 if (msg > MAX_WMMSG) {
1201 SDL_snprintf(message, sizeof(message), "Received windows message: %p UNKNOWN (%d) -- 0x%x, 0x%x\r\n", hwnd, msg, wParam, lParam);
1202 } else {
1203 SDL_snprintf(message, sizeof(message), "Received windows message: %p %s -- 0x%x, 0x%x\r\n", hwnd, wmtab[msg], wParam, lParam);
1204 }
1205 OutputDebugStringA(message);
1206 }
1207#endif // WMMSG_DEBUG
1208
1209
1210 if (g_WindowsMessageHook && data->in_modal_loop) {
1211 // Synthesize a message for window hooks so they can modify the message if desired
1212 if (!DispatchModalLoopMessageHook(&hwnd, &msg, &wParam, &lParam)) {
1213 return 0;
1214 }
1215 }
1216
1217#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1218 if (WIN_HandleIMEMessage(hwnd, msg, wParam, &lParam, data->videodata)) {
1219 return 0;
1220 }
1221#endif
1222
1223 switch (msg) {
1224
1225 case WM_SHOWWINDOW:
1226 {
1227 if (wParam) {
1228 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_SHOWN, 0, 0);
1229 } else {
1230 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_HIDDEN, 0, 0);
1231 }
1232 } break;
1233
1234#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1235 case WM_NCACTIVATE:
1236 {
1237 // Don't immediately clip the cursor in case we're clicking minimize/maximize buttons
1238 // This is the only place that this flag is set. This causes all subsequent calls to
1239 // WIN_UpdateClipCursor for this window to be no-ops in this frame's message-pumping.
1240 // This flag is unset at the end of message pumping each frame for every window, and
1241 // should never be carried over between frames.
1242 data->skip_update_clipcursor = true;
1243
1244 /* Update the focus here, since it's possible to get WM_ACTIVATE and WM_SETFOCUS without
1245 actually being the foreground window, but this appears to get called in all cases where
1246 the global foreground window changes to and from this window. */
1247 WIN_UpdateFocus(data->window, !!wParam);
1248 } break;
1249
1250 case WM_ACTIVATE:
1251 {
1252 // Update the focus in case we changed focus to a child window and then away from the application
1253 WIN_UpdateFocus(data->window, !!LOWORD(wParam));
1254 } break;
1255
1256 case WM_MOUSEACTIVATE:
1257 {
1258 if (SDL_WINDOW_IS_POPUP(data->window)) {
1259 return MA_NOACTIVATE;
1260 }
1261
1262 // Check parents to see if they are in relative mouse mode and focused
1263 SDL_Window *parent = data->window->parent;
1264 while (parent) {
1265 if ((parent->flags & SDL_WINDOW_INPUT_FOCUS) &&
1266 (parent->flags & SDL_WINDOW_MOUSE_RELATIVE_MODE)) {
1267 return MA_NOACTIVATE;
1268 }
1269 parent = parent->parent;
1270 }
1271 } break;
1272
1273 case WM_SETFOCUS:
1274 {
1275 // Update the focus in case it's changing between top-level windows in the same application
1276 WIN_UpdateFocus(data->window, true);
1277 } break;
1278
1279 case WM_KILLFOCUS:
1280 case WM_ENTERIDLE:
1281 {
1282 // Update the focus in case it's changing between top-level windows in the same application
1283 WIN_UpdateFocus(data->window, false);
1284 } break;
1285
1286 case WM_POINTERENTER:
1287 {
1288 if (!data->videodata->GetPointerType) {
1289 break; // Not on Windows8 or later? We shouldn't get this event, but just in case...
1290 }
1291
1292 const UINT32 pointerid = GET_POINTERID_WPARAM(wParam);
1293 void *hpointer = (void *) (size_t) pointerid;
1294 POINTER_INPUT_TYPE pointer_type = PT_POINTER;
1295 if (!data->videodata->GetPointerType(pointerid, &pointer_type)) {
1296 break; // oh well.
1297 } else if (pointer_type != PT_PEN) {
1298 break; // we only care about pens here.
1299 } else if (SDL_FindPenByHandle(hpointer)) {
1300 break; // we already have this one, don't readd it.
1301 }
1302
1303 // one can use GetPointerPenInfo() to get the current state of the pen, and check POINTER_PEN_INFO::penMask,
1304 // but the docs aren't clear if these masks are _always_ set for pens with specific features, or if they
1305 // could be unset at this moment because Windows is still deciding what capabilities the pen has, and/or
1306 // doesn't yet have valid data for them. As such, just say everything that the interface supports is
1307 // available...we don't expose this information through the public API at the moment anyhow.
1308 SDL_PenInfo info;
1309 SDL_zero(info);
1310 info.capabilities = SDL_PEN_CAPABILITY_PRESSURE | SDL_PEN_CAPABILITY_XTILT | SDL_PEN_CAPABILITY_YTILT | SDL_PEN_CAPABILITY_DISTANCE | SDL_PEN_CAPABILITY_ROTATION | SDL_PEN_CAPABILITY_ERASER;
1311 info.max_tilt = 90.0f;
1312 info.num_buttons = 1;
1313 info.subtype = SDL_PEN_TYPE_PENCIL;
1314 SDL_AddPenDevice(0, NULL, &info, hpointer);
1315 returnCode = 0;
1316 } break;
1317
1318 case WM_POINTERCAPTURECHANGED:
1319 case WM_POINTERLEAVE:
1320 {
1321 const UINT32 pointerid = GET_POINTERID_WPARAM(wParam);
1322 void *hpointer = (void *) (size_t) pointerid;
1323 const SDL_PenID pen = SDL_FindPenByHandle(hpointer);
1324 if (pen == 0) {
1325 break; // not a pen, or not a pen we already knew about.
1326 }
1327
1328 // if this just left the _window_, we don't care. If this is no longer visible to the tablet, time to remove it!
1329 if ((msg == WM_POINTERCAPTURECHANGED) || !IS_POINTER_INCONTACT_WPARAM(wParam)) {
1330 SDL_RemovePenDevice(WIN_GetEventTimestamp(), pen);
1331 }
1332 returnCode = 0;
1333 } break;
1334
1335 case WM_POINTERUPDATE: {
1336 POINTER_INPUT_TYPE pointer_type = PT_POINTER;
1337 if (!data->videodata->GetPointerType || !data->videodata->GetPointerType(GET_POINTERID_WPARAM(wParam), &pointer_type)) {
1338 break; // oh well.
1339 }
1340
1341 if (pointer_type == PT_MOUSE) {
1342 data->last_pointer_update = lParam;
1343 returnCode = 0;
1344 break;
1345 }
1346 }
1347 SDL_FALLTHROUGH;
1348
1349 case WM_POINTERDOWN:
1350 case WM_POINTERUP: {
1351 POINTER_PEN_INFO pen_info;
1352 const UINT32 pointerid = GET_POINTERID_WPARAM(wParam);
1353 void *hpointer = (void *) (size_t) pointerid;
1354 const SDL_PenID pen = SDL_FindPenByHandle(hpointer);
1355 if (pen == 0) {
1356 break; // not a pen, or not a pen we already knew about.
1357 } else if (!data->videodata->GetPointerPenInfo || !data->videodata->GetPointerPenInfo(pointerid, &pen_info)) {
1358 break; // oh well.
1359 }
1360
1361 const Uint64 timestamp = WIN_GetEventTimestamp();
1362 SDL_Window *window = data->window;
1363
1364 // if lifting off, do it first, so any motion changes don't cause app issues.
1365 if (msg == WM_POINTERUP) {
1366 SDL_SendPenTouch(timestamp, pen, window, (pen_info.penFlags & PEN_FLAG_INVERTED) != 0, false);
1367 }
1368
1369 POINT position;
1370 position.x = (LONG) GET_X_LPARAM(lParam);
1371 position.y = (LONG) GET_Y_LPARAM(lParam);
1372 ScreenToClient(data->hwnd, &position);
1373
1374 SDL_SendPenMotion(timestamp, pen, window, (float) position.x, (float) position.y);
1375 SDL_SendPenButton(timestamp, pen, window, 1, (pen_info.penFlags & PEN_FLAG_BARREL) != 0);
1376 SDL_SendPenButton(timestamp, pen, window, 2, (pen_info.penFlags & PEN_FLAG_ERASER) != 0);
1377
1378 if (pen_info.penMask & PEN_MASK_PRESSURE) {
1379 SDL_SendPenAxis(timestamp, pen, window, SDL_PEN_AXIS_PRESSURE, ((float) pen_info.pressure) / 1024.0f); // pen_info.pressure is in the range 0..1024.
1380 }
1381
1382 if (pen_info.penMask & PEN_MASK_ROTATION) {
1383 SDL_SendPenAxis(timestamp, pen, window, SDL_PEN_AXIS_ROTATION, ((float) pen_info.rotation)); // it's already in the range of 0 to 359.
1384 }
1385
1386 if (pen_info.penMask & PEN_MASK_TILT_X) {
1387 SDL_SendPenAxis(timestamp, pen, window, SDL_PEN_AXIS_XTILT, ((float) pen_info.tiltX)); // it's already in the range of -90 to 90..
1388 }
1389
1390 if (pen_info.penMask & PEN_MASK_TILT_Y) {
1391 SDL_SendPenAxis(timestamp, pen, window, SDL_PEN_AXIS_YTILT, ((float) pen_info.tiltY)); // it's already in the range of -90 to 90..
1392 }
1393
1394 // if setting down, do it last, so the pen is positioned correctly from the first contact.
1395 if (msg == WM_POINTERDOWN) {
1396 SDL_SendPenTouch(timestamp, pen, window, (pen_info.penFlags & PEN_FLAG_INVERTED) != 0, true);
1397 }
1398
1399 returnCode = 0;
1400 } break;
1401
1402 case WM_MOUSEMOVE:
1403 {
1404 SDL_Window *window = data->window;
1405
1406 if (window->flags & SDL_WINDOW_INPUT_FOCUS) {
1407 bool wish_clip_cursor = (
1408 window->flags & (SDL_WINDOW_MOUSE_RELATIVE_MODE | SDL_WINDOW_MOUSE_GRABBED) ||
1409 (window->mouse_rect.w > 0 && window->mouse_rect.h > 0)
1410 );
1411 if (wish_clip_cursor) {
1412 data->skip_update_clipcursor = false;
1413 WIN_UpdateClipCursor(window);
1414 }
1415 }
1416
1417 if (!data->mouse_tracked) {
1418 TRACKMOUSEEVENT trackMouseEvent;
1419
1420 trackMouseEvent.cbSize = sizeof(TRACKMOUSEEVENT);
1421 trackMouseEvent.dwFlags = TME_LEAVE;
1422 trackMouseEvent.hwndTrack = data->hwnd;
1423
1424 if (TrackMouseEvent(&trackMouseEvent)) {
1425 data->mouse_tracked = true;
1426 }
1427
1428 WIN_CheckAsyncMouseRelease(WIN_GetEventTimestamp(), data);
1429 }
1430
1431 if (!data->videodata->raw_mouse_enabled) {
1432 // Only generate mouse events for real mouse
1433 if (GetMouseMessageSource((ULONG)GetMessageExtraInfo()) == SDL_MOUSE_EVENT_SOURCE_MOUSE &&
1434 lParam != data->last_pointer_update) {
1435 SDL_SendMouseMotion(WIN_GetEventTimestamp(), window, SDL_GLOBAL_MOUSE_ID, false, (float)GET_X_LPARAM(lParam), (float)GET_Y_LPARAM(lParam));
1436 }
1437 }
1438 } break;
1439
1440 case WM_LBUTTONUP:
1441 case WM_RBUTTONUP:
1442 case WM_MBUTTONUP:
1443 case WM_XBUTTONUP:
1444 case WM_LBUTTONDOWN:
1445 case WM_LBUTTONDBLCLK:
1446 case WM_RBUTTONDOWN:
1447 case WM_RBUTTONDBLCLK:
1448 case WM_MBUTTONDOWN:
1449 case WM_MBUTTONDBLCLK:
1450 case WM_XBUTTONDOWN:
1451 case WM_XBUTTONDBLCLK:
1452 {
1453 /* SDL_Mouse *mouse = SDL_GetMouse(); */
1454 if (!data->videodata->raw_mouse_enabled) {
1455 if (GetMouseMessageSource((ULONG)GetMessageExtraInfo()) == SDL_MOUSE_EVENT_SOURCE_MOUSE &&
1456 lParam != data->last_pointer_update) {
1457 WIN_CheckWParamMouseButtons(WIN_GetEventTimestamp(), wParam, data, SDL_GLOBAL_MOUSE_ID);
1458 }
1459 }
1460 } break;
1461
1462#if 0 // We handle raw input all at once instead of using a syscall for each mouse event
1463 case WM_INPUT:
1464 {
1465 HRAWINPUT hRawInput = (HRAWINPUT)lParam;
1466 RAWINPUT inp;
1467 UINT size = sizeof(inp);
1468
1469 // Relative mouse motion is delivered to the window with keyboard focus
1470 if (data->window != SDL_GetKeyboardFocus()) {
1471 break;
1472 }
1473
1474 GetRawInputData(hRawInput, RID_INPUT, &inp, &size, sizeof(RAWINPUTHEADER));
1475 if (inp.header.dwType == RIM_TYPEMOUSE) {
1476 WIN_HandleRawMouseInput(WIN_GetEventTimestamp(), data, inp.header.hDevice, &inp.data.mouse);
1477 } else if (inp.header.dwType == RIM_TYPEKEYBOARD) {
1478 WIN_HandleRawKeyboardInput(WIN_GetEventTimestamp(), data, inp.header.hDevice, &inp.data.keyboard);
1479 }
1480 } break;
1481#endif
1482
1483 case WM_MOUSEWHEEL:
1484 case WM_MOUSEHWHEEL:
1485 {
1486 if (!data->videodata->raw_mouse_enabled) {
1487 short amount = GET_WHEEL_DELTA_WPARAM(wParam);
1488 float fAmount = (float)amount / WHEEL_DELTA;
1489 if (msg == WM_MOUSEWHEEL) {
1490 SDL_SendMouseWheel(WIN_GetEventTimestamp(), data->window, SDL_GLOBAL_MOUSE_ID, 0.0f, fAmount, SDL_MOUSEWHEEL_NORMAL);
1491 } else {
1492 SDL_SendMouseWheel(WIN_GetEventTimestamp(), data->window, SDL_GLOBAL_MOUSE_ID, fAmount, 0.0f, SDL_MOUSEWHEEL_NORMAL);
1493 }
1494 }
1495 } break;
1496
1497 case WM_MOUSELEAVE:
1498 if (!(data->window->flags & SDL_WINDOW_MOUSE_CAPTURE)) {
1499 if (SDL_GetMouseFocus() == data->window && !SDL_GetMouse()->relative_mode && !IsIconic(hwnd)) {
1500 SDL_Mouse *mouse;
1501 POINT cursorPos;
1502 GetCursorPos(&cursorPos);
1503 ScreenToClient(hwnd, &cursorPos);
1504 mouse = SDL_GetMouse();
1505 if (!mouse->was_touch_mouse_events) { // we're not a touch handler causing a mouse leave?
1506 SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, SDL_GLOBAL_MOUSE_ID, false, (float)cursorPos.x, (float)cursorPos.y);
1507 } else { // touch handling?
1508 mouse->was_touch_mouse_events = false; // not anymore
1509 if (mouse->touch_mouse_events) { // convert touch to mouse events
1510 SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, SDL_TOUCH_MOUSEID, false, (float)cursorPos.x, (float)cursorPos.y);
1511 } else { // normal handling
1512 SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, SDL_GLOBAL_MOUSE_ID, false, (float)cursorPos.x, (float)cursorPos.y);
1513 }
1514 }
1515 }
1516
1517 if (!SDL_GetMouse()->relative_mode) {
1518 // When WM_MOUSELEAVE is fired we can be assured that the cursor has left the window
1519 SDL_SetMouseFocus(NULL);
1520 }
1521 }
1522
1523 // Once we get WM_MOUSELEAVE we're guaranteed that the window is no longer tracked
1524 data->mouse_tracked = false;
1525
1526 returnCode = 0;
1527 break;
1528#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1529
1530 case WM_KEYDOWN:
1531 case WM_SYSKEYDOWN:
1532 {
1533 if (SkipAltGrLeftControl(wParam, lParam)) {
1534 returnCode = 0;
1535 break;
1536 }
1537
1538 bool virtual_key = false;
1539 Uint16 rawcode = 0;
1540 SDL_Scancode code = WindowsScanCodeToSDLScanCode(lParam, wParam, &rawcode, &virtual_key);
1541
1542 // Detect relevant keyboard shortcuts
1543 if (code == SDL_SCANCODE_F4 && (SDL_GetModState() & SDL_KMOD_ALT)) {
1544 // ALT+F4: Close window
1545 if (ShouldGenerateWindowCloseOnAltF4()) {
1546 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_CLOSE_REQUESTED, 0, 0);
1547 }
1548 }
1549
1550 if (virtual_key || !data->videodata->raw_keyboard_enabled || data->window->text_input_active) {
1551 SDL_SendKeyboardKey(WIN_GetEventTimestamp(), SDL_GLOBAL_KEYBOARD_ID, rawcode, code, true);
1552 }
1553 }
1554
1555 returnCode = 0;
1556 break;
1557
1558 case WM_SYSKEYUP:
1559 case WM_KEYUP:
1560 {
1561 if (SkipAltGrLeftControl(wParam, lParam)) {
1562 returnCode = 0;
1563 break;
1564 }
1565
1566 bool virtual_key = false;
1567 Uint16 rawcode = 0;
1568 SDL_Scancode code = WindowsScanCodeToSDLScanCode(lParam, wParam, &rawcode, &virtual_key);
1569 const bool *keyboardState = SDL_GetKeyboardState(NULL);
1570
1571 if (virtual_key || !data->videodata->raw_keyboard_enabled || data->window->text_input_active) {
1572 if (code == SDL_SCANCODE_PRINTSCREEN && !keyboardState[code]) {
1573 SDL_SendKeyboardKey(WIN_GetEventTimestamp(), SDL_GLOBAL_KEYBOARD_ID, rawcode, code, true);
1574 }
1575 SDL_SendKeyboardKey(WIN_GetEventTimestamp(), SDL_GLOBAL_KEYBOARD_ID, rawcode, code, false);
1576 }
1577 }
1578 returnCode = 0;
1579 break;
1580
1581 case WM_UNICHAR:
1582 if (wParam == UNICODE_NOCHAR) {
1583 returnCode = 1;
1584 } else {
1585 if (SDL_TextInputActive(data->window)) {
1586 char text[5];
1587 char *end = SDL_UCS4ToUTF8((Uint32)wParam, text);
1588 *end = '\0';
1589 SDL_SendKeyboardText(text);
1590 }
1591 returnCode = 0;
1592 }
1593 break;
1594
1595 case WM_CHAR:
1596 if (SDL_TextInputActive(data->window)) {
1597 /* Characters outside Unicode Basic Multilingual Plane (BMP)
1598 * are coded as so called "surrogate pair" in two separate UTF-16 character events.
1599 * Cache high surrogate until next character event. */
1600 if (IS_HIGH_SURROGATE(wParam)) {
1601 data->high_surrogate = (WCHAR)wParam;
1602 } else {
1603 WCHAR utf16[3];
1604
1605 utf16[0] = data->high_surrogate ? data->high_surrogate : (WCHAR)wParam;
1606 utf16[1] = data->high_surrogate ? (WCHAR)wParam : L'\0';
1607 utf16[2] = L'\0';
1608
1609 char utf8[5];
1610 int result = WIN_WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16, -1, utf8, sizeof(utf8), NULL, NULL);
1611 if (result > 0) {
1612 SDL_SendKeyboardText(utf8);
1613 }
1614 data->high_surrogate = L'\0';
1615 }
1616 } else {
1617 data->high_surrogate = L'\0';
1618 }
1619
1620 returnCode = 0;
1621 break;
1622
1623#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1624#ifdef WM_INPUTLANGCHANGE
1625 case WM_INPUTLANGCHANGE:
1626 {
1627 WIN_UpdateKeymap(true);
1628 }
1629 returnCode = 1;
1630 break;
1631#endif // WM_INPUTLANGCHANGE
1632
1633 case WM_NCLBUTTONDOWN:
1634 {
1635 data->in_title_click = true;
1636
1637 // Fix for 500ms hang after user clicks on the title bar, but before moving mouse
1638 // Reference: https://gamedev.net/forums/topic/672094-keeping-things-moving-during-win32-moveresize-events/5254386/
1639 if (SendMessage(hwnd, WM_NCHITTEST, wParam, lParam) == HTCAPTION) {
1640 POINT cursorPos;
1641 GetCursorPos(&cursorPos);
1642 ScreenToClient(hwnd, &cursorPos);
1643 PostMessage(hwnd, WM_MOUSEMOVE, 0, cursorPos.x | cursorPos.y << 16);
1644 }
1645 } break;
1646
1647 case WM_CAPTURECHANGED:
1648 {
1649 data->in_title_click = false;
1650
1651 // The mouse may have been released during a modal loop
1652 WIN_CheckAsyncMouseRelease(WIN_GetEventTimestamp(), data);
1653 } break;
1654
1655#ifdef WM_GETMINMAXINFO
1656 case WM_GETMINMAXINFO:
1657 {
1658 MINMAXINFO *info;
1659 RECT size;
1660 int x, y;
1661 int w, h;
1662 int min_w, min_h;
1663 int max_w, max_h;
1664 BOOL constrain_max_size;
1665
1666 // If this is an expected size change, allow it
1667 if (data->expected_resize) {
1668 break;
1669 }
1670
1671 // Get the current position of our window
1672 GetWindowRect(hwnd, &size);
1673 x = size.left;
1674 y = size.top;
1675
1676 // Calculate current size of our window
1677 SDL_GetWindowSize(data->window, &w, &h);
1678 SDL_GetWindowMinimumSize(data->window, &min_w, &min_h);
1679 SDL_GetWindowMaximumSize(data->window, &max_w, &max_h);
1680
1681 /* Store in min_w and min_h difference between current size and minimal
1682 size so we don't need to call AdjustWindowRectEx twice */
1683 min_w -= w;
1684 min_h -= h;
1685 if (max_w && max_h) {
1686 max_w -= w;
1687 max_h -= h;
1688 constrain_max_size = TRUE;
1689 } else {
1690 constrain_max_size = FALSE;
1691 }
1692
1693 if (!(SDL_GetWindowFlags(data->window) & SDL_WINDOW_BORDERLESS) && !SDL_WINDOW_IS_POPUP(data->window)) {
1694 size.top = 0;
1695 size.left = 0;
1696 size.bottom = h;
1697 size.right = w;
1698 WIN_AdjustWindowRectForHWND(hwnd, &size, 0);
1699 w = size.right - size.left;
1700 h = size.bottom - size.top;
1701#ifdef HIGHDPI_DEBUG
1702 SDL_Log("WM_GETMINMAXINFO: max window size: %dx%d using dpi: %u", w, h, dpi);
1703#endif
1704 }
1705
1706 // Fix our size to the current size
1707 info = (MINMAXINFO *)lParam;
1708 if (SDL_GetWindowFlags(data->window) & SDL_WINDOW_RESIZABLE) {
1709 if (SDL_GetWindowFlags(data->window) & SDL_WINDOW_BORDERLESS) {
1710 int screenW = GetSystemMetrics(SM_CXSCREEN);
1711 int screenH = GetSystemMetrics(SM_CYSCREEN);
1712 info->ptMaxSize.x = SDL_max(w, screenW);
1713 info->ptMaxSize.y = SDL_max(h, screenH);
1714 info->ptMaxPosition.x = SDL_min(0, ((screenW - w) / 2));
1715 info->ptMaxPosition.y = SDL_min(0, ((screenH - h) / 2));
1716 }
1717 info->ptMinTrackSize.x = (LONG)w + min_w;
1718 info->ptMinTrackSize.y = (LONG)h + min_h;
1719 if (constrain_max_size) {
1720 info->ptMaxTrackSize.x = (LONG)w + max_w;
1721 info->ptMaxTrackSize.y = (LONG)h + max_h;
1722 }
1723 } else {
1724 info->ptMaxSize.x = w;
1725 info->ptMaxSize.y = h;
1726 info->ptMaxPosition.x = x;
1727 info->ptMaxPosition.y = y;
1728 info->ptMinTrackSize.x = w;
1729 info->ptMinTrackSize.y = h;
1730 info->ptMaxTrackSize.x = w;
1731 info->ptMaxTrackSize.y = h;
1732 }
1733 }
1734 returnCode = 0;
1735 break;
1736#endif // WM_GETMINMAXINFO
1737
1738 case WM_WINDOWPOSCHANGING:
1739
1740 if (data->expected_resize) {
1741 returnCode = 0;
1742 }
1743 break;
1744
1745 case WM_WINDOWPOSCHANGED:
1746 {
1747 SDL_Window *win;
1748 const SDL_DisplayID original_displayID = data->last_displayID;
1749 const WINDOWPOS *windowpos = (WINDOWPOS *)lParam;
1750 const bool iconic = IsIconic(hwnd);
1751 const bool zoomed = IsZoomed(hwnd);
1752 RECT rect;
1753 int x, y;
1754 int w, h;
1755
1756 if (windowpos->flags & SWP_SHOWWINDOW) {
1757 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_SHOWN, 0, 0);
1758 }
1759
1760 if (iconic) {
1761 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0);
1762 } else if (zoomed) {
1763 if (data->window->flags & SDL_WINDOW_MINIMIZED) {
1764 // If going from minimized to maximized, send the restored event first.
1765 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESTORED, 0, 0);
1766 }
1767 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MAXIMIZED, 0, 0);
1768 data->force_ws_maximizebox = true;
1769 } else if (data->window->flags & (SDL_WINDOW_MAXIMIZED | SDL_WINDOW_MINIMIZED)) {
1770 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESTORED, 0, 0);
1771
1772 /* If resizable was forced on for the maximized window, clear the style flags now,
1773 * but not if the window is fullscreen, as this needs to be preserved in that case.
1774 */
1775 if (!(data->window->flags & SDL_WINDOW_FULLSCREEN)) {
1776 data->force_ws_maximizebox = false;
1777 WIN_SetWindowResizable(SDL_GetVideoDevice(), data->window, !!(data->window->flags & SDL_WINDOW_RESIZABLE));
1778 }
1779 }
1780
1781 if (windowpos->flags & SWP_HIDEWINDOW) {
1782 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_HIDDEN, 0, 0);
1783 }
1784
1785 // When the window is minimized it's resized to the dock icon size, ignore this
1786 if (iconic) {
1787 break;
1788 }
1789
1790 if (data->initializing) {
1791 break;
1792 }
1793
1794 if (!data->disable_move_size_events) {
1795 if (GetClientRect(hwnd, &rect) && !WIN_IsRectEmpty(&rect)) {
1796 ClientToScreen(hwnd, (LPPOINT) &rect);
1797 ClientToScreen(hwnd, (LPPOINT) &rect + 1);
1798
1799 x = rect.left;
1800 y = rect.top;
1801
1802 SDL_GlobalToRelativeForWindow(data->window, x, y, &x, &y);
1803 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MOVED, x, y);
1804 }
1805
1806 // Moving the window from one display to another can change the size of the window (in the handling of SDL_EVENT_WINDOW_MOVED), so we need to re-query the bounds
1807 if (GetClientRect(hwnd, &rect) && !WIN_IsRectEmpty(&rect)) {
1808 w = rect.right;
1809 h = rect.bottom;
1810
1811 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESIZED, w, h);
1812 }
1813 }
1814
1815 WIN_UpdateClipCursor(data->window);
1816
1817 // Update the window display position
1818 data->last_displayID = SDL_GetDisplayForWindow(data->window);
1819
1820 if (data->last_displayID != original_displayID) {
1821 // Display changed, check ICC profile
1822 WIN_UpdateWindowICCProfile(data->window, true);
1823 }
1824
1825 // Update the position of any child windows
1826 for (win = data->window->first_child; win; win = win->next_sibling) {
1827 // Don't update hidden child popup windows, their relative position doesn't change
1828 if (SDL_WINDOW_IS_POPUP(win) && !(win->flags & SDL_WINDOW_HIDDEN)) {
1829 WIN_SetWindowPositionInternal(win, SWP_NOCOPYBITS | SWP_NOACTIVATE, SDL_WINDOWRECT_CURRENT);
1830 }
1831 }
1832
1833 // Forces a WM_PAINT event
1834 InvalidateRect(hwnd, NULL, FALSE);
1835
1836 } break;
1837
1838 case WM_ENTERSIZEMOVE:
1839 case WM_ENTERMENULOOP:
1840 {
1841 if (g_WindowsMessageHook) {
1842 if (!DispatchModalLoopMessageHook(&hwnd, &msg, &wParam, &lParam)) {
1843 return 0;
1844 }
1845 }
1846
1847 ++data->in_modal_loop;
1848 if (data->in_modal_loop == 1) {
1849 data->initial_size_rect.left = data->window->x;
1850 data->initial_size_rect.right = data->window->x + data->window->w;
1851 data->initial_size_rect.top = data->window->y;
1852 data->initial_size_rect.bottom = data->window->y + data->window->h;
1853
1854 SetTimer(hwnd, (UINT_PTR)SDL_IterateMainCallbacks, USER_TIMER_MINIMUM, NULL);
1855 }
1856 } break;
1857
1858 case WM_TIMER:
1859 {
1860 if (wParam == (UINT_PTR)SDL_IterateMainCallbacks) {
1861 SDL_OnWindowLiveResizeUpdate(data->window);
1862 return 0;
1863 }
1864 } break;
1865
1866 case WM_EXITSIZEMOVE:
1867 case WM_EXITMENULOOP:
1868 {
1869 --data->in_modal_loop;
1870 if (data->in_modal_loop == 0) {
1871 KillTimer(hwnd, (UINT_PTR)SDL_IterateMainCallbacks);
1872 }
1873 } break;
1874
1875 case WM_SIZING:
1876 {
1877 WPARAM edge = wParam;
1878 RECT* dragRect = (RECT*)lParam;
1879 RECT clientDragRect = *dragRect;
1880 bool lock_aspect_ratio = (data->window->max_aspect == data->window->min_aspect) ? true : false;
1881 RECT rc;
1882 LONG w, h;
1883 float new_aspect;
1884
1885 // if aspect ratio constraints are not enabled then skip this message
1886 if (data->window->min_aspect <= 0 && data->window->max_aspect <= 0) {
1887 break;
1888 }
1889
1890 // unadjust the dragRect from the window rect to the client rect
1891 SetRectEmpty(&rc);
1892 if (!AdjustWindowRectEx(&rc, GetWindowStyle(hwnd), GetMenu(hwnd) != NULL, GetWindowExStyle(hwnd))) {
1893 break;
1894 }
1895
1896 clientDragRect.left -= rc.left;
1897 clientDragRect.top -= rc.top;
1898 clientDragRect.right -= rc.right;
1899 clientDragRect.bottom -= rc.bottom;
1900
1901 w = clientDragRect.right - clientDragRect.left;
1902 h = clientDragRect.bottom - clientDragRect.top;
1903 new_aspect = w / (float)h;
1904
1905 // handle the special case in which the min ar and max ar are the same so the window can size symmetrically
1906 if (lock_aspect_ratio) {
1907 switch (edge) {
1908 case WMSZ_LEFT:
1909 case WMSZ_RIGHT:
1910 h = (int)SDL_roundf(w / data->window->max_aspect);
1911 break;
1912 default:
1913 // resizing via corners or top or bottom
1914 w = (int)SDL_roundf(h * data->window->max_aspect);
1915 break;
1916 }
1917 } else {
1918 switch (edge) {
1919 case WMSZ_LEFT:
1920 case WMSZ_RIGHT:
1921 if (data->window->max_aspect > 0.0f && new_aspect > data->window->max_aspect) {
1922 w = (int)SDL_roundf(h * data->window->max_aspect);
1923 } else if (data->window->min_aspect > 0.0f && new_aspect < data->window->min_aspect) {
1924 w = (int)SDL_roundf(h * data->window->min_aspect);
1925 }
1926 break;
1927 case WMSZ_TOP:
1928 case WMSZ_BOTTOM:
1929 if (data->window->min_aspect > 0.0f && new_aspect < data->window->min_aspect) {
1930 h = (int)SDL_roundf(w / data->window->min_aspect);
1931 } else if (data->window->max_aspect > 0.0f && new_aspect > data->window->max_aspect) {
1932 h = (int)SDL_roundf(w / data->window->max_aspect);
1933 }
1934 break;
1935
1936 default:
1937 // resizing via corners
1938 if (data->window->max_aspect > 0.0f && new_aspect > data->window->max_aspect) {
1939 w = (int)SDL_roundf(h * data->window->max_aspect);
1940 } else if (data->window->min_aspect > 0.0f && new_aspect < data->window->min_aspect) {
1941 h = (int)SDL_roundf(w / data->window->min_aspect);
1942 }
1943 break;
1944 }
1945 }
1946
1947 switch (edge) {
1948 case WMSZ_LEFT:
1949 clientDragRect.left = clientDragRect.right - w;
1950 if (lock_aspect_ratio) {
1951 clientDragRect.top = (data->initial_size_rect.bottom + data->initial_size_rect.top - h) / 2;
1952 }
1953 clientDragRect.bottom = h + clientDragRect.top;
1954 break;
1955 case WMSZ_BOTTOMLEFT:
1956 clientDragRect.left = clientDragRect.right - w;
1957 clientDragRect.bottom = h + clientDragRect.top;
1958 break;
1959 case WMSZ_RIGHT:
1960 clientDragRect.right = w + clientDragRect.left;
1961 if (lock_aspect_ratio) {
1962 clientDragRect.top = (data->initial_size_rect.bottom + data->initial_size_rect.top - h) / 2;
1963 }
1964 clientDragRect.bottom = h + clientDragRect.top;
1965 break;
1966 case WMSZ_TOPRIGHT:
1967 clientDragRect.right = w + clientDragRect.left;
1968 clientDragRect.top = clientDragRect.bottom - h;
1969 break;
1970 case WMSZ_TOP:
1971 if (lock_aspect_ratio) {
1972 clientDragRect.left = (data->initial_size_rect.right + data->initial_size_rect.left - w) / 2;
1973 }
1974 clientDragRect.right = w + clientDragRect.left;
1975 clientDragRect.top = clientDragRect.bottom - h;
1976 break;
1977 case WMSZ_TOPLEFT:
1978 clientDragRect.left = clientDragRect.right - w;
1979 clientDragRect.top = clientDragRect.bottom - h;
1980 break;
1981 case WMSZ_BOTTOM:
1982 if (lock_aspect_ratio) {
1983 clientDragRect.left = (data->initial_size_rect.right + data->initial_size_rect.left - w) / 2;
1984 }
1985 clientDragRect.right = w + clientDragRect.left;
1986 clientDragRect.bottom = h + clientDragRect.top;
1987 break;
1988 case WMSZ_BOTTOMRIGHT:
1989 clientDragRect.right = w + clientDragRect.left;
1990 clientDragRect.bottom = h + clientDragRect.top;
1991 break;
1992 }
1993
1994 // convert the client rect to a window rect
1995 if (!AdjustWindowRectEx(&clientDragRect, GetWindowStyle(hwnd), GetMenu(hwnd) != NULL, GetWindowExStyle(hwnd))) {
1996 break;
1997 }
1998
1999 *dragRect = clientDragRect;
2000 }
2001 break;
2002
2003 case WM_SETCURSOR:
2004 {
2005 Uint16 hittest;
2006
2007 hittest = LOWORD(lParam);
2008 if (hittest == HTCLIENT) {
2009 SetCursor(SDL_cursor);
2010 returnCode = TRUE;
2011 } else if (!g_WindowFrameUsableWhileCursorHidden && !SDL_cursor) {
2012 SetCursor(NULL);
2013 returnCode = TRUE;
2014 }
2015 } break;
2016
2017 // We were occluded, refresh our display
2018 case WM_PAINT:
2019 {
2020 RECT rect;
2021 if (GetUpdateRect(hwnd, &rect, FALSE)) {
2022 const LONG style = GetWindowLong(hwnd, GWL_EXSTYLE);
2023
2024 /* Composited windows will continue to receive WM_PAINT messages for update
2025 regions until the window is actually painted through Begin/EndPaint */
2026 if (style & WS_EX_COMPOSITED) {
2027 PAINTSTRUCT ps;
2028 BeginPaint(hwnd, &ps);
2029 EndPaint(hwnd, &ps);
2030 }
2031
2032 ValidateRect(hwnd, NULL);
2033 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_EXPOSED, 0, 0);
2034 }
2035 }
2036 returnCode = 0;
2037 break;
2038
2039 // We'll do our own drawing, prevent flicker
2040 case WM_ERASEBKGND:
2041 if (ShouldClearWindowOnEraseBackground(data)) {
2042 RECT client_rect;
2043 HBRUSH brush;
2044 data->videodata->cleared = true;
2045 GetClientRect(hwnd, &client_rect);
2046 brush = CreateSolidBrush(0);
2047 FillRect(GetDC(hwnd), &client_rect, brush);
2048 DeleteObject(brush);
2049 }
2050 return 1;
2051
2052 case WM_SYSCOMMAND:
2053 {
2054 if (!g_WindowsEnableMenuMnemonics) {
2055 if ((wParam & 0xFFF0) == SC_KEYMENU) {
2056 return 0;
2057 }
2058 }
2059
2060#if defined(SC_SCREENSAVE) || defined(SC_MONITORPOWER)
2061 // Don't start the screensaver or blank the monitor in fullscreen apps
2062 if ((wParam & 0xFFF0) == SC_SCREENSAVE ||
2063 (wParam & 0xFFF0) == SC_MONITORPOWER) {
2064 if (SDL_GetVideoDevice()->suspend_screensaver) {
2065 return 0;
2066 }
2067 }
2068#endif // System has screensaver support
2069 } break;
2070
2071 case WM_CLOSE:
2072 {
2073 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_CLOSE_REQUESTED, 0, 0);
2074 }
2075 returnCode = 0;
2076 break;
2077
2078 case WM_TOUCH:
2079 if (data->videodata->GetTouchInputInfo && data->videodata->CloseTouchInputHandle) {
2080 UINT i, num_inputs = LOWORD(wParam);
2081 bool isstack;
2082 PTOUCHINPUT inputs = SDL_small_alloc(TOUCHINPUT, num_inputs, &isstack);
2083 if (inputs && data->videodata->GetTouchInputInfo((HTOUCHINPUT)lParam, num_inputs, inputs, sizeof(TOUCHINPUT))) {
2084 RECT rect;
2085 float x, y;
2086
2087 if (!GetClientRect(hwnd, &rect) || WIN_IsRectEmpty(&rect)) {
2088 if (inputs) {
2089 SDL_small_free(inputs, isstack);
2090 }
2091 break;
2092 }
2093 ClientToScreen(hwnd, (LPPOINT)&rect);
2094 ClientToScreen(hwnd, (LPPOINT)&rect + 1);
2095 rect.top *= 100;
2096 rect.left *= 100;
2097 rect.bottom *= 100;
2098 rect.right *= 100;
2099
2100 for (i = 0; i < num_inputs; ++i) {
2101 PTOUCHINPUT input = &inputs[i];
2102 const int w = (rect.right - rect.left);
2103 const int h = (rect.bottom - rect.top);
2104
2105 const SDL_TouchID touchId = (SDL_TouchID)((uintptr_t)input->hSource);
2106 const SDL_FingerID fingerId = (input->dwID + 1);
2107
2108 /* TODO: Can we use GetRawInputDeviceInfo and HID info to
2109 determine if this is a direct or indirect touch device?
2110 */
2111 if (SDL_AddTouch(touchId, SDL_TOUCH_DEVICE_DIRECT, (input->dwFlags & TOUCHEVENTF_PEN) == TOUCHEVENTF_PEN ? "pen" : "touch") < 0) {
2112 continue;
2113 }
2114
2115 // Get the normalized coordinates for the window
2116 if (w <= 1) {
2117 x = 0.5f;
2118 } else {
2119 x = (float)(input->x - rect.left) / (w - 1);
2120 }
2121 if (h <= 1) {
2122 y = 0.5f;
2123 } else {
2124 y = (float)(input->y - rect.top) / (h - 1);
2125 }
2126
2127 // FIXME: Should we use the input->dwTime field for the tick source of the timestamp?
2128 if (input->dwFlags & TOUCHEVENTF_DOWN) {
2129 SDL_SendTouch(WIN_GetEventTimestamp(), touchId, fingerId, data->window, SDL_EVENT_FINGER_DOWN, x, y, 1.0f);
2130 }
2131 if (input->dwFlags & TOUCHEVENTF_MOVE) {
2132 SDL_SendTouchMotion(WIN_GetEventTimestamp(), touchId, fingerId, data->window, x, y, 1.0f);
2133 }
2134 if (input->dwFlags & TOUCHEVENTF_UP) {
2135 SDL_SendTouch(WIN_GetEventTimestamp(), touchId, fingerId, data->window, SDL_EVENT_FINGER_UP, x, y, 1.0f);
2136 }
2137 }
2138 }
2139 SDL_small_free(inputs, isstack);
2140
2141 data->videodata->CloseTouchInputHandle((HTOUCHINPUT)lParam);
2142 return 0;
2143 }
2144 break;
2145
2146#ifdef HAVE_TPCSHRD_H
2147
2148 case WM_TABLET_QUERYSYSTEMGESTURESTATUS:
2149 /* See https://msdn.microsoft.com/en-us/library/windows/desktop/bb969148(v=vs.85).aspx .
2150 * If we're handling our own touches, we don't want any gestures.
2151 * Not all of these settings are documented.
2152 * The use of the undocumented ones was suggested by https://github.com/bjarkeck/GCGJ/blob/master/Monogame/Windows/WinFormsGameForm.cs . */
2153 return TABLET_DISABLE_PRESSANDHOLD | TABLET_DISABLE_PENTAPFEEDBACK | TABLET_DISABLE_PENBARRELFEEDBACK | TABLET_DISABLE_TOUCHUIFORCEON | TABLET_DISABLE_TOUCHUIFORCEOFF | TABLET_DISABLE_TOUCHSWITCH | TABLET_DISABLE_FLICKS | TABLET_DISABLE_SMOOTHSCROLLING | TABLET_DISABLE_FLICKFALLBACKKEYS; // disables press and hold (right-click) gesture
2154 // disables UI feedback on pen up (waves)
2155 // disables UI feedback on pen button down (circle)
2156 // disables pen flicks (back, forward, drag down, drag up)
2157
2158#endif // HAVE_TPCSHRD_H
2159
2160 case WM_DROPFILES:
2161 {
2162 UINT i;
2163 HDROP drop = (HDROP)wParam;
2164 UINT count = DragQueryFile(drop, 0xFFFFFFFF, NULL, 0);
2165 for (i = 0; i < count; ++i) {
2166 UINT size = DragQueryFile(drop, i, NULL, 0) + 1;
2167 LPTSTR buffer = (LPTSTR)SDL_malloc(sizeof(TCHAR) * size);
2168 if (buffer) {
2169 if (DragQueryFile(drop, i, buffer, size)) {
2170 char *file = WIN_StringToUTF8(buffer);
2171 SDL_SendDropFile(data->window, NULL, file);
2172 SDL_free(file);
2173 }
2174 SDL_free(buffer);
2175 }
2176 }
2177 SDL_SendDropComplete(data->window);
2178 DragFinish(drop);
2179 return 0;
2180 } break;
2181
2182 case WM_DISPLAYCHANGE:
2183 {
2184 // Reacquire displays if any were added or removed
2185 WIN_RefreshDisplays(SDL_GetVideoDevice());
2186 } break;
2187
2188 case WM_NCCALCSIZE:
2189 {
2190 SDL_WindowFlags window_flags = SDL_GetWindowFlags(data->window);
2191 if (wParam == TRUE && (window_flags & SDL_WINDOW_BORDERLESS) && !(window_flags & SDL_WINDOW_FULLSCREEN)) {
2192 // When borderless, need to tell windows that the size of the non-client area is 0
2193 NCCALCSIZE_PARAMS *params = (NCCALCSIZE_PARAMS *)lParam;
2194 WINDOWPLACEMENT placement;
2195 if (GetWindowPlacement(hwnd, &placement) && placement.showCmd == SW_MAXIMIZE) {
2196 // Maximized borderless windows should use the monitor work area.
2197 HMONITOR hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL);
2198 if (!hMonitor) {
2199 // The returned monitor can be null when restoring from minimized, so use the last coordinates.
2200 const POINT pt = { data->window->windowed.x, data->window->windowed.y };
2201 hMonitor = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST);
2202 }
2203 if (hMonitor) {
2204 MONITORINFO info;
2205 SDL_zero(info);
2206 info.cbSize = sizeof(info);
2207 if (GetMonitorInfo(hMonitor, &info)) {
2208 params->rgrc[0] = info.rcWork;
2209 }
2210 }
2211 } else if (!(window_flags & SDL_WINDOW_RESIZABLE) && !data->force_ws_maximizebox) {
2212 int w, h;
2213 if (data->window->last_size_pending) {
2214 w = data->window->pending.w;
2215 h = data->window->pending.h;
2216 } else {
2217 w = data->window->floating.w;
2218 h = data->window->floating.h;
2219 }
2220 params->rgrc[0].right = params->rgrc[0].left + w;
2221 params->rgrc[0].bottom = params->rgrc[0].top + h;
2222 }
2223 return 0;
2224 }
2225 } break;
2226
2227 case WM_NCHITTEST:
2228 {
2229 SDL_Window *window = data->window;
2230
2231 if (window->flags & SDL_WINDOW_TOOLTIP) {
2232 return HTTRANSPARENT;
2233 }
2234
2235 if (window->hit_test) {
2236 POINT winpoint;
2237 winpoint.x = GET_X_LPARAM(lParam);
2238 winpoint.y = GET_Y_LPARAM(lParam);
2239 if (ScreenToClient(hwnd, &winpoint)) {
2240 SDL_Point point;
2241 SDL_HitTestResult rc;
2242 point.x = winpoint.x;
2243 point.y = winpoint.y;
2244 rc = window->hit_test(window, &point, window->hit_test_data);
2245 switch (rc) {
2246#define POST_HIT_TEST(ret) \
2247 { \
2248 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_HIT_TEST, 0, 0); \
2249 return ret; \
2250 }
2251 case SDL_HITTEST_DRAGGABLE:
2252 {
2253 /* If the mouse button state is something other than none or left button down,
2254 * return HTCLIENT, or Windows will eat the button press.
2255 */
2256 SDL_MouseButtonFlags buttonState = SDL_GetGlobalMouseState(NULL, NULL);
2257 if (buttonState && !(buttonState & SDL_BUTTON_LMASK)) {
2258 // Set focus in case it was lost while previously moving over a draggable area.
2259 SDL_SetMouseFocus(window);
2260 return HTCLIENT;
2261 }
2262
2263 POST_HIT_TEST(HTCAPTION);
2264 }
2265 case SDL_HITTEST_RESIZE_TOPLEFT:
2266 POST_HIT_TEST(HTTOPLEFT);
2267 case SDL_HITTEST_RESIZE_TOP:
2268 POST_HIT_TEST(HTTOP);
2269 case SDL_HITTEST_RESIZE_TOPRIGHT:
2270 POST_HIT_TEST(HTTOPRIGHT);
2271 case SDL_HITTEST_RESIZE_RIGHT:
2272 POST_HIT_TEST(HTRIGHT);
2273 case SDL_HITTEST_RESIZE_BOTTOMRIGHT:
2274 POST_HIT_TEST(HTBOTTOMRIGHT);
2275 case SDL_HITTEST_RESIZE_BOTTOM:
2276 POST_HIT_TEST(HTBOTTOM);
2277 case SDL_HITTEST_RESIZE_BOTTOMLEFT:
2278 POST_HIT_TEST(HTBOTTOMLEFT);
2279 case SDL_HITTEST_RESIZE_LEFT:
2280 POST_HIT_TEST(HTLEFT);
2281#undef POST_HIT_TEST
2282 case SDL_HITTEST_NORMAL:
2283 return HTCLIENT;
2284 }
2285 }
2286 // If we didn't return, this will call DefWindowProc below.
2287 }
2288 } break;
2289
2290 case WM_GETDPISCALEDSIZE:
2291 // Windows 10 Creators Update+
2292 /* Documented as only being sent to windows that are per-monitor V2 DPI aware.
2293
2294 Experimentation shows it's only sent during interactive dragging, not in response to
2295 SetWindowPos. */
2296 if (data->videodata->GetDpiForWindow && data->videodata->AdjustWindowRectExForDpi) {
2297 /* Windows expects applications to scale their window rects linearly
2298 when dragging between monitors with different DPI's.
2299 e.g. a 100x100 window dragged to a 200% scaled monitor
2300 becomes 200x200.
2301
2302 For SDL, we instead want the client size to scale linearly.
2303 This is not the same as the window rect scaling linearly,
2304 because Windows doesn't scale the non-client area (titlebar etc.)
2305 linearly. So, we need to handle this message to request custom
2306 scaling. */
2307
2308 const int nextDPI = (int)wParam;
2309 const int prevDPI = (int)data->videodata->GetDpiForWindow(hwnd);
2310 SIZE *sizeInOut = (SIZE *)lParam;
2311
2312 int frame_w, frame_h;
2313 int query_client_w_win, query_client_h_win;
2314
2315#ifdef HIGHDPI_DEBUG
2316 SDL_Log("WM_GETDPISCALEDSIZE: current DPI: %d potential DPI: %d input size: (%dx%d)",
2317 prevDPI, nextDPI, sizeInOut->cx, sizeInOut->cy);
2318#endif
2319
2320 // Subtract the window frame size that would have been used at prevDPI
2321 {
2322 RECT rect = { 0 };
2323
2324 if (!(data->window->flags & SDL_WINDOW_BORDERLESS) && !SDL_WINDOW_IS_POPUP(data->window)) {
2325 WIN_AdjustWindowRectForHWND(hwnd, &rect, prevDPI);
2326 }
2327
2328 frame_w = -rect.left + rect.right;
2329 frame_h = -rect.top + rect.bottom;
2330
2331 query_client_w_win = sizeInOut->cx - frame_w;
2332 query_client_h_win = sizeInOut->cy - frame_h;
2333 }
2334
2335 // Add the window frame size that would be used at nextDPI
2336 {
2337 RECT rect = { 0 };
2338 rect.right = query_client_w_win;
2339 rect.bottom = query_client_h_win;
2340
2341 if (!(data->window->flags & SDL_WINDOW_BORDERLESS) && !SDL_WINDOW_IS_POPUP(data->window)) {
2342 WIN_AdjustWindowRectForHWND(hwnd, &rect, nextDPI);
2343 }
2344
2345 // This is supposed to control the suggested rect param of WM_DPICHANGED
2346 sizeInOut->cx = rect.right - rect.left;
2347 sizeInOut->cy = rect.bottom - rect.top;
2348 }
2349
2350#ifdef HIGHDPI_DEBUG
2351 SDL_Log("WM_GETDPISCALEDSIZE: output size: (%dx%d)", sizeInOut->cx, sizeInOut->cy);
2352#endif
2353 return TRUE;
2354 }
2355 break;
2356
2357 case WM_DPICHANGED:
2358 // Windows 8.1+
2359 {
2360 const int newDPI = HIWORD(wParam);
2361 RECT *const suggestedRect = (RECT *)lParam;
2362 int w, h;
2363
2364#ifdef HIGHDPI_DEBUG
2365 SDL_Log("WM_DPICHANGED: to %d\tsuggested rect: (%d, %d), (%dx%d)", newDPI,
2366 suggestedRect->left, suggestedRect->top, suggestedRect->right - suggestedRect->left, suggestedRect->bottom - suggestedRect->top);
2367#endif
2368
2369 if (data->expected_resize) {
2370 /* This DPI change is coming from an explicit SetWindowPos call within SDL.
2371 Assume all call sites are calculating the DPI-aware frame correctly, so
2372 we don't need to do any further adjustment. */
2373#ifdef HIGHDPI_DEBUG
2374 SDL_Log("WM_DPICHANGED: Doing nothing, assuming window is already sized correctly");
2375#endif
2376 return 0;
2377 }
2378
2379 // Interactive user-initiated resizing/movement
2380 {
2381 /* Calculate the new frame w/h such that
2382 the client area size is maintained. */
2383 RECT rect = { 0 };
2384 rect.right = data->window->w;
2385 rect.bottom = data->window->h;
2386
2387 if (!(data->window->flags & SDL_WINDOW_BORDERLESS)) {
2388 WIN_AdjustWindowRectForHWND(hwnd, &rect, newDPI);
2389 }
2390
2391 w = rect.right - rect.left;
2392 h = rect.bottom - rect.top;
2393 }
2394
2395#ifdef HIGHDPI_DEBUG
2396 SDL_Log("WM_DPICHANGED: current SDL window size: (%dx%d)\tcalling SetWindowPos: (%d, %d), (%dx%d)",
2397 data->window->w, data->window->h,
2398 suggestedRect->left, suggestedRect->top, w, h);
2399#endif
2400
2401 data->expected_resize = true;
2402 SetWindowPos(hwnd,
2403 NULL,
2404 suggestedRect->left,
2405 suggestedRect->top,
2406 w,
2407 h,
2408 SWP_NOZORDER | SWP_NOACTIVATE);
2409 data->expected_resize = false;
2410 return 0;
2411 }
2412 break;
2413
2414 case WM_SETTINGCHANGE:
2415 if (wParam == 0 && lParam != 0 && SDL_wcscmp((wchar_t *)lParam, L"ImmersiveColorSet") == 0) {
2416 SDL_SetSystemTheme(WIN_GetSystemTheme());
2417 WIN_UpdateDarkModeForHWND(hwnd);
2418 }
2419 if (wParam == SPI_SETMOUSE || wParam == SPI_SETMOUSESPEED) {
2420 WIN_UpdateMouseSystemScale();
2421 }
2422 break;
2423
2424#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2425 }
2426
2427 // If there's a window proc, assume it's going to handle messages
2428 if (data->wndproc) {
2429 return CallWindowProc(data->wndproc, hwnd, msg, wParam, lParam);
2430 } else if (returnCode >= 0) {
2431 return returnCode;
2432 } else {
2433 return CallWindowProc(DefWindowProc, hwnd, msg, wParam, lParam);
2434 }
2435}
2436
2437int WIN_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS)
2438{
2439 if (g_WindowsEnableMessageLoop) {
2440#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2441 DWORD timeout, ret;
2442 timeout = timeoutNS < 0 ? INFINITE : (DWORD)SDL_NS_TO_MS(timeoutNS);
2443 ret = MsgWaitForMultipleObjects(0, NULL, FALSE, timeout, QS_ALLINPUT);
2444 if (ret == WAIT_OBJECT_0) {
2445 return 1;
2446 } else {
2447 return 0;
2448 }
2449#else
2450 // MsgWaitForMultipleObjects is desktop-only.
2451 MSG msg;
2452 BOOL message_result;
2453 UINT_PTR timer_id = 0;
2454 if (timeoutNS > 0) {
2455 timer_id = SetTimer(NULL, 0, (UINT)SDL_NS_TO_MS(timeoutNS), NULL);
2456 message_result = GetMessage(&msg, 0, 0, 0);
2457 KillTimer(NULL, timer_id);
2458 } else if (timeoutNS == 0) {
2459 message_result = PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
2460 } else {
2461 message_result = GetMessage(&msg, 0, 0, 0);
2462 }
2463 if (message_result) {
2464 if (msg.message == WM_TIMER && !msg.hwnd && msg.wParam == timer_id) {
2465 return 0;
2466 }
2467 if (g_WindowsMessageHook) {
2468 if (!g_WindowsMessageHook(g_WindowsMessageHookData, &msg)) {
2469 return 1;
2470 }
2471 }
2472 // Always translate the message in case it's a non-SDL window (e.g. with Qt integration)
2473 TranslateMessage(&msg);
2474 DispatchMessage(&msg);
2475 return 1;
2476 } else {
2477 return 0;
2478 }
2479#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2480 } else {
2481 // Fail the wait so the caller falls back to polling
2482 return -1;
2483 }
2484}
2485
2486void WIN_SendWakeupEvent(SDL_VideoDevice *_this, SDL_Window *window)
2487{
2488 SDL_WindowData *data = window->internal;
2489 PostMessage(data->hwnd, data->videodata->_SDL_WAKEUP, 0, 0);
2490}
2491
2492void WIN_PumpEvents(SDL_VideoDevice *_this)
2493{
2494 MSG msg;
2495#ifdef _MSC_VER // We explicitly want to use GetTickCount(), not GetTickCount64()
2496#pragma warning(push)
2497#pragma warning(disable : 28159)
2498#endif
2499 DWORD end_ticks = GetTickCount() + 1;
2500#ifdef _MSC_VER
2501#pragma warning(pop)
2502#endif
2503 int new_messages = 0;
2504#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2505 const bool *keystate;
2506 SDL_Window *focusWindow;
2507#endif
2508
2509 if (_this->internal->gameinput_context) {
2510 WIN_UpdateGameInput(_this);
2511 }
2512
2513 if (g_WindowsEnableMessageLoop) {
2514 SDL_processing_messages = true;
2515
2516 while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
2517 if (g_WindowsMessageHook) {
2518 if (!g_WindowsMessageHook(g_WindowsMessageHookData, &msg)) {
2519 continue;
2520 }
2521 }
2522
2523#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2524 // Don't dispatch any mouse motion queued prior to or including the last mouse warp
2525 if (msg.message == WM_MOUSEMOVE && SDL_last_warp_time) {
2526 if (!SDL_TICKS_PASSED(msg.time, (SDL_last_warp_time + 1))) {
2527 continue;
2528 }
2529
2530 // This mouse message happened after the warp
2531 SDL_last_warp_time = 0;
2532 }
2533#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2534
2535 WIN_SetMessageTick(msg.time);
2536
2537 // Always translate the message in case it's a non-SDL window (e.g. with Qt integration)
2538 TranslateMessage(&msg);
2539 DispatchMessage(&msg);
2540
2541 // Make sure we don't busy loop here forever if there are lots of events coming in
2542 if (SDL_TICKS_PASSED(msg.time, end_ticks)) {
2543 /* We might get a few new messages generated by the Steam overlay or other application hooks
2544 In this case those messages will be processed before any pending input, so we want to continue after those messages.
2545 (thanks to Peter Deayton for his investigation here)
2546 */
2547 const int MAX_NEW_MESSAGES = 3;
2548 ++new_messages;
2549 if (new_messages > MAX_NEW_MESSAGES) {
2550 break;
2551 }
2552 }
2553 }
2554
2555 SDL_processing_messages = false;
2556 }
2557
2558#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2559 /* Windows loses a shift KEYUP event when you have both pressed at once and let go of one.
2560 You won't get a KEYUP until both are released, and that keyup will only be for the second
2561 key you released. Take heroic measures and check the keystate as of the last handled event,
2562 and if we think a key is pressed when Windows doesn't, unstick it in SDL's state. */
2563 keystate = SDL_GetKeyboardState(NULL);
2564 if (keystate[SDL_SCANCODE_LSHIFT] && !(GetKeyState(VK_LSHIFT) & 0x8000)) {
2565 SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_LSHIFT, false);
2566 }
2567 if (keystate[SDL_SCANCODE_RSHIFT] && !(GetKeyState(VK_RSHIFT) & 0x8000)) {
2568 SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_RSHIFT, false);
2569 }
2570
2571 /* The Windows key state gets lost when using Windows+Space or Windows+G shortcuts and
2572 not grabbing the keyboard. Note: If we *are* grabbing the keyboard, GetKeyState()
2573 will return inaccurate results for VK_LWIN and VK_RWIN but we don't need it anyway. */
2574 focusWindow = SDL_GetKeyboardFocus();
2575 if (!focusWindow || !(focusWindow->flags & SDL_WINDOW_KEYBOARD_GRABBED)) {
2576 if (keystate[SDL_SCANCODE_LGUI] && !(GetKeyState(VK_LWIN) & 0x8000)) {
2577 SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_LGUI, false);
2578 }
2579 if (keystate[SDL_SCANCODE_RGUI] && !(GetKeyState(VK_RWIN) & 0x8000)) {
2580 SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_RGUI, false);
2581 }
2582 }
2583
2584 // Update the clipping rect in case someone else has stolen it
2585 if (_this) {
2586 SDL_Window *window = _this->windows;
2587 while (window) {
2588 SDL_WindowData *data = window->internal;
2589 if (data && data->skip_update_clipcursor) {
2590 data->skip_update_clipcursor = false;
2591 WIN_UpdateClipCursor(window);
2592 }
2593 window = window->next;
2594 }
2595 }
2596
2597 // Update mouse capture
2598 WIN_UpdateMouseCapture();
2599
2600 if (!_this->internal->gameinput_context) {
2601 WIN_CheckKeyboardAndMouseHotplug(_this, false);
2602 }
2603
2604 WIN_UpdateIMECandidates(_this);
2605
2606#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2607
2608#ifdef SDL_PLATFORM_GDK
2609 GDK_DispatchTaskQueue();
2610#endif
2611}
2612
2613static int app_registered = 0;
2614LPTSTR SDL_Appname = NULL;
2615Uint32 SDL_Appstyle = 0;
2616HINSTANCE SDL_Instance = NULL;
2617
2618static void WIN_CleanRegisterApp(WNDCLASSEX wcex)
2619{
2620#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2621 if (wcex.hIcon) {
2622 DestroyIcon(wcex.hIcon);
2623 }
2624 if (wcex.hIconSm) {
2625 DestroyIcon(wcex.hIconSm);
2626 }
2627#endif
2628 SDL_free(SDL_Appname);
2629 SDL_Appname = NULL;
2630}
2631
2632#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2633static BOOL CALLBACK WIN_ResourceNameCallback(HMODULE hModule, LPCTSTR lpType, LPTSTR lpName, LONG_PTR lParam)
2634{
2635 WNDCLASSEX *wcex = (WNDCLASSEX *)lParam;
2636
2637 (void)lpType; // We already know that the resource type is RT_GROUP_ICON.
2638
2639 /* We leave hIconSm as NULL as it will allow Windows to automatically
2640 choose the appropriate small icon size to suit the current DPI. */
2641 wcex->hIcon = LoadIcon(hModule, lpName);
2642
2643 // Do not bother enumerating any more.
2644 return FALSE;
2645}
2646#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2647
2648// Register the class for this application
2649bool SDL_RegisterApp(const char *name, Uint32 style, void *hInst)
2650{
2651 WNDCLASSEX wcex;
2652#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2653 const char *hint;
2654#endif
2655
2656 // Only do this once...
2657 if (app_registered) {
2658 ++app_registered;
2659 return true;
2660 }
2661 SDL_assert(!SDL_Appname);
2662 if (!name) {
2663 name = "SDL_app";
2664#if defined(CS_BYTEALIGNCLIENT) || defined(CS_OWNDC)
2665 style = (CS_BYTEALIGNCLIENT | CS_OWNDC);
2666#endif
2667 }
2668 SDL_Appname = WIN_UTF8ToString(name);
2669 SDL_Appstyle = style;
2670 SDL_Instance = hInst ? (HINSTANCE)hInst : GetModuleHandle(NULL);
2671
2672 // Register the application class
2673 wcex.cbSize = sizeof(WNDCLASSEX);
2674 wcex.hCursor = NULL;
2675 wcex.hIcon = NULL;
2676 wcex.hIconSm = NULL;
2677 wcex.lpszMenuName = NULL;
2678 wcex.lpszClassName = SDL_Appname;
2679 wcex.style = SDL_Appstyle;
2680 wcex.hbrBackground = NULL;
2681 wcex.lpfnWndProc = WIN_WindowProc;
2682 wcex.hInstance = SDL_Instance;
2683 wcex.cbClsExtra = 0;
2684 wcex.cbWndExtra = 0;
2685
2686#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2687 hint = SDL_GetHint(SDL_HINT_WINDOWS_INTRESOURCE_ICON);
2688 if (hint && *hint) {
2689 wcex.hIcon = LoadIcon(SDL_Instance, MAKEINTRESOURCE(SDL_atoi(hint)));
2690
2691 hint = SDL_GetHint(SDL_HINT_WINDOWS_INTRESOURCE_ICON_SMALL);
2692 if (hint && *hint) {
2693 wcex.hIconSm = LoadIcon(SDL_Instance, MAKEINTRESOURCE(SDL_atoi(hint)));
2694 }
2695 } else {
2696 // Use the first icon as a default icon, like in the Explorer.
2697 EnumResourceNames(SDL_Instance, RT_GROUP_ICON, WIN_ResourceNameCallback, (LONG_PTR)&wcex);
2698 }
2699#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2700
2701 if (!RegisterClassEx(&wcex)) {
2702 WIN_CleanRegisterApp(wcex);
2703 return SDL_SetError("Couldn't register application class");
2704 }
2705
2706 app_registered = 1;
2707 return true;
2708}
2709
2710// Unregisters the windowclass registered in SDL_RegisterApp above.
2711void SDL_UnregisterApp(void)
2712{
2713 WNDCLASSEX wcex;
2714
2715 // SDL_RegisterApp might not have been called before
2716 if (!app_registered) {
2717 return;
2718 }
2719 --app_registered;
2720 if (app_registered == 0) {
2721 // Ensure the icons are initialized.
2722 wcex.hIcon = NULL;
2723 wcex.hIconSm = NULL;
2724 // Check for any registered window classes.
2725#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2726 if (GetClassInfoEx(SDL_Instance, SDL_Appname, &wcex)) {
2727 UnregisterClass(SDL_Appname, SDL_Instance);
2728 }
2729#endif
2730 WIN_CleanRegisterApp(wcex);
2731 }
2732}
2733
2734#endif // SDL_VIDEO_DRIVER_WINDOWS