Added support for raw mouse and keyboard using GameInput on Windows

Fixes https://github.com/libsdl-org/SDL/issues/10442
This commit is contained in:
Sam Lantinga 2024-08-07 06:48:36 -07:00
parent 8fdca9c691
commit 001dbc5da8
11 changed files with 729 additions and 12 deletions

View File

@ -573,6 +573,7 @@
<ClInclude Include="..\..\src\video\windows\SDL_windowsevents.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowsframebuffer.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowskeyboard.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowsgameinput.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowsmessagebox.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowsmodes.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowsmouse.h" />
@ -850,6 +851,7 @@
<ClCompile Include="..\..\src\video\windows\SDL_windowsevents.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowsframebuffer.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowskeyboard.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowsgameinput.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowsmessagebox.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowsmodes.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowsmouse.c" />

View File

@ -217,6 +217,7 @@
<ClCompile Include="..\..\src\video\windows\SDL_windowsevents.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowsframebuffer.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowskeyboard.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowsgameinput.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowsmessagebox.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowsmodes.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowsmouse.c" />
@ -447,6 +448,7 @@
<ClInclude Include="..\..\src\video\windows\SDL_windowsevents.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowsframebuffer.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowskeyboard.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowsgameinput.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowsmessagebox.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowsmodes.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowsmouse.h" />

View File

@ -479,6 +479,7 @@
<ClInclude Include="..\..\src\video\windows\SDL_windowsevents.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowsframebuffer.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowskeyboard.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowsgameinput.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowsmessagebox.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowsmodes.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowsmouse.h" />
@ -708,6 +709,7 @@
<ClCompile Include="..\..\src\video\windows\SDL_windowsevents.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowsframebuffer.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowskeyboard.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowsgameinput.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowsmessagebox.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowsmodes.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowsmouse.c" />

View File

@ -684,6 +684,9 @@
<ClInclude Include="..\..\src\video\windows\SDL_windowskeyboard.h">
<Filter>video\windows</Filter>
</ClInclude>
<ClInclude Include="..\..\src\video\windows\SDL_windowsgameinput.h">
<Filter>video\windows</Filter>
</ClInclude>
<ClInclude Include="..\..\src\video\windows\SDL_windowsmessagebox.h">
<Filter>video\windows</Filter>
</ClInclude>
@ -1340,6 +1343,9 @@
<ClCompile Include="..\..\src\video\windows\SDL_windowskeyboard.c">
<Filter>video\windows</Filter>
</ClCompile>
<ClCompile Include="..\..\src\video\windows\SDL_windowsgameinput.c">
<Filter>video\windows</Filter>
</ClCompile>
<ClCompile Include="..\..\src\video\windows\SDL_windowsmessagebox.c">
<Filter>video\windows</Filter>
</ClCompile>

View File

@ -3823,6 +3823,20 @@ extern "C" {
*/
#define SDL_HINT_WINDOWS_ENABLE_MESSAGELOOP "SDL_WINDOWS_ENABLE_MESSAGELOOP"
/**
* A variable controlling whether GameInput is used for raw keyboard and mouse on Windows.
*
* The variable can be set to the following values:
*
* - "0": GameInput is not used for raw keyboard and mouse events.
* - "1": GameInput is used for raw keyboard and mouse events, if available. (default)
*
* This hint should be set before SDL is initialized.
*
* \since This hint is available since SDL 3.0.0.
*/
#define SDL_HINT_WINDOWS_GAMEINPUT "SDL_WINDOWS_GAMEINPUT"
/**
* A variable controlling whether raw keyboard events are used on Windows.
*

View File

@ -2233,6 +2233,10 @@ void WIN_PumpEvents(SDL_VideoDevice *_this)
SDL_Window *focusWindow;
#endif
if (_this->internal->gameinput_context) {
WIN_UpdateGameInput(_this);
}
if (g_WindowsEnableMessageLoop) {
SDL_processing_messages = SDL_TRUE;
@ -2310,7 +2314,9 @@ void WIN_PumpEvents(SDL_VideoDevice *_this)
/* Update mouse capture */
WIN_UpdateMouseCapture();
WIN_CheckKeyboardAndMouseHotplug(_this, SDL_FALSE);
if (!_this->internal->gameinput_context) {
WIN_CheckKeyboardAndMouseHotplug(_this, SDL_FALSE);
}
WIN_UpdateIMECandidates(_this);

View File

@ -0,0 +1,625 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_windowsvideo.h"
#if defined(__has_include) && __has_include(<GameInput.h>)
#define HAVE_GAMEINPUT_H
#endif
#ifdef HAVE_GAMEINPUT_H
#include <stdbool.h>
#define COBJMACROS
#include <GameInput.h>
#include "../../events/SDL_mouse_c.h"
#include "../../events/SDL_keyboard_c.h"
#include "../../events/scancodes_windows.h"
#define MAX_GAMEINPUT_BUTTONS 7 // GameInputMouseWheelTiltRight is the highest button
static const Uint8 GAMEINPUT_button_map[MAX_GAMEINPUT_BUTTONS] = {
SDL_BUTTON_LEFT,
SDL_BUTTON_RIGHT,
SDL_BUTTON_MIDDLE,
SDL_BUTTON_X1,
SDL_BUTTON_X2,
6,
7
};
typedef struct GAMEINPUT_Device
{
IGameInputDevice *pDevice;
const GameInputDeviceInfo *info;
char *name;
Uint32 instance_id; /* generated by SDL */
SDL_bool registered;
SDL_bool delete_requested;
IGameInputReading *last_mouse_reading;
IGameInputReading *last_keyboard_reading;
} GAMEINPUT_Device;
struct WIN_GameInputData
{
void *hGameInputDLL;
IGameInput *pGameInput;
GameInputCallbackToken gameinput_callback_token;
int num_devices;
GAMEINPUT_Device **devices;
GameInputKind enabled_input;
SDL_Mutex *lock;
uint64_t timestamp_offset;
};
static int GAMEINPUT_InternalAddOrFind(WIN_GameInputData *data, IGameInputDevice *pDevice)
{
GAMEINPUT_Device **devicelist = NULL;
GAMEINPUT_Device *device = NULL;
const GameInputDeviceInfo *info;
int retval = -1;
info = IGameInputDevice_GetDeviceInfo(pDevice);
SDL_LockMutex(data->lock);
{
for (int i = 0; i < data->num_devices; ++i) {
device = data->devices[i];
if (device && device->pDevice == pDevice) {
/* we're already added */
device->delete_requested = SDL_FALSE;
retval = 0;
goto done;
}
}
device = (GAMEINPUT_Device *)SDL_calloc(1, sizeof(*device));
if (!device) {
goto done;
}
devicelist = (GAMEINPUT_Device **)SDL_realloc(data->devices, (data->num_devices + 1) * sizeof(*devicelist));
if (!devicelist) {
SDL_free(device);
goto done;
}
if (info->deviceStrings) {
/* In theory we could get the manufacturer and product strings here, but they're NULL for all the devices I've tested */
}
if (info->displayName) {
/* This could give us a product string, but it's NULL for all the devices I've tested */
}
IGameInputDevice_AddRef(pDevice);
device->pDevice = pDevice;
device->instance_id = SDL_GetNextObjectID();
device->info = info;
data->devices = devicelist;
data->devices[data->num_devices++] = device;
retval = 0;
}
done:
SDL_UnlockMutex(data->lock);
return retval;
}
static int GAMEINPUT_InternalRemoveByIndex(WIN_GameInputData *data, int idx)
{
GAMEINPUT_Device **devicelist = NULL;
GAMEINPUT_Device *device;
int retval = -1;
SDL_LockMutex(data->lock);
{
if (idx < 0 || idx >= data->num_devices) {
retval = SDL_SetError("GAMEINPUT_InternalRemoveByIndex argument idx %d is out of range", idx);
goto done;
}
device = data->devices[idx];
if (device) {
if (device->registered) {
if (device->info->supportedInput & GameInputKindMouse) {
SDL_RemoveMouse(device->instance_id, SDL_TRUE);
}
if (device->info->supportedInput & GameInputKindKeyboard) {
SDL_RemoveKeyboard(device->instance_id, SDL_TRUE);
}
if (device->last_mouse_reading) {
IGameInputReading_Release(device->last_mouse_reading);
device->last_mouse_reading = NULL;
}
if (device->last_keyboard_reading) {
IGameInputReading_Release(device->last_keyboard_reading);
device->last_keyboard_reading = NULL;
}
}
IGameInputDevice_Release(device->pDevice);
SDL_free(device->name);
SDL_free(device);
}
data->devices[idx] = NULL;
if (data->num_devices == 1) {
/* last element in the list, free the entire list then */
SDL_free(data->devices);
data->devices = NULL;
} else {
if (idx != data->num_devices - 1) {
size_t bytes = sizeof(*devicelist) * (data->num_devices - idx);
SDL_memmove(&data->devices[idx], &data->devices[idx + 1], bytes);
}
}
/* decrement the count and return */
retval = data->num_devices--;
}
done:
SDL_UnlockMutex(data->lock);
return retval;
}
static void CALLBACK GAMEINPUT_InternalDeviceCallback(
_In_ GameInputCallbackToken callbackToken,
_In_ void* context,
_In_ IGameInputDevice *pDevice,
_In_ uint64_t timestamp,
_In_ GameInputDeviceStatus currentStatus,
_In_ GameInputDeviceStatus previousStatus)
{
WIN_GameInputData *data = (WIN_GameInputData *)context;
int idx = 0;
GAMEINPUT_Device *device = NULL;
if (!pDevice) {
/* This should never happen, but ignore it if it does */
return;
}
if (currentStatus & GameInputDeviceConnected) {
GAMEINPUT_InternalAddOrFind(data, pDevice);
} else {
for (idx = 0; idx < data->num_devices; ++idx) {
device = data->devices[idx];
if (device && device->pDevice == pDevice) {
/* will be deleted on the next Detect call */
device->delete_requested = SDL_TRUE;
break;
}
}
}
}
int WIN_InitGameInput(SDL_VideoDevice *_this)
{
WIN_GameInputData *data;
HRESULT hr;
int retval = -1;
if (_this->internal->gameinput_context) {
return 0;
}
data = (WIN_GameInputData *)SDL_calloc(1, sizeof(*data));
if (!data) {
goto done;
}
_this->internal->gameinput_context = data;
data->lock = SDL_CreateMutex();
if (!data->lock) {
goto done;
}
data->hGameInputDLL = SDL_LoadObject("gameinput.dll");
if (!data->hGameInputDLL) {
goto done;
}
typedef HRESULT (WINAPI *GameInputCreate_t)(IGameInput * *gameInput);
GameInputCreate_t GameInputCreateFunc = (GameInputCreate_t)SDL_LoadFunction(data->hGameInputDLL, "GameInputCreate");
if (!GameInputCreateFunc) {
goto done;
}
hr = GameInputCreateFunc(&data->pGameInput);
if (FAILED(hr)) {
SDL_SetError("GameInputCreate failure with HRESULT of %08X", hr);
goto done;
}
hr = IGameInput_RegisterDeviceCallback(data->pGameInput,
NULL,
(GameInputKindMouse | GameInputKindKeyboard),
GameInputDeviceConnected,
GameInputBlockingEnumeration,
data,
GAMEINPUT_InternalDeviceCallback,
&data->gameinput_callback_token);
if (FAILED(hr)) {
SDL_SetError("IGameInput::RegisterDeviceCallback failure with HRESULT of %08X", hr);
goto done;
}
// Calculate the relative offset between SDL timestamps and GameInput timestamps
Uint64 now = SDL_GetTicksNS();
uint64_t timestampUS = IGameInput_GetCurrentTimestamp(data->pGameInput);
data->timestamp_offset = (SDL_NS_TO_US(now) - timestampUS);
retval = 0;
done:
if (retval < 0) {
WIN_QuitGameInput(_this);
}
return retval;
}
static void GAMEINPUT_InitialMouseReading(WIN_GameInputData *data, SDL_Window *window, GAMEINPUT_Device *device, IGameInputReading *reading)
{
GameInputMouseState state;
if (SUCCEEDED(IGameInputReading_GetMouseState(reading, &state))) {
Uint64 timestamp = SDL_US_TO_NS(IGameInputReading_GetTimestamp(reading) + data->timestamp_offset);
SDL_MouseID mouseID = device->instance_id;
for (int i = 0; i < MAX_GAMEINPUT_BUTTONS; ++i) {
const GameInputMouseButtons mask = (1 << i);
SDL_SendMouseButton(timestamp, window, mouseID, (state.buttons & mask) ? SDL_PRESSED : SDL_RELEASED, GAMEINPUT_button_map[i]);
}
}
}
static void GAMEINPUT_HandleMouseDelta(WIN_GameInputData *data, SDL_Window *window, GAMEINPUT_Device *device, IGameInputReading *last_reading, IGameInputReading *reading)
{
GameInputMouseState last;
GameInputMouseState state;
if (SUCCEEDED(IGameInputReading_GetMouseState(last_reading, &last)) &&
SUCCEEDED(IGameInputReading_GetMouseState(reading, &state))) {
Uint64 timestamp = SDL_US_TO_NS(IGameInputReading_GetTimestamp(reading) + data->timestamp_offset);
SDL_MouseID mouseID = device->instance_id;
GameInputMouseState delta;
delta.buttons = (state.buttons ^ last.buttons);
delta.positionX = (state.positionX - last.positionX);
delta.positionY = (state.positionY - last.positionY);
delta.wheelX = (state.wheelX - last.wheelX);
delta.wheelY = (state.wheelY - last.wheelY);
if (delta.positionX || delta.positionY) {
SDL_SendMouseMotion(timestamp, window, mouseID, SDL_TRUE, (float)delta.positionX, (float)delta.positionY);
}
if (delta.buttons) {
for (int i = 0; i < MAX_GAMEINPUT_BUTTONS; ++i) {
const GameInputMouseButtons mask = (1 << i);
if (delta.buttons & mask) {
SDL_SendMouseButton(timestamp, window, mouseID, (state.buttons & mask) ? SDL_PRESSED : SDL_RELEASED, GAMEINPUT_button_map[i]);
}
}
}
if (delta.wheelX || delta.wheelY) {
float fAmountX = (float)delta.wheelX / WHEEL_DELTA;
float fAmountY = (float)delta.wheelY / WHEEL_DELTA;
SDL_SendMouseWheel(timestamp, SDL_GetMouseFocus(), device->instance_id, fAmountX, fAmountY, SDL_MOUSEWHEEL_NORMAL);
}
}
}
static SDL_Scancode GetScancodeFromKeyState(const GameInputKeyState *state)
{
Uint8 index = (Uint8)(state->scanCode & 0xFF);
if ((state->scanCode & 0xFF00) == 0xE000) {
index |= 0x80;
}
return windows_scancode_table[index];
}
static SDL_bool KeysHaveScancode(const GameInputKeyState *keys, uint32_t count, SDL_Scancode scancode)
{
for (uint32_t i = 0; i < count; ++i) {
if (GetScancodeFromKeyState(&keys[i]) == scancode) {
return SDL_TRUE;
}
}
return SDL_FALSE;
}
static void GAMEINPUT_InitialKeyboardReading(WIN_GameInputData *data, SDL_Window *window, GAMEINPUT_Device *device, IGameInputReading *reading)
{
Uint64 timestamp = SDL_US_TO_NS(IGameInputReading_GetTimestamp(reading) + data->timestamp_offset);
SDL_KeyboardID keyboardID = device->instance_id;
uint32_t max_keys = device->info->keyboardInfo->maxSimultaneousKeys;
GameInputKeyState *keys = SDL_stack_alloc(GameInputKeyState, max_keys);
if (!keys) {
return;
}
uint32_t num_keys = IGameInputReading_GetKeyState(reading, max_keys, keys);
if (!num_keys) {
// FIXME: We probably need to track key state by keyboardID
SDL_ResetKeyboard();
return;
}
// Go through and send key up events for any key that's not held down
int num_scancodes;
const Uint8 *keyboard_state = SDL_GetKeyboardState(&num_scancodes);
for (int i = 0; i < num_scancodes; ++i) {
if (keyboard_state[i] && !KeysHaveScancode(keys, num_keys, (SDL_Scancode)i)) {
SDL_SendKeyboardKey(timestamp, keyboardID, keys[i].scanCode, (SDL_Scancode)i, SDL_RELEASED);
}
}
// Go through and send key down events for any key that's held down
for (uint32_t i = 0; i < num_keys; ++i) {
SDL_SendKeyboardKey(timestamp, keyboardID, keys[i].scanCode, GetScancodeFromKeyState(&keys[i]), SDL_PRESSED);
}
}
#ifdef DEBUG_KEYS
static void DumpKeys(const char *prefix, GameInputKeyState *keys, uint32_t count)
{
SDL_Log("%s", prefix);
for (uint32_t i = 0; i < count; ++i) {
char str[5];
*SDL_UCS4ToUTF8(keys[i].codePoint, str) = '\0';
SDL_Log(" Key 0x%.2x (%s)\n", keys[i].scanCode, str);
}
}
#endif // DEBUG_KEYS
static void GAMEINPUT_HandleKeyboardDelta(WIN_GameInputData *data, SDL_Window *window, GAMEINPUT_Device *device, IGameInputReading *last_reading, IGameInputReading *reading)
{
Uint64 timestamp = SDL_US_TO_NS(IGameInputReading_GetTimestamp(reading) + data->timestamp_offset);
SDL_KeyboardID keyboardID = device->instance_id;
uint32_t max_keys = device->info->keyboardInfo->maxSimultaneousKeys;
GameInputKeyState *last = SDL_stack_alloc(GameInputKeyState, max_keys);
GameInputKeyState *keys = SDL_stack_alloc(GameInputKeyState, max_keys);
if (!last || !keys) {
return;
}
uint32_t index_last = 0;
uint32_t index_keys = 0;
uint32_t num_last = IGameInputReading_GetKeyState(last_reading, max_keys, last);
uint32_t num_keys = IGameInputReading_GetKeyState(reading, max_keys, keys);
#ifdef DEBUG_KEYS
SDL_Log("Timestamp: %llu\n", timestamp);
DumpKeys("Last keys:", last, num_last);
DumpKeys("New keys:", keys, num_keys);
#endif
while (index_last < num_last || index_keys < num_keys) {
if (index_last < num_last && index_keys < num_keys) {
if (last[index_last].scanCode == keys[index_keys].scanCode) {
// No change
++index_last;
++index_keys;
} else {
// This key was released
SDL_SendKeyboardKey(timestamp, keyboardID, last[index_last].scanCode, GetScancodeFromKeyState(&last[index_last]), SDL_RELEASED);
++index_last;
}
} else if (index_last < num_last) {
// This key was released
SDL_SendKeyboardKey(timestamp, keyboardID, last[index_last].scanCode, GetScancodeFromKeyState(&last[index_last]), SDL_RELEASED);
++index_last;
} else {
// This key was pressed
SDL_SendKeyboardKey(timestamp, keyboardID, keys[index_keys].scanCode, GetScancodeFromKeyState(&keys[index_keys]), SDL_PRESSED);
++index_keys;
}
}
}
void WIN_UpdateGameInput(SDL_VideoDevice *_this)
{
WIN_GameInputData *data = _this->internal->gameinput_context;
SDL_LockMutex(data->lock);
{
// Key events and relative mouse motion both go to the window with keyboard focus
SDL_Window *window = SDL_GetKeyboardFocus();
for (int i = 0; i < data->num_devices; ++i) {
GAMEINPUT_Device *device = data->devices[i];
IGameInputReading *reading;
if (!device->registered) {
if (device->info->supportedInput & GameInputKindMouse) {
SDL_AddMouse(device->instance_id, device->name, SDL_TRUE);
}
if (device->info->supportedInput & GameInputKindKeyboard) {
SDL_AddKeyboard(device->instance_id, device->name, SDL_TRUE);
}
device->registered = SDL_TRUE;
}
if (device->delete_requested) {
GAMEINPUT_InternalRemoveByIndex(data, i--);
continue;
}
if (!(device->info->supportedInput & data->enabled_input)) {
continue;
}
if (!window) {
continue;
}
if (data->enabled_input & GameInputKindMouse) {
if (device->last_mouse_reading) {
HRESULT hr;
while (SUCCEEDED(hr = IGameInput_GetNextReading(data->pGameInput, device->last_mouse_reading, GameInputKindMouse, device->pDevice, &reading))) {
GAMEINPUT_HandleMouseDelta(data, window, device, device->last_mouse_reading, reading);
IGameInputReading_Release(device->last_mouse_reading);
device->last_mouse_reading = reading;
}
if (hr != GAMEINPUT_E_READING_NOT_FOUND) {
// The last reading is too old, resynchronize
IGameInputReading_Release(device->last_mouse_reading);
device->last_mouse_reading = NULL;
}
}
if (!device->last_mouse_reading) {
if (SUCCEEDED(IGameInput_GetCurrentReading(data->pGameInput, GameInputKindMouse, device->pDevice, &reading))) {
GAMEINPUT_InitialMouseReading(data, window, device, reading);
device->last_mouse_reading = reading;
}
}
}
if (data->enabled_input & GameInputKindKeyboard) {
if (window->text_input_active) {
// Reset raw input while text input is active
if (device->last_keyboard_reading) {
IGameInputReading_Release(device->last_keyboard_reading);
device->last_keyboard_reading = NULL;
}
} else {
if (device->last_keyboard_reading) {
HRESULT hr;
while (SUCCEEDED(hr = IGameInput_GetNextReading(data->pGameInput, device->last_keyboard_reading, GameInputKindKeyboard, device->pDevice, &reading))) {
GAMEINPUT_HandleKeyboardDelta(data, window, device, device->last_keyboard_reading, reading);
IGameInputReading_Release(device->last_keyboard_reading);
device->last_keyboard_reading = reading;
}
if (hr != GAMEINPUT_E_READING_NOT_FOUND) {
// The last reading is too old, resynchronize
IGameInputReading_Release(device->last_keyboard_reading);
device->last_keyboard_reading = NULL;
}
}
if (!device->last_keyboard_reading) {
if (SUCCEEDED(IGameInput_GetCurrentReading(data->pGameInput, GameInputKindKeyboard, device->pDevice, &reading))) {
GAMEINPUT_InitialKeyboardReading(data, window, device, reading);
device->last_keyboard_reading = reading;
}
}
}
}
}
}
SDL_UnlockMutex(data->lock);
}
int WIN_UpdateGameInputEnabled(SDL_VideoDevice *_this)
{
WIN_GameInputData *data = _this->internal->gameinput_context;
SDL_bool raw_mouse_enabled = _this->internal->raw_mouse_enabled;
SDL_bool raw_keyboard_enabled = _this->internal->raw_keyboard_enabled;
SDL_LockMutex(data->lock);
{
data->enabled_input = (raw_mouse_enabled ? GameInputKindMouse : 0) |
(raw_keyboard_enabled ? GameInputKindKeyboard : 0);
// Reset input if not enabled
for (int i = 0; i < data->num_devices; ++i) {
GAMEINPUT_Device *device = data->devices[i];
if (device->last_mouse_reading && !raw_mouse_enabled) {
IGameInputReading_Release(device->last_mouse_reading);
device->last_mouse_reading = NULL;
}
if (device->last_keyboard_reading && !raw_keyboard_enabled) {
IGameInputReading_Release(device->last_keyboard_reading);
device->last_keyboard_reading = NULL;
}
}
}
SDL_UnlockMutex(data->lock);
return 0;
}
void WIN_QuitGameInput(SDL_VideoDevice *_this)
{
WIN_GameInputData *data = _this->internal->gameinput_context;
if (!data) {
return;
}
if (data->pGameInput) {
/* free the callback */
if (data->gameinput_callback_token != GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE) {
IGameInput_UnregisterCallback(data->pGameInput, data->gameinput_callback_token, /*timeoutInUs:*/ 10000);
data->gameinput_callback_token = GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE;
}
/* free the list */
while (data->num_devices > 0) {
GAMEINPUT_InternalRemoveByIndex(data, 0);
}
IGameInput_Release(data->pGameInput);
data->pGameInput = NULL;
}
if (data->hGameInputDLL) {
SDL_UnloadObject(data->hGameInputDLL);
data->hGameInputDLL = NULL;
}
if (data->lock) {
SDL_DestroyMutex(data->lock);
data->lock = NULL;
}
SDL_free(data);
_this->internal->gameinput_context = NULL;
}
#else /* !HAVE_GAMEINPUT_H */
int WIN_InitGameInput(SDL_VideoDevice* _this)
{
return SDL_Unsupported();
}
int WIN_UpdateGameInputEnabled(SDL_VideoDevice *_this)
{
return SDL_Unsupported();
}
void WIN_UpdateGameInput(SDL_VideoDevice* _this)
{
return;
}
void WIN_QuitGameInput(SDL_VideoDevice* _this)
{
return;
}
#endif /* HAVE_GAMEINPUT_H */

View File

@ -0,0 +1,29 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
typedef struct WIN_GameInputData WIN_GameInputData;
extern int WIN_InitGameInput(SDL_VideoDevice *_this);
extern int WIN_UpdateGameInputEnabled(SDL_VideoDevice *_this);
extern void WIN_UpdateGameInput(SDL_VideoDevice *_this);
extern void WIN_QuitGameInput(SDL_VideoDevice *_this);

View File

@ -203,14 +203,36 @@ int WIN_SetRawMouseEnabled(SDL_VideoDevice *_this, SDL_bool enabled)
{
SDL_VideoData *data = _this->internal;
data->raw_mouse_enabled = enabled;
return WIN_UpdateRawInputEnabled(_this);
if (data->gameinput_context) {
if (WIN_UpdateGameInputEnabled(_this) < 0) {
data->raw_mouse_enabled = !enabled;
return -1;
}
} else {
if (WIN_UpdateRawInputEnabled(_this) < 0) {
data->raw_mouse_enabled = !enabled;
return -1;
}
}
return 0;
}
int WIN_SetRawKeyboardEnabled(SDL_VideoDevice *_this, SDL_bool enabled)
{
SDL_VideoData *data = _this->internal;
data->raw_keyboard_enabled = enabled;
return WIN_UpdateRawInputEnabled(_this);
if (data->gameinput_context) {
if (WIN_UpdateGameInputEnabled(_this) < 0) {
data->raw_keyboard_enabled = !enabled;
return -1;
}
} else {
if (WIN_UpdateRawInputEnabled(_this) < 0) {
data->raw_keyboard_enabled = !enabled;
return -1;
}
}
return 0;
}
#else

View File

@ -491,6 +491,10 @@ int WIN_VideoInit(SDL_VideoDevice *_this)
SDL_Log("DPI awareness: %s", WIN_GetDPIAwareness(_this));
#endif
if (SDL_GetHintBoolean(SDL_HINT_WINDOWS_GAMEINPUT, SDL_TRUE)) {
WIN_InitGameInput(_this);
}
#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
/* For Xbox, we just need to create the single display */
{
@ -511,7 +515,9 @@ int WIN_VideoInit(SDL_VideoDevice *_this)
WIN_InitKeyboard(_this);
WIN_InitMouse(_this);
WIN_InitDeviceNotification();
WIN_CheckKeyboardAndMouseHotplug(_this, SDL_TRUE);
if (!_this->internal->gameinput_context) {
WIN_CheckKeyboardAndMouseHotplug(_this, SDL_TRUE);
}
#endif
SDL_AddHintCallback(SDL_HINT_WINDOWS_RAW_KEYBOARD, UpdateWindowsRawKeyboard, _this);
@ -530,13 +536,6 @@ void WIN_VideoQuit(SDL_VideoDevice *_this)
{
SDL_VideoData *data = _this->internal;
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
WIN_QuitModes(_this);
WIN_QuitDeviceNotification();
WIN_QuitKeyboard(_this);
WIN_QuitMouse(_this);
#endif
SDL_DelHintCallback(SDL_HINT_WINDOWS_RAW_KEYBOARD, UpdateWindowsRawKeyboard, _this);
SDL_DelHintCallback(SDL_HINT_WINDOWS_ENABLE_MESSAGELOOP, UpdateWindowsEnableMessageLoop, NULL);
SDL_DelHintCallback(SDL_HINT_WINDOWS_ENABLE_MENU_MNEMONICS, UpdateWindowsEnableMenuMnemonics, NULL);
@ -544,13 +543,20 @@ void WIN_VideoQuit(SDL_VideoDevice *_this)
WIN_SetRawMouseEnabled(_this, SDL_FALSE);
WIN_SetRawKeyboardEnabled(_this, SDL_FALSE);
WIN_QuitGameInput(_this);
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
WIN_QuitModes(_this);
WIN_QuitDeviceNotification();
WIN_QuitKeyboard(_this);
WIN_QuitMouse(_this);
#if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES))
if (data->oleinitialized) {
OleUninitialize();
data->oleinitialized = SDL_FALSE;
}
#endif /* !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) */
if (data->coinitialized) {
WIN_CoUninitialize();
data->coinitialized = SDL_FALSE;

View File

@ -47,6 +47,7 @@
#include "SDL_windowsclipboard.h"
#include "SDL_windowsevents.h"
#include "SDL_windowsgameinput.h"
#include "SDL_windowsopengl.h"
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
@ -437,6 +438,8 @@ struct SDL_VideoData
SDL_bool pending_E1_key_sequence;
Uint32 raw_input_enabled;
WIN_GameInputData *gameinput_context;
#ifndef SDL_DISABLE_WINDOWS_IME
SDL_bool ime_initialized;
SDL_bool ime_enabled;