Atlas - SDL_windowsevents.c

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