Atlas - SDL_windowsevents.c

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