#include "SDL_internal.h"
#ifdef SDL_VIDEO_DRIVER_WINDOWS
#include "SDL_windowsvideo.h"
#include "../../events/SDL_events_c.h"
#include "../../events/SDL_touch_c.h"
#include "../../events/scancodes_windows.h"
#include "../../main/SDL_main_callbacks.h"
#include "../../core/windows/SDL_hid.h"
#include <shellapi.h>
#include <setupapi.h>
#include <windowsx.h>
#ifdef HAVE_TPCSHRD_H
#include <tpcshrd.h>
#endif
#if 0#endif
#ifdef WMMSG_DEBUG
#include <stdio.h>
#include "wmmsg.h"
#endif
#if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES))
#include <shobjidl.h>
#endif
#ifdef SDL_PLATFORM_GDK
#include "../../core/gdk/SDL_gdk.h"
#endif
#ifndef WM_NCUAHDRAWCAPTION
#define WM_NCUAHDRAWCAPTION 0xAE
#endif
#ifndef WM_NCUAHDRAWFRAME
#define WM_NCUAHDRAWFRAME 0xAF
#endif
#ifndef WM_XBUTTONDOWN
#define WM_XBUTTONDOWN 0x020B
#endif
#ifndef WM_XBUTTONUP
#define WM_XBUTTONUP 0x020C
#endif
#ifndef GET_XBUTTON_WPARAM
#define GET_XBUTTON_WPARAM(w) (HIWORD(w))
#endif
#ifndef WM_INPUT
#define WM_INPUT 0x00ff
#endif
#ifndef WM_TOUCH
#define WM_TOUCH 0x0240
#endif
#ifndef WM_MOUSEHWHEEL
#define WM_MOUSEHWHEEL 0x020E
#endif
#ifndef RI_MOUSE_HWHEEL
#define RI_MOUSE_HWHEEL 0x0800
#endif
#ifndef WM_POINTERUPDATE
#define WM_POINTERUPDATE 0x0245
#endif
#ifndef WM_POINTERDOWN
#define WM_POINTERDOWN 0x0246
#endif
#ifndef WM_POINTERUP
#define WM_POINTERUP 0x0247
#endif
#ifndef WM_POINTERENTER
#define WM_POINTERENTER 0x0249
#endif
#ifndef WM_POINTERLEAVE
#define WM_POINTERLEAVE 0x024A
#endif
#ifndef WM_POINTERCAPTURECHANGED
#define WM_POINTERCAPTURECHANGED 0x024C
#endif
#ifndef WM_UNICHAR
#define WM_UNICHAR 0x0109
#endif
#ifndef WM_DPICHANGED
#define WM_DPICHANGED 0x02E0
#endif
#ifndef WM_GETDPISCALEDSIZE
#define WM_GETDPISCALEDSIZE 0x02E4
#endif
#ifndef TOUCHEVENTF_PEN
#define TOUCHEVENTF_PEN 0x0040
#endif
#ifndef MAPVK_VK_TO_VSC_EX
#define MAPVK_VK_TO_VSC_EX 4
#endif
#ifndef WC_ERR_INVALID_CHARS
#define WC_ERR_INVALID_CHARS 0x00000080
#endif
#ifndef IS_HIGH_SURROGATE
#define IS_HIGH_SURROGATE(x) (((x) >= 0xd800) && ((x) <= 0xdbff))
#endif
#ifndef USER_TIMER_MINIMUM
#define USER_TIMER_MINIMUM 0x0000000A
#endif
#define SDL_TICKS_PASSED(A, B) ((Sint32)((B) - (A)) <= 0)
#ifdef _WIN64
typedef Uint64 QWORD; #endif
static bool SDL_processing_messages;
static DWORD message_tick;
static Uint64 timestamp_offset;
static void WIN_SetMessageTick(DWORD tick)
{
message_tick = tick;
}
static Uint64 WIN_GetEventTimestamp(void)
{
const Uint64 TIMESTAMP_WRAP_OFFSET = SDL_MS_TO_NS(0x100000000LL);
Uint64 timestamp, now;
if (!SDL_processing_messages) {
return 0;
}
now = SDL_GetTicksNS();
timestamp = SDL_MS_TO_NS(message_tick);
timestamp += timestamp_offset;
if (!timestamp_offset) {
timestamp_offset = (now - timestamp);
timestamp = now;
} else if ((Sint64)(now - timestamp - TIMESTAMP_WRAP_OFFSET) >= 0) {
timestamp_offset += TIMESTAMP_WRAP_OFFSET;
timestamp += TIMESTAMP_WRAP_OFFSET;
} else if (timestamp > now) {
timestamp_offset -= (timestamp - now);
timestamp = now;
}
return timestamp;
}
static SDL_WindowsMessageHook g_WindowsMessageHook = NULL;
static void *g_WindowsMessageHookData = NULL;
void SDL_SetWindowsMessageHook(SDL_WindowsMessageHook callback, void *userdata)
{
g_WindowsMessageHook = callback;
g_WindowsMessageHookData = userdata;
}
static SDL_Scancode WindowsScanCodeToSDLScanCode(LPARAM lParam, WPARAM wParam, Uint16 *rawcode, bool *virtual_key)
{
SDL_Scancode code;
Uint8 index;
Uint16 keyFlags = HIWORD(lParam);
Uint16 scanCode = LOBYTE(keyFlags);
scanCode &= ~0x80;
*virtual_key = (scanCode == 0);
if (scanCode != 0) {
if ((keyFlags & KF_EXTENDED) == KF_EXTENDED) {
scanCode = MAKEWORD(scanCode, 0xe0);
} else if (scanCode == 0x45) {
scanCode = 0xe046;
}
} else {
Uint16 vkCode = LOWORD(wParam);
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
scanCode = LOWORD(MapVirtualKey(vkCode, WIN_IsWindowsXP() ? MAPVK_VK_TO_VSC : MAPVK_VK_TO_VSC_EX));
#endif
if (scanCode == 0xe11d) {
scanCode = 0xe046;
}
}
index = LOBYTE(scanCode) | (HIBYTE(scanCode) ? 0x80 : 0x00);
code = windows_scancode_table[index];
*rawcode = scanCode;
return code;
}
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
static bool WIN_ShouldIgnoreFocusClick(SDL_WindowData *data)
{
return !SDL_WINDOW_IS_POPUP(data->window) &&
!SDL_GetHintBoolean(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, false);
}
static void WIN_CheckWParamMouseButton(Uint64 timestamp, bool bwParamMousePressed, Uint32 mouseFlags, bool bSwapButtons, SDL_WindowData *data, Uint8 button, SDL_MouseID mouseID)
{
if (bSwapButtons) {
if (button == SDL_BUTTON_LEFT) {
button = SDL_BUTTON_RIGHT;
} else if (button == SDL_BUTTON_RIGHT) {
button = SDL_BUTTON_LEFT;
}
}
if (data->focus_click_pending & SDL_BUTTON_MASK(button)) {
if (!bwParamMousePressed) {
data->focus_click_pending &= ~SDL_BUTTON_MASK(button);
WIN_UpdateClipCursor(data->window);
}
return;
}
if (bwParamMousePressed && !(mouseFlags & SDL_BUTTON_MASK(button))) {
SDL_SendMouseButton(timestamp, data->window, mouseID, button, true);
} else if (!bwParamMousePressed && (mouseFlags & SDL_BUTTON_MASK(button))) {
SDL_SendMouseButton(timestamp, data->window, mouseID, button, false);
}
}
static void WIN_CheckWParamMouseButtons(Uint64 timestamp, WPARAM wParam, SDL_WindowData *data, SDL_MouseID mouseID)
{
if (wParam != data->mouse_button_flags) {
SDL_MouseButtonFlags mouseFlags = SDL_GetMouseState(NULL, NULL);
WIN_CheckWParamMouseButton(timestamp, (wParam & MK_LBUTTON), mouseFlags, false, data, SDL_BUTTON_LEFT, mouseID);
WIN_CheckWParamMouseButton(timestamp, (wParam & MK_MBUTTON), mouseFlags, false, data, SDL_BUTTON_MIDDLE, mouseID);
WIN_CheckWParamMouseButton(timestamp, (wParam & MK_RBUTTON), mouseFlags, false, data, SDL_BUTTON_RIGHT, mouseID);
WIN_CheckWParamMouseButton(timestamp, (wParam & MK_XBUTTON1), mouseFlags, false, data, SDL_BUTTON_X1, mouseID);
WIN_CheckWParamMouseButton(timestamp, (wParam & MK_XBUTTON2), mouseFlags, false, data, SDL_BUTTON_X2, mouseID);
data->mouse_button_flags = wParam;
}
}
static void WIN_CheckAsyncMouseRelease(Uint64 timestamp, SDL_WindowData *data)
{
SDL_MouseID mouseID = SDL_GLOBAL_MOUSE_ID;
Uint32 mouseFlags;
SHORT keyState;
bool swapButtons;
mouseFlags = SDL_GetMouseState(NULL, NULL);
swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0;
keyState = GetAsyncKeyState(VK_LBUTTON);
if (!(keyState & 0x8000)) {
WIN_CheckWParamMouseButton(timestamp, false, mouseFlags, swapButtons, data, SDL_BUTTON_LEFT, mouseID);
}
keyState = GetAsyncKeyState(VK_RBUTTON);
if (!(keyState & 0x8000)) {
WIN_CheckWParamMouseButton(timestamp, false, mouseFlags, swapButtons, data, SDL_BUTTON_RIGHT, mouseID);
}
keyState = GetAsyncKeyState(VK_MBUTTON);
if (!(keyState & 0x8000)) {
WIN_CheckWParamMouseButton(timestamp, false, mouseFlags, swapButtons, data, SDL_BUTTON_MIDDLE, mouseID);
}
keyState = GetAsyncKeyState(VK_XBUTTON1);
if (!(keyState & 0x8000)) {
WIN_CheckWParamMouseButton(timestamp, false, mouseFlags, swapButtons, data, SDL_BUTTON_X1, mouseID);
}
keyState = GetAsyncKeyState(VK_XBUTTON2);
if (!(keyState & 0x8000)) {
WIN_CheckWParamMouseButton(timestamp, false, mouseFlags, swapButtons, data, SDL_BUTTON_X2, mouseID);
}
data->mouse_button_flags = (WPARAM)-1;
}
static void WIN_UpdateFocus(SDL_Window *window, bool expect_focus, DWORD pos)
{
SDL_WindowData *data = window->internal;
HWND hwnd = data->hwnd;
bool had_focus = (SDL_GetKeyboardFocus() == window);
bool has_focus = (GetForegroundWindow() == hwnd);
if (had_focus == has_focus || has_focus != expect_focus) {
return;
}
if (has_focus) {
POINT cursorPos;
if (WIN_ShouldIgnoreFocusClick(data) && !(window->flags & SDL_WINDOW_MOUSE_CAPTURE)) {
bool swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0;
if (GetAsyncKeyState(VK_LBUTTON)) {
data->focus_click_pending |= !swapButtons ? SDL_BUTTON_LMASK : SDL_BUTTON_RMASK;
}
if (GetAsyncKeyState(VK_RBUTTON)) {
data->focus_click_pending |= !swapButtons ? SDL_BUTTON_RMASK : SDL_BUTTON_LMASK;
}
if (GetAsyncKeyState(VK_MBUTTON)) {
data->focus_click_pending |= SDL_BUTTON_MMASK;
}
if (GetAsyncKeyState(VK_XBUTTON1)) {
data->focus_click_pending |= SDL_BUTTON_X1MASK;
}
if (GetAsyncKeyState(VK_XBUTTON2)) {
data->focus_click_pending |= SDL_BUTTON_X2MASK;
}
}
SDL_SetKeyboardFocus(window->keyboard_focus ? window->keyboard_focus : window);
if (!SDL_GetMouse()->relative_mode) {
cursorPos.x = (LONG)GET_X_LPARAM(pos);
cursorPos.y = (LONG)GET_Y_LPARAM(pos);
ScreenToClient(hwnd, &cursorPos);
SDL_SendMouseMotion(WIN_GetEventTimestamp(), window, SDL_GLOBAL_MOUSE_ID, false, (float)cursorPos.x, (float)cursorPos.y);
}
WIN_CheckAsyncMouseRelease(WIN_GetEventTimestamp(), data);
WIN_UpdateClipCursor(window);
WIN_CheckClipboardUpdate(data->videodata);
SDL_ToggleModState(SDL_KMOD_CAPS, (GetKeyState(VK_CAPITAL) & 0x0001) ? true : false);
SDL_ToggleModState(SDL_KMOD_NUM, (GetKeyState(VK_NUMLOCK) & 0x0001) ? true : false);
SDL_ToggleModState(SDL_KMOD_SCROLL, (GetKeyState(VK_SCROLL) & 0x0001) ? true : false);
WIN_UpdateWindowICCProfile(data->window, true);
} else {
data->in_window_deactivation = true;
SDL_SetKeyboardFocus(NULL);
if (SDL_GetMouse()->relative_mode) {
SDL_SetMouseFocus(NULL);
}
WIN_ResetDeadKeys();
WIN_UnclipCursorForWindow(window);
data->in_window_deactivation = false;
}
}
#endif
static bool ShouldGenerateWindowCloseOnAltF4(void)
{
return SDL_GetHintBoolean(SDL_HINT_WINDOWS_CLOSE_ON_ALT_F4, true);
}
static bool ShouldClearWindowOnEraseBackground(SDL_WindowData *data)
{
switch (data->hint_erase_background_mode) {
case SDL_ERASEBACKGROUNDMODE_NEVER:
return false;
case SDL_ERASEBACKGROUNDMODE_INITIAL:
return !data->videodata->cleared;
case SDL_ERASEBACKGROUNDMODE_ALWAYS:
return true;
default:
return !data->videodata->cleared;
}
}
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
#define MI_WP_SIGNATURE 0xFF515700
#define MI_WP_SIGNATURE_MASK 0xFFFFFF00
#define IsTouchEvent(dw) ((dw)&MI_WP_SIGNATURE_MASK) == MI_WP_SIGNATURE
typedef enum
{
SDL_MOUSE_EVENT_SOURCE_UNKNOWN,
SDL_MOUSE_EVENT_SOURCE_MOUSE,
SDL_MOUSE_EVENT_SOURCE_TOUCH,
SDL_MOUSE_EVENT_SOURCE_PEN,
} SDL_MOUSE_EVENT_SOURCE;
static SDL_MOUSE_EVENT_SOURCE GetMouseMessageSource(ULONG extrainfo)
{
if (IsTouchEvent(extrainfo)) {
if (extrainfo & 0x80) {
return SDL_MOUSE_EVENT_SOURCE_TOUCH;
} else {
return SDL_MOUSE_EVENT_SOURCE_PEN;
}
}
return SDL_MOUSE_EVENT_SOURCE_MOUSE;
}
#endif
static SDL_WindowData *WIN_GetWindowDataFromHWND(HWND hwnd)
{
SDL_VideoDevice *_this = SDL_GetVideoDevice();
SDL_Window *window;
if (_this) {
for (window = _this->windows; window; window = window->next) {
SDL_WindowData *data = window->internal;
if (data && data->hwnd == hwnd) {
return data;
}
}
}
return NULL;
}
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
LRESULT CALLBACK
WIN_KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
KBDLLHOOKSTRUCT *hookData = (KBDLLHOOKSTRUCT *)lParam;
SDL_VideoData *data = SDL_GetVideoDevice()->internal;
SDL_Scancode scanCode;
if (nCode < 0 || nCode != HC_ACTION) {
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
if (hookData->scanCode == 0x21d) {
return 1;
}
switch (hookData->vkCode) {
case VK_LWIN:
scanCode = SDL_SCANCODE_LGUI;
break;
case VK_RWIN:
scanCode = SDL_SCANCODE_RGUI;
break;
case VK_LMENU:
scanCode = SDL_SCANCODE_LALT;
break;
case VK_RMENU:
scanCode = SDL_SCANCODE_RALT;
break;
case VK_LCONTROL:
scanCode = SDL_SCANCODE_LCTRL;
break;
case VK_RCONTROL:
scanCode = SDL_SCANCODE_RCTRL;
break;
case VK_SNAPSHOT:
scanCode = SDL_SCANCODE_PRINTSCREEN;
break;
case VK_TAB:
scanCode = SDL_SCANCODE_TAB;
break;
case VK_ESCAPE:
scanCode = SDL_SCANCODE_ESCAPE;
break;
default:
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
if (!data->raw_keyboard_enabled) {
SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, hookData->scanCode, scanCode, true);
}
} else {
if (!data->raw_keyboard_enabled) {
SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, hookData->scanCode, scanCode, false);
}
if (hookData->vkCode <= 0xFF && data->pre_hook_key_state[hookData->vkCode]) {
data->pre_hook_key_state[hookData->vkCode] = 0;
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
}
return 1;
}
static bool WIN_SwapButtons(HANDLE hDevice)
{
if (hDevice == NULL) {
return false;
}
return GetSystemMetrics(SM_SWAPBUTTON) != 0;
}
static void WIN_HandleRawMouseInput(Uint64 timestamp, SDL_VideoData *data, HANDLE hDevice, RAWMOUSE *rawmouse)
{
static struct {
USHORT usButtonFlags;
Uint8 button;
bool down;
} raw_buttons[] = {
{ RI_MOUSE_LEFT_BUTTON_DOWN, SDL_BUTTON_LEFT, true },
{ RI_MOUSE_LEFT_BUTTON_UP, SDL_BUTTON_LEFT, false },
{ RI_MOUSE_RIGHT_BUTTON_DOWN, SDL_BUTTON_RIGHT, true },
{ RI_MOUSE_RIGHT_BUTTON_UP, SDL_BUTTON_RIGHT, false },
{ RI_MOUSE_MIDDLE_BUTTON_DOWN, SDL_BUTTON_MIDDLE, true },
{ RI_MOUSE_MIDDLE_BUTTON_UP, SDL_BUTTON_MIDDLE, false },
{ RI_MOUSE_BUTTON_4_DOWN, SDL_BUTTON_X1, true },
{ RI_MOUSE_BUTTON_4_UP, SDL_BUTTON_X1, false },
{ RI_MOUSE_BUTTON_5_DOWN, SDL_BUTTON_X2, true },
{ RI_MOUSE_BUTTON_5_UP, SDL_BUTTON_X2, false }
};
int dx = (int)rawmouse->lLastX;
int dy = (int)rawmouse->lLastY;
bool haveMotion = (dx || dy);
bool haveButton = (rawmouse->usButtonFlags != 0);
bool isAbsolute = ((rawmouse->usFlags & MOUSE_MOVE_ABSOLUTE) != 0);
SDL_MouseID mouseID = (SDL_MouseID)(uintptr_t)hDevice;
if (!data->raw_mouse_enabled) {
return;
}
SDL_Window *window = SDL_GetKeyboardFocus();
if (!window) {
return;
}
SDL_Mouse *mouse = SDL_GetMouse();
if (!mouse) {
return;
}
if (GetMouseMessageSource(rawmouse->ulExtraInformation) != SDL_MOUSE_EVENT_SOURCE_MOUSE ||
(SDL_TouchDevicesAvailable() && (rawmouse->ulExtraInformation & 0x80) == 0x80)) {
return;
}
SDL_WindowData *windowdata = window->internal;
if (haveMotion && !windowdata->in_modal_loop) {
if (!isAbsolute) {
SDL_SendMouseMotion(timestamp, window, mouseID, true, (float)dx, (float)dy);
} else {
bool remote_desktop = (GetSystemMetrics(SM_REMOTESESSION) == TRUE);
bool virtual_desktop = ((rawmouse->usFlags & MOUSE_VIRTUAL_DESKTOP) != 0);
bool raw_coordinates = ((rawmouse->usFlags & 0x40) != 0);
int w = GetSystemMetrics(virtual_desktop ? SM_CXVIRTUALSCREEN : SM_CXSCREEN);
int h = GetSystemMetrics(virtual_desktop ? SM_CYVIRTUALSCREEN : SM_CYSCREEN);
int x = raw_coordinates ? dx : (int)(((float)dx / 65535.0f) * w);
int y = raw_coordinates ? dy : (int)(((float)dy / 65535.0f) * h);
int relX, relY;
if (data->last_raw_mouse_position.x == 0 && data->last_raw_mouse_position.y == 0) {
data->last_raw_mouse_position.x = x;
data->last_raw_mouse_position.y = y;
}
relX = x - data->last_raw_mouse_position.x;
relY = y - data->last_raw_mouse_position.y;
if (remote_desktop) {
if (!windowdata->in_title_click && !windowdata->focus_click_pending) {
static int wobble;
float floatX = (float)x / w;
float floatY = (float)y / h;
if (floatX <= 0.01f || floatX >= 0.99f || floatY <= 0.01f || floatY >= 0.99f || y < 32) {
RECT rect = windowdata->cursor_clipped_rect;
int warpX = rect.left + ((rect.right - rect.left) / 2) + wobble;
int warpY = rect.top + ((rect.bottom - rect.top) / 2);
WIN_SetCursorPos(warpX, warpY);
++wobble;
if (wobble > 1) {
wobble = -1;
}
} else {
const int MAX_RELATIVE_MOTION = (h / 6);
if (SDL_abs(relX) < MAX_RELATIVE_MOTION &&
SDL_abs(relY) < MAX_RELATIVE_MOTION) {
SDL_SendMouseMotion(timestamp, window, mouseID, true, (float)relX, (float)relY);
}
}
}
} else if (mouse->pen_mouse_events) {
const int MAXIMUM_TABLET_RELATIVE_MOTION = 32;
if (SDL_abs(relX) > MAXIMUM_TABLET_RELATIVE_MOTION ||
SDL_abs(relY) > MAXIMUM_TABLET_RELATIVE_MOTION) {
} else {
SDL_SendMouseMotion(timestamp, window, mouseID, true, (float)relX, (float)relY);
}
} else {
int screen_x = virtual_desktop ? GetSystemMetrics(SM_XVIRTUALSCREEN) : 0;
int screen_y = virtual_desktop ? GetSystemMetrics(SM_YVIRTUALSCREEN) : 0;
if (!data->raw_input_fake_pen_id) {
data->raw_input_fake_pen_id = SDL_AddPenDevice(timestamp, "raw mouse input", window, NULL, (void *)(size_t)-1, true);
}
SDL_SendPenMotion(timestamp, data->raw_input_fake_pen_id, window, (float)(x + screen_x - window->x), (float)(y + screen_y - window->y));
}
data->last_raw_mouse_position.x = x;
data->last_raw_mouse_position.y = y;
}
}
if (haveButton) {
for (int i = 0; i < SDL_arraysize(raw_buttons); ++i) {
if (rawmouse->usButtonFlags & raw_buttons[i].usButtonFlags) {
Uint8 button = raw_buttons[i].button;
bool down = raw_buttons[i].down;
if (button == SDL_BUTTON_LEFT) {
if (WIN_SwapButtons(hDevice)) {
button = SDL_BUTTON_RIGHT;
}
} else if (button == SDL_BUTTON_RIGHT) {
if (WIN_SwapButtons(hDevice)) {
button = SDL_BUTTON_LEFT;
}
}
if (windowdata->focus_click_pending & SDL_BUTTON_MASK(button)) {
if (!down) {
windowdata->focus_click_pending &= ~SDL_BUTTON_MASK(button);
WIN_UpdateClipCursor(window);
}
continue;
}
SDL_SendMouseButton(timestamp, window, mouseID, button, down);
}
}
if (rawmouse->usButtonFlags & RI_MOUSE_WHEEL) {
SHORT amount = (SHORT)rawmouse->usButtonData;
float fAmount = (float)amount / WHEEL_DELTA;
SDL_SendMouseWheel(WIN_GetEventTimestamp(), window, mouseID, 0.0f, fAmount, SDL_MOUSEWHEEL_NORMAL);
} else if (rawmouse->usButtonFlags & RI_MOUSE_HWHEEL) {
SHORT amount = (SHORT)rawmouse->usButtonData;
float fAmount = (float)amount / WHEEL_DELTA;
SDL_SendMouseWheel(WIN_GetEventTimestamp(), window, mouseID, fAmount, 0.0f, SDL_MOUSEWHEEL_NORMAL);
}
windowdata->mouse_button_flags = (WPARAM)-1;
}
}
static void WIN_HandleRawKeyboardInput(Uint64 timestamp, SDL_VideoData *data, HANDLE hDevice, RAWKEYBOARD *rawkeyboard)
{
SDL_KeyboardID keyboardID = (SDL_KeyboardID)(uintptr_t)hDevice;
if (!data->raw_keyboard_enabled) {
return;
}
if (rawkeyboard->Flags & RI_KEY_E1) {
data->pending_E1_key_sequence = true;
return;
}
if ((rawkeyboard->Flags & RI_KEY_E0) && rawkeyboard->MakeCode == 0x2A) {
return;
}
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
if (!rawkeyboard->MakeCode) {
rawkeyboard->MakeCode = LOWORD(MapVirtualKey(rawkeyboard->VKey, WIN_IsWindowsXP() ? MAPVK_VK_TO_VSC : MAPVK_VK_TO_VSC_EX));
}
#endif
if (!rawkeyboard->MakeCode) {
return;
}
bool down = !(rawkeyboard->Flags & RI_KEY_BREAK);
SDL_Scancode code;
USHORT rawcode = rawkeyboard->MakeCode;
if (data->pending_E1_key_sequence) {
rawcode |= 0xE100;
if (rawkeyboard->MakeCode == 0x45) {
code = SDL_SCANCODE_PAUSE;
} else {
code = SDL_SCANCODE_UNKNOWN;
}
data->pending_E1_key_sequence = false;
} else {
Uint8 index = (Uint8)rawkeyboard->MakeCode;
if (rawkeyboard->Flags & RI_KEY_E0) {
rawcode |= 0xE000;
index |= 0x80;
}
code = windows_scancode_table[index];
}
if (down) {
SDL_Window *focus = SDL_GetKeyboardFocus();
if ((!data->raw_keyboard_flag_inputsink && !focus) || (focus && focus->text_input_active)) {
return;
}
}
SDL_SendKeyboardKey(timestamp, keyboardID, rawcode, code, down);
}
void WIN_PollRawInput(SDL_VideoDevice *_this, Uint64 poll_start)
{
SDL_VideoData *data = _this->internal;
UINT size, i, count, total = 0;
RAWINPUT *input;
Uint64 poll_finish;
if (data->rawinput_offset == 0) {
BOOL isWow64;
data->rawinput_offset = sizeof(RAWINPUTHEADER);
if (IsWow64Process(GetCurrentProcess(), &isWow64) && isWow64) {
data->rawinput_offset += 8;
}
}
input = (RAWINPUT *)data->rawinput;
for (;;) {
size = data->rawinput_size - (UINT)((BYTE *)input - data->rawinput);
count = GetRawInputBuffer(input, &size, sizeof(RAWINPUTHEADER));
poll_finish = SDL_GetTicksNS();
if (count == 0 || count == (UINT)-1) {
if (!data->rawinput || (count == (UINT)-1 && GetLastError() == ERROR_INSUFFICIENT_BUFFER)) {
const UINT RAWINPUT_BUFFER_SIZE_INCREMENT = 96; BYTE *rawinput = (BYTE *)SDL_realloc(data->rawinput, data->rawinput_size + RAWINPUT_BUFFER_SIZE_INCREMENT);
if (!rawinput) {
break;
}
input = (RAWINPUT *)(rawinput + ((BYTE *)input - data->rawinput));
data->rawinput = rawinput;
data->rawinput_size += RAWINPUT_BUFFER_SIZE_INCREMENT;
} else {
break;
}
} else {
total += count;
while (count--) {
input = NEXTRAWINPUTBLOCK(input);
}
}
}
if (total > 0) {
Uint64 delta = poll_finish - poll_start;
UINT mouse_total = 0;
for (i = 0, input = (RAWINPUT *)data->rawinput; i < total; ++i, input = NEXTRAWINPUTBLOCK(input)) {
if (input->header.dwType == RIM_TYPEMOUSE) {
mouse_total += 1;
}
}
int mouse_index = 0;
for (i = 0, input = (RAWINPUT *)data->rawinput; i < total; ++i, input = NEXTRAWINPUTBLOCK(input)) {
if (input->header.dwType == RIM_TYPEMOUSE) {
mouse_index += 1; RAWMOUSE *rawmouse = (RAWMOUSE *)((BYTE *)input + data->rawinput_offset);
Uint64 time = poll_finish - (delta * (mouse_total - mouse_index)) / mouse_total;
WIN_HandleRawMouseInput(time, data, input->header.hDevice, rawmouse);
} else if (input->header.dwType == RIM_TYPEKEYBOARD) {
RAWKEYBOARD *rawkeyboard = (RAWKEYBOARD *)((BYTE *)input + data->rawinput_offset);
WIN_HandleRawKeyboardInput(poll_finish, data, input->header.hDevice, rawkeyboard);
}
}
}
data->last_rawinput_poll = poll_finish;
}
static void AddDeviceID(Uint32 deviceID, Uint32 **list, int *count)
{
int new_count = (*count + 1);
Uint32 *new_list = (Uint32 *)SDL_realloc(*list, new_count * sizeof(*new_list));
if (!new_list) {
return;
}
new_list[new_count - 1] = deviceID;
*count = new_count;
*list = new_list;
}
static bool HasDeviceID(Uint32 deviceID, const Uint32 *list, int count)
{
for (int i = 0; i < count; ++i) {
if (deviceID == list[i]) {
return true;
}
}
return false;
}
static char *GetDeviceName(HANDLE hDevice, HDEVINFO devinfo, const char *instance, Uint16 vendor, Uint16 product, const char *default_name, bool hid_loaded)
{
char *vendor_name = NULL;
char *product_name = NULL;
char *name = NULL;
WCHAR vend[256], prod[256];
vend[0] = 0;
prod[0] = 0;
if (hid_loaded) {
char devName[MAX_PATH + 1];
devName[0] = '\0';
UINT cap = sizeof(devName) - 1;
UINT len = GetRawInputDeviceInfoA(hDevice, RIDI_DEVICENAME, devName, &cap);
if (len != (UINT)-1) {
devName[len] = '\0';
HANDLE hFile = CreateFileA(devName, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (hFile != INVALID_HANDLE_VALUE) {
SDL_HidD_GetManufacturerString(hFile, vend, sizeof(vend));
SDL_HidD_GetProductString(hFile, prod, sizeof(prod));
CloseHandle(hFile);
}
}
}
if (vend[0]) {
vendor_name = WIN_StringToUTF8W(vend);
}
if (prod[0]) {
product_name = WIN_StringToUTF8W(prod);
} else {
SP_DEVINFO_DATA data;
SDL_zero(data);
data.cbSize = sizeof(data);
for (DWORD i = 0;; ++i) {
if (!SetupDiEnumDeviceInfo(devinfo, i, &data)) {
if (GetLastError() == ERROR_NO_MORE_ITEMS) {
break;
} else {
continue;
}
}
char DeviceInstanceId[64];
if (!SetupDiGetDeviceInstanceIdA(devinfo, &data, DeviceInstanceId, sizeof(DeviceInstanceId), NULL))
continue;
if (SDL_strcasecmp(instance, DeviceInstanceId) == 0) {
DWORD size = 0;
if (SetupDiGetDeviceRegistryPropertyW(devinfo, &data, SPDRP_DEVICEDESC, NULL, (PBYTE)prod, sizeof(prod), &size)) {
size /= sizeof(*prod);
if (size >= SDL_arraysize(prod)) {
size = (SDL_arraysize(prod) - 1);
}
prod[size] = 0;
if (vendor || product) {
SDL_asprintf(&product_name, "%S (0x%.4x/0x%.4x)", prod, vendor, product);
} else {
product_name = WIN_StringToUTF8W(prod);
}
}
break;
}
}
}
if (!product_name && (vendor || product)) {
SDL_asprintf(&product_name, "%s (0x%.4x/0x%.4x)", default_name, vendor, product);
}
name = SDL_CreateDeviceName(vendor, product, vendor_name, product_name, default_name);
SDL_free(vendor_name);
SDL_free(product_name);
return name;
}
void WIN_CheckKeyboardAndMouseHotplug(bool hid_loaded)
{
SDL_VideoDevice *_this = SDL_GetVideoDevice();
PRAWINPUTDEVICELIST raw_devices = NULL;
UINT raw_device_count = 0;
int old_keyboard_count = 0;
SDL_KeyboardID *old_keyboards = NULL;
int new_keyboard_count = 0;
SDL_KeyboardID *new_keyboards = NULL;
int old_mouse_count = 0;
SDL_MouseID *old_mice = NULL;
int new_mouse_count = 0;
SDL_MouseID *new_mice = NULL;
if (!_this ||
SDL_strcmp(_this->name, "windows") != 0 ||
!_this->internal->detect_device_hotplug ||
_this->internal->gameinput_context) {
return;
}
if ((GetRawInputDeviceList(NULL, &raw_device_count, sizeof(RAWINPUTDEVICELIST)) == -1) || (!raw_device_count)) {
return; }
raw_devices = (PRAWINPUTDEVICELIST)SDL_malloc(sizeof(RAWINPUTDEVICELIST) * raw_device_count);
if (!raw_devices) {
return; }
raw_device_count = GetRawInputDeviceList(raw_devices, &raw_device_count, sizeof(RAWINPUTDEVICELIST));
if (raw_device_count == (UINT)-1) {
SDL_free(raw_devices);
raw_devices = NULL;
return; }
HDEVINFO devinfo = SetupDiGetClassDevsA(NULL, NULL, NULL, (DIGCF_ALLCLASSES | DIGCF_PRESENT));
old_keyboards = SDL_GetKeyboards(&old_keyboard_count);
old_mice = SDL_GetMice(&old_mouse_count);
for (UINT i = 0; i < raw_device_count; i++) {
RID_DEVICE_INFO rdi;
char devName[MAX_PATH] = { 0 };
UINT rdiSize = sizeof(rdi);
UINT nameSize = SDL_arraysize(devName);
int vendor = 0, product = 0;
DWORD dwType = raw_devices[i].dwType;
char *instance, *ptr, *name;
if (dwType != RIM_TYPEKEYBOARD && dwType != RIM_TYPEMOUSE) {
continue;
}
rdi.cbSize = sizeof(rdi);
if (GetRawInputDeviceInfoA(raw_devices[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) == ((UINT)-1) ||
GetRawInputDeviceInfoA(raw_devices[i].hDevice, RIDI_DEVICENAME, devName, &nameSize) == ((UINT)-1)) {
continue;
}
instance = devName;
while (*instance == '\\' || *instance == '?') {
++instance;
}
for (ptr = instance; *ptr; ++ptr) {
if (*ptr == '#') {
*ptr = '\\';
}
if (*ptr == '{') {
if (ptr > instance && ptr[-1] == '\\') {
--ptr;
}
break;
}
}
*ptr = '\0';
SDL_sscanf(instance, "HID\\VID_%X&PID_%X&", &vendor, &product);
switch (dwType) {
case RIM_TYPEKEYBOARD:
if (SDL_IsKeyboard((Uint16)vendor, (Uint16)product, rdi.keyboard.dwNumberOfKeysTotal)) {
SDL_KeyboardID keyboardID = (Uint32)(uintptr_t)raw_devices[i].hDevice;
AddDeviceID(keyboardID, &new_keyboards, &new_keyboard_count);
if (!HasDeviceID(keyboardID, old_keyboards, old_keyboard_count)) {
name = GetDeviceName(raw_devices[i].hDevice, devinfo, instance, (Uint16)vendor, (Uint16)product, "Keyboard", hid_loaded);
SDL_AddKeyboard(keyboardID, name);
SDL_free(name);
}
}
break;
case RIM_TYPEMOUSE:
if (SDL_IsMouse((Uint16)vendor, (Uint16)product)) {
SDL_MouseID mouseID = (Uint32)(uintptr_t)raw_devices[i].hDevice;
AddDeviceID(mouseID, &new_mice, &new_mouse_count);
if (!HasDeviceID(mouseID, old_mice, old_mouse_count)) {
name = GetDeviceName(raw_devices[i].hDevice, devinfo, instance, (Uint16)vendor, (Uint16)product, "Mouse", hid_loaded);
SDL_AddMouse(mouseID, name);
SDL_free(name);
}
}
break;
default:
break;
}
}
for (int i = old_keyboard_count; i--;) {
if (!HasDeviceID(old_keyboards[i], new_keyboards, new_keyboard_count)) {
SDL_RemoveKeyboard(old_keyboards[i]);
}
}
for (int i = old_mouse_count; i--;) {
if (!HasDeviceID(old_mice[i], new_mice, new_mouse_count)) {
SDL_RemoveMouse(old_mice[i]);
}
}
SDL_free(old_keyboards);
SDL_free(new_keyboards);
SDL_free(old_mice);
SDL_free(new_mice);
SetupDiDestroyDeviceInfoList(devinfo);
SDL_free(raw_devices);
}
#endif
static bool SkipAltGrLeftControl(WPARAM wParam, LPARAM lParam)
{
if (wParam != VK_CONTROL) {
return false;
}
if (lParam & 0x01000000) {
return false;
}
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
MSG next_msg;
DWORD msg_time = GetMessageTime();
if (PeekMessage(&next_msg, NULL, 0, 0, PM_NOREMOVE)) {
if (next_msg.message == WM_KEYDOWN ||
next_msg.message == WM_SYSKEYDOWN) {
if (next_msg.wParam == VK_MENU && (next_msg.lParam & 0x01000000) && next_msg.time == msg_time) {
return true;
}
}
}
#endif
return false;
}
static bool DispatchModalLoopMessageHook(HWND *hwnd, UINT *msg, WPARAM *wParam, LPARAM *lParam)
{
MSG dummy;
SDL_zero(dummy);
dummy.hwnd = *hwnd;
dummy.message = *msg;
dummy.wParam = *wParam;
dummy.lParam = *lParam;
if (g_WindowsMessageHook(g_WindowsMessageHookData, &dummy)) {
*msg = dummy.message;
*wParam = dummy.wParam;
*lParam = dummy.lParam;
return true;
}
return false;
}
LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
SDL_WindowData *data;
LRESULT returnCode = -1;
data = WIN_GetWindowDataFromHWND(hwnd);
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
if (!data) {
data = (SDL_WindowData *)GetProp(hwnd, TEXT("SDL_WindowData"));
}
#endif
if (!data) {
return CallWindowProc(DefWindowProc, hwnd, msg, wParam, lParam);
}
#ifdef WMMSG_DEBUG
{
char message[1024];
if (msg > MAX_WMMSG) {
SDL_snprintf(message, sizeof(message), "Received windows message: %p UNKNOWN (%d) -- 0x%x, 0x%x\r\n", hwnd, msg, wParam, lParam);
} else {
SDL_snprintf(message, sizeof(message), "Received windows message: %p %s -- 0x%x, 0x%x\r\n", hwnd, wmtab[msg], wParam, lParam);
}
OutputDebugStringA(message);
}
#endif
if (g_WindowsMessageHook && data->in_modal_loop) {
if (!DispatchModalLoopMessageHook(&hwnd, &msg, &wParam, &lParam)) {
return 0;
}
}
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
if (WIN_HandleIMEMessage(hwnd, msg, wParam, &lParam, data->videodata)) {
return 0;
}
#endif
switch (msg) {
case WM_SHOWWINDOW:
{
if (wParam) {
SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_SHOWN, 0, 0);
} else {
SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_HIDDEN, 0, 0);
}
} break;
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
case WM_NCACTIVATE:
{
data->postpone_clipcursor = true;
data->clipcursor_queued = true;
WIN_UpdateFocus(data->window, !!wParam, GetMessagePos());
if (data->window->flags & SDL_WINDOW_BORDERLESS) {
lParam = -1; }
} break;
case WM_NCUAHDRAWCAPTION:
case WM_NCUAHDRAWFRAME:
{
if (data->window->flags & SDL_WINDOW_BORDERLESS) {
returnCode = 0;
}
} break;
case WM_ACTIVATE:
{
WIN_UpdateFocus(data->window, !!LOWORD(wParam), GetMessagePos());
} break;
case WM_MOUSEACTIVATE:
{
if (SDL_WINDOW_IS_POPUP(data->window)) {
return MA_NOACTIVATE;
}
SDL_Window *parent = data->window->parent;
while (parent) {
if ((parent->flags & SDL_WINDOW_INPUT_FOCUS) &&
(parent->flags & SDL_WINDOW_MOUSE_RELATIVE_MODE)) {
return MA_NOACTIVATE;
}
parent = parent->parent;
}
} break;
case WM_SETFOCUS:
{
WIN_UpdateFocus(data->window, true, GetMessagePos());
} break;
case WM_KILLFOCUS:
case WM_ENTERIDLE:
{
WIN_UpdateFocus(data->window, false, GetMessagePos());
} break;
case WM_POINTERENTER:
{
const UINT32 pointerid = GET_POINTERID_WPARAM(wParam);
POINTER_INPUT_TYPE pointer_type = PT_POINTER;
if (!data->videodata->GetPointerType) {
break; } else if (!data->videodata->GetPointerType(pointerid, &pointer_type)) {
break; } else if (pointer_type != PT_PEN) {
break; }
void *hpointer = (void *)(size_t)1; const SDL_PenID pen = SDL_FindPenByHandle(hpointer);
if (pen) {
SDL_SendPenProximity(WIN_GetEventTimestamp(), pen, data->window, true, true);
} else {
SDL_PenInfo info;
SDL_zero(info);
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;
info.max_tilt = 90.0f;
info.num_buttons = 1;
info.subtype = SDL_PEN_TYPE_PENCIL;
SDL_AddPenDevice(WIN_GetEventTimestamp(), NULL, data->window, &info, hpointer, true);
}
returnCode = 0;
} break;
case WM_POINTERCAPTURECHANGED:
case WM_POINTERLEAVE:
{
const UINT32 pointerid = GET_POINTERID_WPARAM(wParam);
POINTER_INPUT_TYPE pointer_type = PT_POINTER;
if (!data->videodata->GetPointerType) {
break; } else if (!data->videodata->GetPointerType(pointerid, &pointer_type)) {
break; } else if (pointer_type != PT_PEN) {
break; }
void *hpointer = (void *)(size_t)1; const SDL_PenID pen = SDL_FindPenByHandle(hpointer);
if (pen == 0) {
break; }
if ((msg == WM_POINTERCAPTURECHANGED) || !IS_POINTER_INCONTACT_WPARAM(wParam)) {
SDL_SendPenProximity(WIN_GetEventTimestamp(), pen, data->window, false, false);
}
returnCode = 0;
} break;
case WM_POINTERDOWN:
case WM_POINTERUP:
case WM_POINTERUPDATE: {
const UINT32 pointerid = GET_POINTERID_WPARAM(wParam);
POINTER_INPUT_TYPE pointer_type = PT_POINTER;
if (!data->videodata->GetPointerType || !data->videodata->GetPointerType(pointerid, &pointer_type)) {
break; } else if ((msg == WM_POINTERUPDATE) && (pointer_type == PT_MOUSE)) {
data->last_pointer_update = lParam;
returnCode = 0;
break;
} else if (pointer_type != PT_PEN) {
break; }
void *hpointer = (void *)(size_t)1; const SDL_PenID pen = SDL_FindPenByHandle(hpointer);
POINTER_PEN_INFO pen_info;
if (pen == 0) {
break; } else if (!data->videodata->GetPointerPenInfo || !data->videodata->GetPointerPenInfo(pointerid, &pen_info)) {
break; }
const Uint64 timestamp = WIN_GetEventTimestamp();
SDL_Window *window = data->window;
const bool istouching = IS_POINTER_INCONTACT_WPARAM(wParam) && IS_POINTER_FIRSTBUTTON_WPARAM(wParam);
if (!istouching) {
SDL_SendPenTouch(timestamp, pen, window, (pen_info.penFlags & PEN_FLAG_INVERTED) != 0, false);
}
const POINTER_INFO *pointer_info = &pen_info.pointerInfo;
RECT tablet_bounds, tablet_mapping;
float fx, fy;
if (!data->videodata->GetPointerDeviceRects || !data->videodata->GetPointerDeviceRects(pointer_info->sourceDevice, &tablet_bounds, &tablet_mapping)) {
POINT position = { (LONG) GET_X_LPARAM(lParam), (LONG) GET_Y_LPARAM(lParam) };
ScreenToClient(data->hwnd, &position);
fx = (float) position.x;
fy = (float) position.y;
} else {
int ix, iy;
SDL_GetWindowPosition(window, &ix, &iy);
const SDL_FPoint window_pos = { (float) ix, (float) iy };
const float facX = pointer_info->ptHimetricLocationRaw.x / (float) (tablet_bounds.right );
const float facY = pointer_info->ptHimetricLocationRaw.y / (float) (tablet_bounds.bottom);
const float w = tablet_mapping.right - tablet_mapping.left;
const float h = tablet_mapping.bottom - tablet_mapping.top;
fx = (tablet_mapping.left + (facX * w)) - window_pos.x;
fy = (tablet_mapping.top + (facY * h)) - window_pos.y;
}
SDL_SendPenMotion(timestamp, pen, window, fx, fy);
SDL_SendPenButton(timestamp, pen, window, 1, (pen_info.penFlags & PEN_FLAG_BARREL) != 0);
SDL_SendPenButton(timestamp, pen, window, 2, (pen_info.penFlags & PEN_FLAG_ERASER) != 0);
if (pen_info.penMask & PEN_MASK_PRESSURE) {
SDL_SendPenAxis(timestamp, pen, window, SDL_PEN_AXIS_PRESSURE, ((float) pen_info.pressure) / 1024.0f); }
if (pen_info.penMask & PEN_MASK_ROTATION) {
SDL_SendPenAxis(timestamp, pen, window, SDL_PEN_AXIS_ROTATION, ((float) pen_info.rotation)); }
if (pen_info.penMask & PEN_MASK_TILT_X) {
SDL_SendPenAxis(timestamp, pen, window, SDL_PEN_AXIS_XTILT, ((float) pen_info.tiltX)); }
if (pen_info.penMask & PEN_MASK_TILT_Y) {
SDL_SendPenAxis(timestamp, pen, window, SDL_PEN_AXIS_YTILT, ((float) pen_info.tiltY)); }
if (istouching) {
SDL_SendPenTouch(timestamp, pen, window, (pen_info.penFlags & PEN_FLAG_INVERTED) != 0, true);
}
returnCode = 0;
} break;
case WM_MOUSEMOVE:
{
SDL_Window *window = data->window;
if (window->flags & SDL_WINDOW_INPUT_FOCUS) {
bool wish_clip_cursor = (
window->flags & (SDL_WINDOW_MOUSE_RELATIVE_MODE | SDL_WINDOW_MOUSE_GRABBED) ||
(window->mouse_rect.w > 0 && window->mouse_rect.h > 0)
);
if (wish_clip_cursor) { data->clipcursor_queued = true;
}
}
if (!data->mouse_tracked) {
TRACKMOUSEEVENT trackMouseEvent;
trackMouseEvent.cbSize = sizeof(TRACKMOUSEEVENT);
trackMouseEvent.dwFlags = TME_LEAVE;
trackMouseEvent.hwndTrack = data->hwnd;
if (TrackMouseEvent(&trackMouseEvent)) {
data->mouse_tracked = true;
}
WIN_CheckAsyncMouseRelease(WIN_GetEventTimestamp(), data);
}
if (!data->videodata->raw_mouse_enabled) {
if (GetMouseMessageSource((ULONG)GetMessageExtraInfo()) == SDL_MOUSE_EVENT_SOURCE_MOUSE &&
lParam != data->last_pointer_update) {
SDL_SendMouseMotion(WIN_GetEventTimestamp(), window, SDL_GLOBAL_MOUSE_ID, false, (float)GET_X_LPARAM(lParam), (float)GET_Y_LPARAM(lParam));
}
}
} break;
case WM_LBUTTONUP:
case WM_RBUTTONUP:
case WM_MBUTTONUP:
case WM_XBUTTONUP:
case WM_LBUTTONDOWN:
case WM_LBUTTONDBLCLK:
case WM_RBUTTONDOWN:
case WM_RBUTTONDBLCLK:
case WM_MBUTTONDOWN:
case WM_MBUTTONDBLCLK:
case WM_XBUTTONDOWN:
case WM_XBUTTONDBLCLK:
{
if (!data->videodata->raw_mouse_enabled) {
if (GetMouseMessageSource((ULONG)GetMessageExtraInfo()) == SDL_MOUSE_EVENT_SOURCE_MOUSE &&
lParam != data->last_pointer_update) {
WIN_CheckWParamMouseButtons(WIN_GetEventTimestamp(), wParam, data, SDL_GLOBAL_MOUSE_ID);
}
}
} break;
#if 0#endif
case WM_MOUSEWHEEL:
case WM_MOUSEHWHEEL:
{
if (!data->videodata->raw_mouse_enabled) {
short amount = GET_WHEEL_DELTA_WPARAM(wParam);
float fAmount = (float)amount / WHEEL_DELTA;
if (msg == WM_MOUSEWHEEL) {
SDL_SendMouseWheel(WIN_GetEventTimestamp(), data->window, SDL_GLOBAL_MOUSE_ID, 0.0f, fAmount, SDL_MOUSEWHEEL_NORMAL);
} else {
SDL_SendMouseWheel(WIN_GetEventTimestamp(), data->window, SDL_GLOBAL_MOUSE_ID, fAmount, 0.0f, SDL_MOUSEWHEEL_NORMAL);
}
}
} break;
case WM_MOUSELEAVE:
if (!(data->window->flags & SDL_WINDOW_MOUSE_CAPTURE)) {
if (SDL_GetMouseFocus() == data->window && !SDL_GetMouse()->relative_mode && !IsIconic(hwnd)) {
SDL_Mouse *mouse;
DWORD pos = GetMessagePos();
POINT cursorPos;
cursorPos.x = GET_X_LPARAM(pos);
cursorPos.y = GET_Y_LPARAM(pos);
ScreenToClient(hwnd, &cursorPos);
mouse = SDL_GetMouse();
if (!mouse->was_touch_mouse_events) { SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, SDL_GLOBAL_MOUSE_ID, false, (float)cursorPos.x, (float)cursorPos.y);
} else { mouse->was_touch_mouse_events = false; if (mouse->touch_mouse_events) { SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, SDL_TOUCH_MOUSEID, false, (float)cursorPos.x, (float)cursorPos.y);
} else { SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, SDL_GLOBAL_MOUSE_ID, false, (float)cursorPos.x, (float)cursorPos.y);
}
}
}
if (!SDL_GetMouse()->relative_mode) {
SDL_SetMouseFocus(NULL);
}
}
data->mouse_tracked = false;
returnCode = 0;
break;
#endif
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
{
if (SkipAltGrLeftControl(wParam, lParam)) {
returnCode = 0;
break;
}
bool virtual_key = false;
Uint16 rawcode = 0;
SDL_Scancode code = WindowsScanCodeToSDLScanCode(lParam, wParam, &rawcode, &virtual_key);
if (code == SDL_SCANCODE_F4 && (SDL_GetModState() & SDL_KMOD_ALT)) {
if (ShouldGenerateWindowCloseOnAltF4()) {
SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_CLOSE_REQUESTED, 0, 0);
}
}
if (virtual_key || !data->videodata->raw_keyboard_enabled || data->window->text_input_active) {
SDL_SendKeyboardKey(WIN_GetEventTimestamp(), SDL_GLOBAL_KEYBOARD_ID, rawcode, code, true);
}
}
returnCode = 0;
break;
case WM_SYSKEYUP:
case WM_KEYUP:
{
if (SkipAltGrLeftControl(wParam, lParam)) {
returnCode = 0;
break;
}
bool virtual_key = false;
Uint16 rawcode = 0;
SDL_Scancode code = WindowsScanCodeToSDLScanCode(lParam, wParam, &rawcode, &virtual_key);
const bool *keyboardState = SDL_GetKeyboardState(NULL);
if (virtual_key || !data->videodata->raw_keyboard_enabled || data->window->text_input_active) {
if (code == SDL_SCANCODE_PRINTSCREEN && !keyboardState[code]) {
SDL_SendKeyboardKey(WIN_GetEventTimestamp(), SDL_GLOBAL_KEYBOARD_ID, rawcode, code, true);
}
SDL_SendKeyboardKey(WIN_GetEventTimestamp(), SDL_GLOBAL_KEYBOARD_ID, rawcode, code, false);
}
}
returnCode = 0;
break;
case WM_UNICHAR:
if (wParam == UNICODE_NOCHAR) {
returnCode = 1;
} else {
if (SDL_TextInputActive(data->window)) {
char text[5];
char *end = SDL_UCS4ToUTF8((Uint32)wParam, text);
*end = '\0';
SDL_SendKeyboardText(text);
}
returnCode = 0;
}
break;
case WM_CHAR:
if (SDL_TextInputActive(data->window)) {
if (IS_HIGH_SURROGATE(wParam)) {
data->high_surrogate = (WCHAR)wParam;
} else {
WCHAR utf16[3];
utf16[0] = data->high_surrogate ? data->high_surrogate : (WCHAR)wParam;
utf16[1] = data->high_surrogate ? (WCHAR)wParam : L'\0';
utf16[2] = L'\0';
char utf8[5];
int result = WIN_WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16, -1, utf8, sizeof(utf8), NULL, NULL);
if (result > 0) {
SDL_SendKeyboardText(utf8);
}
data->high_surrogate = L'\0';
}
} else {
data->high_surrogate = L'\0';
}
returnCode = 0;
break;
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
#ifdef WM_INPUTLANGCHANGE
case WM_INPUTLANGCHANGE:
{
WIN_UpdateKeymap(true);
}
returnCode = 1;
break;
#endif
case WM_NCLBUTTONDOWN:
{
data->in_title_click = true;
if (SendMessage(hwnd, WM_NCHITTEST, wParam, lParam) == HTCAPTION) {
POINT cursorPos;
GetCursorPos(&cursorPos); ScreenToClient(hwnd, &cursorPos);
PostMessage(hwnd, WM_MOUSEMOVE, 0, cursorPos.x | (((Uint32)((Sint16)cursorPos.y)) << 16));
}
} break;
case WM_CAPTURECHANGED:
{
data->in_title_click = false;
WIN_CheckAsyncMouseRelease(WIN_GetEventTimestamp(), data);
} break;
#ifdef WM_GETMINMAXINFO
case WM_GETMINMAXINFO:
{
MINMAXINFO *info;
RECT size;
int x, y;
int w, h;
int min_w, min_h;
int max_w, max_h;
BOOL constrain_max_size;
if (data->expected_resize) {
break;
}
GetWindowRect(hwnd, &size);
x = size.left;
y = size.top;
SDL_GetWindowSize(data->window, &w, &h);
SDL_GetWindowMinimumSize(data->window, &min_w, &min_h);
SDL_GetWindowMaximumSize(data->window, &max_w, &max_h);
min_w -= w;
min_h -= h;
if (max_w && max_h) {
max_w -= w;
max_h -= h;
constrain_max_size = TRUE;
} else {
constrain_max_size = FALSE;
}
if (!(SDL_GetWindowFlags(data->window) & SDL_WINDOW_BORDERLESS) && !SDL_WINDOW_IS_POPUP(data->window)) {
size.top = 0;
size.left = 0;
size.bottom = h;
size.right = w;
WIN_AdjustWindowRectForHWND(hwnd, &size, 0);
w = size.right - size.left;
h = size.bottom - size.top;
#ifdef HIGHDPI_DEBUG
SDL_Log("WM_GETMINMAXINFO: max window size: %dx%d using dpi: %u", w, h, dpi);
#endif
}
info = (MINMAXINFO *)lParam;
if (SDL_GetWindowFlags(data->window) & SDL_WINDOW_RESIZABLE) {
if (SDL_GetWindowFlags(data->window) & SDL_WINDOW_BORDERLESS) {
int screenW = GetSystemMetrics(SM_CXSCREEN);
int screenH = GetSystemMetrics(SM_CYSCREEN);
info->ptMaxSize.x = SDL_max(w, screenW);
info->ptMaxSize.y = SDL_max(h, screenH);
info->ptMaxPosition.x = SDL_min(0, ((screenW - w) / 2));
info->ptMaxPosition.y = SDL_min(0, ((screenH - h) / 2));
}
info->ptMinTrackSize.x = (LONG)w + min_w;
info->ptMinTrackSize.y = (LONG)h + min_h;
if (constrain_max_size) {
info->ptMaxTrackSize.x = (LONG)w + max_w;
info->ptMaxTrackSize.y = (LONG)h + max_h;
}
} else {
info->ptMaxSize.x = w;
info->ptMaxSize.y = h;
info->ptMaxPosition.x = x;
info->ptMaxPosition.y = y;
info->ptMinTrackSize.x = w;
info->ptMinTrackSize.y = h;
info->ptMaxTrackSize.x = w;
info->ptMaxTrackSize.y = h;
}
}
returnCode = 0;
break;
#endif
case WM_WINDOWPOSCHANGING:
if (data->expected_resize) {
returnCode = 0;
} else if (data->in_modal_loop) {
WINDOWPOS *windowpos = (WINDOWPOS *)lParam;
if (data->last_modal_width == windowpos->cx && data->last_modal_height == windowpos->cy) {
windowpos->flags |= SWP_NOSIZE;
}
data->last_modal_width = windowpos->cx;
data->last_modal_height = windowpos->cy;
returnCode = 0;
}
break;
case WM_WINDOWPOSCHANGED:
{
SDL_Window *win;
const SDL_DisplayID original_displayID = data->window->displayID;
const WINDOWPOS *windowpos = (WINDOWPOS *)lParam;
bool iconic;
bool zoomed;
RECT rect;
int x, y;
int w, h;
if (windowpos->flags & SWP_SHOWWINDOW) {
SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_SHOWN, 0, 0);
}
iconic = IsIconic(hwnd);
zoomed = IsZoomed(hwnd);
if (iconic) {
SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0);
} else if (zoomed) {
if (data->window->flags & SDL_WINDOW_MINIMIZED) {
SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESTORED, 0, 0);
}
SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MAXIMIZED, 0, 0);
data->force_ws_maximizebox = true;
} else if (data->window->flags & (SDL_WINDOW_MAXIMIZED | SDL_WINDOW_MINIMIZED)) {
SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESTORED, 0, 0);
if (!(data->window->flags & SDL_WINDOW_FULLSCREEN)) {
data->force_ws_maximizebox = false;
WIN_SetWindowResizable(SDL_GetVideoDevice(), data->window, !!(data->window->flags & SDL_WINDOW_RESIZABLE));
}
}
if (windowpos->flags & SWP_HIDEWINDOW) {
SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_HIDDEN, 0, 0);
}
if (iconic) {
break;
}
if (data->initializing) {
break;
}
if (!data->disable_move_size_events) {
if (GetClientRect(hwnd, &rect) && WIN_WindowRectValid(&rect)) {
ClientToScreen(hwnd, (LPPOINT) &rect);
ClientToScreen(hwnd, (LPPOINT) &rect + 1);
x = rect.left;
y = rect.top;
SDL_GlobalToRelativeForWindow(data->window, x, y, &x, &y);
SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MOVED, x, y);
}
if (GetClientRect(hwnd, &rect) && WIN_WindowRectValid(&rect)) {
w = rect.right;
h = rect.bottom;
SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESIZED, w, h);
}
}
WIN_UpdateClipCursor(data->window);
if (data->window->displayID != original_displayID) {
WIN_UpdateWindowICCProfile(data->window, true);
}
for (win = data->window->first_child; win; win = win->next_sibling) {
if (SDL_WINDOW_IS_POPUP(win) && !(win->flags & SDL_WINDOW_HIDDEN)) {
WIN_SetWindowPositionInternal(win, SWP_NOCOPYBITS | SWP_NOACTIVATE, SDL_WINDOWRECT_CURRENT);
}
}
} break;
case WM_ENTERSIZEMOVE:
case WM_ENTERMENULOOP:
{
if (g_WindowsMessageHook) {
if (!DispatchModalLoopMessageHook(&hwnd, &msg, &wParam, &lParam)) {
return 0;
}
}
++data->in_modal_loop;
if (data->in_modal_loop == 1) {
RECT rect;
SDL_zero(rect);
GetWindowRect(data->hwnd, &rect);
data->last_modal_width = rect.right - rect.left;
data->last_modal_height = rect.bottom - rect.top;
data->initial_size_rect.left = data->window->x;
data->initial_size_rect.right = data->window->x + data->window->w;
data->initial_size_rect.top = data->window->y;
data->initial_size_rect.bottom = data->window->y + data->window->h;
SetTimer(hwnd, (UINT_PTR)SDL_IterateMainCallbacks, USER_TIMER_MINIMUM, NULL);
SDL_ResetKeyboard();
}
} break;
case WM_TIMER:
{
if (wParam == (UINT_PTR)SDL_IterateMainCallbacks) {
SDL_OnWindowLiveResizeUpdate(data->window);
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
#if 0#endif
#endif
return 0;
}
} break;
case WM_EXITSIZEMOVE:
case WM_EXITMENULOOP:
{
--data->in_modal_loop;
if (data->in_modal_loop == 0) {
KillTimer(hwnd, (UINT_PTR)SDL_IterateMainCallbacks);
}
} break;
case WM_SIZING:
{
WPARAM edge = wParam;
RECT* dragRect = (RECT*)lParam;
RECT clientDragRect = *dragRect;
bool lock_aspect_ratio = (data->window->max_aspect == data->window->min_aspect) ? true : false;
RECT rc;
LONG w, h;
float new_aspect;
if (data->window->min_aspect <= 0 && data->window->max_aspect <= 0) {
break;
}
SetRectEmpty(&rc);
if (!AdjustWindowRectEx(&rc, GetWindowStyle(hwnd), GetMenu(hwnd) != NULL, GetWindowExStyle(hwnd))) {
break;
}
clientDragRect.left -= rc.left;
clientDragRect.top -= rc.top;
clientDragRect.right -= rc.right;
clientDragRect.bottom -= rc.bottom;
w = clientDragRect.right - clientDragRect.left;
h = clientDragRect.bottom - clientDragRect.top;
new_aspect = w / (float)h;
if (lock_aspect_ratio) {
switch (edge) {
case WMSZ_LEFT:
case WMSZ_RIGHT:
h = (int)SDL_roundf(w / data->window->max_aspect);
break;
default:
w = (int)SDL_roundf(h * data->window->max_aspect);
break;
}
} else {
switch (edge) {
case WMSZ_LEFT:
case WMSZ_RIGHT:
if (data->window->max_aspect > 0.0f && new_aspect > data->window->max_aspect) {
w = (int)SDL_roundf(h * data->window->max_aspect);
} else if (data->window->min_aspect > 0.0f && new_aspect < data->window->min_aspect) {
w = (int)SDL_roundf(h * data->window->min_aspect);
}
break;
case WMSZ_TOP:
case WMSZ_BOTTOM:
if (data->window->min_aspect > 0.0f && new_aspect < data->window->min_aspect) {
h = (int)SDL_roundf(w / data->window->min_aspect);
} else if (data->window->max_aspect > 0.0f && new_aspect > data->window->max_aspect) {
h = (int)SDL_roundf(w / data->window->max_aspect);
}
break;
default:
if (data->window->max_aspect > 0.0f && new_aspect > data->window->max_aspect) {
w = (int)SDL_roundf(h * data->window->max_aspect);
} else if (data->window->min_aspect > 0.0f && new_aspect < data->window->min_aspect) {
h = (int)SDL_roundf(w / data->window->min_aspect);
}
break;
}
}
switch (edge) {
case WMSZ_LEFT:
clientDragRect.left = clientDragRect.right - w;
if (lock_aspect_ratio) {
clientDragRect.top = (data->initial_size_rect.bottom + data->initial_size_rect.top - h) / 2;
}
clientDragRect.bottom = h + clientDragRect.top;
break;
case WMSZ_BOTTOMLEFT:
clientDragRect.left = clientDragRect.right - w;
clientDragRect.bottom = h + clientDragRect.top;
break;
case WMSZ_RIGHT:
clientDragRect.right = w + clientDragRect.left;
if (lock_aspect_ratio) {
clientDragRect.top = (data->initial_size_rect.bottom + data->initial_size_rect.top - h) / 2;
}
clientDragRect.bottom = h + clientDragRect.top;
break;
case WMSZ_TOPRIGHT:
clientDragRect.right = w + clientDragRect.left;
clientDragRect.top = clientDragRect.bottom - h;
break;
case WMSZ_TOP:
if (lock_aspect_ratio) {
clientDragRect.left = (data->initial_size_rect.right + data->initial_size_rect.left - w) / 2;
}
clientDragRect.right = w + clientDragRect.left;
clientDragRect.top = clientDragRect.bottom - h;
break;
case WMSZ_TOPLEFT:
clientDragRect.left = clientDragRect.right - w;
clientDragRect.top = clientDragRect.bottom - h;
break;
case WMSZ_BOTTOM:
if (lock_aspect_ratio) {
clientDragRect.left = (data->initial_size_rect.right + data->initial_size_rect.left - w) / 2;
}
clientDragRect.right = w + clientDragRect.left;
clientDragRect.bottom = h + clientDragRect.top;
break;
case WMSZ_BOTTOMRIGHT:
clientDragRect.right = w + clientDragRect.left;
clientDragRect.bottom = h + clientDragRect.top;
break;
}
if (!AdjustWindowRectEx(&clientDragRect, GetWindowStyle(hwnd), GetMenu(hwnd) != NULL, GetWindowExStyle(hwnd))) {
break;
}
*dragRect = clientDragRect;
}
break;
case WM_SETCURSOR:
{
Uint16 hittest;
hittest = LOWORD(lParam);
if (hittest == HTCLIENT) {
SetCursor(SDL_cursor);
returnCode = TRUE;
} else if (!g_WindowFrameUsableWhileCursorHidden && !SDL_cursor) {
SetCursor(NULL);
returnCode = TRUE;
}
} break;
case WM_PAINT:
{
RECT rect;
if (GetUpdateRect(hwnd, &rect, FALSE)) {
const LONG style = GetWindowLong(hwnd, GWL_EXSTYLE);
if (style & WS_EX_COMPOSITED) {
PAINTSTRUCT ps;
BeginPaint(hwnd, &ps);
EndPaint(hwnd, &ps);
}
ValidateRect(hwnd, NULL);
SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_EXPOSED, 0, 0);
}
}
returnCode = 0;
break;
case WM_ERASEBKGND:
if (ShouldClearWindowOnEraseBackground(data)) {
RECT client_rect;
HBRUSH brush;
data->videodata->cleared = true;
GetClientRect(hwnd, &client_rect);
brush = CreateSolidBrush(0);
FillRect(GetDC(hwnd), &client_rect, brush);
DeleteObject(brush);
}
return 1;
case WM_SYSCOMMAND:
{
if (!g_WindowsEnableMenuMnemonics) {
if ((wParam & 0xFFF0) == SC_KEYMENU) {
return 0;
}
}
#if defined(SC_SCREENSAVE) || defined(SC_MONITORPOWER)
if ((wParam & 0xFFF0) == SC_SCREENSAVE ||
(wParam & 0xFFF0) == SC_MONITORPOWER) {
if (SDL_GetVideoDevice()->suspend_screensaver) {
return 0;
}
}
#endif } break;
case WM_CLOSE:
{
SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_CLOSE_REQUESTED, 0, 0);
}
returnCode = 0;
break;
case WM_TOUCH:
if (data->videodata->GetTouchInputInfo && data->videodata->CloseTouchInputHandle) {
UINT i, num_inputs = LOWORD(wParam);
bool isstack;
PTOUCHINPUT inputs = SDL_small_alloc(TOUCHINPUT, num_inputs, &isstack);
if (inputs && data->videodata->GetTouchInputInfo((HTOUCHINPUT)lParam, num_inputs, inputs, sizeof(TOUCHINPUT))) {
RECT rect;
float x, y;
if (!GetClientRect(hwnd, &rect) || !WIN_WindowRectValid(&rect)) {
if (inputs) {
SDL_small_free(inputs, isstack);
}
break;
}
ClientToScreen(hwnd, (LPPOINT)&rect);
ClientToScreen(hwnd, (LPPOINT)&rect + 1);
rect.top *= 100;
rect.left *= 100;
rect.bottom *= 100;
rect.right *= 100;
for (i = 0; i < num_inputs; ++i) {
PTOUCHINPUT input = &inputs[i];
const int w = (rect.right - rect.left);
const int h = (rect.bottom - rect.top);
const SDL_TouchID touchId = (SDL_TouchID)((uintptr_t)input->hSource);
const SDL_FingerID fingerId = (input->dwID + 1);
if (SDL_AddTouch(touchId, SDL_TOUCH_DEVICE_DIRECT, (input->dwFlags & TOUCHEVENTF_PEN) == TOUCHEVENTF_PEN ? "pen" : "touch") < 0) {
continue;
}
if (w <= 1) {
x = 0.5f;
} else {
x = (float)(input->x - rect.left) / (w - 1);
}
if (h <= 1) {
y = 0.5f;
} else {
y = (float)(input->y - rect.top) / (h - 1);
}
if (input->dwFlags & TOUCHEVENTF_DOWN) {
SDL_SendTouch(WIN_GetEventTimestamp(), touchId, fingerId, data->window, SDL_EVENT_FINGER_DOWN, x, y, 1.0f);
}
if (input->dwFlags & TOUCHEVENTF_MOVE) {
SDL_SendTouchMotion(WIN_GetEventTimestamp(), touchId, fingerId, data->window, x, y, 1.0f);
}
if (input->dwFlags & TOUCHEVENTF_UP) {
SDL_SendTouch(WIN_GetEventTimestamp(), touchId, fingerId, data->window, SDL_EVENT_FINGER_UP, x, y, 1.0f);
}
}
}
SDL_small_free(inputs, isstack);
data->videodata->CloseTouchInputHandle((HTOUCHINPUT)lParam);
return 0;
}
break;
#ifdef HAVE_TPCSHRD_H
case WM_TABLET_QUERYSYSTEMGESTURESTATUS:
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;
#endif
case WM_DROPFILES:
{
UINT i;
HDROP drop = (HDROP)wParam;
UINT count = DragQueryFile(drop, 0xFFFFFFFF, NULL, 0);
for (i = 0; i < count; ++i) {
UINT size = DragQueryFile(drop, i, NULL, 0) + 1;
LPTSTR buffer = (LPTSTR)SDL_malloc(sizeof(TCHAR) * size);
if (buffer) {
if (DragQueryFile(drop, i, buffer, size)) {
char *file = WIN_StringToUTF8(buffer);
SDL_SendDropFile(data->window, NULL, file);
SDL_free(file);
}
SDL_free(buffer);
}
}
SDL_SendDropComplete(data->window);
DragFinish(drop);
return 0;
} break;
case WM_DISPLAYCHANGE:
{
WIN_RefreshDisplays(SDL_GetVideoDevice());
} break;
case WM_NCCALCSIZE:
{
SDL_WindowFlags window_flags = data->window->flags;
if (wParam == TRUE && (window_flags & SDL_WINDOW_BORDERLESS) && !(window_flags & SDL_WINDOW_FULLSCREEN)) {
NCCALCSIZE_PARAMS *params = (NCCALCSIZE_PARAMS *)lParam;
WINDOWPLACEMENT placement;
if (GetWindowPlacement(hwnd, &placement) && placement.showCmd == SW_MAXIMIZE) {
HMONITOR hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL);
if (!hMonitor) {
const POINT pt = { data->window->windowed.x, data->window->windowed.y };
hMonitor = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST);
}
if (hMonitor) {
MONITORINFO info;
SDL_zero(info);
info.cbSize = sizeof(info);
if (GetMonitorInfo(hMonitor, &info)) {
params->rgrc[0] = info.rcWork;
}
}
} else if (!(window_flags & SDL_WINDOW_RESIZABLE) && !data->force_ws_maximizebox) {
int w, h;
if (data->window->last_size_pending) {
w = data->window->pending.w;
h = data->window->pending.h;
} else {
w = data->window->floating.w;
h = data->window->floating.h;
}
params->rgrc[0].right = params->rgrc[0].left + w;
params->rgrc[0].bottom = params->rgrc[0].top + h;
}
return 0;
}
} break;
case WM_NCHITTEST:
{
SDL_Window *window = data->window;
if (window->flags & SDL_WINDOW_TOOLTIP) {
return HTTRANSPARENT;
}
if (window->hit_test) {
POINT winpoint;
winpoint.x = GET_X_LPARAM(lParam);
winpoint.y = GET_Y_LPARAM(lParam);
if (ScreenToClient(hwnd, &winpoint)) {
SDL_Point point;
SDL_HitTestResult rc;
point.x = winpoint.x;
point.y = winpoint.y;
rc = window->hit_test(window, &point, window->hit_test_data);
switch (rc) {
#define POST_HIT_TEST(ret) \
{ \
SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_HIT_TEST, 0, 0); \
return ret; \
}
case SDL_HITTEST_DRAGGABLE:
{
SDL_MouseButtonFlags buttonState = SDL_GetGlobalMouseState(NULL, NULL);
if (buttonState && !(buttonState & SDL_BUTTON_LMASK)) {
SDL_SetMouseFocus(window);
return HTCLIENT;
}
POST_HIT_TEST(HTCAPTION);
}
case SDL_HITTEST_RESIZE_TOPLEFT:
POST_HIT_TEST(HTTOPLEFT);
case SDL_HITTEST_RESIZE_TOP:
POST_HIT_TEST(HTTOP);
case SDL_HITTEST_RESIZE_TOPRIGHT:
POST_HIT_TEST(HTTOPRIGHT);
case SDL_HITTEST_RESIZE_RIGHT:
POST_HIT_TEST(HTRIGHT);
case SDL_HITTEST_RESIZE_BOTTOMRIGHT:
POST_HIT_TEST(HTBOTTOMRIGHT);
case SDL_HITTEST_RESIZE_BOTTOM:
POST_HIT_TEST(HTBOTTOM);
case SDL_HITTEST_RESIZE_BOTTOMLEFT:
POST_HIT_TEST(HTBOTTOMLEFT);
case SDL_HITTEST_RESIZE_LEFT:
POST_HIT_TEST(HTLEFT);
#undef POST_HIT_TEST
case SDL_HITTEST_NORMAL:
return HTCLIENT;
}
}
}
} break;
case WM_GETDPISCALEDSIZE:
if (data->videodata->GetDpiForWindow && data->videodata->AdjustWindowRectExForDpi) {
const int nextDPI = (int)wParam;
const int prevDPI = (int)data->videodata->GetDpiForWindow(hwnd);
SIZE *sizeInOut = (SIZE *)lParam;
int frame_w, frame_h;
int query_client_w_win, query_client_h_win;
#ifdef HIGHDPI_DEBUG
SDL_Log("WM_GETDPISCALEDSIZE: current DPI: %d potential DPI: %d input size: (%dx%d)",
prevDPI, nextDPI, sizeInOut->cx, sizeInOut->cy);
#endif
{
RECT rect = { 0 };
if (!(data->window->flags & SDL_WINDOW_BORDERLESS) && !SDL_WINDOW_IS_POPUP(data->window)) {
WIN_AdjustWindowRectForHWND(hwnd, &rect, prevDPI);
}
frame_w = -rect.left + rect.right;
frame_h = -rect.top + rect.bottom;
query_client_w_win = sizeInOut->cx - frame_w;
query_client_h_win = sizeInOut->cy - frame_h;
}
{
RECT rect = { 0 };
rect.right = query_client_w_win;
rect.bottom = query_client_h_win;
if (!(data->window->flags & SDL_WINDOW_BORDERLESS) && !SDL_WINDOW_IS_POPUP(data->window)) {
WIN_AdjustWindowRectForHWND(hwnd, &rect, nextDPI);
}
sizeInOut->cx = rect.right - rect.left;
sizeInOut->cy = rect.bottom - rect.top;
}
#ifdef HIGHDPI_DEBUG
SDL_Log("WM_GETDPISCALEDSIZE: output size: (%dx%d)", sizeInOut->cx, sizeInOut->cy);
#endif
return TRUE;
}
break;
case WM_DPICHANGED:
{
const int newDPI = HIWORD(wParam);
RECT *const suggestedRect = (RECT *)lParam;
int w, h;
#ifdef HIGHDPI_DEBUG
SDL_Log("WM_DPICHANGED: to %d\tsuggested rect: (%d, %d), (%dx%d)", newDPI,
suggestedRect->left, suggestedRect->top, suggestedRect->right - suggestedRect->left, suggestedRect->bottom - suggestedRect->top);
#endif
if (data->expected_resize) {
#ifdef HIGHDPI_DEBUG
SDL_Log("WM_DPICHANGED: Doing nothing, assuming window is already sized correctly");
#endif
return 0;
}
{
RECT rect = { 0 };
rect.right = data->window->w;
rect.bottom = data->window->h;
if (!(data->window->flags & SDL_WINDOW_BORDERLESS)) {
WIN_AdjustWindowRectForHWND(hwnd, &rect, newDPI);
}
w = rect.right - rect.left;
h = rect.bottom - rect.top;
}
#ifdef HIGHDPI_DEBUG
SDL_Log("WM_DPICHANGED: current SDL window size: (%dx%d)\tcalling SetWindowPos: (%d, %d), (%dx%d)",
data->window->w, data->window->h,
suggestedRect->left, suggestedRect->top, w, h);
#endif
data->expected_resize = true;
SetWindowPos(hwnd,
NULL,
suggestedRect->left,
suggestedRect->top,
w,
h,
SWP_NOZORDER | SWP_NOACTIVATE);
data->expected_resize = false;
return 0;
}
break;
case WM_SETTINGCHANGE:
if (wParam == 0 && lParam != 0 && SDL_wcscmp((wchar_t *)lParam, L"ImmersiveColorSet") == 0) {
SDL_SetSystemTheme(WIN_GetSystemTheme());
WIN_UpdateDarkModeForHWND(hwnd);
}
if (wParam == SPI_SETMOUSE || wParam == SPI_SETMOUSESPEED) {
WIN_UpdateMouseSystemScale();
}
if (wParam == SPI_SETWORKAREA) {
WIN_UpdateDisplayUsableBounds(SDL_GetVideoDevice());
}
break;
#endif
default:
break;
}
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
if (msg && msg == data->videodata->WM_TASKBAR_BUTTON_CREATED) {
data->taskbar_button_created = true;
WIN_ApplyWindowProgress(SDL_GetVideoDevice(), data->window);
}
#endif
if (data->wndproc) {
return CallWindowProc(data->wndproc, hwnd, msg, wParam, lParam);
} else if (returnCode >= 0) {
return returnCode;
} else {
return CallWindowProc(DefWindowProc, hwnd, msg, wParam, lParam);
}
}
int WIN_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS)
{
if (g_WindowsEnableMessageLoop) {
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
DWORD timeout, ret;
timeout = timeoutNS < 0 ? INFINITE : (DWORD)SDL_NS_TO_MS(timeoutNS);
ret = MsgWaitForMultipleObjects(0, NULL, FALSE, timeout, QS_ALLINPUT);
if (ret == WAIT_OBJECT_0) {
return 1;
} else {
return 0;
}
#else
MSG msg;
BOOL message_result;
UINT_PTR timer_id = 0;
if (timeoutNS > 0) {
timer_id = SetTimer(NULL, 0, (UINT)SDL_NS_TO_MS(timeoutNS), NULL);
message_result = GetMessage(&msg, 0, 0, 0);
KillTimer(NULL, timer_id);
} else if (timeoutNS == 0) {
message_result = PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
} else {
message_result = GetMessage(&msg, 0, 0, 0);
}
if (message_result) {
if (msg.message == WM_TIMER && !msg.hwnd && msg.wParam == timer_id) {
return 0;
}
if (g_WindowsMessageHook) {
if (!g_WindowsMessageHook(g_WindowsMessageHookData, &msg)) {
return 1;
}
}
TranslateMessage(&msg);
DispatchMessage(&msg);
return 1;
} else {
return 0;
}
#endif } else {
return -1;
}
}
void WIN_SendWakeupEvent(SDL_VideoDevice *_this, SDL_Window *window)
{
SDL_WindowData *data = window->internal;
PostMessage(data->hwnd, data->videodata->_SDL_WAKEUP, 0, 0);
}
void WIN_PumpEventsForHWND(SDL_VideoDevice *_this, HWND hwnd)
{
MSG msg;
if (g_WindowsEnableMessageLoop) {
SDL_processing_messages = true;
while (PeekMessage(&msg, hwnd, 0, 0, PM_REMOVE)) {
WIN_SetMessageTick(msg.time);
TranslateMessage(&msg);
DispatchMessage(&msg);
}
SDL_processing_messages = false;
}
}
void WIN_PumpEvents(SDL_VideoDevice *_this)
{
MSG msg;
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 28159)
#endif
DWORD end_ticks = GetTickCount() + 1;
#ifdef _MSC_VER
#pragma warning(pop)
#endif
int new_messages = 0;
if (_this->internal->gameinput_context) {
WIN_UpdateGameInput(_this);
}
if (g_WindowsEnableMessageLoop) {
SDL_processing_messages = true;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (g_WindowsMessageHook) {
if (!g_WindowsMessageHook(g_WindowsMessageHookData, &msg)) {
continue;
}
}
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
if (msg.message == WM_MOUSEMOVE && SDL_last_warp_time) {
if (!SDL_TICKS_PASSED(msg.time, (SDL_last_warp_time + 1))) {
continue;
}
SDL_last_warp_time = 0;
}
#endif
WIN_SetMessageTick(msg.time);
TranslateMessage(&msg);
DispatchMessage(&msg);
if (SDL_TICKS_PASSED(msg.time, end_ticks)) {
const int MAX_NEW_MESSAGES = 3;
++new_messages;
if (new_messages > MAX_NEW_MESSAGES) {
break;
}
}
}
SDL_processing_messages = false;
}
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
const bool *keystate = SDL_GetKeyboardState(NULL);
if (keystate[SDL_SCANCODE_LSHIFT] && !(GetKeyState(VK_LSHIFT) & 0x8000)) {
SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_LSHIFT, false);
}
if (keystate[SDL_SCANCODE_RSHIFT] && !(GetKeyState(VK_RSHIFT) & 0x8000)) {
SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_RSHIFT, false);
}
SDL_Window *focusWindow = SDL_GetKeyboardFocus();
if (!focusWindow || !(focusWindow->flags & SDL_WINDOW_KEYBOARD_GRABBED)) {
if (keystate[SDL_SCANCODE_LGUI] && !(GetKeyState(VK_LWIN) & 0x8000)) {
SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_LGUI, false);
}
if (keystate[SDL_SCANCODE_RGUI] && !(GetKeyState(VK_RWIN) & 0x8000)) {
SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_RGUI, false);
}
}
if (_this) {
SDL_Window *window = _this->windows;
while (window) {
bool refresh_clipcursor = false;
SDL_WindowData *data = window->internal;
if (data) {
refresh_clipcursor = data->clipcursor_queued;
data->clipcursor_queued = false; data->postpone_clipcursor = false; }
if (refresh_clipcursor) {
WIN_UpdateClipCursor(window);
}
window = window->next;
}
}
focusWindow = SDL_GetKeyboardFocus();
if (focusWindow && (focusWindow->flags & SDL_WINDOW_MOUSE_CAPTURE)) {
SDL_WindowData *data = focusWindow->internal;
if (!data->mouse_tracked) {
POINT cursorPos;
if (GetCursorPos(&cursorPos) && ScreenToClient(data->hwnd, &cursorPos)) {
bool swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0;
SDL_MouseID mouseID = SDL_GLOBAL_MOUSE_ID;
SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, mouseID, false, (float)cursorPos.x, (float)cursorPos.y);
SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID,
!swapButtons ? SDL_BUTTON_LEFT : SDL_BUTTON_RIGHT,
(GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0);
SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID,
!swapButtons ? SDL_BUTTON_RIGHT : SDL_BUTTON_LEFT,
(GetAsyncKeyState(VK_RBUTTON) & 0x8000) != 0);
SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID,
SDL_BUTTON_MIDDLE,
(GetAsyncKeyState(VK_MBUTTON) & 0x8000) != 0);
SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID,
SDL_BUTTON_X1,
(GetAsyncKeyState(VK_XBUTTON1) & 0x8000) != 0);
SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID,
SDL_BUTTON_X2,
(GetAsyncKeyState(VK_XBUTTON2) & 0x8000) != 0);
}
}
}
WIN_UpdateIMECandidates(_this);
#endif
#ifdef SDL_PLATFORM_GDK
GDK_DispatchTaskQueue();
#endif
}
static int app_registered = 0;
LPTSTR SDL_Appname = NULL;
Uint32 SDL_Appstyle = 0;
HINSTANCE SDL_Instance = NULL;
static void WIN_CleanRegisterApp(WNDCLASSEX wcex)
{
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
if (wcex.hIcon) {
DestroyIcon(wcex.hIcon);
}
if (wcex.hIconSm) {
DestroyIcon(wcex.hIconSm);
}
#endif
SDL_free(SDL_Appname);
SDL_Appname = NULL;
}
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
static BOOL CALLBACK WIN_ResourceNameCallback(HMODULE hModule, LPCTSTR lpType, LPTSTR lpName, LONG_PTR lParam)
{
WNDCLASSEX *wcex = (WNDCLASSEX *)lParam;
(void)lpType;
wcex->hIcon = LoadIcon(hModule, lpName);
return FALSE;
}
#endif
bool SDL_RegisterApp(const char *name, Uint32 style, void *hInst)
{
WNDCLASSEX wcex;
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
const char *hint;
#endif
if (app_registered) {
++app_registered;
return true;
}
SDL_assert(!SDL_Appname);
if (!name) {
name = "SDL_app";
#if defined(CS_BYTEALIGNCLIENT) || defined(CS_OWNDC)
style = (CS_BYTEALIGNCLIENT | CS_OWNDC);
#endif
}
SDL_Appname = WIN_UTF8ToString(name);
SDL_Appstyle = style;
SDL_Instance = hInst ? (HINSTANCE)hInst : GetModuleHandle(NULL);
SDL_zero(wcex);
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.lpszClassName = SDL_Appname;
wcex.style = SDL_Appstyle;
wcex.lpfnWndProc = WIN_WindowProc;
wcex.hInstance = SDL_Instance;
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
hint = SDL_GetHint(SDL_HINT_WINDOWS_INTRESOURCE_ICON);
if (hint && *hint) {
wcex.hIcon = LoadIcon(SDL_Instance, MAKEINTRESOURCE(SDL_atoi(hint)));
hint = SDL_GetHint(SDL_HINT_WINDOWS_INTRESOURCE_ICON_SMALL);
if (hint && *hint) {
wcex.hIconSm = LoadIcon(SDL_Instance, MAKEINTRESOURCE(SDL_atoi(hint)));
}
} else {
EnumResourceNames(SDL_Instance, RT_GROUP_ICON, WIN_ResourceNameCallback, (LONG_PTR)&wcex);
}
#endif
if (!RegisterClassEx(&wcex)) {
WIN_CleanRegisterApp(wcex);
return SDL_SetError("Couldn't register application class");
}
app_registered = 1;
return true;
}
void SDL_UnregisterApp(void)
{
WNDCLASSEX wcex;
if (!app_registered) {
return;
}
--app_registered;
if (app_registered == 0) {
wcex.hIcon = NULL;
wcex.hIconSm = NULL;
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
if (GetClassInfoEx(SDL_Instance, SDL_Appname, &wcex)) {
UnregisterClass(SDL_Appname, SDL_Instance);
}
#endif
WIN_CleanRegisterApp(wcex);
}
}
#endif