Atlas - SDL_windowsevents.c

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