#include "SDL_internal.h"
#ifdef SDL_JOYSTICK_GAMEINPUT
#include "../SDL_sysjoystick.h"
#include "../usb_ids.h"
#include "../../core/windows/SDL_windows.h"
#include "../../core/windows/SDL_gameinput.h"
#if defined(SDL_PLATFORM_GDK)
#define SDL_GAMEINPUT_DEFAULT true
#else
#define SDL_GAMEINPUT_DEFAULT false
#endif
#if GAMEINPUT_API_VERSION >= 2
#endif
enum
{
SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE = 11
};
enum
{
SDL_GAMEINPUT_RAWTYPE_NONE,
SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_GUITAR,
SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_DRUM_KIT,
SDL_GAMEINPUT_RAWTYPE_GUITAR_HERO_LIVE_GUITAR,
SDL_GAMEINPUT_RAWTYPE_LEGACY_ADAPTER,
};
typedef struct GAMEINPUT_InternalDevice
{
IGameInputDevice *device;
char path[(APP_LOCAL_DEVICE_ID_SIZE * 2) + 1];
char *name;
SDL_GUID guid; SDL_JoystickID device_instance; const GameInputDeviceInfo *info;
int raw_type;
int steam_virtual_gamepad_slot;
bool isAdded;
bool isDeleteRequested;
} GAMEINPUT_InternalDevice;
typedef struct GAMEINPUT_InternalList
{
GAMEINPUT_InternalDevice **devices;
int count;
} GAMEINPUT_InternalList;
typedef struct joystick_hwdata
{
GAMEINPUT_InternalDevice *devref;
bool report_sensors;
GameInputRumbleParams rumbleParams;
GameInputCallbackToken system_button_callback_token;
} GAMEINPUT_InternalJoystickHwdata;
static GAMEINPUT_InternalList g_GameInputList = { NULL };
static IGameInput *g_pGameInput = NULL;
static GameInputCallbackToken g_GameInputCallbackToken = 0;
static Uint64 g_GameInputTimestampOffset;
extern "C"
{
extern bool SDL_XINPUT_Enabled(void);
}
static bool GAMEINPUT_InternalIsGamepad(const GameInputDeviceInfo *info)
{
if (info->supportedInput & GameInputKindGamepad) {
return true;
}
return false;
}
static Uint8 GAMEINPUT_GetDeviceRawType(const GameInputDeviceInfo *info)
{
#if GAMEINPUT_API_VERSION >= 3
GameInputKind supportedInput = info->supportedInput;
if (supportedInput & GameInputKindRawDeviceReport) {
switch (info->vendorId) {
case USB_VENDOR_MADCATZ:
switch (info->productId) {
case USB_PRODUCT_MADCATZ_XB1_STRATOCASTER_GUITAR:
return SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_GUITAR;
case USB_PRODUCT_MADCATZ_XB1_DRUM_KIT:
return SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_DRUM_KIT;
case USB_PRODUCT_MADCATZ_XB1_LEGACY_ADAPTER:
return SDL_GAMEINPUT_RAWTYPE_LEGACY_ADAPTER;
default:
break;
}
break;
case USB_VENDOR_PDP:
switch (info->productId) {
case USB_PRODUCT_PDP_XB1_JAGUAR_GUITAR:
case USB_PRODUCT_PDP_XB1_RIFFMASTER_GUITAR:
return SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_GUITAR;
case USB_PRODUCT_PDP_XB1_DRUM_KIT:
return SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_DRUM_KIT;
default:
break;
}
break;
case USB_VENDOR_CRKD:
switch (info->productId) {
case USB_PRODUCT_PDP_XB1_JAGUAR_GUITAR:
return SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_GUITAR;
default:
break;
}
break;
case USB_VENDOR_RED_OCTANE:
switch (info->productId) {
case USB_PRODUCT_RED_OCTANE_XB1_GUITAR_HERO_LIVE_GUITAR:
return SDL_GAMEINPUT_RAWTYPE_GUITAR_HERO_LIVE_GUITAR;
default:
break;
}
break;
}
}
#endif return SDL_GAMEINPUT_RAWTYPE_NONE;
}
static Uint8 GAMEINPUT_GetDeviceSubtype(const GameInputDeviceInfo *info)
{
GameInputKind supportedInput = info->supportedInput;
Uint8 rawType = GAMEINPUT_GetDeviceRawType(info);
if (rawType == SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_GUITAR || rawType == SDL_GAMEINPUT_RAWTYPE_GUITAR_HERO_LIVE_GUITAR) {
return SDL_JOYSTICK_TYPE_GUITAR;
}
if (rawType == SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_DRUM_KIT) {
return SDL_JOYSTICK_TYPE_DRUM_KIT;
}
if (supportedInput & GameInputKindRacingWheel) {
return SDL_JOYSTICK_TYPE_WHEEL;
}
if (supportedInput & GameInputKindArcadeStick) {
return SDL_JOYSTICK_TYPE_ARCADE_STICK;
}
if (supportedInput & GameInputKindFlightStick) {
return SDL_JOYSTICK_TYPE_FLIGHT_STICK;
}
if (supportedInput & (GameInputKindGamepad | GameInputKindController)) {
return SDL_JOYSTICK_TYPE_GAMEPAD;
}
return 0;
}
#if GAMEINPUT_API_VERSION >= 1
static int GetSteamVirtualGamepadSlot(const char *device_path)
{
int slot = -1;
(void)SDL_sscanf(device_path, "\\\\.\\pipe\\HID#VID_045E&PID_028E&IG_00#%*X&%*X&%*X#%d#%*u", &slot);
return slot;
}
#endif
static bool GAMEINPUT_InternalAddOrFind(IGameInputDevice *pDevice)
{
GAMEINPUT_InternalDevice **devicelist = NULL;
GAMEINPUT_InternalDevice *elem = NULL;
const GameInputDeviceInfo *info = NULL;
Uint16 bus = SDL_HARDWARE_BUS_USB;
Uint16 vendor = 0;
Uint16 product = 0;
Uint16 version = 0;
const char *product_string = NULL;
Uint8 driver_signature = 'g';
Uint8 subtype = 0;
int raw_type = SDL_GAMEINPUT_RAWTYPE_NONE;
char tmp[4];
int idx = 0;
SDL_AssertJoysticksLocked();
#if GAMEINPUT_API_VERSION >= 1
HRESULT hr = pDevice->GetDeviceInfo(&info);
if (FAILED(hr)) {
return WIN_SetErrorFromHRESULT("IGameInputDevice::GetDeviceInfo", hr);
}
#else
info = pDevice->GetDeviceInfo();
#endif
if (false ) {
bus = SDL_HARDWARE_BUS_BLUETOOTH;
} else {
bus = SDL_HARDWARE_BUS_USB;
}
vendor = info->vendorId;
product = info->productId;
subtype = GAMEINPUT_GetDeviceSubtype(info);
raw_type = GAMEINPUT_GetDeviceRawType(info);
#if GAMEINPUT_API_VERSION >= 1
if (info->displayName) {
product_string = info->displayName;
}
#else
if (info->displayName) {
product_string = info->displayName->data;
}
#endif
if (SDL_ShouldIgnoreJoystick(vendor, product, version, product_string) ||
SDL_JoystickHandledByAnotherDriver(&SDL_GAMEINPUT_JoystickDriver, vendor, product, version, product_string)) {
return true;
}
#if defined(SDL_JOYSTICK_DINPUT) && defined(SDL_HAPTIC_DINPUT)
if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_DIRECTINPUT, true) && info->forceFeedbackMotorCount > 0 && pDevice->IsForceFeedbackMotorPoweredOn(0)) {
return true;
}
#endif
if (!GAMEINPUT_InternalIsGamepad(info) && raw_type == SDL_GAMEINPUT_RAWTYPE_NONE) {
#if defined(SDL_JOYSTICK_DINPUT) || defined(SDL_JOYSTICK_XINPUT)
if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_DIRECTINPUT, true) || SDL_XINPUT_Enabled()) {
return true;
}
#endif
if (info->supportedInput & GameInputKindController) {
driver_signature = 0;
subtype = 0;
} else {
return true;
}
}
for (idx = 0; idx < g_GameInputList.count; ++idx) {
elem = g_GameInputList.devices[idx];
if (elem && elem->device == pDevice) {
elem->isDeleteRequested = false;
return true;
}
}
elem = (GAMEINPUT_InternalDevice *)SDL_calloc(1, sizeof(*elem));
if (!elem) {
return false;
}
devicelist = (GAMEINPUT_InternalDevice **)SDL_realloc(g_GameInputList.devices, sizeof(elem) * (g_GameInputList.count + 1LL));
if (!devicelist) {
SDL_free(elem);
return false;
}
for (idx = 0; idx < APP_LOCAL_DEVICE_ID_SIZE; ++idx) {
SDL_snprintf(tmp, SDL_arraysize(tmp), "%02hhX", info->deviceId.value[idx]);
SDL_strlcat(elem->path, tmp, SDL_arraysize(elem->path));
}
pDevice->AddRef();
elem->device = pDevice;
elem->name = SDL_CreateJoystickName(vendor, product, NULL, product_string);
elem->guid = SDL_CreateJoystickGUID(bus, vendor, product, version, NULL, product_string, driver_signature, subtype);
elem->device_instance = SDL_GetNextObjectID();
elem->info = info;
elem->raw_type = raw_type;
#if GAMEINPUT_API_VERSION >= 1
elem->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot(info->pnpPath);
#else
elem->steam_virtual_gamepad_slot = -1;
#endif
g_GameInputList.devices = devicelist;
g_GameInputList.devices[g_GameInputList.count++] = elem;
return true;
}
static bool GAMEINPUT_InternalRemoveByIndex(int idx)
{
GAMEINPUT_InternalDevice **devicelist = NULL;
GAMEINPUT_InternalDevice *elem;
int bytes = 0;
SDL_AssertJoysticksLocked();
if (idx < 0 || idx >= g_GameInputList.count) {
return SDL_SetError("GAMEINPUT_InternalRemoveByIndex argument idx %d is out of range", idx);
}
elem = g_GameInputList.devices[idx];
if (elem) {
elem->device->Release();
SDL_free(elem->name);
SDL_free(elem);
}
g_GameInputList.devices[idx] = NULL;
if (g_GameInputList.count == 1) {
SDL_free(g_GameInputList.devices);
g_GameInputList.devices = NULL;
} else {
if (idx != g_GameInputList.count - 1) {
bytes = sizeof(*devicelist) * (g_GameInputList.count - idx);
SDL_memmove(&g_GameInputList.devices[idx], &g_GameInputList.devices[idx + 1], bytes);
}
}
--g_GameInputList.count;
return true;
}
static GAMEINPUT_InternalDevice *GAMEINPUT_InternalFindByIndex(int idx)
{
SDL_AssertJoysticksLocked();
return g_GameInputList.devices[idx];
}
static void CALLBACK GAMEINPUT_InternalJoystickDeviceCallback(
_In_ GameInputCallbackToken callbackToken,
_In_ void *context,
_In_ IGameInputDevice *device,
_In_ uint64_t timestamp,
_In_ GameInputDeviceStatus currentStatus,
_In_ GameInputDeviceStatus previousStatus)
{
int idx = 0;
GAMEINPUT_InternalDevice *elem = NULL;
if (!device) {
return;
}
SDL_LockJoysticks();
if (currentStatus & GameInputDeviceConnected) {
GAMEINPUT_InternalAddOrFind(device);
} else {
for (idx = 0; idx < g_GameInputList.count; ++idx) {
elem = g_GameInputList.devices[idx];
if (elem && elem->device == device) {
elem->isDeleteRequested = true;
break;
}
}
}
SDL_UnlockJoysticks();
}
static void GAMEINPUT_JoystickDetect(void);
static void GAMEINPUT_JoystickQuit(void);
static bool GAMEINPUT_IsRawGameInputEnabled(void)
{
#if GAMEINPUT_API_VERSION >= 3
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_GAMEINPUT_RAW, true);
#else
return false;
#endif
}
static bool GAMEINPUT_JoystickInit(void)
{
HRESULT hr;
if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_GAMEINPUT, SDL_GAMEINPUT_DEFAULT) && !GAMEINPUT_IsRawGameInputEnabled()) {
return true;
}
if (!SDL_InitGameInput(&g_pGameInput)) {
return false;
}
#if GAMEINPUT_API_VERSION >= 2
g_pGameInput->SetFocusPolicy(GameInputEnableBackgroundInput | GameInputEnableBackgroundGuideButton | GameInputEnableBackgroundShareButton);
#endif
GameInputKind kind = GameInputKindUnknown;
if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_GAMEINPUT, SDL_GAMEINPUT_DEFAULT)) {
kind |= GameInputKindController;
}
if (GAMEINPUT_IsRawGameInputEnabled()) {
kind |= GameInputKindRawDeviceReport;
}
hr = g_pGameInput->RegisterDeviceCallback(NULL,
kind,
GameInputDeviceConnected,
GameInputBlockingEnumeration,
NULL,
GAMEINPUT_InternalJoystickDeviceCallback,
&g_GameInputCallbackToken);
if (FAILED(hr)) {
GAMEINPUT_JoystickQuit();
return WIN_SetErrorFromHRESULT("IGameInput::RegisterDeviceCallback", hr);
}
Uint64 now = SDL_GetTicksNS();
uint64_t timestampUS = g_pGameInput->GetCurrentTimestamp();
g_GameInputTimestampOffset = (SDL_NS_TO_US(now) - timestampUS);
GAMEINPUT_JoystickDetect();
return true;
}
static int GAMEINPUT_JoystickGetCount(void)
{
SDL_AssertJoysticksLocked();
return g_GameInputList.count;
}
static void GAMEINPUT_JoystickDetect(void)
{
int idx;
GAMEINPUT_InternalDevice *elem = NULL;
SDL_AssertJoysticksLocked();
for (idx = 0; idx < g_GameInputList.count; ++idx) {
elem = g_GameInputList.devices[idx];
if (!elem) {
continue;
}
if (!elem->isAdded) {
SDL_PrivateJoystickAdded(elem->device_instance);
elem->isAdded = true;
}
if (elem->isDeleteRequested || !(elem->device->GetDeviceStatus() & GameInputDeviceConnected)) {
SDL_PrivateJoystickRemoved(elem->device_instance);
GAMEINPUT_InternalRemoveByIndex(idx--);
}
}
}
static bool GAMEINPUT_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
{
SDL_AssertJoysticksLocked();
if (g_pGameInput) {
if (vendor_id == USB_VENDOR_MICROSOFT &&
product_id == USB_PRODUCT_XBOX_ONE_XBOXGIP_CONTROLLER) {
return true;
}
for (int i = 0; i < g_GameInputList.count; ++i) {
GAMEINPUT_InternalDevice *elem = g_GameInputList.devices[i];
if (elem && vendor_id == elem->info->vendorId && product_id == elem->info->productId) {
return true;
}
}
}
return false;
}
static const char *GAMEINPUT_JoystickGetDeviceName(int device_index)
{
return GAMEINPUT_InternalFindByIndex(device_index)->name;
}
static const char *GAMEINPUT_JoystickGetDevicePath(int device_index)
{
return GAMEINPUT_InternalFindByIndex(device_index)->path;
}
static int GAMEINPUT_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
{
return GAMEINPUT_InternalFindByIndex(device_index)->steam_virtual_gamepad_slot;
}
static int GAMEINPUT_JoystickGetDevicePlayerIndex(int device_index)
{
return -1;
}
static void GAMEINPUT_JoystickSetDevicePlayerIndex(int device_index, int player_index)
{
}
static SDL_GUID GAMEINPUT_JoystickGetDeviceGUID(int device_index)
{
return GAMEINPUT_InternalFindByIndex(device_index)->guid;
}
static SDL_JoystickID GAMEINPUT_JoystickGetDeviceInstanceID(int device_index)
{
return GAMEINPUT_InternalFindByIndex(device_index)->device_instance;
}
static void GAMEINPUT_UpdatePowerInfo(SDL_Joystick *joystick, IGameInputDevice *device)
{
#if 0#endif
}
#if GAMEINPUT_API_VERSION >= 1
static void CALLBACK GAMEINPUT_InternalSystemButtonCallback(
_In_ GameInputCallbackToken callbackToken,
_In_ void * context,
_In_ IGameInputDevice * device,
_In_ uint64_t timestampUS,
_In_ GameInputSystemButtons currentButtons,
_In_ GameInputSystemButtons previousButtons)
{
SDL_Joystick *joystick = (SDL_Joystick *)context;
GameInputSystemButtons changedButtons = (previousButtons ^ currentButtons);
if (changedButtons) {
Uint64 timestamp = SDL_US_TO_NS(timestampUS + g_GameInputTimestampOffset);
SDL_LockJoysticks();
if (changedButtons & GameInputSystemButtonGuide) {
bool down = ((currentButtons & GameInputSystemButtonGuide) != 0);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, down);
}
if (changedButtons & GameInputSystemButtonShare) {
bool down = ((currentButtons & GameInputSystemButtonShare) != 0);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE, down);
}
SDL_UnlockJoysticks();
}
}
#endif
static bool GAMEINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index)
{
GAMEINPUT_InternalDevice *elem = GAMEINPUT_InternalFindByIndex(device_index);
const GameInputDeviceInfo *info = elem->info;
GAMEINPUT_InternalJoystickHwdata *hwdata = NULL;
if (!elem) {
return false;
}
hwdata = (GAMEINPUT_InternalJoystickHwdata *)SDL_calloc(1, sizeof(*hwdata));
if (!hwdata) {
return false;
}
hwdata->devref = elem;
joystick->hwdata = hwdata;
if (GAMEINPUT_InternalIsGamepad(info) || GAMEINPUT_GetDeviceRawType(info) != SDL_GAMEINPUT_RAWTYPE_NONE) {
joystick->naxes = 6;
joystick->nbuttons = 11;
joystick->nhats = 1;
#if GAMEINPUT_API_VERSION >= 1
if (info->supportedSystemButtons != GameInputSystemButtonNone) {
if (info->supportedSystemButtons & GameInputSystemButtonShare) {
++joystick->nbuttons;
}
g_pGameInput->RegisterSystemButtonCallback(elem->device, (GameInputSystemButtonGuide | GameInputSystemButtonShare), joystick, GAMEINPUT_InternalSystemButtonCallback, &hwdata->system_button_callback_token);
}
#endif } else {
#if GAMEINPUT_API_VERSION >= 3
joystick->naxes = info->controllerInfo->controllerAxisCount;
joystick->nbuttons = info->controllerInfo->controllerButtonCount;
joystick->nhats = info->controllerInfo->controllerSwitchCount;
#else
joystick->naxes = info->controllerAxisCount;
joystick->nbuttons = info->controllerButtonCount;
joystick->nhats = info->controllerSwitchCount;
#endif }
if (info->supportedRumbleMotors & (GameInputRumbleLowFrequency | GameInputRumbleHighFrequency)) {
SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
}
if (info->supportedRumbleMotors & (GameInputRumbleLeftTrigger | GameInputRumbleRightTrigger)) {
SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true);
}
#ifdef GAMEINPUT_SENSOR_SUPPORT
if (info->supportedInput & GameInputKindSensors) {
if (info->sensorsInfo->supportedSensors & GameInputSensorsGyrometer) {
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f);
}
if (info->sensorsInfo->supportedSensors & GameInputSensorsAccelerometer) {
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f);
}
}
#endif
return true;
}
static bool GAMEINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
{
GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata;
GameInputRumbleParams *params = &hwdata->rumbleParams;
params->lowFrequency = (float)low_frequency_rumble / (float)SDL_MAX_UINT16;
params->highFrequency = (float)high_frequency_rumble / (float)SDL_MAX_UINT16;
hwdata->devref->device->SetRumbleState(params);
return true;
}
static bool GAMEINPUT_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
{
GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata;
GameInputRumbleParams *params = &hwdata->rumbleParams;
params->leftTrigger = (float)left_rumble / (float)SDL_MAX_UINT16;
params->rightTrigger = (float)right_rumble / (float)SDL_MAX_UINT16;
hwdata->devref->device->SetRumbleState(params);
return true;
}
static bool GAMEINPUT_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
{
return SDL_Unsupported();
}
static bool GAMEINPUT_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
{
return SDL_Unsupported();
}
static bool GAMEINPUT_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
{
joystick->hwdata->report_sensors = enabled;
return true;
}
static void GAMEINPUT_GuitarUpdate(SDL_Joystick *joystick, IGameInputReading *reading, Uint64 timestamp)
{
IGameInputRawDeviceReport* rawState;
if (reading->GetRawReport(&rawState)) {
static WORD s_GuitarButtons[] = {
0x0010, 0x0020, 0x0040, 0x0080, 0x0008, 0, 0x0004, 0, 0x4000, 0x1000, 0, };
Uint8 btnidx = 0, hat = 0;
uint8_t rawData[40];
SDL_memset(rawData, 0, sizeof(rawData));
size_t len = rawState->GetRawData(sizeof(rawData), rawData);
uint16_t buttons = rawData[0] | rawData[1] << 8;
if (len >= 10) {
for (btnidx = 0; btnidx < SDL_arraysize(s_GuitarButtons); ++btnidx) {
WORD button_mask = s_GuitarButtons[btnidx];
if (!button_mask) {
continue;
}
bool down = ((buttons & button_mask) != 0);
SDL_SendJoystickButton(timestamp, joystick, btnidx, down);
}
if (buttons & 0x0100) {
hat |= SDL_HAT_UP;
}
if (buttons & 0x0200) {
hat |= SDL_HAT_DOWN;
}
if (buttons & 0x0400) {
hat |= SDL_HAT_LEFT;
}
if (buttons & 0x0800) {
hat |= SDL_HAT_RIGHT;
}
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, (rawData[3] * 257) - 32768);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, rawData[2] >= 0xD0);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (rawData[6]) ? 32767 : -32768);
static const Sint16 effects_mappings[] = {-26880, -13568, -1792, 11008, 24576};
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, effects_mappings[rawData[4] >> 4]);
}
}
}
static void GAMEINPUT_GamepadUpdate(SDL_Joystick *joystick, IGameInputReading *reading, Uint64 timestamp) {
GameInputGamepadState state;
static WORD s_XInputButtons[] = {
GameInputGamepadA, GameInputGamepadB, GameInputGamepadX, GameInputGamepadY, GameInputGamepadView, 0, GameInputGamepadMenu, GameInputGamepadLeftThumbstick, GameInputGamepadRightThumbstick, GameInputGamepadLeftShoulder, GameInputGamepadRightShoulder, };
Uint8 btnidx = 0, hat = 0;
if (reading->GetGamepadState(&state)) {
for (btnidx = 0; btnidx < SDL_arraysize(s_XInputButtons); ++btnidx) {
WORD button_mask = s_XInputButtons[btnidx];
if (!button_mask) {
continue;
}
bool down = ((state.buttons & button_mask) != 0);
SDL_SendJoystickButton(timestamp, joystick, btnidx, down);
}
if (state.buttons & GameInputGamepadDPadUp) {
hat |= SDL_HAT_UP;
}
if (state.buttons & GameInputGamepadDPadDown) {
hat |= SDL_HAT_DOWN;
}
if (state.buttons & GameInputGamepadDPadLeft) {
hat |= SDL_HAT_LEFT;
}
if (state.buttons & GameInputGamepadDPadRight) {
hat |= SDL_HAT_RIGHT;
}
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
#define CONVERT_AXIS(v) (Sint16)(((v) < 0.0f) ? ((v)*32768.0f) : ((v)*32767.0f))
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, CONVERT_AXIS(state.leftThumbstickX));
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, CONVERT_AXIS(-state.leftThumbstickY));
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, CONVERT_AXIS(state.rightThumbstickX));
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, CONVERT_AXIS(-state.rightThumbstickY));
#undef CONVERT_AXIS
#define CONVERT_TRIGGER(v) (Sint16)((v)*65535.0f - 32768.0f)
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, CONVERT_TRIGGER(state.leftTrigger));
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, CONVERT_TRIGGER(state.rightTrigger));
#undef CONVERT_TRIGGER
}
}
static void GAMEINPUT_ControllerUpdate(SDL_Joystick *joystick, IGameInputReading *reading, Uint64 timestamp)
{
bool *button_state = SDL_stack_alloc(bool, joystick->nbuttons);
float *axis_state = SDL_stack_alloc(float, joystick->naxes);
GameInputSwitchPosition *switch_state = SDL_stack_alloc(GameInputSwitchPosition, joystick->nhats);
if (button_state) {
uint32_t i;
uint32_t button_count = reading->GetControllerButtonState(joystick->nbuttons, button_state);
for (i = 0; i < button_count; ++i) {
SDL_SendJoystickButton(timestamp, joystick, (Uint8)i, button_state[i]);
}
SDL_stack_free(button_state);
}
#define CONVERT_AXIS(v) (Sint16)((v)*65535.0f - 32768.0f)
if (axis_state) {
uint32_t i;
uint32_t axis_count = reading->GetControllerAxisState(joystick->naxes, axis_state);
for (i = 0; i < axis_count; ++i) {
SDL_SendJoystickAxis(timestamp, joystick, (Uint8)i, CONVERT_AXIS(axis_state[i]));
}
SDL_stack_free(axis_state);
}
#undef CONVERT_AXIS
if (switch_state) {
uint32_t i;
uint32_t switch_count = reading->GetControllerSwitchState(joystick->nhats, switch_state);
for (i = 0; i < switch_count; ++i) {
Uint8 hat;
switch (switch_state[i]) {
case GameInputSwitchUp:
hat = SDL_HAT_UP;
break;
case GameInputSwitchUpRight:
hat = SDL_HAT_UP | SDL_HAT_RIGHT;
break;
case GameInputSwitchRight:
hat = SDL_HAT_RIGHT;
break;
case GameInputSwitchDownRight:
hat = SDL_HAT_DOWN | SDL_HAT_RIGHT;
break;
case GameInputSwitchDown:
hat = SDL_HAT_DOWN;
break;
case GameInputSwitchDownLeft:
hat = SDL_HAT_DOWN | SDL_HAT_LEFT;
break;
case GameInputSwitchLeft:
hat = SDL_HAT_LEFT;
break;
case GameInputSwitchUpLeft:
hat = SDL_HAT_UP | SDL_HAT_LEFT;
break;
case GameInputSwitchCenter:
default:
hat = SDL_HAT_CENTERED;
break;
}
SDL_SendJoystickHat(timestamp, joystick, (Uint8)i, hat);
}
SDL_stack_free(switch_state);
}
}
static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick)
{
GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata;
GAMEINPUT_InternalDevice *internal_device = hwdata->devref;
IGameInputDevice *device = hwdata->devref->device;
const GameInputDeviceInfo *info = hwdata->devref->info;
IGameInputReading *reading = NULL;
Uint64 timestamp;
HRESULT hr;
hr = g_pGameInput->GetCurrentReading(info->supportedInput, device, &reading);
if (FAILED(hr)) {
return;
}
timestamp = SDL_US_TO_NS(reading->GetTimestamp() + g_GameInputTimestampOffset);
if (internal_device->raw_type == SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_GUITAR) {
GAMEINPUT_GuitarUpdate(joystick, reading, timestamp);
} else if (GAMEINPUT_InternalIsGamepad(info)) {
GAMEINPUT_GamepadUpdate(joystick, reading, timestamp);
} else {
GAMEINPUT_ControllerUpdate(joystick, reading, timestamp);
}
#ifdef GAMEINPUT_SENSOR_SUPPORT
if (hwdata->report_sensors) {
GameInputSensorsState sensor_state;
if (reading->GetSensorsState(&sensor_state)) {
if ((info->sensorsInfo->supportedSensors & GameInputSensorsGyrometer) != 0) {
float data[3] = {
sensor_state.angularVelocityInRadPerSecX,
sensor_state.angularVelocityInRadPerSecY,
sensor_state.angularVelocityInRadPerSecZ
};
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, timestamp, data, SDL_arraysize(data));
}
if ((info->sensorsInfo->supportedSensors & GameInputSensorsAccelerometer) != 0) {
float data[3] = {
sensor_state.accelerationInGX * SDL_STANDARD_GRAVITY,
sensor_state.accelerationInGY * SDL_STANDARD_GRAVITY,
sensor_state.accelerationInGZ * SDL_STANDARD_GRAVITY
};
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, timestamp, data, SDL_arraysize(data));
}
}
}
#endif
reading->Release();
GAMEINPUT_UpdatePowerInfo(joystick, device);
}
static void GAMEINPUT_JoystickClose(SDL_Joystick *joystick)
{
GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata;
if (hwdata->system_button_callback_token) {
#if GAMEINPUT_API_VERSION >= 1
g_pGameInput->UnregisterCallback(hwdata->system_button_callback_token);
#else
g_pGameInput->UnregisterCallback(hwdata->system_button_callback_token, 10000);
#endif
}
SDL_free(hwdata);
joystick->hwdata = NULL;
}
static void GAMEINPUT_JoystickQuit(void)
{
if (g_pGameInput) {
if (g_GameInputCallbackToken) {
#if GAMEINPUT_API_VERSION >= 1
g_pGameInput->UnregisterCallback(g_GameInputCallbackToken);
#else
g_pGameInput->UnregisterCallback(g_GameInputCallbackToken, 10000);
#endif
g_GameInputCallbackToken = 0;
}
while (g_GameInputList.count > 0) {
GAMEINPUT_InternalRemoveByIndex(0);
}
SDL_QuitGameInput();
g_pGameInput = NULL;
}
}
static bool GAMEINPUT_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
{
GAMEINPUT_InternalDevice *elem = GAMEINPUT_InternalFindByIndex(device_index);
if (!GAMEINPUT_InternalIsGamepad(elem->info) && elem->raw_type == SDL_GAMEINPUT_RAWTYPE_NONE) {
return false;
}
out->a.kind = EMappingKind_Button;
out->a.target = SDL_GAMEPAD_BUTTON_SOUTH;
out->b.kind = EMappingKind_Button;
out->b.target = SDL_GAMEPAD_BUTTON_EAST;
out->x.kind = EMappingKind_Button;
out->x.target = SDL_GAMEPAD_BUTTON_WEST;
out->y.kind = EMappingKind_Button;
out->y.target = SDL_GAMEPAD_BUTTON_NORTH;
out->back.kind = EMappingKind_Button;
out->back.target = SDL_GAMEPAD_BUTTON_BACK;
#if GAMEINPUT_API_VERSION >= 1
if (elem->info->supportedSystemButtons & GameInputSystemButtonGuide) {
out->guide.kind = EMappingKind_Button;
out->guide.target = SDL_GAMEPAD_BUTTON_GUIDE;
}
if (elem->info->supportedSystemButtons & GameInputSystemButtonShare) {
out->misc1.kind = EMappingKind_Button;
out->misc1.target = SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE;
}
#endif
out->start.kind = EMappingKind_Button;
out->start.target = SDL_GAMEPAD_BUTTON_START;
out->leftstick.kind = EMappingKind_Button;
out->leftstick.target = SDL_GAMEPAD_BUTTON_LEFT_STICK;
out->rightstick.kind = EMappingKind_Button;
out->rightstick.target = SDL_GAMEPAD_BUTTON_RIGHT_STICK;
out->leftshoulder.kind = EMappingKind_Button;
out->leftshoulder.target = SDL_GAMEPAD_BUTTON_LEFT_SHOULDER;
out->rightshoulder.kind = EMappingKind_Button;
out->rightshoulder.target = SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER;
out->dpup.kind = EMappingKind_Hat;
out->dpup.target = SDL_HAT_UP;
out->dpdown.kind = EMappingKind_Hat;
out->dpdown.target = SDL_HAT_DOWN;
out->dpleft.kind = EMappingKind_Hat;
out->dpleft.target = SDL_HAT_LEFT;
out->dpright.kind = EMappingKind_Hat;
out->dpright.target = SDL_HAT_RIGHT;
out->leftx.kind = EMappingKind_Axis;
out->leftx.target = SDL_GAMEPAD_AXIS_LEFTX;
out->lefty.kind = EMappingKind_Axis;
out->lefty.target = SDL_GAMEPAD_AXIS_LEFTY;
out->rightx.kind = EMappingKind_Axis;
out->rightx.target = SDL_GAMEPAD_AXIS_RIGHTX;
out->righty.kind = EMappingKind_Axis;
out->righty.target = SDL_GAMEPAD_AXIS_RIGHTY;
out->lefttrigger.kind = EMappingKind_Axis;
out->lefttrigger.target = SDL_GAMEPAD_AXIS_LEFT_TRIGGER;
out->righttrigger.kind = EMappingKind_Axis;
out->righttrigger.target = SDL_GAMEPAD_AXIS_RIGHT_TRIGGER;
return true;
}
SDL_JoystickDriver SDL_GAMEINPUT_JoystickDriver =
{
GAMEINPUT_JoystickInit,
GAMEINPUT_JoystickGetCount,
GAMEINPUT_JoystickDetect,
GAMEINPUT_JoystickIsDevicePresent,
GAMEINPUT_JoystickGetDeviceName,
GAMEINPUT_JoystickGetDevicePath,
GAMEINPUT_JoystickGetDeviceSteamVirtualGamepadSlot,
GAMEINPUT_JoystickGetDevicePlayerIndex,
GAMEINPUT_JoystickSetDevicePlayerIndex,
GAMEINPUT_JoystickGetDeviceGUID,
GAMEINPUT_JoystickGetDeviceInstanceID,
GAMEINPUT_JoystickOpen,
GAMEINPUT_JoystickRumble,
GAMEINPUT_JoystickRumbleTriggers,
GAMEINPUT_JoystickSetLED,
GAMEINPUT_JoystickSendEffect,
GAMEINPUT_JoystickSetSensorsEnabled,
GAMEINPUT_JoystickUpdate,
GAMEINPUT_JoystickClose,
GAMEINPUT_JoystickQuit,
GAMEINPUT_JoystickGetGamepadMapping
};
#endif