Path: blob/master/thirdparty/sdl/joystick/hidapi/SDL_hidapi_steamdeck.c
9906 views
/*1Simple DirectMedia Layer2Copyright (C) 2023 Max Maisel <[email protected]>34This software is provided 'as-is', without any express or implied5warranty. In no event will the authors be held liable for any damages6arising from the use of this software.78Permission is granted to anyone to use this software for any purpose,9including commercial applications, and to alter it and redistribute it10freely, subject to the following restrictions:11121. The origin of this software must not be misrepresented; you must not13claim that you wrote the original software. If you use this software14in a product, an acknowledgment in the product documentation would be15appreciated but is not required.162. Altered source versions must be plainly marked as such, and must not be17misrepresented as being the original software.183. This notice may not be removed or altered from any source distribution.19*/20#include "SDL_internal.h"2122#ifdef SDL_JOYSTICK_HIDAPI2324#include "../SDL_sysjoystick.h"25#include "SDL_hidapijoystick_c.h"2627#ifdef SDL_JOYSTICK_HIDAPI_STEAMDECK2829/*****************************************************************************************************/3031#include "steam/controller_constants.h"32#include "steam/controller_structs.h"3334enum35{36SDL_GAMEPAD_BUTTON_STEAM_DECK_QAM = 11,37SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE1,38SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE1,39SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE2,40SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE2,41SDL_GAMEPAD_NUM_STEAM_DECK_BUTTONS,42};4344typedef enum45{46STEAMDECK_LBUTTON_R2 = 0x00000001,47STEAMDECK_LBUTTON_L2 = 0x00000002,48STEAMDECK_LBUTTON_R = 0x00000004,49STEAMDECK_LBUTTON_L = 0x00000008,50STEAMDECK_LBUTTON_Y = 0x00000010,51STEAMDECK_LBUTTON_B = 0x00000020,52STEAMDECK_LBUTTON_X = 0x00000040,53STEAMDECK_LBUTTON_A = 0x00000080,54STEAMDECK_LBUTTON_DPAD_UP = 0x00000100,55STEAMDECK_LBUTTON_DPAD_RIGHT = 0x00000200,56STEAMDECK_LBUTTON_DPAD_LEFT = 0x00000400,57STEAMDECK_LBUTTON_DPAD_DOWN = 0x00000800,58STEAMDECK_LBUTTON_VIEW = 0x00001000,59STEAMDECK_LBUTTON_STEAM = 0x00002000,60STEAMDECK_LBUTTON_MENU = 0x00004000,61STEAMDECK_LBUTTON_L5 = 0x00008000,62STEAMDECK_LBUTTON_R5 = 0x00010000,63STEAMDECK_LBUTTON_LEFT_PAD = 0x00020000,64STEAMDECK_LBUTTON_RIGHT_PAD = 0x00040000,65STEAMDECK_LBUTTON_L3 = 0x00400000,66STEAMDECK_LBUTTON_R3 = 0x04000000,6768STEAMDECK_HBUTTON_L4 = 0x00000200,69STEAMDECK_HBUTTON_R4 = 0x00000400,70STEAMDECK_HBUTTON_QAM = 0x00040000,71} SteamDeckButtons;7273typedef struct74{75Uint32 update_rate_us;76Uint32 sensor_timestamp_us;77Uint64 last_button_state;78Uint8 watchdog_counter;79} SDL_DriverSteamDeck_Context;8081static bool DisableDeckLizardMode(SDL_hid_device *dev)82{83int rc;84Uint8 buffer[HID_FEATURE_REPORT_BYTES + 1] = { 0 };85FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1);8687msg->header.type = ID_CLEAR_DIGITAL_MAPPINGS;8889rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer));90if (rc != sizeof(buffer))91return false;9293msg->header.type = ID_SET_SETTINGS_VALUES;94msg->header.length = 5 * sizeof(ControllerSetting);95msg->payload.setSettingsValues.settings[0].settingNum = SETTING_SMOOTH_ABSOLUTE_MOUSE;96msg->payload.setSettingsValues.settings[0].settingValue = 0;97msg->payload.setSettingsValues.settings[1].settingNum = SETTING_LEFT_TRACKPAD_MODE;98msg->payload.setSettingsValues.settings[1].settingValue = TRACKPAD_NONE;99msg->payload.setSettingsValues.settings[2].settingNum = SETTING_RIGHT_TRACKPAD_MODE; // disable mouse100msg->payload.setSettingsValues.settings[2].settingValue = TRACKPAD_NONE;101msg->payload.setSettingsValues.settings[3].settingNum = SETTING_LEFT_TRACKPAD_CLICK_PRESSURE; // disable clicky pad102msg->payload.setSettingsValues.settings[3].settingValue = 0xFFFF;103msg->payload.setSettingsValues.settings[4].settingNum = SETTING_RIGHT_TRACKPAD_CLICK_PRESSURE; // disable clicky pad104msg->payload.setSettingsValues.settings[4].settingValue = 0xFFFF;105106rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer));107if (rc != sizeof(buffer))108return false;109110// There may be a lingering report read back after changing settings.111// Discard it.112SDL_hid_get_feature_report(dev, buffer, sizeof(buffer));113114return true;115}116117static bool FeedDeckLizardWatchdog(SDL_hid_device *dev)118{119int rc;120Uint8 buffer[HID_FEATURE_REPORT_BYTES + 1] = { 0 };121FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1);122123msg->header.type = ID_CLEAR_DIGITAL_MAPPINGS;124125rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer));126if (rc != sizeof(buffer))127return false;128129msg->header.type = ID_SET_SETTINGS_VALUES;130msg->header.length = 1 * sizeof(ControllerSetting);131msg->payload.setSettingsValues.settings[0].settingNum = SETTING_RIGHT_TRACKPAD_MODE;132msg->payload.setSettingsValues.settings[0].settingValue = TRACKPAD_NONE;133134rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer));135if (rc != sizeof(buffer))136return false;137138// There may be a lingering report read back after changing settings.139// Discard it.140SDL_hid_get_feature_report(dev, buffer, sizeof(buffer));141142return true;143}144145static void HIDAPI_DriverSteamDeck_HandleState(SDL_HIDAPI_Device *device,146SDL_Joystick *joystick,147ValveInReport_t *pInReport)148{149float values[3];150SDL_DriverSteamDeck_Context *ctx = (SDL_DriverSteamDeck_Context *)device->context;151Uint64 timestamp = SDL_GetTicksNS();152153if (pInReport->payload.deckState.ulButtons != ctx->last_button_state) {154Uint8 hat = 0;155156SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH,157((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_A) != 0));158SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST,159((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_B) != 0));160SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST,161((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_X) != 0));162SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH,163((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_Y) != 0));164165SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,166((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_L) != 0));167SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,168((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_R) != 0));169170SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK,171((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_VIEW) != 0));172SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START,173((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_MENU) != 0));174SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE,175((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_STEAM) != 0));176SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_QAM,177((pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_QAM) != 0));178179SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK,180((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_L3) != 0));181SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK,182((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_R3) != 0));183184SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE1,185((pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_R4) != 0));186SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE1,187((pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_L4) != 0));188SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE2,189((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_R5) != 0));190SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE2,191((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_L5) != 0));192193if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_UP) {194hat |= SDL_HAT_UP;195}196if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_DOWN) {197hat |= SDL_HAT_DOWN;198}199if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_LEFT) {200hat |= SDL_HAT_LEFT;201}202if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_RIGHT) {203hat |= SDL_HAT_RIGHT;204}205SDL_SendJoystickHat(timestamp, joystick, 0, hat);206207ctx->last_button_state = pInReport->payload.deckState.ulButtons;208}209210SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER,211(int)pInReport->payload.deckState.sTriggerRawL * 2 - 32768);212SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER,213(int)pInReport->payload.deckState.sTriggerRawR * 2 - 32768);214215SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX,216pInReport->payload.deckState.sLeftStickX);217SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY,218-pInReport->payload.deckState.sLeftStickY);219SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX,220pInReport->payload.deckState.sRightStickX);221SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY,222-pInReport->payload.deckState.sRightStickY);223224ctx->sensor_timestamp_us += ctx->update_rate_us;225226values[0] = (pInReport->payload.deckState.sGyroX / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));227values[1] = (pInReport->payload.deckState.sGyroZ / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));228values[2] = (-pInReport->payload.deckState.sGyroY / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));229SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->sensor_timestamp_us, values, 3);230231values[0] = (pInReport->payload.deckState.sAccelX / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;232values[1] = (pInReport->payload.deckState.sAccelZ / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;233values[2] = (-pInReport->payload.deckState.sAccelY / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;234SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->sensor_timestamp_us, values, 3);235}236237/*****************************************************************************************************/238239static void HIDAPI_DriverSteamDeck_RegisterHints(SDL_HintCallback callback, void *userdata)240{241SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK, callback, userdata);242}243244static void HIDAPI_DriverSteamDeck_UnregisterHints(SDL_HintCallback callback, void *userdata)245{246SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK, callback, userdata);247}248249static bool HIDAPI_DriverSteamDeck_IsEnabled(void)250{251return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK,252SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));253}254255static bool HIDAPI_DriverSteamDeck_IsSupportedDevice(256SDL_HIDAPI_Device *device,257const char *name,258SDL_GamepadType type,259Uint16 vendor_id,260Uint16 product_id,261Uint16 version,262int interface_number,263int interface_class,264int interface_subclass,265int interface_protocol)266{267return SDL_IsJoystickSteamDeck(vendor_id, product_id);268}269270static bool HIDAPI_DriverSteamDeck_InitDevice(SDL_HIDAPI_Device *device)271{272int size;273Uint8 data[64];274SDL_DriverSteamDeck_Context *ctx;275276ctx = (SDL_DriverSteamDeck_Context *)SDL_calloc(1, sizeof(*ctx));277if (ctx == NULL) {278return false;279}280281// Always 1kHz according to USB descriptor, but actually about 4 ms.282ctx->update_rate_us = 4000;283284device->context = ctx;285286// Read a report to see if this is the correct endpoint.287// Mouse, Keyboard and Controller have the same VID/PID but288// only the controller hidraw device receives hid reports.289size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 16);290if (size == 0)291return false;292293if (!DisableDeckLizardMode(device->dev))294return false;295296HIDAPI_SetDeviceName(device, "Steam Deck");297298return HIDAPI_JoystickConnected(device, NULL);299}300301static int HIDAPI_DriverSteamDeck_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)302{303return -1;304}305306static void HIDAPI_DriverSteamDeck_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)307{308}309310static bool HIDAPI_DriverSteamDeck_UpdateDevice(SDL_HIDAPI_Device *device)311{312SDL_DriverSteamDeck_Context *ctx = (SDL_DriverSteamDeck_Context *)device->context;313SDL_Joystick *joystick = NULL;314int r;315uint8_t data[64];316ValveInReport_t *pInReport = (ValveInReport_t *)data;317318if (device->num_joysticks > 0) {319joystick = SDL_GetJoystickFromID(device->joysticks[0]);320if (joystick == NULL) {321return false;322}323} else {324return false;325}326327if (ctx->watchdog_counter++ > 200) {328ctx->watchdog_counter = 0;329if (!FeedDeckLizardWatchdog(device->dev))330return false;331}332333SDL_memset(data, 0, sizeof(data));334335do {336r = SDL_hid_read(device->dev, data, sizeof(data));337338if (r < 0) {339// Failed to read from controller340HIDAPI_JoystickDisconnected(device, device->joysticks[0]);341return false;342} else if (r == 64 &&343pInReport->header.unReportVersion == k_ValveInReportMsgVersion &&344pInReport->header.ucType == ID_CONTROLLER_DECK_STATE &&345pInReport->header.ucLength == 64) {346HIDAPI_DriverSteamDeck_HandleState(device, joystick, pInReport);347}348} while (r > 0);349350return true;351}352353static bool HIDAPI_DriverSteamDeck_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)354{355SDL_DriverSteamDeck_Context *ctx = (SDL_DriverSteamDeck_Context *)device->context;356float update_rate_in_hz = 1.0f / (float)(ctx->update_rate_us) * 1.0e6f;357358SDL_AssertJoysticksLocked();359360// Initialize the joystick capabilities361joystick->nbuttons = SDL_GAMEPAD_NUM_STEAM_DECK_BUTTONS;362joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;363joystick->nhats = 1;364365SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, update_rate_in_hz);366SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, update_rate_in_hz);367368return true;369}370371static bool HIDAPI_DriverSteamDeck_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)372{373int rc;374Uint8 buffer[HID_FEATURE_REPORT_BYTES + 1] = { 0 };375FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1);376377msg->header.type = ID_TRIGGER_RUMBLE_CMD;378msg->payload.simpleRumble.unRumbleType = 0;379msg->payload.simpleRumble.unIntensity = HAPTIC_INTENSITY_SYSTEM;380msg->payload.simpleRumble.unLeftMotorSpeed = low_frequency_rumble;381msg->payload.simpleRumble.unRightMotorSpeed = high_frequency_rumble;382msg->payload.simpleRumble.nLeftGain = 2;383msg->payload.simpleRumble.nRightGain = 0;384385rc = SDL_hid_send_feature_report(device->dev, buffer, sizeof(buffer));386if (rc != sizeof(buffer))387return false;388return true;389}390391static bool HIDAPI_DriverSteamDeck_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)392{393return SDL_Unsupported();394}395396static Uint32 HIDAPI_DriverSteamDeck_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)397{398return SDL_JOYSTICK_CAP_RUMBLE;399}400401static bool HIDAPI_DriverSteamDeck_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)402{403return SDL_Unsupported();404}405406static bool HIDAPI_DriverSteamDeck_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)407{408return SDL_Unsupported();409}410411static bool HIDAPI_DriverSteamDeck_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)412{413// On steam deck, sensors are enabled by default. Nothing to do here.414return true;415}416417static void HIDAPI_DriverSteamDeck_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)418{419// Lizard mode id automatically re-enabled by watchdog. Nothing to do here.420}421422static void HIDAPI_DriverSteamDeck_FreeDevice(SDL_HIDAPI_Device *device)423{424}425426SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamDeck = {427SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK,428true,429HIDAPI_DriverSteamDeck_RegisterHints,430HIDAPI_DriverSteamDeck_UnregisterHints,431HIDAPI_DriverSteamDeck_IsEnabled,432HIDAPI_DriverSteamDeck_IsSupportedDevice,433HIDAPI_DriverSteamDeck_InitDevice,434HIDAPI_DriverSteamDeck_GetDevicePlayerIndex,435HIDAPI_DriverSteamDeck_SetDevicePlayerIndex,436HIDAPI_DriverSteamDeck_UpdateDevice,437HIDAPI_DriverSteamDeck_OpenJoystick,438HIDAPI_DriverSteamDeck_RumbleJoystick,439HIDAPI_DriverSteamDeck_RumbleJoystickTriggers,440HIDAPI_DriverSteamDeck_GetJoystickCapabilities,441HIDAPI_DriverSteamDeck_SetJoystickLED,442HIDAPI_DriverSteamDeck_SendJoystickEffect,443HIDAPI_DriverSteamDeck_SetSensorsEnabled,444HIDAPI_DriverSteamDeck_CloseJoystick,445HIDAPI_DriverSteamDeck_FreeDevice,446};447448#endif // SDL_JOYSTICK_HIDAPI_STEAMDECK449450#endif // SDL_JOYSTICK_HIDAPI451452453