#include "SDL_internal.h"
#include "SDL_windowsvideo.h"
#ifdef HAVE_GAMEINPUT_H
#include "../../core/windows/SDL_gameinput.h"
extern "C" {
#include "../../events/SDL_mouse_c.h"
#include "../../events/SDL_keyboard_c.h"
#include "../../events/scancodes_windows.h"
}
#define MAX_GAMEINPUT_BUTTONS 7
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; bool registered;
bool delete_requested;
IGameInputReading *last_mouse_reading;
IGameInputReading *last_keyboard_reading;
} GAMEINPUT_Device;
struct WIN_GameInputData
{
IGameInput *pGameInput;
GameInputCallbackToken gameinput_callback_token;
int num_devices;
GAMEINPUT_Device **devices;
GameInputKind enabled_input;
SDL_Mutex *lock;
uint64_t timestamp_offset;
};
static bool GAMEINPUT_InternalAddOrFind(WIN_GameInputData *data, IGameInputDevice *pDevice)
{
GAMEINPUT_Device **devicelist = NULL;
GAMEINPUT_Device *device = NULL;
const GameInputDeviceInfo *info;
bool result = false;
#if GAMEINPUT_API_VERSION >= 1
HRESULT hr = pDevice->GetDeviceInfo(&info);
if (FAILED(hr)) {
return WIN_SetErrorFromHRESULT("IGameInputDevice_GetDeviceInfo", hr);
}
#else
info = pDevice->GetDeviceInfo();
#endif
SDL_LockMutex(data->lock);
{
for (int i = 0; i < data->num_devices; ++i) {
device = data->devices[i];
if (device && device->pDevice == pDevice) {
device->delete_requested = false;
result = true;
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->displayName) {
}
pDevice->AddRef();
device->pDevice = pDevice;
device->instance_id = SDL_GetNextObjectID();
device->info = info;
data->devices = devicelist;
data->devices[data->num_devices++] = device;
result = true;
}
done:
SDL_UnlockMutex(data->lock);
return result;
}
static bool GAMEINPUT_InternalRemoveByIndex(WIN_GameInputData *data, int idx)
{
GAMEINPUT_Device **devicelist = NULL;
GAMEINPUT_Device *device;
bool result = false;
SDL_LockMutex(data->lock);
{
if (idx < 0 || idx >= data->num_devices) {
result = 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);
}
if (device->info->supportedInput & GameInputKindKeyboard) {
SDL_RemoveKeyboard(device->instance_id);
}
if (device->last_mouse_reading) {
device->last_mouse_reading->Release();
device->last_mouse_reading = NULL;
}
if (device->last_keyboard_reading) {
device->last_keyboard_reading->Release();
device->last_keyboard_reading = NULL;
}
}
device->pDevice->Release();
SDL_free(device->name);
SDL_free(device);
}
data->devices[idx] = NULL;
if (data->num_devices == 1) {
SDL_free(data->devices);
data->devices = NULL;
} else {
if (idx != data->num_devices - 1) {
size_t bytes = sizeof(*devicelist) * (data->num_devices - idx - 1);
SDL_memmove(&data->devices[idx], &data->devices[idx + 1], bytes);
}
}
--data->num_devices;
result = true;
}
done:
SDL_UnlockMutex(data->lock);
return result;
}
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) {
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) {
device->delete_requested = true;
break;
}
}
}
}
bool WIN_InitGameInput(SDL_VideoDevice *_this)
{
WIN_GameInputData *data;
HRESULT hr;
Uint64 now;
uint64_t timestampUS;
bool result = false;
if (_this->internal->gameinput_context) {
return true;
}
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;
}
if (!SDL_InitGameInput(&data->pGameInput)) {
goto done;
}
hr = data->pGameInput->RegisterDeviceCallback(NULL,
(GameInputKindMouse | GameInputKindKeyboard),
GameInputDeviceConnected,
GameInputBlockingEnumeration,
data,
GAMEINPUT_InternalDeviceCallback,
&data->gameinput_callback_token);
if (FAILED(hr)) {
WIN_SetErrorFromHRESULT("IGameInput::RegisterDeviceCallback", hr);
goto done;
}
now = SDL_GetTicksNS();
timestampUS = data->pGameInput->GetCurrentTimestamp();
data->timestamp_offset = (SDL_NS_TO_US(now) - timestampUS);
result = true;
done:
if (!result) {
WIN_QuitGameInput(_this);
}
return result;
}
static void GAMEINPUT_InitialMouseReading(WIN_GameInputData *data, SDL_Window *window, GAMEINPUT_Device *device, IGameInputReading *reading)
{
GameInputMouseState state;
if (reading->GetMouseState(&state)) {
Uint64 timestamp = SDL_US_TO_NS(reading->GetTimestamp() + data->timestamp_offset);
SDL_MouseID mouseID = device->instance_id;
for (int i = 0; i < MAX_GAMEINPUT_BUTTONS; ++i) {
const GameInputMouseButtons mask = GameInputMouseButtons(1 << i);
bool down = ((state.buttons & mask) != 0);
SDL_SendMouseButton(timestamp, window, mouseID, GAMEINPUT_button_map[i], down);
}
window->internal->mouse_button_flags = (WPARAM)-1;
}
}
static void GAMEINPUT_HandleMouseDelta(WIN_GameInputData *data, SDL_Window *window, GAMEINPUT_Device *device, IGameInputReading *last_reading, IGameInputReading *reading)
{
GameInputMouseState last;
GameInputMouseState state;
if (last_reading->GetMouseState(&last) && reading->GetMouseState(&state)) {
Uint64 timestamp = SDL_US_TO_NS(reading->GetTimestamp() + 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, true, (float)delta.positionX, (float)delta.positionY);
}
if (delta.buttons) {
for (int i = 0; i < MAX_GAMEINPUT_BUTTONS; ++i) {
const GameInputMouseButtons mask = GameInputMouseButtons(1 << i);
if (delta.buttons & mask) {
bool down = ((state.buttons & mask) != 0);
SDL_SendMouseButton(timestamp, window, mouseID, GAMEINPUT_button_map[i], down);
}
}
window->internal->mouse_button_flags = (WPARAM)-1;
}
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 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 true;
}
}
return false;
}
static void GAMEINPUT_InitialKeyboardReading(WIN_GameInputData *data, SDL_Window *window, GAMEINPUT_Device *device, IGameInputReading *reading)
{
Uint64 timestamp = SDL_US_TO_NS(reading->GetTimestamp() + 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 = reading->GetKeyState(max_keys, keys);
if (!num_keys) {
SDL_ResetKeyboard();
return;
}
int num_scancodes;
const bool *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, false);
}
}
for (uint32_t i = 0; i < num_keys; ++i) {
SDL_SendKeyboardKey(timestamp, keyboardID, keys[i].scanCode, GetScancodeFromKeyState(&keys[i]), true);
}
}
#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)", keys[i].scanCode, str);
}
}
#endif
static void GAMEINPUT_HandleKeyboardDelta(WIN_GameInputData *data, SDL_Window *window, GAMEINPUT_Device *device, IGameInputReading *last_reading, IGameInputReading *reading)
{
Uint64 timestamp = SDL_US_TO_NS(reading->GetTimestamp() + 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 = last_reading->GetKeyState(max_keys, last);
uint32_t num_keys = reading->GetKeyState(max_keys, keys);
#ifdef DEBUG_KEYS
SDL_Log("Timestamp: %llu", 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) {
++index_last;
++index_keys;
} else {
SDL_SendKeyboardKey(timestamp, keyboardID, last[index_last].scanCode, GetScancodeFromKeyState(&last[index_last]), false);
++index_last;
}
} else if (index_last < num_last) {
SDL_SendKeyboardKey(timestamp, keyboardID, last[index_last].scanCode, GetScancodeFromKeyState(&last[index_last]), false);
++index_last;
} else {
SDL_SendKeyboardKey(timestamp, keyboardID, keys[index_keys].scanCode, GetScancodeFromKeyState(&keys[index_keys]), true);
++index_keys;
}
}
}
void WIN_UpdateGameInput(SDL_VideoDevice *_this)
{
WIN_GameInputData *data = _this->internal->gameinput_context;
SDL_LockMutex(data->lock);
{
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);
}
if (device->info->supportedInput & GameInputKindKeyboard) {
SDL_AddKeyboard(device->instance_id, device->name);
}
device->registered = 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 = data->pGameInput->GetNextReading(device->last_mouse_reading, GameInputKindMouse, device->pDevice, &reading))) {
GAMEINPUT_HandleMouseDelta(data, window, device, device->last_mouse_reading, reading);
device->last_mouse_reading->Release();
device->last_mouse_reading = reading;
}
if (hr != GAMEINPUT_E_READING_NOT_FOUND) {
if (SUCCEEDED(data->pGameInput->GetCurrentReading(GameInputKindMouse, device->pDevice, &reading))) {
GAMEINPUT_HandleMouseDelta(data, window, device, device->last_mouse_reading, reading);
device->last_mouse_reading->Release();
device->last_mouse_reading = reading;
}
}
} else {
if (SUCCEEDED(data->pGameInput->GetCurrentReading(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) {
if (device->last_keyboard_reading) {
device->last_keyboard_reading->Release();
device->last_keyboard_reading = NULL;
}
} else {
if (device->last_keyboard_reading) {
HRESULT hr;
while (SUCCEEDED(hr = data->pGameInput->GetNextReading(device->last_keyboard_reading, GameInputKindKeyboard, device->pDevice, &reading))) {
GAMEINPUT_HandleKeyboardDelta(data, window, device, device->last_keyboard_reading, reading);
device->last_keyboard_reading->Release();
device->last_keyboard_reading = reading;
}
if (hr != GAMEINPUT_E_READING_NOT_FOUND) {
if (SUCCEEDED(data->pGameInput->GetCurrentReading(GameInputKindKeyboard, device->pDevice, &reading))) {
GAMEINPUT_HandleKeyboardDelta(data, window, device, device->last_keyboard_reading, reading);
device->last_keyboard_reading->Release();
device->last_keyboard_reading = reading;
}
}
} else {
if (SUCCEEDED(data->pGameInput->GetCurrentReading(GameInputKindKeyboard, device->pDevice, &reading))) {
GAMEINPUT_InitialKeyboardReading(data, window, device, reading);
device->last_keyboard_reading = reading;
}
}
}
}
}
}
SDL_UnlockMutex(data->lock);
}
bool WIN_UpdateGameInputEnabled(SDL_VideoDevice *_this)
{
WIN_GameInputData *data = _this->internal->gameinput_context;
bool raw_mouse_enabled = _this->internal->raw_mouse_enabled;
bool raw_keyboard_enabled = _this->internal->raw_keyboard_enabled;
SDL_LockMutex(data->lock);
{
data->enabled_input = (raw_mouse_enabled ? GameInputKindMouse : GameInputKindUnknown) |
(raw_keyboard_enabled ? GameInputKindKeyboard : GameInputKindUnknown);
for (int i = 0; i < data->num_devices; ++i) {
GAMEINPUT_Device *device = data->devices[i];
if (device->last_mouse_reading && !raw_mouse_enabled) {
device->last_mouse_reading->Release();
device->last_mouse_reading = NULL;
}
if (device->last_keyboard_reading && !raw_keyboard_enabled) {
device->last_keyboard_reading->Release();
device->last_keyboard_reading = NULL;
}
}
}
SDL_UnlockMutex(data->lock);
return true;
}
void WIN_QuitGameInput(SDL_VideoDevice *_this)
{
WIN_GameInputData *data = _this->internal->gameinput_context;
if (!data) {
return;
}
if (data->pGameInput) {
if (data->gameinput_callback_token) {
#if GAMEINPUT_API_VERSION >= 1
data->pGameInput->UnregisterCallback(data->gameinput_callback_token);
#else
data->pGameInput->UnregisterCallback(data->gameinput_callback_token, 10000);
#endif
data->gameinput_callback_token = 0;
}
while (data->num_devices > 0) {
GAMEINPUT_InternalRemoveByIndex(data, 0);
}
SDL_QuitGameInput();
data->pGameInput = NULL;
}
if (data->lock) {
SDL_DestroyMutex(data->lock);
data->lock = NULL;
}
SDL_free(data);
_this->internal->gameinput_context = NULL;
}
#else
bool WIN_InitGameInput(SDL_VideoDevice *_this)
{
return SDL_Unsupported();
}
bool WIN_UpdateGameInputEnabled(SDL_VideoDevice *_this)
{
return SDL_Unsupported();
}
void WIN_UpdateGameInput(SDL_VideoDevice *_this)
{
return;
}
void WIN_QuitGameInput(SDL_VideoDevice *_this)
{
return;
}
#endif