feat(gx): add incomplete 'CGxDeviceGLSDL' (#2)

* chore(build): add vendored SDL 3.0.0 library

* chore(build): add vendored glew-cmake-2.2.0 library

* feat(console): in the presence of -opengl launch flag, change GxApi to OpenGl

* feat(gx): add uncompleted CGxDeviceGLSDL targeting Windows and Linux

* chore(build): change SDL3 linkage from shared (bad) to to static (good)
This commit is contained in:
phaneron 2023-11-18 10:50:16 -05:00 committed by GitHub
parent 934e0fb600
commit 706c8903a1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2043 changed files with 663533 additions and 5 deletions

View file

@ -0,0 +1,234 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 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.
*/
/* This driver supports the Nintendo Switch Joy-Cons pair controllers */
#include "SDL_internal.h"
#ifdef SDL_JOYSTICK_HIDAPI
#include "SDL_hidapijoystick_c.h"
#include "../SDL_sysjoystick.h"
static void HIDAPI_DriverCombined_RegisterHints(SDL_HintCallback callback, void *userdata)
{
}
static void HIDAPI_DriverCombined_UnregisterHints(SDL_HintCallback callback, void *userdata)
{
}
static SDL_bool HIDAPI_DriverCombined_IsEnabled(void)
{
return SDL_TRUE;
}
static SDL_bool HIDAPI_DriverCombined_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
{
/* This is always explicitly created for combined devices */
return SDL_FALSE;
}
static SDL_bool HIDAPI_DriverCombined_InitDevice(SDL_HIDAPI_Device *device)
{
return HIDAPI_JoystickConnected(device, NULL);
}
static int HIDAPI_DriverCombined_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
{
return -1;
}
static void HIDAPI_DriverCombined_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
{
}
static SDL_bool HIDAPI_DriverCombined_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
int i;
char *serial = NULL, *new_serial;
size_t serial_length = 0, new_length;
SDL_AssertJoysticksLocked();
for (i = 0; i < device->num_children; ++i) {
SDL_HIDAPI_Device *child = device->children[i];
if (!child->driver->OpenJoystick(child, joystick)) {
while (i-- > 0) {
child = device->children[i];
child->driver->CloseJoystick(child, joystick);
}
if (serial) {
SDL_free(serial);
}
return SDL_FALSE;
}
/* Extend the serial number with the child serial number */
if (joystick->serial) {
new_length = serial_length + 1 + SDL_strlen(joystick->serial);
new_serial = (char *)SDL_realloc(serial, new_length);
if (new_serial) {
if (serial) {
SDL_strlcat(new_serial, ",", new_length);
SDL_strlcat(new_serial, joystick->serial, new_length);
} else {
SDL_strlcpy(new_serial, joystick->serial, new_length);
}
serial = new_serial;
serial_length = new_length;
}
SDL_free(joystick->serial);
joystick->serial = NULL;
}
}
/* Update the joystick with the combined serial numbers */
if (joystick->serial) {
SDL_free(joystick->serial);
}
joystick->serial = serial;
return SDL_TRUE;
}
static int HIDAPI_DriverCombined_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
{
int i;
int result = -1;
for (i = 0; i < device->num_children; ++i) {
SDL_HIDAPI_Device *child = device->children[i];
if (child->driver->RumbleJoystick(child, joystick, low_frequency_rumble, high_frequency_rumble) == 0) {
result = 0;
}
}
return result;
}
static int HIDAPI_DriverCombined_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
{
int i;
int result = -1;
for (i = 0; i < device->num_children; ++i) {
SDL_HIDAPI_Device *child = device->children[i];
if (child->driver->RumbleJoystickTriggers(child, joystick, left_rumble, right_rumble) == 0) {
result = 0;
}
}
return result;
}
static Uint32 HIDAPI_DriverCombined_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
int i;
Uint32 caps = 0;
for (i = 0; i < device->num_children; ++i) {
SDL_HIDAPI_Device *child = device->children[i];
caps |= child->driver->GetJoystickCapabilities(child, joystick);
}
return caps;
}
static int HIDAPI_DriverCombined_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
{
int i;
int result = -1;
for (i = 0; i < device->num_children; ++i) {
SDL_HIDAPI_Device *child = device->children[i];
if (child->driver->SetJoystickLED(child, joystick, red, green, blue) == 0) {
result = 0;
}
}
return result;
}
static int HIDAPI_DriverCombined_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
{
return SDL_Unsupported();
}
static int HIDAPI_DriverCombined_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled)
{
int i;
int result = -1;
for (i = 0; i < device->num_children; ++i) {
SDL_HIDAPI_Device *child = device->children[i];
if (child->driver->SetJoystickSensorsEnabled(child, joystick, enabled) == 0) {
result = 0;
}
}
return result;
}
static SDL_bool HIDAPI_DriverCombined_UpdateDevice(SDL_HIDAPI_Device *device)
{
int i;
int result = SDL_TRUE;
for (i = 0; i < device->num_children; ++i) {
SDL_HIDAPI_Device *child = device->children[i];
if (!child->driver->UpdateDevice(child)) {
result = SDL_FALSE;
}
}
return result;
}
static void HIDAPI_DriverCombined_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
int i;
for (i = 0; i < device->num_children; ++i) {
SDL_HIDAPI_Device *child = device->children[i];
child->driver->CloseJoystick(child, joystick);
}
}
static void HIDAPI_DriverCombined_FreeDevice(SDL_HIDAPI_Device *device)
{
}
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverCombined = {
"SDL_JOYSTICK_HIDAPI_COMBINED",
SDL_TRUE,
HIDAPI_DriverCombined_RegisterHints,
HIDAPI_DriverCombined_UnregisterHints,
HIDAPI_DriverCombined_IsEnabled,
HIDAPI_DriverCombined_IsSupportedDevice,
HIDAPI_DriverCombined_InitDevice,
HIDAPI_DriverCombined_GetDevicePlayerIndex,
HIDAPI_DriverCombined_SetDevicePlayerIndex,
HIDAPI_DriverCombined_UpdateDevice,
HIDAPI_DriverCombined_OpenJoystick,
HIDAPI_DriverCombined_RumbleJoystick,
HIDAPI_DriverCombined_RumbleJoystickTriggers,
HIDAPI_DriverCombined_GetJoystickCapabilities,
HIDAPI_DriverCombined_SetJoystickLED,
HIDAPI_DriverCombined_SendJoystickEffect,
HIDAPI_DriverCombined_SetJoystickSensorsEnabled,
HIDAPI_DriverCombined_CloseJoystick,
HIDAPI_DriverCombined_FreeDevice,
};
#endif /* SDL_JOYSTICK_HIDAPI */

View file

@ -0,0 +1,556 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 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"
#ifdef SDL_JOYSTICK_HIDAPI
#include "../../SDL_hints_c.h"
#include "../SDL_sysjoystick.h"
#include "SDL_hidapijoystick_c.h"
#include "SDL_hidapi_rumble.h"
#include "../../hidapi/SDL_hidapi_c.h"
#ifdef SDL_JOYSTICK_HIDAPI_GAMECUBE
/* Define this if you want to log all packets from the controller */
/*#define DEBUG_GAMECUBE_PROTOCOL*/
#define MAX_CONTROLLERS 4
typedef struct
{
SDL_bool pc_mode;
SDL_JoystickID joysticks[MAX_CONTROLLERS];
Uint8 wireless[MAX_CONTROLLERS];
Uint8 min_axis[MAX_CONTROLLERS * SDL_GAMEPAD_AXIS_MAX];
Uint8 max_axis[MAX_CONTROLLERS * SDL_GAMEPAD_AXIS_MAX];
Uint8 rumbleAllowed[MAX_CONTROLLERS];
Uint8 rumble[1 + MAX_CONTROLLERS];
/* Without this variable, hid_write starts to lag a TON */
SDL_bool rumbleUpdate;
SDL_bool m_bUseButtonLabels;
SDL_bool useRumbleBrake;
} SDL_DriverGameCube_Context;
static void HIDAPI_DriverGameCube_RegisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, callback, userdata);
}
static void HIDAPI_DriverGameCube_UnregisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, callback, userdata);
}
static SDL_bool HIDAPI_DriverGameCube_IsEnabled(void)
{
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE,
SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI,
SDL_HIDAPI_DEFAULT));
}
static SDL_bool HIDAPI_DriverGameCube_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
{
if (vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER) {
/* Nintendo Co., Ltd. Wii U GameCube Controller Adapter */
return SDL_TRUE;
}
if (vendor_id == USB_VENDOR_DRAGONRISE && product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER) {
/* EVORETRO GameCube Controller Adapter */
return SDL_TRUE;
}
return SDL_FALSE;
}
static void ResetAxisRange(SDL_DriverGameCube_Context *ctx, int joystick_index)
{
SDL_memset(&ctx->min_axis[joystick_index * SDL_GAMEPAD_AXIS_MAX], 128 - 88, SDL_GAMEPAD_AXIS_MAX);
SDL_memset(&ctx->max_axis[joystick_index * SDL_GAMEPAD_AXIS_MAX], 128 + 88, SDL_GAMEPAD_AXIS_MAX);
/* Trigger axes may have a higher resting value */
ctx->min_axis[joystick_index * SDL_GAMEPAD_AXIS_MAX + SDL_GAMEPAD_AXIS_LEFT_TRIGGER] = 40;
ctx->min_axis[joystick_index * SDL_GAMEPAD_AXIS_MAX + SDL_GAMEPAD_AXIS_RIGHT_TRIGGER] = 40;
}
static void SDLCALL SDL_GameControllerButtonReportingHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
{
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)userdata;
ctx->m_bUseButtonLabels = SDL_GetStringBoolean(hint, SDL_TRUE);
}
static void SDLCALL SDL_JoystickGameCubeRumbleBrakeHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
{
if (hint) {
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)userdata;
ctx->useRumbleBrake = SDL_GetStringBoolean(hint, SDL_FALSE);
}
}
static Uint8 RemapButton(SDL_DriverGameCube_Context *ctx, Uint8 button)
{
if (!ctx->m_bUseButtonLabels) {
/* Use button positions */
switch (button) {
case SDL_GAMEPAD_BUTTON_B:
return SDL_GAMEPAD_BUTTON_X;
case SDL_GAMEPAD_BUTTON_X:
return SDL_GAMEPAD_BUTTON_B;
default:
break;
}
}
return button;
}
static SDL_bool HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverGameCube_Context *ctx;
Uint8 packet[37];
Uint8 *curSlot;
Uint8 i;
int size;
Uint8 initMagic = 0x13;
Uint8 rumbleMagic = 0x11;
#ifdef HAVE_ENABLE_GAMECUBE_ADAPTORS
SDL_EnableGameCubeAdaptors();
#endif
ctx = (SDL_DriverGameCube_Context *)SDL_calloc(1, sizeof(*ctx));
if (ctx == NULL) {
SDL_OutOfMemory();
return SDL_FALSE;
}
device->context = ctx;
ctx->joysticks[0] = 0;
ctx->joysticks[1] = 0;
ctx->joysticks[2] = 0;
ctx->joysticks[3] = 0;
ctx->rumble[0] = rumbleMagic;
ctx->useRumbleBrake = SDL_FALSE;
if (device->vendor_id != USB_VENDOR_NINTENDO) {
ctx->pc_mode = SDL_TRUE;
}
if (ctx->pc_mode) {
for (i = 0; i < MAX_CONTROLLERS; ++i) {
ResetAxisRange(ctx, i);
HIDAPI_JoystickConnected(device, &ctx->joysticks[i]);
}
} else {
/* This is all that's needed to initialize the device. Really! */
if (SDL_hid_write(device->dev, &initMagic, sizeof(initMagic)) != sizeof(initMagic)) {
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
"HIDAPI_DriverGameCube_InitDevice(): Couldn't initialize WUP-028");
return SDL_FALSE;
}
/* Wait for the adapter to initialize */
SDL_Delay(10);
/* Add all the applicable joysticks */
while ((size = SDL_hid_read_timeout(device->dev, packet, sizeof(packet), 0)) > 0) {
#ifdef DEBUG_GAMECUBE_PROTOCOL
HIDAPI_DumpPacket("Nintendo GameCube packet: size = %d", packet, size);
#endif
if (size < 37 || packet[0] != 0x21) {
continue; /* Nothing to do yet...? */
}
/* Go through all 4 slots */
curSlot = packet + 1;
for (i = 0; i < MAX_CONTROLLERS; i += 1, curSlot += 9) {
ctx->wireless[i] = (curSlot[0] & 0x20) != 0;
/* Only allow rumble if the adapter's second USB cable is connected */
ctx->rumbleAllowed[i] = (curSlot[0] & 0x04) && !ctx->wireless[i];
if (curSlot[0] & 0x30) { /* 0x10 - Wired, 0x20 - Wireless */
if (ctx->joysticks[i] == 0) {
ResetAxisRange(ctx, i);
HIDAPI_JoystickConnected(device, &ctx->joysticks[i]);
}
} else {
if (ctx->joysticks[i] != 0) {
HIDAPI_JoystickDisconnected(device, ctx->joysticks[i]);
ctx->joysticks[i] = 0;
}
continue;
}
}
}
}
SDL_AddHintCallback(SDL_HINT_JOYSTICK_GAMECUBE_RUMBLE_BRAKE,
SDL_JoystickGameCubeRumbleBrakeHintChanged, ctx);
SDL_AddHintCallback(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS,
SDL_GameControllerButtonReportingHintChanged, ctx);
HIDAPI_SetDeviceName(device, "Nintendo GameCube Controller");
return SDL_TRUE;
}
static int HIDAPI_DriverGameCube_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
{
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
Uint8 i;
for (i = 0; i < 4; ++i) {
if (instance_id == ctx->joysticks[i]) {
return i;
}
}
return -1;
}
static void HIDAPI_DriverGameCube_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
{
}
static void HIDAPI_DriverGameCube_HandleJoystickPacket(SDL_HIDAPI_Device *device, SDL_DriverGameCube_Context *ctx, const Uint8 *packet, int size)
{
SDL_Joystick *joystick;
Uint8 i, v;
Sint16 axis_value;
Uint64 timestamp = SDL_GetTicksNS();
if (size != 10) {
return; /* How do we handle this packet? */
}
i = packet[0] - 1;
if (i >= MAX_CONTROLLERS) {
return; /* How do we handle this packet? */
}
joystick = SDL_GetJoystickFromInstanceID(ctx->joysticks[i]);
if (joystick == NULL) {
/* Hasn't been opened yet, skip */
return;
}
#define READ_BUTTON(off, flag, button) \
SDL_SendJoystickButton( \
timestamp, \
joystick, \
RemapButton(ctx, button), \
(packet[off] & flag) ? SDL_PRESSED : SDL_RELEASED);
READ_BUTTON(1, 0x02, 0) /* A */
READ_BUTTON(1, 0x04, 1) /* B */
READ_BUTTON(1, 0x01, 2) /* X */
READ_BUTTON(1, 0x08, 3) /* Y */
READ_BUTTON(2, 0x80, 4) /* DPAD_LEFT */
READ_BUTTON(2, 0x20, 5) /* DPAD_RIGHT */
READ_BUTTON(2, 0x40, 6) /* DPAD_DOWN */
READ_BUTTON(2, 0x10, 7) /* DPAD_UP */
READ_BUTTON(2, 0x02, 8) /* START */
READ_BUTTON(1, 0x80, 9) /* RIGHTSHOULDER */
/* These two buttons are for the bottoms of the analog triggers.
* More than likely, you're going to want to read the axes instead!
* -flibit
*/
READ_BUTTON(1, 0x20, 10) /* TRIGGERRIGHT */
READ_BUTTON(1, 0x10, 11) /* TRIGGERLEFT */
#undef READ_BUTTON
#define READ_AXIS(off, axis, invert) \
v = invert ? (0xff - packet[off]) : packet[off]; \
if (v < ctx->min_axis[i * SDL_GAMEPAD_AXIS_MAX + axis]) \
ctx->min_axis[i * SDL_GAMEPAD_AXIS_MAX + axis] = v; \
if (v > ctx->max_axis[i * SDL_GAMEPAD_AXIS_MAX + axis]) \
ctx->max_axis[i * SDL_GAMEPAD_AXIS_MAX + axis] = v; \
axis_value = (Sint16)HIDAPI_RemapVal(v, ctx->min_axis[i * SDL_GAMEPAD_AXIS_MAX + axis], ctx->max_axis[i * SDL_GAMEPAD_AXIS_MAX + axis], SDL_MIN_SINT16, SDL_MAX_SINT16); \
SDL_SendJoystickAxis( \
timestamp, \
joystick, \
axis, axis_value);
READ_AXIS(3, SDL_GAMEPAD_AXIS_LEFTX, 0)
READ_AXIS(4, SDL_GAMEPAD_AXIS_LEFTY, 0)
READ_AXIS(6, SDL_GAMEPAD_AXIS_RIGHTX, 1)
READ_AXIS(5, SDL_GAMEPAD_AXIS_RIGHTY, 1)
READ_AXIS(7, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, 0)
READ_AXIS(8, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, 0)
#undef READ_AXIS
}
static void HIDAPI_DriverGameCube_HandleNintendoPacket(SDL_HIDAPI_Device *device, SDL_DriverGameCube_Context *ctx, Uint8 *packet, int size)
{
SDL_Joystick *joystick;
Uint8 *curSlot;
Uint8 i;
Sint16 axis_value;
Uint64 timestamp = SDL_GetTicksNS();
if (size < 37 || packet[0] != 0x21) {
return; /* Nothing to do right now...? */
}
/* Go through all 4 slots */
curSlot = packet + 1;
for (i = 0; i < MAX_CONTROLLERS; i += 1, curSlot += 9) {
ctx->wireless[i] = (curSlot[0] & 0x20) != 0;
/* Only allow rumble if the adapter's second USB cable is connected */
ctx->rumbleAllowed[i] = (curSlot[0] & 0x04) && !ctx->wireless[i];
if (curSlot[0] & 0x30) { /* 0x10 - Wired, 0x20 - Wireless */
if (ctx->joysticks[i] == 0) {
ResetAxisRange(ctx, i);
HIDAPI_JoystickConnected(device, &ctx->joysticks[i]);
}
joystick = SDL_GetJoystickFromInstanceID(ctx->joysticks[i]);
/* Hasn't been opened yet, skip */
if (joystick == NULL) {
continue;
}
} else {
if (ctx->joysticks[i] != 0) {
HIDAPI_JoystickDisconnected(device, ctx->joysticks[i]);
ctx->joysticks[i] = 0;
}
continue;
}
#define READ_BUTTON(off, flag, button) \
SDL_SendJoystickButton( \
timestamp, \
joystick, \
RemapButton(ctx, button), \
(curSlot[off] & flag) ? SDL_PRESSED : SDL_RELEASED);
READ_BUTTON(1, 0x01, 0) /* A */
READ_BUTTON(1, 0x04, 1) /* B */
READ_BUTTON(1, 0x02, 2) /* X */
READ_BUTTON(1, 0x08, 3) /* Y */
READ_BUTTON(1, 0x10, 4) /* DPAD_LEFT */
READ_BUTTON(1, 0x20, 5) /* DPAD_RIGHT */
READ_BUTTON(1, 0x40, 6) /* DPAD_DOWN */
READ_BUTTON(1, 0x80, 7) /* DPAD_UP */
READ_BUTTON(2, 0x01, 8) /* START */
READ_BUTTON(2, 0x02, 9) /* RIGHTSHOULDER */
/* These two buttons are for the bottoms of the analog triggers.
* More than likely, you're going to want to read the axes instead!
* -flibit
*/
READ_BUTTON(2, 0x04, 10) /* TRIGGERRIGHT */
READ_BUTTON(2, 0x08, 11) /* TRIGGERLEFT */
#undef READ_BUTTON
#define READ_AXIS(off, axis) \
if (curSlot[off] < ctx->min_axis[i * SDL_GAMEPAD_AXIS_MAX + axis]) \
ctx->min_axis[i * SDL_GAMEPAD_AXIS_MAX + axis] = curSlot[off]; \
if (curSlot[off] > ctx->max_axis[i * SDL_GAMEPAD_AXIS_MAX + axis]) \
ctx->max_axis[i * SDL_GAMEPAD_AXIS_MAX + axis] = curSlot[off]; \
axis_value = (Sint16)HIDAPI_RemapVal(curSlot[off], ctx->min_axis[i * SDL_GAMEPAD_AXIS_MAX + axis], ctx->max_axis[i * SDL_GAMEPAD_AXIS_MAX + axis], SDL_MIN_SINT16, SDL_MAX_SINT16); \
SDL_SendJoystickAxis( \
timestamp, \
joystick, \
axis, axis_value);
READ_AXIS(3, SDL_GAMEPAD_AXIS_LEFTX)
READ_AXIS(4, SDL_GAMEPAD_AXIS_LEFTY)
READ_AXIS(5, SDL_GAMEPAD_AXIS_RIGHTX)
READ_AXIS(6, SDL_GAMEPAD_AXIS_RIGHTY)
READ_AXIS(7, SDL_GAMEPAD_AXIS_LEFT_TRIGGER)
READ_AXIS(8, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER)
#undef READ_AXIS
}
}
static SDL_bool HIDAPI_DriverGameCube_UpdateDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
Uint8 packet[USB_PACKET_LENGTH];
int size;
/* Read input packet */
while ((size = SDL_hid_read_timeout(device->dev, packet, sizeof(packet), 0)) > 0) {
#ifdef DEBUG_GAMECUBE_PROTOCOL
// HIDAPI_DumpPacket("Nintendo GameCube packet: size = %d", packet, size);
#endif
if (ctx->pc_mode) {
HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, packet, size);
} else {
HIDAPI_DriverGameCube_HandleNintendoPacket(device, ctx, packet, size);
}
}
/* Write rumble packet */
if (ctx->rumbleUpdate) {
SDL_HIDAPI_SendRumble(device, ctx->rumble, sizeof(ctx->rumble));
ctx->rumbleUpdate = SDL_FALSE;
}
/* If we got here, nothing bad happened! */
return SDL_TRUE;
}
static SDL_bool HIDAPI_DriverGameCube_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
Uint8 i;
SDL_AssertJoysticksLocked();
for (i = 0; i < MAX_CONTROLLERS; i += 1) {
if (joystick->instance_id == ctx->joysticks[i]) {
joystick->nbuttons = 12;
joystick->naxes = SDL_GAMEPAD_AXIS_MAX;
joystick->epowerlevel = ctx->wireless[i] ? SDL_JOYSTICK_POWER_UNKNOWN : SDL_JOYSTICK_POWER_WIRED;
return SDL_TRUE;
}
}
return SDL_FALSE; /* Should never get here! */
}
static int HIDAPI_DriverGameCube_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
{
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
Uint8 i, val;
SDL_AssertJoysticksLocked();
if (ctx->pc_mode) {
return SDL_Unsupported();
}
for (i = 0; i < MAX_CONTROLLERS; i += 1) {
if (joystick->instance_id == ctx->joysticks[i]) {
if (ctx->wireless[i]) {
return SDL_SetError("Nintendo GameCube WaveBird controllers do not support rumble");
}
if (!ctx->rumbleAllowed[i]) {
return SDL_SetError("Second USB cable for WUP-028 not connected");
}
if (ctx->useRumbleBrake) {
if (low_frequency_rumble == 0 && high_frequency_rumble > 0) {
val = 0; /* if only low is 0 we want to do a regular stop*/
} else if (low_frequency_rumble == 0 && high_frequency_rumble == 0) {
val = 2; /* if both frequencies are 0 we want to do a hard stop */
} else {
val = 1; /* normal rumble */
}
} else {
val = (low_frequency_rumble > 0 || high_frequency_rumble > 0);
}
if (val != ctx->rumble[i + 1]) {
ctx->rumble[i + 1] = val;
ctx->rumbleUpdate = SDL_TRUE;
}
return 0;
}
}
/* Should never get here! */
return SDL_SetError("Couldn't find joystick");
}
static int HIDAPI_DriverGameCube_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
{
return SDL_Unsupported();
}
static Uint32 HIDAPI_DriverGameCube_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
Uint32 result = 0;
SDL_AssertJoysticksLocked();
if (!ctx->pc_mode) {
Uint8 i;
for (i = 0; i < MAX_CONTROLLERS; i += 1) {
if (joystick->instance_id == ctx->joysticks[i]) {
if (!ctx->wireless[i] && ctx->rumbleAllowed[i]) {
result |= SDL_JOYCAP_RUMBLE;
break;
}
}
}
}
return result;
}
static int HIDAPI_DriverGameCube_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
{
return SDL_Unsupported();
}
static int HIDAPI_DriverGameCube_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
{
return SDL_Unsupported();
}
static int HIDAPI_DriverGameCube_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled)
{
return SDL_Unsupported();
}
static void HIDAPI_DriverGameCube_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
/* Stop rumble activity */
if (ctx->rumbleUpdate) {
SDL_HIDAPI_SendRumble(device, ctx->rumble, sizeof(ctx->rumble));
ctx->rumbleUpdate = SDL_FALSE;
}
}
static void HIDAPI_DriverGameCube_FreeDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
SDL_DelHintCallback(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS,
SDL_GameControllerButtonReportingHintChanged, ctx);
SDL_DelHintCallback(SDL_HINT_JOYSTICK_GAMECUBE_RUMBLE_BRAKE,
SDL_JoystickGameCubeRumbleBrakeHintChanged, ctx);
}
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube = {
SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE,
SDL_TRUE,
HIDAPI_DriverGameCube_RegisterHints,
HIDAPI_DriverGameCube_UnregisterHints,
HIDAPI_DriverGameCube_IsEnabled,
HIDAPI_DriverGameCube_IsSupportedDevice,
HIDAPI_DriverGameCube_InitDevice,
HIDAPI_DriverGameCube_GetDevicePlayerIndex,
HIDAPI_DriverGameCube_SetDevicePlayerIndex,
HIDAPI_DriverGameCube_UpdateDevice,
HIDAPI_DriverGameCube_OpenJoystick,
HIDAPI_DriverGameCube_RumbleJoystick,
HIDAPI_DriverGameCube_RumbleJoystickTriggers,
HIDAPI_DriverGameCube_GetJoystickCapabilities,
HIDAPI_DriverGameCube_SetJoystickLED,
HIDAPI_DriverGameCube_SendJoystickEffect,
HIDAPI_DriverGameCube_SetJoystickSensorsEnabled,
HIDAPI_DriverGameCube_CloseJoystick,
HIDAPI_DriverGameCube_FreeDevice,
};
#endif /* SDL_JOYSTICK_HIDAPI_GAMECUBE */
#endif /* SDL_JOYSTICK_HIDAPI */

View file

@ -0,0 +1,449 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 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"
#ifdef SDL_JOYSTICK_HIDAPI
#include "../SDL_sysjoystick.h"
#include "SDL_hidapijoystick_c.h"
#include "SDL_hidapi_rumble.h"
#ifdef SDL_JOYSTICK_HIDAPI_LUNA
/* Define this if you want to log all packets from the controller */
/*#define DEBUG_LUNA_PROTOCOL*/
/* Sending rumble on macOS blocks for a long time and eventually fails */
#ifndef __MACOS__
#define ENABLE_LUNA_BLUETOOTH_RUMBLE
#endif
enum
{
SDL_CONTROLLER_BUTTON_LUNA_MIC = 15,
SDL_CONTROLLER_NUM_LUNA_BUTTONS,
};
typedef struct
{
Uint8 last_state[USB_PACKET_LENGTH];
} SDL_DriverLuna_Context;
static void HIDAPI_DriverLuna_RegisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_LUNA, callback, userdata);
}
static void HIDAPI_DriverLuna_UnregisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI_LUNA, callback, userdata);
}
static SDL_bool HIDAPI_DriverLuna_IsEnabled(void)
{
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_LUNA, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
}
static SDL_bool HIDAPI_DriverLuna_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
{
return SDL_IsJoystickAmazonLunaController(vendor_id, product_id);
}
static SDL_bool HIDAPI_DriverLuna_InitDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverLuna_Context *ctx;
ctx = (SDL_DriverLuna_Context *)SDL_calloc(1, sizeof(*ctx));
if (ctx == NULL) {
SDL_OutOfMemory();
return SDL_FALSE;
}
device->context = ctx;
HIDAPI_SetDeviceName(device, "Amazon Luna Controller");
return HIDAPI_JoystickConnected(device, NULL);
}
static int HIDAPI_DriverLuna_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
{
return -1;
}
static void HIDAPI_DriverLuna_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
{
}
static SDL_bool HIDAPI_DriverLuna_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
SDL_DriverLuna_Context *ctx = (SDL_DriverLuna_Context *)device->context;
SDL_AssertJoysticksLocked();
SDL_zeroa(ctx->last_state);
/* Initialize the joystick capabilities */
joystick->nbuttons = SDL_CONTROLLER_NUM_LUNA_BUTTONS;
joystick->naxes = SDL_GAMEPAD_AXIS_MAX;
joystick->epowerlevel = SDL_JOYSTICK_POWER_FULL;
return SDL_TRUE;
}
static int HIDAPI_DriverLuna_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
{
#ifdef ENABLE_LUNA_BLUETOOTH_RUMBLE
if (device->product_id == BLUETOOTH_PRODUCT_LUNA_CONTROLLER) {
/* Same packet as on Xbox One controllers connected via Bluetooth */
Uint8 rumble_packet[] = { 0x03, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xEB };
/* Magnitude is 1..100 so scale the 16-bit input here */
rumble_packet[4] = (Uint8)(low_frequency_rumble / 655);
rumble_packet[5] = (Uint8)(high_frequency_rumble / 655);
if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
return SDL_SetError("Couldn't send rumble packet");
}
return 0;
}
#endif /* ENABLE_LUNA_BLUETOOTH_RUMBLE */
/* There is currently no rumble packet over USB */
return SDL_Unsupported();
}
static int HIDAPI_DriverLuna_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
{
return SDL_Unsupported();
}
static Uint32 HIDAPI_DriverLuna_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
Uint32 result = 0;
#ifdef ENABLE_LUNA_BLUETOOTH_RUMBLE
if (device->product_id == BLUETOOTH_PRODUCT_LUNA_CONTROLLER) {
result |= SDL_JOYCAP_RUMBLE;
}
#endif
return result;
}
static int HIDAPI_DriverLuna_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
{
return SDL_Unsupported();
}
static int HIDAPI_DriverLuna_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
{
return SDL_Unsupported();
}
static int HIDAPI_DriverLuna_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled)
{
return SDL_Unsupported();
}
static void HIDAPI_DriverLuna_HandleUSBStatePacket(SDL_Joystick *joystick, SDL_DriverLuna_Context *ctx, Uint8 *data, int size)
{
Uint64 timestamp = SDL_GetTicksNS();
if (ctx->last_state[1] != data[1]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[1] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[1] & 0x02) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[1] & 0x04) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[1] & 0x08) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[1] & 0x10) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[1] & 0x20) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (data[1] & 0x40) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, (data[1] & 0x80) ? SDL_PRESSED : SDL_RELEASED);
}
if (ctx->last_state[2] != data[2]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (data[2] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_CONTROLLER_BUTTON_LUNA_MIC, (data[2] & 0x02) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, (data[2] & 0x04) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, (data[2] & 0x08) ? SDL_PRESSED : SDL_RELEASED);
}
if (ctx->last_state[3] != data[3]) {
SDL_bool dpad_up = SDL_FALSE;
SDL_bool dpad_down = SDL_FALSE;
SDL_bool dpad_left = SDL_FALSE;
SDL_bool dpad_right = SDL_FALSE;
switch (data[3] & 0xf) {
case 0:
dpad_up = SDL_TRUE;
break;
case 1:
dpad_up = SDL_TRUE;
dpad_right = SDL_TRUE;
break;
case 2:
dpad_right = SDL_TRUE;
break;
case 3:
dpad_right = SDL_TRUE;
dpad_down = SDL_TRUE;
break;
case 4:
dpad_down = SDL_TRUE;
break;
case 5:
dpad_left = SDL_TRUE;
dpad_down = SDL_TRUE;
break;
case 6:
dpad_left = SDL_TRUE;
break;
case 7:
dpad_up = SDL_TRUE;
dpad_left = SDL_TRUE;
break;
default:
break;
}
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, dpad_down);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, dpad_up);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, dpad_right);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, dpad_left);
}
#define READ_STICK_AXIS(offset) \
(data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)data[offset], 0x00, 0xff, SDL_MIN_SINT16, SDL_MAX_SINT16))
{
Sint16 axis = READ_STICK_AXIS(4);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
axis = READ_STICK_AXIS(5);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
axis = READ_STICK_AXIS(6);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
axis = READ_STICK_AXIS(7);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
}
#undef READ_STICK_AXIS
#define READ_TRIGGER_AXIS(offset) \
(Sint16) HIDAPI_RemapVal((float)data[offset], 0x00, 0xff, SDL_MIN_SINT16, SDL_MAX_SINT16)
{
Sint16 axis = READ_TRIGGER_AXIS(8);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
axis = READ_TRIGGER_AXIS(9);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
}
#undef READ_TRIGGER_AXIS
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
}
static void HIDAPI_DriverLuna_HandleBluetoothStatePacket(SDL_Joystick *joystick, SDL_DriverLuna_Context *ctx, Uint8 *data, int size)
{
Uint64 timestamp = SDL_GetTicksNS();
if (size >= 2 && data[0] == 0x02) {
/* Home button has dedicated report */
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (data[1] & 0x1) ? SDL_PRESSED : SDL_RELEASED);
return;
}
if (size >= 2 && data[0] == 0x04) {
/* Battery level report */
int level = data[1] * 100 / 0xFF;
if (level == 0) {
SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_EMPTY);
} else if (level <= 20) {
SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_LOW);
} else if (level <= 70) {
SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_MEDIUM);
} else {
SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_FULL);
}
return;
}
if (size < 17 || data[0] != 0x01) {
/* We don't know how to handle this report */
return;
}
if (ctx->last_state[13] != data[13]) {
SDL_bool dpad_up = SDL_FALSE;
SDL_bool dpad_down = SDL_FALSE;
SDL_bool dpad_left = SDL_FALSE;
SDL_bool dpad_right = SDL_FALSE;
switch (data[13] & 0xf) {
case 1:
dpad_up = SDL_TRUE;
break;
case 2:
dpad_up = SDL_TRUE;
dpad_right = SDL_TRUE;
break;
case 3:
dpad_right = SDL_TRUE;
break;
case 4:
dpad_right = SDL_TRUE;
dpad_down = SDL_TRUE;
break;
case 5:
dpad_down = SDL_TRUE;
break;
case 6:
dpad_left = SDL_TRUE;
dpad_down = SDL_TRUE;
break;
case 7:
dpad_left = SDL_TRUE;
break;
case 8:
dpad_up = SDL_TRUE;
dpad_left = SDL_TRUE;
break;
default:
break;
}
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, dpad_down);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, dpad_up);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, dpad_right);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, dpad_left);
}
if (ctx->last_state[14] != data[14]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[14] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[14] & 0x02) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[14] & 0x08) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[14] & 0x10) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[14] & 0x40) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[14] & 0x80) ? SDL_PRESSED : SDL_RELEASED);
}
if (ctx->last_state[15] != data[15]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, (data[15] & 0x08) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, (data[15] & 0x20) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, (data[15] & 0x40) ? SDL_PRESSED : SDL_RELEASED);
}
if (ctx->last_state[16] != data[16]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (data[16] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_CONTROLLER_BUTTON_LUNA_MIC, (data[16] & 0x02) ? SDL_PRESSED : SDL_RELEASED);
}
#define READ_STICK_AXIS(offset) \
(data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)data[offset], 0x00, 0xff, SDL_MIN_SINT16, SDL_MAX_SINT16))
{
Sint16 axis = READ_STICK_AXIS(2);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
axis = READ_STICK_AXIS(4);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
axis = READ_STICK_AXIS(6);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
axis = READ_STICK_AXIS(8);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
}
#undef READ_STICK_AXIS
#define READ_TRIGGER_AXIS(offset) \
(Sint16) HIDAPI_RemapVal((float)((int)(((data[offset] | (data[offset + 1] << 8)) & 0x3ff) - 0x200)), 0x00 - 0x200, 0x3ff - 0x200, SDL_MIN_SINT16, SDL_MAX_SINT16)
{
Sint16 axis = READ_TRIGGER_AXIS(9);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
axis = READ_TRIGGER_AXIS(11);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
}
#undef READ_TRIGGER_AXIS
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
}
static SDL_bool HIDAPI_DriverLuna_UpdateDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverLuna_Context *ctx = (SDL_DriverLuna_Context *)device->context;
SDL_Joystick *joystick = NULL;
Uint8 data[USB_PACKET_LENGTH];
int size = 0;
if (device->num_joysticks > 0) {
joystick = SDL_GetJoystickFromInstanceID(device->joysticks[0]);
} else {
return SDL_FALSE;
}
while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
#ifdef DEBUG_LUNA_PROTOCOL
HIDAPI_DumpPacket("Amazon Luna packet: size = %d", data, size);
#endif
if (joystick == NULL) {
continue;
}
switch (size) {
case 10:
HIDAPI_DriverLuna_HandleUSBStatePacket(joystick, ctx, data, size);
break;
default:
HIDAPI_DriverLuna_HandleBluetoothStatePacket(joystick, ctx, data, size);
break;
}
}
if (size < 0) {
/* Read error, device is disconnected */
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
}
return size >= 0;
}
static void HIDAPI_DriverLuna_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
}
static void HIDAPI_DriverLuna_FreeDevice(SDL_HIDAPI_Device *device)
{
}
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLuna = {
SDL_HINT_JOYSTICK_HIDAPI_LUNA,
SDL_TRUE,
HIDAPI_DriverLuna_RegisterHints,
HIDAPI_DriverLuna_UnregisterHints,
HIDAPI_DriverLuna_IsEnabled,
HIDAPI_DriverLuna_IsSupportedDevice,
HIDAPI_DriverLuna_InitDevice,
HIDAPI_DriverLuna_GetDevicePlayerIndex,
HIDAPI_DriverLuna_SetDevicePlayerIndex,
HIDAPI_DriverLuna_UpdateDevice,
HIDAPI_DriverLuna_OpenJoystick,
HIDAPI_DriverLuna_RumbleJoystick,
HIDAPI_DriverLuna_RumbleJoystickTriggers,
HIDAPI_DriverLuna_GetJoystickCapabilities,
HIDAPI_DriverLuna_SetJoystickLED,
HIDAPI_DriverLuna_SendJoystickEffect,
HIDAPI_DriverLuna_SetJoystickSensorsEnabled,
HIDAPI_DriverLuna_CloseJoystick,
HIDAPI_DriverLuna_FreeDevice,
};
#endif /* SDL_JOYSTICK_HIDAPI_LUNA */
#endif /* SDL_JOYSTICK_HIDAPI */

View file

@ -0,0 +1,49 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 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.
*/
/* These are values used in the controller type byte of the controller GUID */
/* These values come directly out of the hardware, so don't change them */
typedef enum
{
k_eSwitchDeviceInfoControllerType_Unknown = 0,
k_eSwitchDeviceInfoControllerType_JoyConLeft = 1,
k_eSwitchDeviceInfoControllerType_JoyConRight = 2,
k_eSwitchDeviceInfoControllerType_ProController = 3,
k_eSwitchDeviceInfoControllerType_LicProController = 6,
k_eSwitchDeviceInfoControllerType_HVCLeft = 7,
k_eSwitchDeviceInfoControllerType_HVCRight = 8,
k_eSwitchDeviceInfoControllerType_NESLeft = 9,
k_eSwitchDeviceInfoControllerType_NESRight = 10,
k_eSwitchDeviceInfoControllerType_SNES = 11,
k_eSwitchDeviceInfoControllerType_N64 = 12,
k_eSwitchDeviceInfoControllerType_SEGA_Genesis = 13,
} ESwitchDeviceInfoControllerType;
/* These values are used internally but can be updated as needed */
typedef enum
{
k_eWiiExtensionControllerType_Unknown = 0,
k_eWiiExtensionControllerType_None = 128,
k_eWiiExtensionControllerType_Nunchuk = 129,
k_eWiiExtensionControllerType_Gamepad = 130,
k_eWiiExtensionControllerType_WiiUPro = 131,
} EWiiExtensionControllerType;

View file

@ -0,0 +1,993 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 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"
#ifdef SDL_JOYSTICK_HIDAPI
#include "../../SDL_hints_c.h"
#include "../SDL_sysjoystick.h"
#include "SDL_hidapijoystick_c.h"
#include "SDL_hidapi_rumble.h"
#ifdef SDL_JOYSTICK_HIDAPI_PS3
/* Define this if you want to log all packets from the controller */
/*#define DEBUG_PS3_PROTOCOL*/
#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8))
typedef enum
{
k_EPS3ReportIdState = 1,
k_EPS3ReportIdEffects = 1,
} EPS3ReportId;
typedef struct
{
SDL_HIDAPI_Device *device;
SDL_Joystick *joystick;
SDL_bool is_shanwan;
SDL_bool report_sensors;
SDL_bool effects_updated;
int player_index;
Uint8 rumble_left;
Uint8 rumble_right;
Uint8 last_state[USB_PACKET_LENGTH];
} SDL_DriverPS3_Context;
static int HIDAPI_DriverPS3_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size);
static void HIDAPI_DriverPS3_RegisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS3, callback, userdata);
}
static void HIDAPI_DriverPS3_UnregisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS3, callback, userdata);
}
static SDL_bool HIDAPI_DriverPS3_IsEnabled(void)
{
SDL_bool default_value;
#ifdef __MACOS__
/* This works well on macOS */
default_value = SDL_TRUE;
#elif defined(__WINDOWS__)
/* You can't initialize the controller with the stock Windows drivers
* See https://github.com/ViGEm/DsHidMini as an alternative driver
*/
default_value = SDL_FALSE;
#elif defined(__LINUX__)
/* Linux drivers do a better job of managing the transition between
* USB and Bluetooth. There are also some quirks in communicating
* with PS3 controllers that have been implemented in SDL's hidapi
* for libusb, but are not possible to support using hidraw if the
* kernel doesn't already know about them.
*/
default_value = SDL_FALSE;
#else
/* Untested, default off */
default_value = SDL_FALSE;
#endif
if (default_value) {
default_value = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT);
}
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS3, default_value);
}
static SDL_bool HIDAPI_DriverPS3_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
{
if (vendor_id == USB_VENDOR_SONY && product_id == USB_PRODUCT_SONY_DS3) {
return SDL_TRUE;
}
if (vendor_id == USB_VENDOR_SHANWAN && product_id == USB_PRODUCT_SHANWAN_DS3) {
return SDL_TRUE;
}
return SDL_FALSE;
}
static int ReadFeatureReport(SDL_hid_device *dev, Uint8 report_id, Uint8 *report, size_t length)
{
SDL_memset(report, 0, length);
report[0] = report_id;
return SDL_hid_get_feature_report(dev, report, length);
}
static int SendFeatureReport(SDL_hid_device *dev, Uint8 *report, size_t length)
{
return SDL_hid_send_feature_report(dev, report, length);
}
static SDL_bool HIDAPI_DriverPS3_InitDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverPS3_Context *ctx;
SDL_bool is_shanwan = SDL_FALSE;
if (device->vendor_id == USB_VENDOR_SONY &&
SDL_strncasecmp(device->name, "ShanWan", 7) == 0) {
is_shanwan = SDL_TRUE;
}
if (device->vendor_id == USB_VENDOR_SHANWAN ||
device->vendor_id == USB_VENDOR_SHANWAN_ALT) {
is_shanwan = SDL_TRUE;
}
ctx = (SDL_DriverPS3_Context *)SDL_calloc(1, sizeof(*ctx));
if (ctx == NULL) {
SDL_OutOfMemory();
return SDL_FALSE;
}
ctx->device = device;
ctx->is_shanwan = is_shanwan;
device->context = ctx;
/* Set the controller into report mode over Bluetooth */
if (device->is_bluetooth) {
Uint8 data[] = { 0xf4, 0x42, 0x03, 0x00, 0x00 };
SendFeatureReport(device->dev, data, sizeof(data));
}
/* Set the controller into report mode over USB */
if (!device->is_bluetooth) {
Uint8 data[USB_PACKET_LENGTH];
int size = ReadFeatureReport(device->dev, 0xf2, data, 17);
if (size < 0) {
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
"HIDAPI_DriverPS3_InitDevice(): Couldn't read feature report 0xf2");
return SDL_FALSE;
}
#ifdef DEBUG_PS3_PROTOCOL
HIDAPI_DumpPacket("PS3 0xF2 packet: size = %d", data, size);
#endif
size = ReadFeatureReport(device->dev, 0xf5, data, 8);
if (size < 0) {
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
"HIDAPI_DriverPS3_InitDevice(): Couldn't read feature report 0xf5");
return SDL_FALSE;
}
#ifdef DEBUG_PS3_PROTOCOL
HIDAPI_DumpPacket("PS3 0xF5 packet: size = %d", data, size);
#endif
if (!ctx->is_shanwan) {
/* An output report could cause ShanWan controllers to rumble non-stop */
SDL_hid_write(device->dev, data, 1);
}
}
device->type = SDL_GAMEPAD_TYPE_PS3;
HIDAPI_SetDeviceName(device, "PS3 Controller");
return HIDAPI_JoystickConnected(device, NULL);
}
static int HIDAPI_DriverPS3_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
{
return -1;
}
static int HIDAPI_DriverPS3_UpdateEffects(SDL_HIDAPI_Device *device)
{
SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
Uint8 effects[] = {
0x01, 0xff, 0x00, 0xff, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0x27, 0x10, 0x00, 0x32,
0xff, 0x27, 0x10, 0x00, 0x32,
0xff, 0x27, 0x10, 0x00, 0x32,
0xff, 0x27, 0x10, 0x00, 0x32,
0x00, 0x00, 0x00, 0x00, 0x00
};
effects[2] = ctx->rumble_right ? 1 : 0;
effects[4] = ctx->rumble_left;
effects[9] = (0x01 << (1 + (ctx->player_index % 4)));
return HIDAPI_DriverPS3_SendJoystickEffect(device, ctx->joystick, effects, sizeof(effects));
}
static void HIDAPI_DriverPS3_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
{
SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
if (ctx == NULL) {
return;
}
ctx->player_index = player_index;
/* This will set the new LED state based on the new player index */
HIDAPI_DriverPS3_UpdateEffects(device);
}
static SDL_bool HIDAPI_DriverPS3_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
SDL_AssertJoysticksLocked();
ctx->joystick = joystick;
ctx->effects_updated = SDL_FALSE;
ctx->rumble_left = 0;
ctx->rumble_right = 0;
SDL_zeroa(ctx->last_state);
/* Initialize player index (needed for setting LEDs) */
ctx->player_index = SDL_GetJoystickPlayerIndex(joystick);
/* Initialize the joystick capabilities */
joystick->nbuttons = 15;
joystick->naxes = 16;
joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED;
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 100.0f);
return SDL_TRUE;
}
static int HIDAPI_DriverPS3_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
{
SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
ctx->rumble_left = (low_frequency_rumble >> 8);
ctx->rumble_right = (high_frequency_rumble >> 8);
return HIDAPI_DriverPS3_UpdateEffects(device);
}
static int HIDAPI_DriverPS3_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
{
return SDL_Unsupported();
}
static Uint32 HIDAPI_DriverPS3_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
return SDL_JOYCAP_RUMBLE;
}
static int HIDAPI_DriverPS3_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
{
return SDL_Unsupported();
}
static int HIDAPI_DriverPS3_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size)
{
Uint8 data[49];
int report_size, offset;
SDL_zeroa(data);
data[0] = k_EPS3ReportIdEffects;
report_size = sizeof(data);
offset = 1;
SDL_memcpy(&data[offset], effect, SDL_min((sizeof(data) - offset), (size_t)size));
if (SDL_HIDAPI_SendRumble(device, data, report_size) != report_size) {
return SDL_SetError("Couldn't send rumble packet");
}
return 0;
}
static int HIDAPI_DriverPS3_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled)
{
SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
ctx->report_sensors = enabled;
return 0;
}
static float HIDAPI_DriverPS3_ScaleAccel(Sint16 value)
{
/* Accelerometer values are in big endian order */
value = SDL_SwapBE16(value);
return ((float)(value - 511) / 113.0f) * SDL_STANDARD_GRAVITY;
}
static void HIDAPI_DriverPS3_HandleMiniStatePacket(SDL_Joystick *joystick, SDL_DriverPS3_Context *ctx, Uint8 *data, int size)
{
Sint16 axis;
Uint64 timestamp = SDL_GetTicksNS();
if (ctx->last_state[4] != data[4]) {
SDL_bool dpad_up = SDL_FALSE;
SDL_bool dpad_down = SDL_FALSE;
SDL_bool dpad_left = SDL_FALSE;
SDL_bool dpad_right = SDL_FALSE;
switch (data[4] & 0x0f) {
case 0:
dpad_up = SDL_TRUE;
break;
case 1:
dpad_up = SDL_TRUE;
dpad_right = SDL_TRUE;
break;
case 2:
dpad_right = SDL_TRUE;
break;
case 3:
dpad_right = SDL_TRUE;
dpad_down = SDL_TRUE;
break;
case 4:
dpad_down = SDL_TRUE;
break;
case 5:
dpad_left = SDL_TRUE;
dpad_down = SDL_TRUE;
break;
case 6:
dpad_left = SDL_TRUE;
break;
case 7:
dpad_up = SDL_TRUE;
dpad_left = SDL_TRUE;
break;
default:
break;
}
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, dpad_down);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, dpad_up);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, dpad_right);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, dpad_left);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[4] & 0x10) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[4] & 0x20) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[4] & 0x40) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[4] & 0x80) ? SDL_PRESSED : SDL_RELEASED);
}
if (ctx->last_state[5] != data[5]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[5] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[5] & 0x02) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (data[5] & 0x04) ? SDL_JOYSTICK_AXIS_MAX : SDL_JOYSTICK_AXIS_MIN);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (data[5] & 0x08) ? SDL_JOYSTICK_AXIS_MAX : SDL_JOYSTICK_AXIS_MIN);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (data[5] & 0x10) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, (data[5] & 0x20) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, (data[5] & 0x40) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, (data[5] & 0x80) ? SDL_PRESSED : SDL_RELEASED);
}
axis = ((int)data[2] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
axis = ((int)data[3] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
axis = ((int)data[0] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
axis = ((int)data[1] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
}
static void HIDAPI_DriverPS3_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverPS3_Context *ctx, Uint8 *data, int size)
{
Sint16 axis;
Uint64 timestamp = SDL_GetTicksNS();
if (ctx->last_state[2] != data[2]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (data[2] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, (data[2] & 0x02) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, (data[2] & 0x04) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, (data[2] & 0x08) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, (data[2] & 0x10) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, (data[2] & 0x20) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, (data[2] & 0x40) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, (data[2] & 0x80) ? SDL_PRESSED : SDL_RELEASED);
}
if (ctx->last_state[3] != data[3]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[3] & 0x04) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[3] & 0x08) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[3] & 0x10) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[3] & 0x20) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[3] & 0x40) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[3] & 0x80) ? SDL_PRESSED : SDL_RELEASED);
}
if (ctx->last_state[4] != data[4]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (data[4] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
}
axis = ((int)data[18] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
axis = ((int)data[19] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
axis = ((int)data[6] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
axis = ((int)data[7] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
axis = ((int)data[8] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
axis = ((int)data[9] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
/* Buttons are mapped as axes in the order they appear in the button enumeration */
{
static int button_axis_offsets[] = {
24, /* SDL_GAMEPAD_BUTTON_A */
23, /* SDL_GAMEPAD_BUTTON_B */
25, /* SDL_GAMEPAD_BUTTON_X */
22, /* SDL_GAMEPAD_BUTTON_Y */
0, /* SDL_GAMEPAD_BUTTON_BACK */
0, /* SDL_GAMEPAD_BUTTON_GUIDE */
0, /* SDL_GAMEPAD_BUTTON_START */
0, /* SDL_GAMEPAD_BUTTON_LEFT_STICK */
0, /* SDL_GAMEPAD_BUTTON_RIGHT_STICK */
20, /* SDL_GAMEPAD_BUTTON_LEFT_SHOULDER */
21, /* SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER */
14, /* SDL_GAMEPAD_BUTTON_DPAD_UP */
16, /* SDL_GAMEPAD_BUTTON_DPAD_DOWN */
17, /* SDL_GAMEPAD_BUTTON_DPAD_LEFT */
15, /* SDL_GAMEPAD_BUTTON_DPAD_RIGHT */
};
Uint8 i, axis_index = 6;
for (i = 0; i < SDL_arraysize(button_axis_offsets); ++i) {
int offset = button_axis_offsets[i];
if (!offset) {
/* This button doesn't report as an axis */
continue;
}
axis = ((int)data[offset] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, axis_index, axis);
++axis_index;
}
}
if (ctx->report_sensors) {
float sensor_data[3];
sensor_data[0] = HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[41], data[42]));
sensor_data[1] = -HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[45], data[46]));
sensor_data[2] = -HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[43], data[44]));
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, timestamp, sensor_data, SDL_arraysize(sensor_data));
}
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
}
static SDL_bool HIDAPI_DriverPS3_UpdateDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
SDL_Joystick *joystick = NULL;
Uint8 data[USB_PACKET_LENGTH];
int size;
if (device->num_joysticks > 0) {
joystick = SDL_GetJoystickFromInstanceID(device->joysticks[0]);
} else {
return SDL_FALSE;
}
while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
#ifdef DEBUG_PS3_PROTOCOL
HIDAPI_DumpPacket("PS3 packet: size = %d", data, size);
#endif
if (joystick == NULL) {
continue;
}
if (size == 7) {
/* Seen on a ShanWan PS2 -> PS3 USB converter */
HIDAPI_DriverPS3_HandleMiniStatePacket(joystick, ctx, data, size);
/* Wait for the first report to set the LED state after the controller stops blinking */
if (!ctx->effects_updated) {
HIDAPI_DriverPS3_UpdateEffects(device);
ctx->effects_updated = SDL_TRUE;
}
continue;
}
switch (data[0]) {
case k_EPS3ReportIdState:
if (data[1] == 0xFF) {
/* Invalid data packet, ignore */
break;
}
HIDAPI_DriverPS3_HandleStatePacket(joystick, ctx, data, size);
/* Wait for the first report to set the LED state after the controller stops blinking */
if (!ctx->effects_updated) {
HIDAPI_DriverPS3_UpdateEffects(device);
ctx->effects_updated = SDL_TRUE;
}
break;
default:
#ifdef DEBUG_JOYSTICK
SDL_Log("Unknown PS3 packet: 0x%.2x\n", data[0]);
#endif
break;
}
}
if (size < 0) {
/* Read error, device is disconnected */
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
}
return size >= 0;
}
static void HIDAPI_DriverPS3_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
ctx->joystick = NULL;
}
static void HIDAPI_DriverPS3_FreeDevice(SDL_HIDAPI_Device *device)
{
}
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS3 = {
SDL_HINT_JOYSTICK_HIDAPI_PS3,
SDL_TRUE,
HIDAPI_DriverPS3_RegisterHints,
HIDAPI_DriverPS3_UnregisterHints,
HIDAPI_DriverPS3_IsEnabled,
HIDAPI_DriverPS3_IsSupportedDevice,
HIDAPI_DriverPS3_InitDevice,
HIDAPI_DriverPS3_GetDevicePlayerIndex,
HIDAPI_DriverPS3_SetDevicePlayerIndex,
HIDAPI_DriverPS3_UpdateDevice,
HIDAPI_DriverPS3_OpenJoystick,
HIDAPI_DriverPS3_RumbleJoystick,
HIDAPI_DriverPS3_RumbleJoystickTriggers,
HIDAPI_DriverPS3_GetJoystickCapabilities,
HIDAPI_DriverPS3_SetJoystickLED,
HIDAPI_DriverPS3_SendJoystickEffect,
HIDAPI_DriverPS3_SetJoystickSensorsEnabled,
HIDAPI_DriverPS3_CloseJoystick,
HIDAPI_DriverPS3_FreeDevice,
};
static SDL_bool HIDAPI_DriverPS3ThirdParty_IsEnabled(void)
{
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS3,
SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI,
SDL_HIDAPI_DEFAULT));
}
static SDL_bool HIDAPI_DriverPS3ThirdParty_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
{
Uint8 data[USB_PACKET_LENGTH];
int size;
if ((type == SDL_GAMEPAD_TYPE_PS3 && vendor_id != USB_VENDOR_SONY) ||
HIDAPI_SupportsPlaystationDetection(vendor_id, product_id)) {
if (device && device->dev) {
size = ReadFeatureReport(device->dev, 0x03, data, sizeof(data));
if (size == 8 && data[2] == 0x26) {
/* Supported third party controller */
return SDL_TRUE;
} else {
return SDL_FALSE;
}
} else {
/* Might be supported by this driver, enumerate and find out */
return SDL_TRUE;
}
}
return SDL_FALSE;
}
static SDL_bool HIDAPI_DriverPS3ThirdParty_InitDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverPS3_Context *ctx;
ctx = (SDL_DriverPS3_Context *)SDL_calloc(1, sizeof(*ctx));
if (ctx == NULL) {
SDL_OutOfMemory();
return SDL_FALSE;
}
ctx->device = device;
device->context = ctx;
device->type = SDL_GAMEPAD_TYPE_PS3;
if (device->vendor_id == USB_VENDOR_LOGITECH &&
device->product_id == USB_PRODUCT_LOGITECH_CHILLSTREAM) {
HIDAPI_SetDeviceName(device, "Logitech ChillStream");
}
return HIDAPI_JoystickConnected(device, NULL);
}
static int HIDAPI_DriverPS3ThirdParty_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
{
return -1;
}
static void HIDAPI_DriverPS3ThirdParty_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
{
}
static SDL_bool HIDAPI_DriverPS3ThirdParty_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
SDL_AssertJoysticksLocked();
ctx->joystick = joystick;
SDL_zeroa(ctx->last_state);
/* Initialize the joystick capabilities */
joystick->nbuttons = 15;
joystick->naxes = 16;
joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED;
return SDL_TRUE;
}
static int HIDAPI_DriverPS3ThirdParty_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
{
return SDL_Unsupported();
}
static int HIDAPI_DriverPS3ThirdParty_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
{
return SDL_Unsupported();
}
static Uint32 HIDAPI_DriverPS3ThirdParty_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
return 0;
}
static int HIDAPI_DriverPS3ThirdParty_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
{
return SDL_Unsupported();
}
static int HIDAPI_DriverPS3ThirdParty_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size)
{
return SDL_Unsupported();
}
static int HIDAPI_DriverPS3ThirdParty_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled)
{
return SDL_Unsupported();
}
static void HIDAPI_DriverPS3ThirdParty_HandleStatePacket18(SDL_Joystick *joystick, SDL_DriverPS3_Context *ctx, Uint8 *data, int size)
{
Sint16 axis;
Uint64 timestamp = SDL_GetTicksNS();
if (ctx->last_state[0] != data[0]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[0] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[0] & 0x02) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[0] & 0x04) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[0] & 0x08) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[0] & 0x10) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[0] & 0x20) ? SDL_PRESSED : SDL_RELEASED);
}
if (ctx->last_state[1] != data[1]) {
SDL_bool dpad_up = SDL_FALSE;
SDL_bool dpad_down = SDL_FALSE;
SDL_bool dpad_left = SDL_FALSE;
SDL_bool dpad_right = SDL_FALSE;
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (data[1] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, (data[1] & 0x02) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, (data[1] & 0x04) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, (data[1] & 0x08) ? SDL_PRESSED : SDL_RELEASED);
switch (data[1] >> 4) {
case 0:
dpad_up = SDL_TRUE;
break;
case 1:
dpad_up = SDL_TRUE;
dpad_right = SDL_TRUE;
break;
case 2:
dpad_right = SDL_TRUE;
break;
case 3:
dpad_right = SDL_TRUE;
dpad_down = SDL_TRUE;
break;
case 4:
dpad_down = SDL_TRUE;
break;
case 5:
dpad_left = SDL_TRUE;
dpad_down = SDL_TRUE;
break;
case 6:
dpad_left = SDL_TRUE;
break;
case 7:
dpad_up = SDL_TRUE;
dpad_left = SDL_TRUE;
break;
default:
break;
}
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, dpad_down);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, dpad_up);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, dpad_right);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, dpad_left);
}
axis = ((int)data[16] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
axis = ((int)data[17] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
axis = ((int)data[2] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
axis = ((int)data[3] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
axis = ((int)data[4] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
axis = ((int)data[5] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
/* Buttons are mapped as axes in the order they appear in the button enumeration */
{
static int button_axis_offsets[] = {
12, /* SDL_GAMEPAD_BUTTON_A */
11, /* SDL_GAMEPAD_BUTTON_B */
13, /* SDL_GAMEPAD_BUTTON_X */
10, /* SDL_GAMEPAD_BUTTON_Y */
0, /* SDL_GAMEPAD_BUTTON_BACK */
0, /* SDL_GAMEPAD_BUTTON_GUIDE */
0, /* SDL_GAMEPAD_BUTTON_START */
0, /* SDL_GAMEPAD_BUTTON_LEFT_STICK */
0, /* SDL_GAMEPAD_BUTTON_RIGHT_STICK */
14, /* SDL_GAMEPAD_BUTTON_LEFT_SHOULDER */
16, /* SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER */
8, /* SDL_GAMEPAD_BUTTON_DPAD_UP */
9, /* SDL_GAMEPAD_BUTTON_DPAD_DOWN */
7, /* SDL_GAMEPAD_BUTTON_DPAD_LEFT */
6, /* SDL_GAMEPAD_BUTTON_DPAD_RIGHT */
};
Uint8 i, axis_index = 6;
for (i = 0; i < SDL_arraysize(button_axis_offsets); ++i) {
int offset = button_axis_offsets[i];
if (!offset) {
/* This button doesn't report as an axis */
continue;
}
axis = ((int)data[offset] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, axis_index, axis);
++axis_index;
}
}
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
}
static void HIDAPI_DriverPS3ThirdParty_HandleStatePacket19(SDL_Joystick *joystick, SDL_DriverPS3_Context *ctx, Uint8 *data, int size)
{
Sint16 axis;
Uint64 timestamp = SDL_GetTicksNS();
if (ctx->last_state[0] != data[0]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[0] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[0] & 0x02) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[0] & 0x04) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[0] & 0x08) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[0] & 0x10) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[0] & 0x20) ? SDL_PRESSED : SDL_RELEASED);
}
if (ctx->last_state[1] != data[1]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (data[1] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, (data[1] & 0x02) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, (data[1] & 0x04) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, (data[1] & 0x08) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (data[1] & 0x10) ? SDL_PRESSED : SDL_RELEASED);
}
if (ctx->device->vendor_id == USB_VENDOR_SAITEK && ctx->device->product_id == USB_PRODUCT_SAITEK_CYBORG_V3) {
/* Cyborg V.3 Rumble Pad doesn't set the dpad bits as expected, so use the axes instead */
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, data[10] ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, data[9] ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, data[7] ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, data[8] ? SDL_PRESSED : SDL_RELEASED);
} else {
if (ctx->last_state[2] != data[2]) {
SDL_bool dpad_up = SDL_FALSE;
SDL_bool dpad_down = SDL_FALSE;
SDL_bool dpad_left = SDL_FALSE;
SDL_bool dpad_right = SDL_FALSE;
switch (data[2] & 0x0f) {
case 0:
dpad_up = SDL_TRUE;
break;
case 1:
dpad_up = SDL_TRUE;
dpad_right = SDL_TRUE;
break;
case 2:
dpad_right = SDL_TRUE;
break;
case 3:
dpad_right = SDL_TRUE;
dpad_down = SDL_TRUE;
break;
case 4:
dpad_down = SDL_TRUE;
break;
case 5:
dpad_left = SDL_TRUE;
dpad_down = SDL_TRUE;
break;
case 6:
dpad_left = SDL_TRUE;
break;
case 7:
dpad_up = SDL_TRUE;
dpad_left = SDL_TRUE;
break;
default:
break;
}
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, dpad_down);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, dpad_up);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, dpad_right);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, dpad_left);
}
}
axis = ((int)data[17] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
axis = ((int)data[18] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
axis = ((int)data[3] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
axis = ((int)data[4] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
axis = ((int)data[5] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
axis = ((int)data[6] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
/* Buttons are mapped as axes in the order they appear in the button enumeration */
{
static int button_axis_offsets[] = {
13, /* SDL_GAMEPAD_BUTTON_A */
12, /* SDL_GAMEPAD_BUTTON_B */
14, /* SDL_GAMEPAD_BUTTON_X */
11, /* SDL_GAMEPAD_BUTTON_Y */
0, /* SDL_GAMEPAD_BUTTON_BACK */
0, /* SDL_GAMEPAD_BUTTON_GUIDE */
0, /* SDL_GAMEPAD_BUTTON_START */
0, /* SDL_GAMEPAD_BUTTON_LEFT_STICK */
0, /* SDL_GAMEPAD_BUTTON_RIGHT_STICK */
15, /* SDL_GAMEPAD_BUTTON_LEFT_SHOULDER */
16, /* SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER */
9, /* SDL_GAMEPAD_BUTTON_DPAD_UP */
10, /* SDL_GAMEPAD_BUTTON_DPAD_DOWN */
8, /* SDL_GAMEPAD_BUTTON_DPAD_LEFT */
7, /* SDL_GAMEPAD_BUTTON_DPAD_RIGHT */
};
Uint8 i, axis_index = 6;
for (i = 0; i < SDL_arraysize(button_axis_offsets); ++i) {
int offset = button_axis_offsets[i];
if (!offset) {
/* This button doesn't report as an axis */
continue;
}
axis = ((int)data[offset] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, axis_index, axis);
++axis_index;
}
}
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
}
static SDL_bool HIDAPI_DriverPS3ThirdParty_UpdateDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
SDL_Joystick *joystick = NULL;
Uint8 data[USB_PACKET_LENGTH];
int size;
if (device->num_joysticks > 0) {
joystick = SDL_GetJoystickFromInstanceID(device->joysticks[0]);
} else {
return SDL_FALSE;
}
while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
#ifdef DEBUG_PS3_PROTOCOL
HIDAPI_DumpPacket("PS3 packet: size = %d", data, size);
#endif
if (joystick == NULL) {
continue;
}
if (size >= 19) {
HIDAPI_DriverPS3ThirdParty_HandleStatePacket19(joystick, ctx, data, size);
} else if (size == 18) {
/* This packet format was seen with the Logitech ChillStream */
HIDAPI_DriverPS3ThirdParty_HandleStatePacket18(joystick, ctx, data, size);
} else {
#ifdef DEBUG_JOYSTICK
SDL_Log("Unknown PS3 packet, size %d\n", size);
#endif
}
}
if (size < 0) {
/* Read error, device is disconnected */
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
}
return size >= 0;
}
static void HIDAPI_DriverPS3ThirdParty_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
ctx->joystick = NULL;
}
static void HIDAPI_DriverPS3ThirdParty_FreeDevice(SDL_HIDAPI_Device *device)
{
}
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS3ThirdParty = {
SDL_HINT_JOYSTICK_HIDAPI_PS3,
SDL_TRUE,
HIDAPI_DriverPS3_RegisterHints,
HIDAPI_DriverPS3_UnregisterHints,
HIDAPI_DriverPS3ThirdParty_IsEnabled,
HIDAPI_DriverPS3ThirdParty_IsSupportedDevice,
HIDAPI_DriverPS3ThirdParty_InitDevice,
HIDAPI_DriverPS3ThirdParty_GetDevicePlayerIndex,
HIDAPI_DriverPS3ThirdParty_SetDevicePlayerIndex,
HIDAPI_DriverPS3ThirdParty_UpdateDevice,
HIDAPI_DriverPS3ThirdParty_OpenJoystick,
HIDAPI_DriverPS3ThirdParty_RumbleJoystick,
HIDAPI_DriverPS3ThirdParty_RumbleJoystickTriggers,
HIDAPI_DriverPS3ThirdParty_GetJoystickCapabilities,
HIDAPI_DriverPS3ThirdParty_SetJoystickLED,
HIDAPI_DriverPS3ThirdParty_SendJoystickEffect,
HIDAPI_DriverPS3ThirdParty_SetJoystickSensorsEnabled,
HIDAPI_DriverPS3ThirdParty_CloseJoystick,
HIDAPI_DriverPS3ThirdParty_FreeDevice,
};
#endif /* SDL_JOYSTICK_HIDAPI_PS3 */
#endif /* SDL_JOYSTICK_HIDAPI */

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,283 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 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"
#ifdef SDL_JOYSTICK_HIDAPI
/* Handle rumble on a separate thread so it doesn't block the application */
#include "SDL_hidapijoystick_c.h"
#include "SDL_hidapi_rumble.h"
#include "../../thread/SDL_systhread.h"
typedef struct SDL_HIDAPI_RumbleRequest
{
SDL_HIDAPI_Device *device;
Uint8 data[2 * USB_PACKET_LENGTH]; /* need enough space for the biggest report: dualshock4 is 78 bytes */
int size;
SDL_HIDAPI_RumbleSentCallback callback;
void *userdata;
struct SDL_HIDAPI_RumbleRequest *prev;
} SDL_HIDAPI_RumbleRequest;
typedef struct SDL_HIDAPI_RumbleContext
{
SDL_AtomicInt initialized;
SDL_AtomicInt running;
SDL_Thread *thread;
SDL_Semaphore *request_sem;
SDL_HIDAPI_RumbleRequest *requests_head;
SDL_HIDAPI_RumbleRequest *requests_tail;
} SDL_HIDAPI_RumbleContext;
#ifndef SDL_THREAD_SAFETY_ANALYSIS
static
#endif
SDL_Mutex *SDL_HIDAPI_rumble_lock;
static SDL_HIDAPI_RumbleContext rumble_context SDL_GUARDED_BY(SDL_HIDAPI_rumble_lock);
static int SDLCALL SDL_HIDAPI_RumbleThread(void *data)
{
SDL_HIDAPI_RumbleContext *ctx = (SDL_HIDAPI_RumbleContext *)data;
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
while (SDL_AtomicGet(&ctx->running)) {
SDL_HIDAPI_RumbleRequest *request = NULL;
SDL_WaitSemaphore(ctx->request_sem);
SDL_LockMutex(SDL_HIDAPI_rumble_lock);
request = ctx->requests_tail;
if (request) {
if (request == ctx->requests_head) {
ctx->requests_head = NULL;
}
ctx->requests_tail = request->prev;
}
SDL_UnlockMutex(SDL_HIDAPI_rumble_lock);
if (request) {
SDL_LockMutex(request->device->dev_lock);
if (request->device->dev) {
#ifdef DEBUG_RUMBLE
HIDAPI_DumpPacket("Rumble packet: size = %d", request->data, request->size);
#endif
SDL_hid_write(request->device->dev, request->data, request->size);
}
SDL_UnlockMutex(request->device->dev_lock);
if (request->callback) {
request->callback(request->userdata);
}
(void)SDL_AtomicDecRef(&request->device->rumble_pending);
SDL_free(request);
/* Make sure we're not starving report reads when there's lots of rumble */
SDL_Delay(10);
}
}
return 0;
}
static void SDL_HIDAPI_StopRumbleThread(SDL_HIDAPI_RumbleContext *ctx)
{
SDL_HIDAPI_RumbleRequest *request;
SDL_AtomicSet(&ctx->running, SDL_FALSE);
if (ctx->thread) {
int result;
SDL_PostSemaphore(ctx->request_sem);
SDL_WaitThread(ctx->thread, &result);
ctx->thread = NULL;
}
SDL_LockMutex(SDL_HIDAPI_rumble_lock);
while (ctx->requests_tail) {
request = ctx->requests_tail;
if (request == ctx->requests_head) {
ctx->requests_head = NULL;
}
ctx->requests_tail = request->prev;
if (request->callback) {
request->callback(request->userdata);
}
(void)SDL_AtomicDecRef(&request->device->rumble_pending);
SDL_free(request);
}
SDL_UnlockMutex(SDL_HIDAPI_rumble_lock);
if (ctx->request_sem) {
SDL_DestroySemaphore(ctx->request_sem);
ctx->request_sem = NULL;
}
if (SDL_HIDAPI_rumble_lock) {
SDL_DestroyMutex(SDL_HIDAPI_rumble_lock);
SDL_HIDAPI_rumble_lock = NULL;
}
SDL_AtomicSet(&ctx->initialized, SDL_FALSE);
}
static int SDL_HIDAPI_StartRumbleThread(SDL_HIDAPI_RumbleContext *ctx)
{
SDL_HIDAPI_rumble_lock = SDL_CreateMutex();
if (!SDL_HIDAPI_rumble_lock) {
SDL_HIDAPI_StopRumbleThread(ctx);
return -1;
}
ctx->request_sem = SDL_CreateSemaphore(0);
if (!ctx->request_sem) {
SDL_HIDAPI_StopRumbleThread(ctx);
return -1;
}
SDL_AtomicSet(&ctx->running, SDL_TRUE);
ctx->thread = SDL_CreateThreadInternal(SDL_HIDAPI_RumbleThread, "HIDAPI Rumble", 0, ctx);
if (!ctx->thread) {
SDL_HIDAPI_StopRumbleThread(ctx);
return -1;
}
return 0;
}
int SDL_HIDAPI_LockRumble(void)
{
SDL_HIDAPI_RumbleContext *ctx = &rumble_context;
if (SDL_AtomicCAS(&ctx->initialized, SDL_FALSE, SDL_TRUE)) {
if (SDL_HIDAPI_StartRumbleThread(ctx) < 0) {
return -1;
}
}
SDL_LockMutex(SDL_HIDAPI_rumble_lock);
return 0;
}
SDL_bool SDL_HIDAPI_GetPendingRumbleLocked(SDL_HIDAPI_Device *device, Uint8 **data, int **size, int *maximum_size)
{
SDL_HIDAPI_RumbleContext *ctx = &rumble_context;
SDL_HIDAPI_RumbleRequest *request, *found;
found = NULL;
for (request = ctx->requests_tail; request; request = request->prev) {
if (request->device == device) {
found = request;
}
}
if (found) {
*data = found->data;
*size = &found->size;
*maximum_size = sizeof(found->data);
return SDL_TRUE;
}
return SDL_FALSE;
}
int SDL_HIDAPI_SendRumbleAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size)
{
return SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(device, data, size, NULL, NULL);
}
int SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size, SDL_HIDAPI_RumbleSentCallback callback, void *userdata)
{
SDL_HIDAPI_RumbleContext *ctx = &rumble_context;
SDL_HIDAPI_RumbleRequest *request;
if (size > sizeof(request->data)) {
SDL_HIDAPI_UnlockRumble();
return SDL_SetError("Couldn't send rumble, size %d is greater than %d", size, (int)sizeof(request->data));
}
request = (SDL_HIDAPI_RumbleRequest *)SDL_calloc(1, sizeof(*request));
if (request == NULL) {
SDL_HIDAPI_UnlockRumble();
return SDL_OutOfMemory();
}
request->device = device;
SDL_memcpy(request->data, data, size);
request->size = size;
request->callback = callback;
request->userdata = userdata;
SDL_AtomicIncRef(&device->rumble_pending);
if (ctx->requests_head) {
ctx->requests_head->prev = request;
} else {
ctx->requests_tail = request;
}
ctx->requests_head = request;
/* Make sure we unlock before posting the semaphore so the rumble thread can run immediately */
SDL_HIDAPI_UnlockRumble();
SDL_PostSemaphore(ctx->request_sem);
return size;
}
void SDL_HIDAPI_UnlockRumble(void)
{
SDL_UnlockMutex(SDL_HIDAPI_rumble_lock);
}
int SDL_HIDAPI_SendRumble(SDL_HIDAPI_Device *device, const Uint8 *data, int size)
{
Uint8 *pending_data;
int *pending_size;
int maximum_size;
if (size <= 0) {
return SDL_SetError("Tried to send rumble with invalid size");
}
if (SDL_HIDAPI_LockRumble() != 0) {
return -1;
}
/* check if there is a pending request for the device and update it */
if (SDL_HIDAPI_GetPendingRumbleLocked(device, &pending_data, &pending_size, &maximum_size) &&
size == *pending_size && data[0] == pending_data[0]) {
SDL_memcpy(pending_data, data, size);
SDL_HIDAPI_UnlockRumble();
return size;
}
return SDL_HIDAPI_SendRumbleAndUnlock(device, data, size);
}
void SDL_HIDAPI_QuitRumble(void)
{
SDL_HIDAPI_RumbleContext *ctx = &rumble_context;
if (SDL_AtomicGet(&ctx->running)) {
SDL_HIDAPI_StopRumbleThread(ctx);
}
}
#endif /* SDL_JOYSTICK_HIDAPI */

View file

@ -0,0 +1,42 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 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"
#ifdef SDL_JOYSTICK_HIDAPI
/* Handle rumble on a separate thread so it doesn't block the application */
/* Advanced API */
#ifdef SDL_THREAD_SAFETY_ANALYSIS
extern SDL_Mutex *SDL_HIDAPI_rumble_lock;
#endif
int SDL_HIDAPI_LockRumble(void) SDL_TRY_ACQUIRE(0, SDL_HIDAPI_rumble_lock);
SDL_bool SDL_HIDAPI_GetPendingRumbleLocked(SDL_HIDAPI_Device *device, Uint8 **data, int **size, int *maximum_size);
int SDL_HIDAPI_SendRumbleAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size) SDL_RELEASE(SDL_HIDAPI_rumble_lock);
typedef void (*SDL_HIDAPI_RumbleSentCallback)(void *userdata);
int SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size, SDL_HIDAPI_RumbleSentCallback callback, void *userdata) SDL_RELEASE(SDL_HIDAPI_rumble_lock);
void SDL_HIDAPI_UnlockRumble(void) SDL_RELEASE(SDL_HIDAPI_rumble_lock);
/* Simple API, will replace any pending rumble with the new data */
int SDL_HIDAPI_SendRumble(SDL_HIDAPI_Device *device, const Uint8 *data, int size);
void SDL_HIDAPI_QuitRumble(void);
#endif /* SDL_JOYSTICK_HIDAPI */

View file

@ -0,0 +1,601 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 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"
#ifdef SDL_JOYSTICK_HIDAPI
#include "../SDL_sysjoystick.h"
#include "SDL_hidapijoystick_c.h"
#include "SDL_hidapi_rumble.h"
#ifdef SDL_JOYSTICK_HIDAPI_SHIELD
/* Define this if you want to log all packets from the controller */
/*#define DEBUG_SHIELD_PROTOCOL*/
#define CMD_BATTERY_STATE 0x07
#define CMD_RUMBLE 0x39
#define CMD_CHARGE_STATE 0x3A
/* Milliseconds between polls of battery state */
#define BATTERY_POLL_INTERVAL_MS 60000
/* Milliseconds between retransmission of rumble to keep motors running */
#define RUMBLE_REFRESH_INTERVAL_MS 500
/* Reports that are too small are dropped over Bluetooth */
#define HID_REPORT_SIZE 33
enum
{
SDL_CONTROLLER_BUTTON_SHIELD_V103_TOUCHPAD = SDL_GAMEPAD_BUTTON_MISC1 + 1,
SDL_CONTROLLER_BUTTON_SHIELD_V103_MINUS,
SDL_CONTROLLER_BUTTON_SHIELD_V103_PLUS,
SDL_CONTROLLER_NUM_SHIELD_V103_BUTTONS,
SDL_CONTROLLER_NUM_SHIELD_V104_BUTTONS = SDL_GAMEPAD_BUTTON_MISC1 + 1,
};
typedef enum
{
k_ShieldReportIdControllerState = 0x01,
k_ShieldReportIdControllerTouch = 0x02,
k_ShieldReportIdCommandResponse = 0x03,
k_ShieldReportIdCommandRequest = 0x04,
} EShieldReportId;
/* This same report structure is used for both requests and responses */
typedef struct
{
Uint8 report_id;
Uint8 cmd;
Uint8 seq_num;
Uint8 payload[HID_REPORT_SIZE - 3];
} ShieldCommandReport_t;
SDL_COMPILE_TIME_ASSERT(ShieldCommandReport_t, sizeof(ShieldCommandReport_t) == HID_REPORT_SIZE);
typedef struct
{
Uint8 seq_num;
SDL_JoystickPowerLevel battery_level;
SDL_bool charging;
Uint64 last_battery_query_time;
SDL_bool rumble_report_pending;
SDL_bool rumble_update_pending;
Uint8 left_motor_amplitude;
Uint8 right_motor_amplitude;
Uint64 last_rumble_time;
Uint8 last_state[USB_PACKET_LENGTH];
} SDL_DriverShield_Context;
static void HIDAPI_DriverShield_RegisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SHIELD, callback, userdata);
}
static void HIDAPI_DriverShield_UnregisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SHIELD, callback, userdata);
}
static SDL_bool HIDAPI_DriverShield_IsEnabled(void)
{
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_SHIELD, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
}
static SDL_bool HIDAPI_DriverShield_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
{
return SDL_IsJoystickNVIDIASHIELDController(vendor_id, product_id);
}
static SDL_bool HIDAPI_DriverShield_InitDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverShield_Context *ctx;
ctx = (SDL_DriverShield_Context *)SDL_calloc(1, sizeof(*ctx));
if (ctx == NULL) {
SDL_OutOfMemory();
return SDL_FALSE;
}
device->context = ctx;
HIDAPI_SetDeviceName(device, "NVIDIA SHIELD Controller");
return HIDAPI_JoystickConnected(device, NULL);
}
static int HIDAPI_DriverShield_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
{
return -1;
}
static void HIDAPI_DriverShield_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
{
}
static int HIDAPI_DriverShield_SendCommand(SDL_HIDAPI_Device *device, Uint8 cmd, const void *data, int size)
{
SDL_DriverShield_Context *ctx = (SDL_DriverShield_Context *)device->context;
ShieldCommandReport_t cmd_pkt;
if (size > sizeof(cmd_pkt.payload)) {
return SDL_SetError("Command data exceeds HID report size");
}
if (SDL_HIDAPI_LockRumble() != 0) {
return -1;
}
cmd_pkt.report_id = k_ShieldReportIdCommandRequest;
cmd_pkt.cmd = cmd;
cmd_pkt.seq_num = ctx->seq_num++;
if (data) {
SDL_memcpy(cmd_pkt.payload, data, size);
}
/* Zero unused data in the payload */
if (size != sizeof(cmd_pkt.payload)) {
SDL_memset(&cmd_pkt.payload[size], 0, sizeof(cmd_pkt.payload) - size);
}
if (SDL_HIDAPI_SendRumbleAndUnlock(device, (Uint8 *)&cmd_pkt, sizeof(cmd_pkt)) != sizeof(cmd_pkt)) {
return SDL_SetError("Couldn't send command packet");
}
return 0;
}
static SDL_bool HIDAPI_DriverShield_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
SDL_DriverShield_Context *ctx = (SDL_DriverShield_Context *)device->context;
SDL_AssertJoysticksLocked();
ctx->rumble_report_pending = SDL_FALSE;
ctx->rumble_update_pending = SDL_FALSE;
ctx->left_motor_amplitude = 0;
ctx->right_motor_amplitude = 0;
ctx->last_rumble_time = 0;
SDL_zeroa(ctx->last_state);
/* Initialize the joystick capabilities */
if (device->product_id == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103) {
joystick->nbuttons = SDL_CONTROLLER_NUM_SHIELD_V103_BUTTONS;
joystick->naxes = SDL_GAMEPAD_AXIS_MAX;
joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED;
SDL_PrivateJoystickAddTouchpad(joystick, 1);
} else {
joystick->nbuttons = SDL_CONTROLLER_NUM_SHIELD_V104_BUTTONS;
joystick->naxes = SDL_GAMEPAD_AXIS_MAX;
joystick->epowerlevel = SDL_JOYSTICK_POWER_UNKNOWN;
}
/* Request battery and charging info */
ctx->last_battery_query_time = SDL_GetTicks();
HIDAPI_DriverShield_SendCommand(device, CMD_CHARGE_STATE, NULL, 0);
HIDAPI_DriverShield_SendCommand(device, CMD_BATTERY_STATE, NULL, 0);
return SDL_TRUE;
}
static int HIDAPI_DriverShield_SendNextRumble(SDL_HIDAPI_Device *device)
{
SDL_DriverShield_Context *ctx = device->context;
Uint8 rumble_data[3];
if (!ctx->rumble_update_pending) {
return 0;
}
rumble_data[0] = 0x01; /* enable */
rumble_data[1] = ctx->left_motor_amplitude;
rumble_data[2] = ctx->right_motor_amplitude;
ctx->rumble_update_pending = SDL_FALSE;
ctx->last_rumble_time = SDL_GetTicks();
return HIDAPI_DriverShield_SendCommand(device, CMD_RUMBLE, rumble_data, sizeof(rumble_data));
}
static int HIDAPI_DriverShield_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
{
if (device->product_id == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103) {
Uint8 rumble_packet[] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
rumble_packet[2] = (low_frequency_rumble >> 8);
rumble_packet[4] = (high_frequency_rumble >> 8);
if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
return SDL_SetError("Couldn't send rumble packet");
}
return 0;
} else {
SDL_DriverShield_Context *ctx = device->context;
/* The rumble motors are quite intense, so tone down the intensity like the official driver does */
ctx->left_motor_amplitude = low_frequency_rumble >> 11;
ctx->right_motor_amplitude = high_frequency_rumble >> 11;
ctx->rumble_update_pending = SDL_TRUE;
if (ctx->rumble_report_pending) {
/* We will service this after the hardware acknowledges the previous request */
return 0;
}
return HIDAPI_DriverShield_SendNextRumble(device);
}
}
static int HIDAPI_DriverShield_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
{
return SDL_Unsupported();
}
static Uint32 HIDAPI_DriverShield_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
return SDL_JOYCAP_RUMBLE;
}
static int HIDAPI_DriverShield_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
{
return SDL_Unsupported();
}
static int HIDAPI_DriverShield_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
{
const Uint8 *data_bytes = data;
if (size > 1) {
/* Single command byte followed by a variable length payload */
return HIDAPI_DriverShield_SendCommand(device, data_bytes[0], &data_bytes[1], size - 1);
} else if (size == 1) {
/* Single command byte with no payload */
return HIDAPI_DriverShield_SendCommand(device, data_bytes[0], NULL, 0);
} else {
return SDL_SetError("Effect data must at least contain a command byte");
}
}
static int HIDAPI_DriverShield_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled)
{
return SDL_Unsupported();
}
static void HIDAPI_DriverShield_HandleStatePacketV103(SDL_Joystick *joystick, SDL_DriverShield_Context *ctx, Uint8 *data, int size)
{
Uint64 timestamp = SDL_GetTicksNS();
if (ctx->last_state[3] != data[3]) {
SDL_bool dpad_up = SDL_FALSE;
SDL_bool dpad_down = SDL_FALSE;
SDL_bool dpad_left = SDL_FALSE;
SDL_bool dpad_right = SDL_FALSE;
switch (data[3]) {
case 0:
dpad_up = SDL_TRUE;
break;
case 1:
dpad_up = SDL_TRUE;
dpad_right = SDL_TRUE;
break;
case 2:
dpad_right = SDL_TRUE;
break;
case 3:
dpad_right = SDL_TRUE;
dpad_down = SDL_TRUE;
break;
case 4:
dpad_down = SDL_TRUE;
break;
case 5:
dpad_left = SDL_TRUE;
dpad_down = SDL_TRUE;
break;
case 6:
dpad_left = SDL_TRUE;
break;
case 7:
dpad_up = SDL_TRUE;
dpad_left = SDL_TRUE;
break;
default:
break;
}
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, dpad_down);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, dpad_up);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, dpad_right);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, dpad_left);
}
if (ctx->last_state[1] != data[1]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[1] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[1] & 0x02) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[1] & 0x04) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[1] & 0x08) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[1] & 0x10) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[1] & 0x20) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, (data[1] & 0x40) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, (data[1] & 0x80) ? SDL_PRESSED : SDL_RELEASED);
}
if (ctx->last_state[2] != data[2]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, (data[2] & 0x02) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_CONTROLLER_BUTTON_SHIELD_V103_PLUS, (data[2] & 0x08) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_CONTROLLER_BUTTON_SHIELD_V103_MINUS, (data[2] & 0x10) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (data[2] & 0x20) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (data[2] & 0x40) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1, (data[2] & 0x80) ? SDL_PRESSED : SDL_RELEASED);
}
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_SwapLE16(*(Sint16 *)&data[4]) - 0x8000);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_SwapLE16(*(Sint16 *)&data[6]) - 0x8000);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_SwapLE16(*(Sint16 *)&data[8]) - 0x8000);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_SwapLE16(*(Sint16 *)&data[10]) - 0x8000);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, SDL_SwapLE16(*(Sint16 *)&data[12]) - 0x8000);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, SDL_SwapLE16(*(Sint16 *)&data[14]) - 0x8000);
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
}
#undef clamp
#define clamp(val, min, max) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val)))
static void HIDAPI_DriverShield_HandleTouchPacketV103(SDL_Joystick *joystick, SDL_DriverShield_Context *ctx, const Uint8 *data, int size)
{
Uint8 touchpad_state;
float touchpad_x, touchpad_y;
Uint64 timestamp = SDL_GetTicksNS();
SDL_SendJoystickButton(timestamp, joystick, SDL_CONTROLLER_BUTTON_SHIELD_V103_TOUCHPAD, (data[1] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
/* It's a triangular pad, but just use the center as the usable touch area */
touchpad_state = !(data[1] & 0x80) ? SDL_PRESSED : SDL_RELEASED;
touchpad_x = clamp((float)(data[2] - 0x70) / 0x50, 0.0f, 1.0f);
touchpad_y = clamp((float)(data[4] - 0x40) / 0x15, 0.0f, 1.0f);
SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, touchpad_state, touchpad_x, touchpad_y, touchpad_state ? 1.0f : 0.0f);
}
static void HIDAPI_DriverShield_HandleStatePacketV104(SDL_Joystick *joystick, SDL_DriverShield_Context *ctx, Uint8 *data, int size)
{
Uint64 timestamp = SDL_GetTicksNS();
if (size < 23) {
return;
}
if (ctx->last_state[2] != data[2]) {
SDL_bool dpad_up = SDL_FALSE;
SDL_bool dpad_down = SDL_FALSE;
SDL_bool dpad_left = SDL_FALSE;
SDL_bool dpad_right = SDL_FALSE;
switch (data[2]) {
case 0:
dpad_up = SDL_TRUE;
break;
case 1:
dpad_up = SDL_TRUE;
dpad_right = SDL_TRUE;
break;
case 2:
dpad_right = SDL_TRUE;
break;
case 3:
dpad_right = SDL_TRUE;
dpad_down = SDL_TRUE;
break;
case 4:
dpad_down = SDL_TRUE;
break;
case 5:
dpad_left = SDL_TRUE;
dpad_down = SDL_TRUE;
break;
case 6:
dpad_left = SDL_TRUE;
break;
case 7:
dpad_up = SDL_TRUE;
dpad_left = SDL_TRUE;
break;
default:
break;
}
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, dpad_down);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, dpad_up);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, dpad_right);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, dpad_left);
}
if (ctx->last_state[3] != data[3]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[3] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[3] & 0x02) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[3] & 0x04) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[3] & 0x08) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[3] & 0x10) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[3] & 0x20) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, (data[3] & 0x40) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, (data[3] & 0x80) ? SDL_PRESSED : SDL_RELEASED);
}
if (ctx->last_state[4] != data[4]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, (data[4] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
}
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_SwapLE16(*(Sint16 *)&data[9]) - 0x8000);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_SwapLE16(*(Sint16 *)&data[11]) - 0x8000);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_SwapLE16(*(Sint16 *)&data[13]) - 0x8000);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_SwapLE16(*(Sint16 *)&data[15]) - 0x8000);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, SDL_SwapLE16(*(Sint16 *)&data[19]) - 0x8000);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, SDL_SwapLE16(*(Sint16 *)&data[21]) - 0x8000);
if (ctx->last_state[17] != data[17]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1, (data[17] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (data[17] & 0x02) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (data[17] & 0x04) ? SDL_PRESSED : SDL_RELEASED);
}
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
}
static SDL_bool HIDAPI_DriverShield_UpdateDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverShield_Context *ctx = (SDL_DriverShield_Context *)device->context;
SDL_Joystick *joystick = NULL;
Uint8 data[USB_PACKET_LENGTH];
int size = 0;
ShieldCommandReport_t *cmd_resp_report;
if (device->num_joysticks > 0) {
joystick = SDL_GetJoystickFromInstanceID(device->joysticks[0]);
} else {
return SDL_FALSE;
}
while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
#ifdef DEBUG_SHIELD_PROTOCOL
HIDAPI_DumpPacket("NVIDIA SHIELD packet: size = %d", data, size);
#endif
/* Byte 0 is HID report ID */
switch (data[0]) {
case k_ShieldReportIdControllerState:
if (joystick == NULL) {
break;
}
if (size == 16) {
HIDAPI_DriverShield_HandleStatePacketV103(joystick, ctx, data, size);
} else {
HIDAPI_DriverShield_HandleStatePacketV104(joystick, ctx, data, size);
}
break;
case k_ShieldReportIdControllerTouch:
if (joystick == NULL) {
break;
}
HIDAPI_DriverShield_HandleTouchPacketV103(joystick, ctx, data, size);
break;
case k_ShieldReportIdCommandResponse:
cmd_resp_report = (ShieldCommandReport_t *)data;
switch (cmd_resp_report->cmd) {
case CMD_RUMBLE:
ctx->rumble_report_pending = SDL_FALSE;
HIDAPI_DriverShield_SendNextRumble(device);
break;
case CMD_CHARGE_STATE:
ctx->charging = cmd_resp_report->payload[0] != 0;
if (joystick) {
SDL_SendJoystickBatteryLevel(joystick, ctx->charging ? SDL_JOYSTICK_POWER_WIRED : ctx->battery_level);
}
break;
case CMD_BATTERY_STATE:
switch (cmd_resp_report->payload[2]) {
case 0:
ctx->battery_level = SDL_JOYSTICK_POWER_EMPTY;
break;
case 1:
ctx->battery_level = SDL_JOYSTICK_POWER_LOW;
break;
case 2: /* 40% */
case 3: /* 60% */
case 4: /* 80% */
ctx->battery_level = SDL_JOYSTICK_POWER_MEDIUM;
break;
case 5:
ctx->battery_level = SDL_JOYSTICK_POWER_FULL;
break;
default:
ctx->battery_level = SDL_JOYSTICK_POWER_UNKNOWN;
break;
}
if (joystick) {
SDL_SendJoystickBatteryLevel(joystick, ctx->charging ? SDL_JOYSTICK_POWER_WIRED : ctx->battery_level);
}
break;
}
break;
}
}
/* Ask for battery state again if we're due for an update */
if (joystick && SDL_GetTicks() >= (ctx->last_battery_query_time + BATTERY_POLL_INTERVAL_MS)) {
ctx->last_battery_query_time = SDL_GetTicks();
HIDAPI_DriverShield_SendCommand(device, CMD_BATTERY_STATE, NULL, 0);
}
/* Retransmit rumble packets if they've lasted longer than the hardware supports */
if ((ctx->left_motor_amplitude != 0 || ctx->right_motor_amplitude != 0) &&
SDL_GetTicks() >= (ctx->last_rumble_time + RUMBLE_REFRESH_INTERVAL_MS)) {
ctx->rumble_update_pending = SDL_TRUE;
HIDAPI_DriverShield_SendNextRumble(device);
}
if (size < 0) {
/* Read error, device is disconnected */
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
}
return size >= 0;
}
static void HIDAPI_DriverShield_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
}
static void HIDAPI_DriverShield_FreeDevice(SDL_HIDAPI_Device *device)
{
}
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverShield = {
SDL_HINT_JOYSTICK_HIDAPI_SHIELD,
SDL_TRUE,
HIDAPI_DriverShield_RegisterHints,
HIDAPI_DriverShield_UnregisterHints,
HIDAPI_DriverShield_IsEnabled,
HIDAPI_DriverShield_IsSupportedDevice,
HIDAPI_DriverShield_InitDevice,
HIDAPI_DriverShield_GetDevicePlayerIndex,
HIDAPI_DriverShield_SetDevicePlayerIndex,
HIDAPI_DriverShield_UpdateDevice,
HIDAPI_DriverShield_OpenJoystick,
HIDAPI_DriverShield_RumbleJoystick,
HIDAPI_DriverShield_RumbleJoystickTriggers,
HIDAPI_DriverShield_GetJoystickCapabilities,
HIDAPI_DriverShield_SetJoystickLED,
HIDAPI_DriverShield_SendJoystickEffect,
HIDAPI_DriverShield_SetJoystickSensorsEnabled,
HIDAPI_DriverShield_CloseJoystick,
HIDAPI_DriverShield_FreeDevice,
};
#endif /* SDL_JOYSTICK_HIDAPI_SHIELD */
#endif /* SDL_JOYSTICK_HIDAPI */

View file

@ -0,0 +1,334 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 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"
#ifdef SDL_JOYSTICK_HIDAPI
#include "../SDL_sysjoystick.h"
#include "SDL_hidapijoystick_c.h"
#include "SDL_hidapi_rumble.h"
#ifdef SDL_JOYSTICK_HIDAPI_STADIA
/* Define this if you want to log all packets from the controller */
/*#define DEBUG_STADIA_PROTOCOL*/
enum
{
SDL_CONTROLLER_BUTTON_STADIA_SHARE = 15,
SDL_CONTROLLER_BUTTON_STADIA_GOOGLE_ASSISTANT,
SDL_CONTROLLER_NUM_STADIA_BUTTONS,
};
typedef struct
{
SDL_bool rumble_supported;
Uint8 last_state[USB_PACKET_LENGTH];
} SDL_DriverStadia_Context;
static void HIDAPI_DriverStadia_RegisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STADIA, callback, userdata);
}
static void HIDAPI_DriverStadia_UnregisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STADIA, callback, userdata);
}
static SDL_bool HIDAPI_DriverStadia_IsEnabled(void)
{
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STADIA, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
}
static SDL_bool HIDAPI_DriverStadia_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
{
return SDL_IsJoystickGoogleStadiaController(vendor_id, product_id);
}
static SDL_bool HIDAPI_DriverStadia_InitDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverStadia_Context *ctx;
ctx = (SDL_DriverStadia_Context *)SDL_calloc(1, sizeof(*ctx));
if (ctx == NULL) {
SDL_OutOfMemory();
return SDL_FALSE;
}
device->context = ctx;
/* Check whether rumble is supported */
{
Uint8 rumble_packet[] = { 0x05, 0x00, 0x00, 0x00, 0x00 };
if (SDL_hid_write(device->dev, rumble_packet, sizeof(rumble_packet)) >= 0) {
ctx->rumble_supported = SDL_TRUE;
}
}
HIDAPI_SetDeviceName(device, "Google Stadia Controller");
return HIDAPI_JoystickConnected(device, NULL);
}
static int HIDAPI_DriverStadia_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
{
return -1;
}
static void HIDAPI_DriverStadia_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
{
}
static SDL_bool HIDAPI_DriverStadia_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context;
SDL_AssertJoysticksLocked();
SDL_zeroa(ctx->last_state);
/* Initialize the joystick capabilities */
joystick->nbuttons = SDL_CONTROLLER_NUM_STADIA_BUTTONS;
joystick->naxes = SDL_GAMEPAD_AXIS_MAX;
joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED;
return SDL_TRUE;
}
static int HIDAPI_DriverStadia_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
{
SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context;
if (ctx->rumble_supported) {
Uint8 rumble_packet[] = { 0x05, 0x00, 0x00, 0x00, 0x00 };
rumble_packet[1] = (low_frequency_rumble & 0xFF);
rumble_packet[2] = (low_frequency_rumble >> 8);
rumble_packet[3] = (high_frequency_rumble & 0xFF);
rumble_packet[4] = (high_frequency_rumble >> 8);
if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
return SDL_SetError("Couldn't send rumble packet");
}
return 0;
} else {
return SDL_Unsupported();
}
}
static int HIDAPI_DriverStadia_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
{
return SDL_Unsupported();
}
static Uint32 HIDAPI_DriverStadia_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context;
Uint32 caps = 0;
if (ctx->rumble_supported) {
caps |= SDL_JOYCAP_RUMBLE;
}
return caps;
}
static int HIDAPI_DriverStadia_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
{
return SDL_Unsupported();
}
static int HIDAPI_DriverStadia_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
{
return SDL_Unsupported();
}
static int HIDAPI_DriverStadia_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled)
{
return SDL_Unsupported();
}
static void HIDAPI_DriverStadia_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverStadia_Context *ctx, Uint8 *data, int size)
{
Sint16 axis;
Uint64 timestamp = SDL_GetTicksNS();
// The format is the same but the original FW will send 10 bytes and January '21 FW update will send 11
if (size < 10 || data[0] != 0x03) {
/* We don't know how to handle this report */
return;
}
if (ctx->last_state[1] != data[1]) {
SDL_bool dpad_up = SDL_FALSE;
SDL_bool dpad_down = SDL_FALSE;
SDL_bool dpad_left = SDL_FALSE;
SDL_bool dpad_right = SDL_FALSE;
switch (data[1]) {
case 0:
dpad_up = SDL_TRUE;
break;
case 1:
dpad_up = SDL_TRUE;
dpad_right = SDL_TRUE;
break;
case 2:
dpad_right = SDL_TRUE;
break;
case 3:
dpad_right = SDL_TRUE;
dpad_down = SDL_TRUE;
break;
case 4:
dpad_down = SDL_TRUE;
break;
case 5:
dpad_left = SDL_TRUE;
dpad_down = SDL_TRUE;
break;
case 6:
dpad_left = SDL_TRUE;
break;
case 7:
dpad_up = SDL_TRUE;
dpad_left = SDL_TRUE;
break;
default:
break;
}
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, dpad_down);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, dpad_up);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, dpad_right);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, dpad_left);
}
if (ctx->last_state[2] != data[2]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (data[2] & 0x40) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (data[2] & 0x10) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, (data[2] & 0x20) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, (data[2] & 0x80) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_CONTROLLER_BUTTON_STADIA_SHARE, (data[2] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_CONTROLLER_BUTTON_STADIA_GOOGLE_ASSISTANT, (data[2] & 0x02) ? SDL_PRESSED : SDL_RELEASED);
}
if (ctx->last_state[3] != data[3]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[3] & 0x40) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[3] & 0x20) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[3] & 0x10) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[3] & 0x08) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[3] & 0x04) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[3] & 0x02) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, (data[3] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
}
#define READ_STICK_AXIS(offset) \
(data[offset] == 0x80 ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x80), 0x01 - 0x80, 0xff - 0x80, SDL_MIN_SINT16, SDL_MAX_SINT16))
{
axis = READ_STICK_AXIS(4);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
axis = READ_STICK_AXIS(5);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
axis = READ_STICK_AXIS(6);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
axis = READ_STICK_AXIS(7);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
}
#undef READ_STICK_AXIS
#define READ_TRIGGER_AXIS(offset) \
(Sint16)(((int)data[offset] * 257) - 32768)
{
axis = READ_TRIGGER_AXIS(8);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
axis = READ_TRIGGER_AXIS(9);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
}
#undef READ_TRIGGER_AXIS
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
}
static SDL_bool HIDAPI_DriverStadia_UpdateDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context;
SDL_Joystick *joystick = NULL;
Uint8 data[USB_PACKET_LENGTH];
int size = 0;
if (device->num_joysticks > 0) {
joystick = SDL_GetJoystickFromInstanceID(device->joysticks[0]);
} else {
return SDL_FALSE;
}
while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
#ifdef DEBUG_STADIA_PROTOCOL
HIDAPI_DumpPacket("Google Stadia packet: size = %d", data, size);
#endif
if (joystick == NULL) {
continue;
}
HIDAPI_DriverStadia_HandleStatePacket(joystick, ctx, data, size);
}
if (size < 0) {
/* Read error, device is disconnected */
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
}
return size >= 0;
}
static void HIDAPI_DriverStadia_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
}
static void HIDAPI_DriverStadia_FreeDevice(SDL_HIDAPI_Device *device)
{
}
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverStadia = {
SDL_HINT_JOYSTICK_HIDAPI_STADIA,
SDL_TRUE,
HIDAPI_DriverStadia_RegisterHints,
HIDAPI_DriverStadia_UnregisterHints,
HIDAPI_DriverStadia_IsEnabled,
HIDAPI_DriverStadia_IsSupportedDevice,
HIDAPI_DriverStadia_InitDevice,
HIDAPI_DriverStadia_GetDevicePlayerIndex,
HIDAPI_DriverStadia_SetDevicePlayerIndex,
HIDAPI_DriverStadia_UpdateDevice,
HIDAPI_DriverStadia_OpenJoystick,
HIDAPI_DriverStadia_RumbleJoystick,
HIDAPI_DriverStadia_RumbleJoystickTriggers,
HIDAPI_DriverStadia_GetJoystickCapabilities,
HIDAPI_DriverStadia_SetJoystickLED,
HIDAPI_DriverStadia_SendJoystickEffect,
HIDAPI_DriverStadia_SetJoystickSensorsEnabled,
HIDAPI_DriverStadia_CloseJoystick,
HIDAPI_DriverStadia_FreeDevice,
};
#endif /* SDL_JOYSTICK_HIDAPI_STADIA */
#endif /* SDL_JOYSTICK_HIDAPI */

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,386 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 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"
#ifdef SDL_JOYSTICK_HIDAPI
#include "../../SDL_hints_c.h"
#include "../SDL_sysjoystick.h"
#include "SDL_hidapijoystick_c.h"
#include "SDL_hidapi_rumble.h"
#ifdef SDL_JOYSTICK_HIDAPI_XBOX360
/* Define this if you want to log all packets from the controller */
/*#define DEBUG_XBOX_PROTOCOL*/
typedef struct
{
SDL_HIDAPI_Device *device;
SDL_Joystick *joystick;
int player_index;
SDL_bool player_lights;
Uint8 last_state[USB_PACKET_LENGTH];
} SDL_DriverXbox360_Context;
static void HIDAPI_DriverXbox360_RegisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata);
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, callback, userdata);
}
static void HIDAPI_DriverXbox360_UnregisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata);
SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, callback, userdata);
}
static SDL_bool HIDAPI_DriverXbox360_IsEnabled(void)
{
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360,
SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)));
}
static SDL_bool HIDAPI_DriverXbox360_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
{
const int XB360W_IFACE_PROTOCOL = 129; /* Wireless */
if (vendor_id == USB_VENDOR_ASTRO && product_id == USB_PRODUCT_ASTRO_C40_XBOX360) {
/* This is the ASTRO C40 in Xbox 360 mode */
return SDL_TRUE;
}
if (vendor_id == USB_VENDOR_NVIDIA) {
/* This is the NVIDIA Shield controller which doesn't talk Xbox controller protocol */
return SDL_FALSE;
}
if ((vendor_id == USB_VENDOR_MICROSOFT && (product_id == 0x0291 || product_id == 0x0719)) ||
(type == SDL_GAMEPAD_TYPE_XBOX360 && interface_protocol == XB360W_IFACE_PROTOCOL)) {
/* This is the wireless dongle, which talks a different protocol */
return SDL_FALSE;
}
if (interface_number > 0) {
/* This is the chatpad or other input interface, not the Xbox 360 interface */
return SDL_FALSE;
}
#ifdef __MACOS__
if (vendor_id == USB_VENDOR_MICROSOFT && product_id == USB_PRODUCT_XBOX360_WIRED_CONTROLLER && version == 0) {
/* This is the Steam Virtual Gamepad, which isn't supported by this driver */
return SDL_FALSE;
}
/* Wired Xbox One controllers are handled by this driver, interfacing with
the 360Controller driver available from:
https://github.com/360Controller/360Controller/releases
Bluetooth Xbox One controllers are handled by the SDL Xbox One driver
*/
if (SDL_IsJoystickBluetoothXboxOne(vendor_id, product_id)) {
return SDL_FALSE;
}
return (type == SDL_GAMEPAD_TYPE_XBOX360 || type == SDL_GAMEPAD_TYPE_XBOXONE) ? SDL_TRUE : SDL_FALSE;
#else
return (type == SDL_GAMEPAD_TYPE_XBOX360) ? SDL_TRUE : SDL_FALSE;
#endif
}
static SDL_bool SetSlotLED(SDL_hid_device *dev, Uint8 slot, SDL_bool on)
{
const SDL_bool blink = SDL_FALSE;
Uint8 mode = on ? ((blink ? 0x02 : 0x06) + slot) : 0;
Uint8 led_packet[] = { 0x01, 0x03, 0x00 };
led_packet[2] = mode;
if (SDL_hid_write(dev, led_packet, sizeof(led_packet)) != sizeof(led_packet)) {
return SDL_FALSE;
}
return SDL_TRUE;
}
static void UpdateSlotLED(SDL_DriverXbox360_Context *ctx)
{
if (ctx->player_lights) {
SetSlotLED(ctx->device->dev, (ctx->player_index % 4), SDL_TRUE);
} else {
SetSlotLED(ctx->device->dev, 0, SDL_FALSE);
}
}
static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
{
SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)userdata;
SDL_bool player_lights = SDL_GetStringBoolean(hint, SDL_TRUE);
if (player_lights != ctx->player_lights) {
ctx->player_lights = player_lights;
UpdateSlotLED(ctx);
}
}
static SDL_bool HIDAPI_DriverXbox360_InitDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverXbox360_Context *ctx;
ctx = (SDL_DriverXbox360_Context *)SDL_calloc(1, sizeof(*ctx));
if (ctx == NULL) {
SDL_OutOfMemory();
return SDL_FALSE;
}
ctx->device = device;
device->context = ctx;
device->type = SDL_GAMEPAD_TYPE_XBOX360;
return HIDAPI_JoystickConnected(device, NULL);
}
static int HIDAPI_DriverXbox360_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
{
return -1;
}
static void HIDAPI_DriverXbox360_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
{
SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;
if (!ctx->joystick) {
return;
}
ctx->player_index = player_index;
UpdateSlotLED(ctx);
}
static SDL_bool HIDAPI_DriverXbox360_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;
SDL_AssertJoysticksLocked();
ctx->joystick = joystick;
SDL_zeroa(ctx->last_state);
/* Initialize player index (needed for setting LEDs) */
ctx->player_index = SDL_GetJoystickPlayerIndex(joystick);
ctx->player_lights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED, SDL_TRUE);
UpdateSlotLED(ctx);
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED,
SDL_PlayerLEDHintChanged, ctx);
/* Initialize the joystick capabilities */
joystick->nbuttons = 15;
joystick->naxes = SDL_GAMEPAD_AXIS_MAX;
joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED;
return SDL_TRUE;
}
static int HIDAPI_DriverXbox360_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
{
#ifdef __MACOS__
if (SDL_IsJoystickBluetoothXboxOne(device->vendor_id, device->product_id)) {
Uint8 rumble_packet[] = { 0x03, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00 };
rumble_packet[4] = (low_frequency_rumble >> 8);
rumble_packet[5] = (high_frequency_rumble >> 8);
if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
return SDL_SetError("Couldn't send rumble packet");
}
} else {
/* On macOS the 360Controller driver uses this short report,
and we need to prefix it with a magic token so hidapi passes it through untouched
*/
Uint8 rumble_packet[] = { 'M', 'A', 'G', 'I', 'C', '0', 0x00, 0x04, 0x00, 0x00 };
rumble_packet[6 + 2] = (low_frequency_rumble >> 8);
rumble_packet[6 + 3] = (high_frequency_rumble >> 8);
if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
return SDL_SetError("Couldn't send rumble packet");
}
}
#else
Uint8 rumble_packet[] = { 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
rumble_packet[3] = (low_frequency_rumble >> 8);
rumble_packet[4] = (high_frequency_rumble >> 8);
if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
return SDL_SetError("Couldn't send rumble packet");
}
#endif
return 0;
}
static int HIDAPI_DriverXbox360_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
{
return SDL_Unsupported();
}
static Uint32 HIDAPI_DriverXbox360_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
/* Doesn't have an RGB LED, so don't return SDL_JOYCAP_LED here */
return SDL_JOYCAP_RUMBLE;
}
static int HIDAPI_DriverXbox360_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
{
return SDL_Unsupported();
}
static int HIDAPI_DriverXbox360_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
{
return SDL_Unsupported();
}
static int HIDAPI_DriverXbox360_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled)
{
return SDL_Unsupported();
}
static void HIDAPI_DriverXbox360_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverXbox360_Context *ctx, Uint8 *data, int size)
{
Sint16 axis;
#ifdef __MACOS__
const SDL_bool invert_y_axes = SDL_FALSE;
#else
const SDL_bool invert_y_axes = SDL_TRUE;
#endif
Uint64 timestamp = SDL_GetTicksNS();
if (ctx->last_state[2] != data[2]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, (data[2] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, (data[2] & 0x02) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, (data[2] & 0x04) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, (data[2] & 0x08) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, (data[2] & 0x10) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (data[2] & 0x20) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, (data[2] & 0x40) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, (data[2] & 0x80) ? SDL_PRESSED : SDL_RELEASED);
}
if (ctx->last_state[3] != data[3]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[3] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[3] & 0x02) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (data[3] & 0x04) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[3] & 0x10) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[3] & 0x20) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[3] & 0x40) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[3] & 0x80) ? SDL_PRESSED : SDL_RELEASED);
}
axis = ((int)data[4] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
axis = ((int)data[5] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
axis = SDL_SwapLE16(*(Sint16 *)(&data[6]));
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
axis = SDL_SwapLE16(*(Sint16 *)(&data[8]));
if (invert_y_axes) {
axis = ~axis;
}
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
axis = SDL_SwapLE16(*(Sint16 *)(&data[10]));
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
axis = SDL_SwapLE16(*(Sint16 *)(&data[12]));
if (invert_y_axes) {
axis = ~axis;
}
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
SDL_memcpy(ctx->last_state, data, SDL_min((size_t)size, sizeof(ctx->last_state)));
}
static SDL_bool HIDAPI_DriverXbox360_UpdateDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;
SDL_Joystick *joystick = NULL;
Uint8 data[USB_PACKET_LENGTH];
int size = 0;
if (device->num_joysticks > 0) {
joystick = SDL_GetJoystickFromInstanceID(device->joysticks[0]);
} else {
return SDL_FALSE;
}
while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
#ifdef DEBUG_XBOX_PROTOCOL
HIDAPI_DumpPacket("Xbox 360 packet: size = %d", data, size);
#endif
if (joystick == NULL) {
continue;
}
if (data[0] == 0x00) {
HIDAPI_DriverXbox360_HandleStatePacket(joystick, ctx, data, size);
}
}
if (size < 0) {
/* Read error, device is disconnected */
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
}
return size >= 0;
}
static void HIDAPI_DriverXbox360_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;
SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED,
SDL_PlayerLEDHintChanged, ctx);
ctx->joystick = NULL;
}
static void HIDAPI_DriverXbox360_FreeDevice(SDL_HIDAPI_Device *device)
{
}
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360 = {
SDL_HINT_JOYSTICK_HIDAPI_XBOX_360,
SDL_TRUE,
HIDAPI_DriverXbox360_RegisterHints,
HIDAPI_DriverXbox360_UnregisterHints,
HIDAPI_DriverXbox360_IsEnabled,
HIDAPI_DriverXbox360_IsSupportedDevice,
HIDAPI_DriverXbox360_InitDevice,
HIDAPI_DriverXbox360_GetDevicePlayerIndex,
HIDAPI_DriverXbox360_SetDevicePlayerIndex,
HIDAPI_DriverXbox360_UpdateDevice,
HIDAPI_DriverXbox360_OpenJoystick,
HIDAPI_DriverXbox360_RumbleJoystick,
HIDAPI_DriverXbox360_RumbleJoystickTriggers,
HIDAPI_DriverXbox360_GetJoystickCapabilities,
HIDAPI_DriverXbox360_SetJoystickLED,
HIDAPI_DriverXbox360_SendJoystickEffect,
HIDAPI_DriverXbox360_SetJoystickSensorsEnabled,
HIDAPI_DriverXbox360_CloseJoystick,
HIDAPI_DriverXbox360_FreeDevice,
};
#endif /* SDL_JOYSTICK_HIDAPI_XBOX360 */
#endif /* SDL_JOYSTICK_HIDAPI */

View file

@ -0,0 +1,379 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 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"
#ifdef SDL_JOYSTICK_HIDAPI
#include "../../SDL_hints_c.h"
#include "../SDL_sysjoystick.h"
#include "SDL_hidapijoystick_c.h"
#include "SDL_hidapi_rumble.h"
#ifdef SDL_JOYSTICK_HIDAPI_XBOX360
/* Define this if you want to log all packets from the controller */
/*#define DEBUG_XBOX_PROTOCOL*/
typedef struct
{
SDL_HIDAPI_Device *device;
SDL_bool connected;
int player_index;
SDL_bool player_lights;
Uint8 last_state[USB_PACKET_LENGTH];
} SDL_DriverXbox360W_Context;
static void HIDAPI_DriverXbox360W_RegisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata);
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, callback, userdata);
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS, callback, userdata);
}
static void HIDAPI_DriverXbox360W_UnregisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata);
SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, callback, userdata);
SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS, callback, userdata);
}
static SDL_bool HIDAPI_DriverXbox360W_IsEnabled(void)
{
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS,
SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT))));
}
static SDL_bool HIDAPI_DriverXbox360W_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
{
const int XB360W_IFACE_PROTOCOL = 129; /* Wireless */
if ((vendor_id == USB_VENDOR_MICROSOFT && (product_id == 0x0291 || product_id == 0x02a9 || product_id == 0x0719) && interface_protocol == 0) ||
(type == SDL_GAMEPAD_TYPE_XBOX360 && interface_protocol == XB360W_IFACE_PROTOCOL)) {
return SDL_TRUE;
}
return SDL_FALSE;
}
static SDL_bool SetSlotLED(SDL_hid_device *dev, Uint8 slot, SDL_bool on)
{
const SDL_bool blink = SDL_FALSE;
Uint8 mode = on ? ((blink ? 0x02 : 0x06) + slot) : 0;
Uint8 led_packet[] = { 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
led_packet[3] = 0x40 + (mode % 0x0e);
if (SDL_hid_write(dev, led_packet, sizeof(led_packet)) != sizeof(led_packet)) {
return SDL_FALSE;
}
return SDL_TRUE;
}
static void UpdateSlotLED(SDL_DriverXbox360W_Context *ctx)
{
if (ctx->player_lights) {
SetSlotLED(ctx->device->dev, (ctx->player_index % 4), SDL_TRUE);
} else {
SetSlotLED(ctx->device->dev, 0, SDL_FALSE);
}
}
static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
{
SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)userdata;
SDL_bool player_lights = SDL_GetStringBoolean(hint, SDL_TRUE);
if (player_lights != ctx->player_lights) {
ctx->player_lights = player_lights;
UpdateSlotLED(ctx);
}
}
static void UpdatePowerLevel(SDL_Joystick *joystick, Uint8 level)
{
float normalized_level = (float)level / 255.0f;
if (normalized_level <= 0.05f) {
SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_EMPTY);
} else if (normalized_level <= 0.20f) {
SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_LOW);
} else if (normalized_level <= 0.70f) {
SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_MEDIUM);
} else {
SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_FULL);
}
}
static SDL_bool HIDAPI_DriverXbox360W_InitDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverXbox360W_Context *ctx;
/* Requests controller presence information from the wireless dongle */
const Uint8 init_packet[] = { 0x08, 0x00, 0x0F, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
HIDAPI_SetDeviceName(device, "Xbox 360 Wireless Controller");
ctx = (SDL_DriverXbox360W_Context *)SDL_calloc(1, sizeof(*ctx));
if (ctx == NULL) {
SDL_OutOfMemory();
return SDL_FALSE;
}
ctx->device = device;
device->context = ctx;
if (SDL_hid_write(device->dev, init_packet, sizeof(init_packet)) != sizeof(init_packet)) {
SDL_SetError("Couldn't write init packet");
return SDL_FALSE;
}
device->type = SDL_GAMEPAD_TYPE_XBOX360;
return SDL_TRUE;
}
static int HIDAPI_DriverXbox360W_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
{
return -1;
}
static void HIDAPI_DriverXbox360W_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
{
SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context;
if (ctx == NULL) {
return;
}
ctx->player_index = player_index;
UpdateSlotLED(ctx);
}
static SDL_bool HIDAPI_DriverXbox360W_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context;
SDL_AssertJoysticksLocked();
SDL_zeroa(ctx->last_state);
/* Initialize player index (needed for setting LEDs) */
ctx->player_index = SDL_GetJoystickPlayerIndex(joystick);
ctx->player_lights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED, SDL_TRUE);
UpdateSlotLED(ctx);
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED,
SDL_PlayerLEDHintChanged, ctx);
/* Initialize the joystick capabilities */
joystick->nbuttons = 15;
joystick->naxes = SDL_GAMEPAD_AXIS_MAX;
joystick->epowerlevel = SDL_JOYSTICK_POWER_UNKNOWN;
return SDL_TRUE;
}
static int HIDAPI_DriverXbox360W_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
{
Uint8 rumble_packet[] = { 0x00, 0x01, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
rumble_packet[5] = (low_frequency_rumble >> 8);
rumble_packet[6] = (high_frequency_rumble >> 8);
if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
return SDL_SetError("Couldn't send rumble packet");
}
return 0;
}
static int HIDAPI_DriverXbox360W_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
{
return SDL_Unsupported();
}
static Uint32 HIDAPI_DriverXbox360W_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
/* Doesn't have an RGB LED, so don't return SDL_JOYCAP_LED here */
return SDL_JOYCAP_RUMBLE;
}
static int HIDAPI_DriverXbox360W_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
{
return SDL_Unsupported();
}
static int HIDAPI_DriverXbox360W_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
{
return SDL_Unsupported();
}
static int HIDAPI_DriverXbox360W_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled)
{
return SDL_Unsupported();
}
static void HIDAPI_DriverXbox360W_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverXbox360W_Context *ctx, Uint8 *data, int size)
{
Sint16 axis;
const SDL_bool invert_y_axes = SDL_TRUE;
Uint64 timestamp = SDL_GetTicksNS();
if (ctx->last_state[2] != data[2]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, (data[2] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, (data[2] & 0x02) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, (data[2] & 0x04) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, (data[2] & 0x08) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, (data[2] & 0x10) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (data[2] & 0x20) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, (data[2] & 0x40) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, (data[2] & 0x80) ? SDL_PRESSED : SDL_RELEASED);
}
if (ctx->last_state[3] != data[3]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[3] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[3] & 0x02) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (data[3] & 0x04) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[3] & 0x10) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[3] & 0x20) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[3] & 0x40) ? SDL_PRESSED : SDL_RELEASED);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[3] & 0x80) ? SDL_PRESSED : SDL_RELEASED);
}
axis = ((int)data[4] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
axis = ((int)data[5] * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
axis = SDL_SwapLE16(*(Sint16 *)(&data[6]));
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
axis = SDL_SwapLE16(*(Sint16 *)(&data[8]));
if (invert_y_axes) {
axis = ~axis;
}
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
axis = SDL_SwapLE16(*(Sint16 *)(&data[10]));
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
axis = SDL_SwapLE16(*(Sint16 *)(&data[12]));
if (invert_y_axes) {
axis = ~axis;
}
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
}
static SDL_bool HIDAPI_DriverXbox360W_UpdateDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context;
SDL_Joystick *joystick = NULL;
Uint8 data[USB_PACKET_LENGTH];
int size;
if (device->num_joysticks > 0) {
joystick = SDL_GetJoystickFromInstanceID(device->joysticks[0]);
}
while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
#ifdef DEBUG_XBOX_PROTOCOL
HIDAPI_DumpPacket("Xbox 360 wireless packet: size = %d", data, size);
#endif
if (size == 2 && data[0] == 0x08) {
SDL_bool connected = (data[1] & 0x80) ? SDL_TRUE : SDL_FALSE;
#ifdef DEBUG_JOYSTICK
SDL_Log("Connected = %s\n", connected ? "TRUE" : "FALSE");
#endif
if (connected != ctx->connected) {
ctx->connected = connected;
if (connected) {
SDL_JoystickID joystickID;
HIDAPI_JoystickConnected(device, &joystickID);
} else if (device->num_joysticks > 0) {
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
}
}
} else if (size == 29 && data[0] == 0x00 && data[1] == 0x0f && data[2] == 0x00 && data[3] == 0xf0) {
/* Serial number is data[7-13] */
#ifdef DEBUG_JOYSTICK
SDL_Log("Battery status (initial): %d\n", data[17]);
#endif
if (joystick) {
UpdatePowerLevel(joystick, data[17]);
}
} else if (size == 29 && data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x00 && data[3] == 0x13) {
#ifdef DEBUG_JOYSTICK
SDL_Log("Battery status: %d\n", data[4]);
#endif
if (joystick) {
UpdatePowerLevel(joystick, data[4]);
}
} else if (size == 29 && data[0] == 0x00 && (data[1] & 0x01) == 0x01) {
if (joystick) {
HIDAPI_DriverXbox360W_HandleStatePacket(joystick, device->dev, ctx, data + 4, size - 4);
}
}
}
if (size < 0 && device->num_joysticks > 0) {
/* Read error, device is disconnected */
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
}
return size >= 0;
}
static void HIDAPI_DriverXbox360W_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context;
SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED,
SDL_PlayerLEDHintChanged, ctx);
}
static void HIDAPI_DriverXbox360W_FreeDevice(SDL_HIDAPI_Device *device)
{
}
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360W = {
SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS,
SDL_TRUE,
HIDAPI_DriverXbox360W_RegisterHints,
HIDAPI_DriverXbox360W_UnregisterHints,
HIDAPI_DriverXbox360W_IsEnabled,
HIDAPI_DriverXbox360W_IsSupportedDevice,
HIDAPI_DriverXbox360W_InitDevice,
HIDAPI_DriverXbox360W_GetDevicePlayerIndex,
HIDAPI_DriverXbox360W_SetDevicePlayerIndex,
HIDAPI_DriverXbox360W_UpdateDevice,
HIDAPI_DriverXbox360W_OpenJoystick,
HIDAPI_DriverXbox360W_RumbleJoystick,
HIDAPI_DriverXbox360W_RumbleJoystickTriggers,
HIDAPI_DriverXbox360W_GetJoystickCapabilities,
HIDAPI_DriverXbox360W_SetJoystickLED,
HIDAPI_DriverXbox360W_SendJoystickEffect,
HIDAPI_DriverXbox360W_SetJoystickSensorsEnabled,
HIDAPI_DriverXbox360W_CloseJoystick,
HIDAPI_DriverXbox360W_FreeDevice,
};
#endif /* SDL_JOYSTICK_HIDAPI_XBOX360 */
#endif /* SDL_JOYSTICK_HIDAPI */

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,162 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 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"
#ifndef SDL_JOYSTICK_HIDAPI_H
#define SDL_JOYSTICK_HIDAPI_H
#include "../usb_ids.h"
/* This is the full set of HIDAPI drivers available */
#define SDL_JOYSTICK_HIDAPI_GAMECUBE
#define SDL_JOYSTICK_HIDAPI_LUNA
#define SDL_JOYSTICK_HIDAPI_PS3
#define SDL_JOYSTICK_HIDAPI_PS4
#define SDL_JOYSTICK_HIDAPI_PS5
#define SDL_JOYSTICK_HIDAPI_STADIA
#define SDL_JOYSTICK_HIDAPI_STEAM /* Simple support for BLE Steam Controller, hint is disabled by default */
#define SDL_JOYSTICK_HIDAPI_SWITCH
#define SDL_JOYSTICK_HIDAPI_WII
#define SDL_JOYSTICK_HIDAPI_XBOX360
#define SDL_JOYSTICK_HIDAPI_XBOXONE
#define SDL_JOYSTICK_HIDAPI_SHIELD
/* Whether HIDAPI is enabled by default */
#define SDL_HIDAPI_DEFAULT SDL_TRUE
/* The maximum size of a USB packet for HID devices */
#define USB_PACKET_LENGTH 64
/* Forward declaration */
struct SDL_HIDAPI_DeviceDriver;
typedef struct SDL_HIDAPI_Device
{
const void *magic;
char *name;
char *path;
Uint16 vendor_id;
Uint16 product_id;
Uint16 version;
char *serial;
SDL_JoystickGUID guid;
int interface_number; /* Available on Windows and Linux */
int interface_class;
int interface_subclass;
int interface_protocol;
Uint16 usage_page; /* Available on Windows and macOS */
Uint16 usage; /* Available on Windows and macOS */
SDL_bool is_bluetooth;
SDL_JoystickType joystick_type;
SDL_GamepadType type;
struct SDL_HIDAPI_DeviceDriver *driver;
void *context;
SDL_Mutex *dev_lock;
SDL_hid_device *dev;
SDL_AtomicInt rumble_pending;
int num_joysticks;
SDL_JoystickID *joysticks;
/* Used during scanning for device changes */
SDL_bool seen;
/* Used to flag that the device is being updated */
SDL_bool updating;
struct SDL_HIDAPI_Device *parent;
int num_children;
struct SDL_HIDAPI_Device **children;
struct SDL_HIDAPI_Device *next;
} SDL_HIDAPI_Device;
typedef struct SDL_HIDAPI_DeviceDriver
{
const char *name;
SDL_bool enabled;
void (*RegisterHints)(SDL_HintCallback callback, void *userdata);
void (*UnregisterHints)(SDL_HintCallback callback, void *userdata);
SDL_bool (*IsEnabled)(void);
SDL_bool (*IsSupportedDevice)(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol);
SDL_bool (*InitDevice)(SDL_HIDAPI_Device *device);
int (*GetDevicePlayerIndex)(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id);
void (*SetDevicePlayerIndex)(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index);
SDL_bool (*UpdateDevice)(SDL_HIDAPI_Device *device);
SDL_bool (*OpenJoystick)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick);
int (*RumbleJoystick)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble);
int (*RumbleJoystickTriggers)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble);
Uint32 (*GetJoystickCapabilities)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick);
int (*SetJoystickLED)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue);
int (*SendJoystickEffect)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size);
int (*SetJoystickSensorsEnabled)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled);
void (*CloseJoystick)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick);
void (*FreeDevice)(SDL_HIDAPI_Device *device);
} SDL_HIDAPI_DeviceDriver;
/* HIDAPI device support */
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverCombined;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverJoyCons;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLuna;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverNintendoClassic;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS3;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS3ThirdParty;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS4;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS5;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverShield;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverStadia;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteam;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverWii;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360W;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne;
/* Return true if a HID device is present and supported as a joystick of the given type */
extern SDL_bool HIDAPI_IsDeviceTypePresent(SDL_GamepadType type);
/* Return true if a HID device is present and supported as a joystick */
extern SDL_bool HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name);
/* Return the type of a joystick if it's present and supported */
extern SDL_JoystickType HIDAPI_GetJoystickTypeFromGUID(SDL_JoystickGUID guid);
/* Return the type of a game controller if it's present and supported */
extern SDL_GamepadType HIDAPI_GetGamepadTypeFromGUID(SDL_JoystickGUID guid);
extern void HIDAPI_UpdateDevices(void);
extern void HIDAPI_SetDeviceName(SDL_HIDAPI_Device *device, const char *name);
extern void HIDAPI_SetDeviceProduct(SDL_HIDAPI_Device *device, Uint16 vendor_id, Uint16 product_id);
extern void HIDAPI_SetDeviceSerial(SDL_HIDAPI_Device *device, const char *serial);
extern SDL_bool HIDAPI_HasConnectedUSBDevice(const char *serial);
extern void HIDAPI_DisconnectBluetoothDevice(const char *serial);
extern SDL_bool HIDAPI_JoystickConnected(SDL_HIDAPI_Device *device, SDL_JoystickID *pJoystickID);
extern void HIDAPI_JoystickDisconnected(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID);
extern void HIDAPI_DumpPacket(const char *prefix, const Uint8 *data, int size);
extern SDL_bool HIDAPI_SupportsPlaystationDetection(Uint16 vendor, Uint16 product);
extern float HIDAPI_RemapVal(float val, float val_min, float val_max, float output_min, float output_max);
#endif /* SDL_JOYSTICK_HIDAPI_H */

View file

@ -0,0 +1,499 @@
/*
Simple DirectMedia Layer
Copyright (C) 2021 Valve Corporation
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.
*/
#ifndef _CONTROLLER_CONSTANTS_
#define _CONTROLLER_CONSTANTS_
#include "controller_structs.h"
#ifdef __cplusplus
extern "C" {
#endif
#define FEATURE_REPORT_SIZE 64
#define VALVE_USB_VID 0x28DE
// Frame update rate (in ms).
#define FAST_SCAN_INTERVAL 6
#define SLOW_SCAN_INTERVAL 9
// Contains each of the USB PIDs for Valve controllers (only add to this enum and never change the order)
enum ValveControllerPID
{
BASTILLE_PID = 0x2202,
CHELL_PID = 0x1101,
D0G_PID = 0x1102,
ELI_PID = 0x1103,
FREEMAN_PID = 0x1104,
D0G_BLE_PID = 0x1105,
D0G_BLE2_PID = 0x1106,
D0GGLE_PID = 0x1142,
};
// This enum contains all of the messages exchanged between the host and the target (only add to this enum and never change the order)
enum FeatureReportMessageIDs
{
ID_SET_DIGITAL_MAPPINGS = 0x80,
ID_CLEAR_DIGITAL_MAPPINGS = 0x81,
ID_GET_DIGITAL_MAPPINGS = 0x82,
ID_GET_ATTRIBUTES_VALUES = 0x83,
ID_GET_ATTRIBUTE_LABEL = 0x84,
ID_SET_DEFAULT_DIGITAL_MAPPINGS = 0x85,
ID_FACTORY_RESET = 0x86,
ID_SET_SETTINGS_VALUES = 0x87,
ID_CLEAR_SETTINGS_VALUES = 0x88,
ID_GET_SETTINGS_VALUES = 0x89,
ID_GET_SETTING_LABEL = 0x8A,
ID_GET_SETTINGS_MAXS = 0x8B,
ID_GET_SETTINGS_DEFAULTS = 0x8C,
ID_SET_CONTROLLER_MODE = 0x8D,
ID_LOAD_DEFAULT_SETTINGS = 0x8E,
ID_TRIGGER_HAPTIC_PULSE = 0x8F,
ID_TURN_OFF_CONTROLLER = 0x9F,
ID_GET_DEVICE_INFO = 0xA1,
ID_CALIBRATE_TRACKPADS = 0xA7,
ID_RESERVED_0 = 0xA8,
ID_SET_SERIAL_NUMBER = 0xA9,
ID_GET_TRACKPAD_CALIBRATION = 0xAA,
ID_GET_TRACKPAD_FACTORY_CALIBRATION = 0xAB,
ID_GET_TRACKPAD_RAW_DATA = 0xAC,
ID_ENABLE_PAIRING = 0xAD,
ID_GET_STRING_ATTRIBUTE = 0xAE,
ID_RADIO_ERASE_RECORDS = 0xAF,
ID_RADIO_WRITE_RECORD = 0xB0,
ID_SET_DONGLE_SETTING = 0xB1,
ID_DONGLE_DISCONNECT_DEVICE = 0xB2,
ID_DONGLE_COMMIT_DEVICE = 0xB3,
ID_DONGLE_GET_WIRELESS_STATE = 0xB4,
ID_CALIBRATE_GYRO = 0xB5,
ID_PLAY_AUDIO = 0xB6,
ID_AUDIO_UPDATE_START = 0xB7,
ID_AUDIO_UPDATE_DATA = 0xB8,
ID_AUDIO_UPDATE_COMPLETE = 0xB9,
ID_GET_CHIPID = 0xBA,
ID_CALIBRATE_JOYSTICK = 0xBF,
ID_CALIBRATE_ANALOG_TRIGGERS = 0xC0,
ID_SET_AUDIO_MAPPING = 0xC1,
ID_CHECK_GYRO_FW_LOAD = 0xC2,
ID_CALIBRATE_ANALOG = 0xC3,
ID_DONGLE_GET_CONNECTED_SLOTS = 0xC4,
};
// Enumeration of all wireless dongle events
typedef enum WirelessEventTypes
{
WIRELESS_EVENT_DISCONNECT = 1,
WIRELESS_EVENT_CONNECT = 2,
WIRELESS_EVENT_PAIR = 3,
} EWirelessEventType;
// Enumeration of generic digital inputs - not all of these will be supported on all controllers (only add to this enum and never change the order)
typedef enum
{
IO_DIGITAL_BUTTON_NONE = -1,
IO_DIGITAL_BUTTON_RIGHT_TRIGGER,
IO_DIGITAL_BUTTON_LEFT_TRIGGER,
IO_DIGITAL_BUTTON_1,
IO_DIGITAL_BUTTON_Y=IO_DIGITAL_BUTTON_1,
IO_DIGITAL_BUTTON_2,
IO_DIGITAL_BUTTON_B=IO_DIGITAL_BUTTON_2,
IO_DIGITAL_BUTTON_3,
IO_DIGITAL_BUTTON_X=IO_DIGITAL_BUTTON_3,
IO_DIGITAL_BUTTON_4,
IO_DIGITAL_BUTTON_A=IO_DIGITAL_BUTTON_4,
IO_DIGITAL_BUTTON_RIGHT_BUMPER,
IO_DIGITAL_BUTTON_LEFT_BUMPER,
IO_DIGITAL_BUTTON_LEFT_JOYSTICK_CLICK,
IO_DIGITAL_BUTTON_ESCAPE,
IO_DIGITAL_BUTTON_STEAM,
IO_DIGITAL_BUTTON_MENU,
IO_DIGITAL_STICK_UP,
IO_DIGITAL_STICK_DOWN,
IO_DIGITAL_STICK_LEFT,
IO_DIGITAL_STICK_RIGHT,
IO_DIGITAL_TOUCH_1,
IO_DIGITAL_BUTTON_UP=IO_DIGITAL_TOUCH_1,
IO_DIGITAL_TOUCH_2,
IO_DIGITAL_BUTTON_RIGHT=IO_DIGITAL_TOUCH_2,
IO_DIGITAL_TOUCH_3,
IO_DIGITAL_BUTTON_LEFT=IO_DIGITAL_TOUCH_3,
IO_DIGITAL_TOUCH_4,
IO_DIGITAL_BUTTON_DOWN=IO_DIGITAL_TOUCH_4,
IO_DIGITAL_BUTTON_BACK_LEFT,
IO_DIGITAL_BUTTON_BACK_RIGHT,
IO_DIGITAL_LEFT_TRACKPAD_N,
IO_DIGITAL_LEFT_TRACKPAD_NE,
IO_DIGITAL_LEFT_TRACKPAD_E,
IO_DIGITAL_LEFT_TRACKPAD_SE,
IO_DIGITAL_LEFT_TRACKPAD_S,
IO_DIGITAL_LEFT_TRACKPAD_SW,
IO_DIGITAL_LEFT_TRACKPAD_W,
IO_DIGITAL_LEFT_TRACKPAD_NW,
IO_DIGITAL_RIGHT_TRACKPAD_N,
IO_DIGITAL_RIGHT_TRACKPAD_NE,
IO_DIGITAL_RIGHT_TRACKPAD_E,
IO_DIGITAL_RIGHT_TRACKPAD_SE,
IO_DIGITAL_RIGHT_TRACKPAD_S,
IO_DIGITAL_RIGHT_TRACKPAD_SW,
IO_DIGITAL_RIGHT_TRACKPAD_W,
IO_DIGITAL_RIGHT_TRACKPAD_NW,
IO_DIGITAL_LEFT_TRACKPAD_DOUBLE_TAP,
IO_DIGITAL_RIGHT_TRACKPAD_DOUBLE_TAP,
IO_DIGITAL_LEFT_TRACKPAD_OUTER_RADIUS,
IO_DIGITAL_RIGHT_TRACKPAD_OUTER_RADIUS,
IO_DIGITAL_LEFT_TRACKPAD_CLICK,
IO_DIGITAL_RIGHT_TRACKPAD_CLICK,
IO_DIGITAL_BATTERY_LOW,
IO_DIGITAL_LEFT_TRIGGER_THRESHOLD,
IO_DIGITAL_RIGHT_TRIGGER_THRESHOLD,
IO_DIGITAL_BUTTON_BACK_LEFT2,
IO_DIGITAL_BUTTON_BACK_RIGHT2,
IO_DIGITAL_BUTTON_ALWAYS_ON,
IO_DIGITAL_BUTTON_ANCILLARY_1,
IO_DIGITAL_BUTTON_MACRO_0,
IO_DIGITAL_BUTTON_MACRO_1,
IO_DIGITAL_BUTTON_MACRO_2,
IO_DIGITAL_BUTTON_MACRO_3,
IO_DIGITAL_BUTTON_MACRO_4,
IO_DIGITAL_BUTTON_MACRO_5,
IO_DIGITAL_BUTTON_MACRO_6,
IO_DIGITAL_BUTTON_MACRO_7,
IO_DIGITAL_BUTTON_MACRO_1FINGER,
IO_DIGITAL_BUTTON_MACRO_2FINGER,
IO_DIGITAL_COUNT
} DigitalIO ;
// Enumeration of generic analog inputs - not all of these will be supported on all controllers (only add to this enum and never change the order)
typedef enum
{
IO_ANALOG_LEFT_STICK_X,
IO_ANALOG_LEFT_STICK_Y,
IO_ANALOG_RIGHT_STICK_X,
IO_ANALOG_RIGHT_STICK_Y,
IO_ANALOG_LEFT_TRIGGER,
IO_ANALOG_RIGHT_TRIGGER,
IO_MOUSE1_X,
IO_MOUSE1_Y,
IO_MOUSE1_Z,
IO_ACCEL_X,
IO_ACCEL_Y,
IO_ACCEL_Z,
IO_GYRO_X,
IO_GYRO_Y,
IO_GYRO_Z,
IO_GYRO_QUAT_W,
IO_GYRO_QUAT_X,
IO_GYRO_QUAT_Y,
IO_GYRO_QUAT_Z,
IO_GYRO_STEERING_VEC,
IO_RAW_TRIGGER_LEFT,
IO_RAW_TRIGGER_RIGHT,
IO_RAW_JOYSTICK_X,
IO_RAW_JOYSTICK_Y,
IO_GYRO_TILT_VEC,
IO_ANALOG_COUNT
} AnalogIO;
// Contains list of all types of devices that the controller emulates (only add to this enum and never change the order)
enum DeviceTypes
{
DEVICE_KEYBOARD,
DEVICE_MOUSE,
DEVICE_GAMEPAD,
DEVICE_MODE_ADJUST,
DEVICE_COUNT
};
// Scan codes for HID keyboards
enum HIDKeyboardKeys
{
KEY_INVALID,
KEY_FIRST = 0x04,
KEY_A = KEY_FIRST, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, KEY_K, KEY_L,
KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z, KEY_1, KEY_2,
KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0, KEY_RETURN, KEY_ESCAPE, KEY_BACKSPACE, KEY_TAB, KEY_SPACE, KEY_DASH, KEY_EQUALS, KEY_LEFT_BRACKET,
KEY_RIGHT_BRACKET, KEY_BACKSLASH, KEY_UNUSED1, KEY_SEMICOLON, KEY_SINGLE_QUOTE, KEY_BACK_TICK, KEY_COMMA, KEY_PERIOD, KEY_FORWARD_SLASH, KEY_CAPSLOCK, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6,
KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_PRINT_SCREEN, KEY_SCROLL_LOCK, KEY_BREAK, KEY_INSERT, KEY_HOME, KEY_PAGE_UP, KEY_DELETE, KEY_END, KEY_PAGE_DOWN, KEY_RIGHT_ARROW,
KEY_LEFT_ARROW, KEY_DOWN_ARROW, KEY_UP_ARROW, KEY_NUM_LOCK, KEY_KEYPAD_FORWARD_SLASH, KEY_KEYPAD_ASTERISK, KEY_KEYPAD_DASH, KEY_KEYPAD_PLUS, KEY_KEYPAD_ENTER, KEY_KEYPAD_1, KEY_KEYPAD_2, KEY_KEYPAD_3, KEY_KEYPAD_4, KEY_KEYPAD_5, KEY_KEYPAD_6, KEY_KEYPAD_7,
KEY_KEYPAD_8, KEY_KEYPAD_9, KEY_KEYPAD_0, KEY_KEYPAD_PERIOD,
KEY_LALT,
KEY_LSHIFT,
KEY_LWIN,
KEY_LCONTROL,
KEY_RALT,
KEY_RSHIFT,
KEY_RWIN,
KEY_RCONTROL,
KEY_VOLUP,
KEY_VOLDOWN,
KEY_MUTE,
KEY_PLAY,
KEY_STOP,
KEY_NEXT,
KEY_PREV,
KEY_LAST = KEY_PREV
};
enum ModifierMasks
{
KEY_LCONTROL_MASK = (1<<0),
KEY_LSHIFT_MASK = (1<<1),
KEY_LALT_MASK = (1<<2),
KEY_LWIN_MASK = (1<<3),
KEY_RCONTROL_MASK = (1<<4),
KEY_RSHIFT_MASK = (1<<5),
KEY_RALT_MASK = (1<<6),
KEY_RWIN_MASK = (1<<7)
};
// Standard mouse buttons as specified in the HID mouse spec
enum MouseButtons
{
MOUSE_BTN_LEFT,
MOUSE_BTN_RIGHT,
MOUSE_BTN_MIDDLE,
MOUSE_BTN_BACK,
MOUSE_BTN_FORWARD,
MOUSE_SCROLL_UP,
MOUSE_SCROLL_DOWN,
MOUSE_BTN_COUNT
};
// Gamepad buttons
enum GamepadButtons
{
GAMEPAD_BTN_TRIGGER_LEFT=1,
GAMEPAD_BTN_TRIGGER_RIGHT,
GAMEPAD_BTN_A,
GAMEPAD_BTN_B,
GAMEPAD_BTN_Y,
GAMEPAD_BTN_X,
GAMEPAD_BTN_SHOULDER_LEFT,
GAMEPAD_BTN_SHOULDER_RIGHT,
GAMEPAD_BTN_LEFT_JOYSTICK,
GAMEPAD_BTN_RIGHT_JOYSTICK,
GAMEPAD_BTN_START,
GAMEPAD_BTN_SELECT,
GAMEPAD_BTN_STEAM,
GAMEPAD_BTN_DPAD_UP,
GAMEPAD_BTN_DPAD_DOWN,
GAMEPAD_BTN_DPAD_LEFT,
GAMEPAD_BTN_DPAD_RIGHT,
GAMEPAD_BTN_LSTICK_UP,
GAMEPAD_BTN_LSTICK_DOWN,
GAMEPAD_BTN_LSTICK_LEFT,
GAMEPAD_BTN_LSTICK_RIGHT,
GAMEPAD_BTN_RSTICK_UP,
GAMEPAD_BTN_RSTICK_DOWN,
GAMEPAD_BTN_RSTICK_LEFT,
GAMEPAD_BTN_RSTICK_RIGHT,
GAMEPAD_BTN_COUNT
};
// Mode adjust
enum ModeAdjustModes
{
MODE_ADJUST_SENSITITY=1,
MODE_ADJUST_LEFT_PAD_SECONDARY_MODE,
MODE_ADJUST_RIGHT_PAD_SECONDARY_MODE,
MODE_ADJUST_COUNT
};
// Read-only attributes of controllers (only add to this enum and never change the order)
typedef enum
{
ATTRIB_UNIQUE_ID,
ATTRIB_PRODUCT_ID,
ATTRIB_PRODUCT_REVISON, // deprecated
ATTRIB_CAPABILITIES = ATTRIB_PRODUCT_REVISON, // intentional aliasing
ATTRIB_FIRMWARE_VERSION, // deprecated
ATTRIB_FIRMWARE_BUILD_TIME,
ATTRIB_RADIO_FIRMWARE_BUILD_TIME,
ATTRIB_RADIO_DEVICE_ID0,
ATTRIB_RADIO_DEVICE_ID1,
ATTRIB_DONGLE_FIRMWARE_BUILD_TIME,
ATTRIB_BOARD_REVISION,
ATTRIB_BOOTLOADER_BUILD_TIME,
ATTRIB_CONNECTION_INTERVAL_IN_US,
ATTRIB_COUNT
} ControllerAttributes;
// Read-only string attributes of controllers (only add to this enum and never change the order)
typedef enum
{
ATTRIB_STR_BOARD_SERIAL,
ATTRIB_STR_UNIT_SERIAL,
ATTRIB_STR_COUNT
} ControllerStringAttributes;
typedef enum
{
STATUS_CODE_NORMAL,
STATUS_CODE_CRITICAL_BATTERY,
STATUS_CODE_GYRO_INIT_ERROR,
} ControllerStatusEventCodes;
typedef enum
{
STATUS_STATE_LOW_BATTERY=0,
} ControllerStatusStateFlags;
typedef enum {
TRACKPAD_ABSOLUTE_MOUSE,
TRACKPAD_RELATIVE_MOUSE,
TRACKPAD_DPAD_FOUR_WAY_DISCRETE,
TRACKPAD_DPAD_FOUR_WAY_OVERLAP,
TRACKPAD_DPAD_EIGHT_WAY,
TRACKPAD_RADIAL_MODE,
TRACKPAD_ABSOLUTE_DPAD,
TRACKPAD_NONE,
TRACKPAD_GESTURE_KEYBOARD,
TRACKPAD_NUM_MODES
} TrackpadDPadMode;
// Read-write controller settings (only add to this enum and never change the order)
typedef enum
{
SETTING_MOUSE_SENSITIVITY,
SETTING_MOUSE_ACCELERATION,
SETTING_TRACKBALL_ROTATION_ANGLE,
SETTING_HAPTIC_INTENSITY,
SETTING_LEFT_GAMEPAD_STICK_ENABLED,
SETTING_RIGHT_GAMEPAD_STICK_ENABLED,
SETTING_USB_DEBUG_MODE,
SETTING_LEFT_TRACKPAD_MODE,
SETTING_RIGHT_TRACKPAD_MODE,
SETTING_MOUSE_POINTER_ENABLED,
SETTING_DPAD_DEADZONE,
SETTING_MINIMUM_MOMENTUM_VEL,
SETTING_MOMENTUM_DECAY_AMMOUNT,
SETTING_TRACKPAD_RELATIVE_MODE_TICKS_PER_PIXEL,
SETTING_HAPTIC_INCREMENT,
SETTING_DPAD_ANGLE_SIN,
SETTING_DPAD_ANGLE_COS,
SETTING_MOMENTUM_VERTICAL_DIVISOR,
SETTING_MOMENTUM_MAXIMUM_VELOCITY,
SETTING_TRACKPAD_Z_ON,
SETTING_TRACKPAD_Z_OFF,
SETTING_SENSITIVY_SCALE_AMMOUNT,
SETTING_LEFT_TRACKPAD_SECONDARY_MODE,
SETTING_RIGHT_TRACKPAD_SECONDARY_MODE,
SETTING_SMOOTH_ABSOLUTE_MOUSE,
SETTING_STEAMBUTTON_POWEROFF_TIME,
SETTING_UNUSED_1,
SETTING_TRACKPAD_OUTER_RADIUS,
SETTING_TRACKPAD_Z_ON_LEFT,
SETTING_TRACKPAD_Z_OFF_LEFT,
SETTING_TRACKPAD_OUTER_SPIN_VEL,
SETTING_TRACKPAD_OUTER_SPIN_RADIUS,
SETTING_TRACKPAD_OUTER_SPIN_HORIZONTAL_ONLY,
SETTING_TRACKPAD_RELATIVE_MODE_DEADZONE,
SETTING_TRACKPAD_RELATIVE_MODE_MAX_VEL,
SETTING_TRACKPAD_RELATIVE_MODE_INVERT_Y,
SETTING_TRACKPAD_DOUBLE_TAP_BEEP_ENABLED,
SETTING_TRACKPAD_DOUBLE_TAP_BEEP_PERIOD,
SETTING_TRACKPAD_DOUBLE_TAP_BEEP_COUNT,
SETTING_TRACKPAD_OUTER_RADIUS_RELEASE_ON_TRANSITION,
SETTING_RADIAL_MODE_ANGLE,
SETTING_HAPTIC_INTENSITY_MOUSE_MODE,
SETTING_LEFT_DPAD_REQUIRES_CLICK,
SETTING_RIGHT_DPAD_REQUIRES_CLICK,
SETTING_LED_BASELINE_BRIGHTNESS,
SETTING_LED_USER_BRIGHTNESS,
SETTING_ENABLE_RAW_JOYSTICK,
SETTING_ENABLE_FAST_SCAN,
SETTING_GYRO_MODE,
SETTING_WIRELESS_PACKET_VERSION,
SETTING_SLEEP_INACTIVITY_TIMEOUT,
SETTING_COUNT,
// This is a special setting value use for callbacks and should not be set/get explicitly.
SETTING_ALL=0xFF
} ControllerSettings;
typedef enum
{
SETTING_DEFAULT,
SETTING_MIN,
SETTING_MAX,
SETTING_DEFAULTMINMAXCOUNT
} SettingDefaultMinMax;
// Bitmask that define which IMU features to enable.
typedef enum
{
SETTING_GYRO_MODE_OFF = 0x0000,
SETTING_GYRO_MODE_STEERING = 0x0001,
SETTING_GYRO_MODE_TILT = 0x0002,
SETTING_GYRO_MODE_SEND_ORIENTATION = 0x0004,
SETTING_GYRO_MODE_SEND_RAW_ACCEL = 0x0008,
SETTING_GYRO_MODE_SEND_RAW_GYRO = 0x0010,
} SettingGyroMode;
// Bitmask for haptic pulse flags
typedef enum
{
HAPTIC_PULSE_NORMAL = 0x0000,
HAPTIC_PULSE_HIGH_PRIORITY = 0x0001,
HAPTIC_PULSE_VERY_HIGH_PRIORITY = 0x0002,
} SettingHapticPulseFlags;
typedef struct
{
// default,min,max in this array in that order
short defaultminmax[SETTING_DEFAULTMINMAXCOUNT];
} SettingValueRange_t;
// below is from controller_constants.c which should be compiled into any code that uses this
extern const SettingValueRange_t g_DefaultSettingValues[SETTING_COUNT];
// Read-write settings for dongle (only add to this enum and never change the order)
typedef enum
{
DONGLE_SETTING_MOUSE_KEYBOARD_ENABLED,
DONGLE_SETTING_COUNT,
} DongleSettings;
typedef enum
{
AUDIO_STARTUP = 0,
AUDIO_SHUTDOWN = 1,
AUDIO_PAIR = 2,
AUDIO_PAIR_SUCCESS = 3,
AUDIO_IDENTIFY = 4,
AUDIO_LIZARDMODE = 5,
AUDIO_NORMALMODE = 6,
AUDIO_MAX_SLOT = 15
} ControllerAudio;
#ifdef __cplusplus
}
#endif
#endif // _CONTROLLER_CONSTANTS_H

View file

@ -0,0 +1,307 @@
/*
Simple DirectMedia Layer
Copyright (C) 2020 Valve Corporation
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.
*/
#ifndef _CONTROLLER_STRUCTS_
#define _CONTROLLER_STRUCTS_
#pragma pack(1)
#define HID_FEATURE_REPORT_BYTES 64
// Header for all host <==> target messages
typedef struct
{
unsigned char type;
unsigned char length;
} FeatureReportHeader;
// Generic controller attribute structure
typedef struct
{
unsigned char attributeTag;
uint32_t attributeValue;
} ControllerAttribute;
// Generic controller settings structure
typedef struct
{
ControllerAttribute attributes[ ( HID_FEATURE_REPORT_BYTES - sizeof( FeatureReportHeader ) ) / sizeof( ControllerAttribute ) ];
} MsgGetAttributes;
// This is the only message struct that application code should use to interact with feature request messages. Any new
// messages should be added to the union. The structures defined here should correspond to the ones defined in
// ValveDeviceCore.cpp.
//
typedef struct
{
FeatureReportHeader header;
union
{
MsgGetAttributes getAttributes;
} payload;
} FeatureReportMsg;
// Roll this version forward anytime that you are breaking compatibility of existing
// message types within ValveInReport_t or the header itself. Hopefully this should
// be super rare and instead you should just add new message payloads to the union,
// or just add fields to the end of existing payload structs which is expected to be
// safe in all code consuming these as they should just consume/copy upto the prior size
// they were aware of when processing.
#define k_ValveInReportMsgVersion 0x01
typedef enum
{
ID_CONTROLLER_STATE = 1,
ID_CONTROLLER_DEBUG = 2,
ID_CONTROLLER_WIRELESS = 3,
ID_CONTROLLER_STATUS = 4,
ID_CONTROLLER_DEBUG2 = 5,
ID_CONTROLLER_SECONDARY_STATE = 6,
ID_CONTROLLER_BLE_STATE = 7,
ID_CONTROLLER_MSG_COUNT
} ValveInReportMessageIDs;
typedef struct
{
unsigned short unReportVersion;
unsigned char ucType;
unsigned char ucLength;
} ValveInReportHeader_t;
// State payload
typedef struct
{
// If packet num matches that on your prior call, then the controller state hasn't been changed since
// your last call and there is no need to process it
uint32 unPacketNum;
// Button bitmask and trigger data.
union
{
uint64 ulButtons;
struct
{
unsigned char _pad0[3];
unsigned char nLeft;
unsigned char nRight;
unsigned char _pad1[3];
} Triggers;
} ButtonTriggerData;
// Left pad coordinates
short sLeftPadX;
short sLeftPadY;
// Right pad coordinates
short sRightPadX;
short sRightPadY;
// This is redundant, packed above, but still sent over wired
unsigned short sTriggerL;
unsigned short sTriggerR;
// FIXME figure out a way to grab this stuff over wireless
short sAccelX;
short sAccelY;
short sAccelZ;
short sGyroX;
short sGyroY;
short sGyroZ;
short sGyroQuatW;
short sGyroQuatX;
short sGyroQuatY;
short sGyroQuatZ;
} ValveControllerStatePacket_t;
// BLE State payload this has to be re-formatted from the normal state because BLE controller shows up as
//a HID device and we don't want to send all the optional parts of the message. Keep in sync with struct above.
typedef struct
{
// If packet num matches that on your prior call, then the controller state hasn't been changed since
// your last call and there is no need to process it
uint32 unPacketNum;
// Button bitmask and trigger data.
union
{
uint64 ulButtons;
struct
{
unsigned char _pad0[3];
unsigned char nLeft;
unsigned char nRight;
unsigned char _pad1[3];
} Triggers;
} ButtonTriggerData;
// Left pad coordinates
short sLeftPadX;
short sLeftPadY;
// Right pad coordinates
short sRightPadX;
short sRightPadY;
//This mimcs how the dongle reconstitutes HID packets, there will be 0-4 shorts depending on gyro mode
unsigned char ucGyroDataType; //TODO could maybe find some unused bits in the button field for this info (is only 2bits)
short sGyro[4];
} ValveControllerBLEStatePacket_t;
// Define a payload for reporting debug information
typedef struct
{
// Left pad coordinates
short sLeftPadX;
short sLeftPadY;
// Right pad coordinates
short sRightPadX;
short sRightPadY;
// Left mouse deltas
short sLeftPadMouseDX;
short sLeftPadMouseDY;
// Right mouse deltas
short sRightPadMouseDX;
short sRightPadMouseDY;
// Left mouse filtered deltas
short sLeftPadMouseFilteredDX;
short sLeftPadMouseFilteredDY;
// Right mouse filtered deltas
short sRightPadMouseFilteredDX;
short sRightPadMouseFilteredDY;
// Pad Z values
unsigned char ucLeftZ;
unsigned char ucRightZ;
// FingerPresent
unsigned char ucLeftFingerPresent;
unsigned char ucRightFingerPresent;
// Timestamps
unsigned char ucLeftTimestamp;
unsigned char ucRightTimestamp;
// Double tap state
unsigned char ucLeftTapState;
unsigned char ucRightTapState;
unsigned int unDigitalIOStates0;
unsigned int unDigitalIOStates1;
} ValveControllerDebugPacket_t;
typedef struct
{
unsigned char ucPadNum;
unsigned char ucPad[3]; // need Data to be word aligned
short Data[20];
unsigned short unNoise;
} ValveControllerTrackpadImage_t;
typedef struct
{
unsigned char ucPadNum;
unsigned char ucOffset;
unsigned char ucPad[2]; // need Data to be word aligned
short rgData[28];
} ValveControllerRawTrackpadImage_t;
// Payload for wireless metadata
typedef struct
{
unsigned char ucEventType;
} SteamControllerWirelessEvent_t;
typedef struct
{
// Current packet number.
unsigned int unPacketNum;
// Event codes and state information.
unsigned short sEventCode;
unsigned short unStateFlags;
// Current battery voltage (mV).
unsigned short sBatteryVoltage;
// Current battery level (0-100).
unsigned char ucBatteryLevel;
} SteamControllerStatusEvent_t;
typedef struct
{
ValveInReportHeader_t header;
union
{
ValveControllerStatePacket_t controllerState;
ValveControllerBLEStatePacket_t controllerBLEState;
ValveControllerDebugPacket_t debugState;
ValveControllerTrackpadImage_t padImage;
ValveControllerRawTrackpadImage_t rawPadImage;
SteamControllerWirelessEvent_t wirelessEvent;
SteamControllerStatusEvent_t statusEvent;
} payload;
} ValveInReport_t;
// Enumeration for BLE packet protocol
enum EBLEPacketReportNums
{
// Skipping past 2-3 because they are escape characters in Uart protocol
k_EBLEReportState = 4,
k_EBLEReportStatus = 5,
};
// Enumeration of data chunks in BLE state packets
enum EBLEOptionDataChunksBitmask
{
// First byte upper nibble
k_EBLEButtonChunk1 = 0x10,
k_EBLEButtonChunk2 = 0x20,
k_EBLEButtonChunk3 = 0x40,
k_EBLELeftJoystickChunk = 0x80,
// Second full byte
k_EBLELeftTrackpadChunk = 0x100,
k_EBLERightTrackpadChunk = 0x200,
k_EBLEIMUAccelChunk = 0x400,
k_EBLEIMUGyroChunk = 0x800,
k_EBLEIMUQuatChunk = 0x1000,
};
#pragma pack()
#endif // _CONTROLLER_STRUCTS