Path: blob/master/thirdparty/sdl/joystick/hidapi/SDL_hidapi_luna.c
9906 views
/*1Simple DirectMedia Layer2Copyright (C) 1997-2025 Sam Lantinga <[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"26#include "SDL_hidapi_rumble.h"2728#ifdef SDL_JOYSTICK_HIDAPI_LUNA2930// Define this if you want to log all packets from the controller31// #define DEBUG_LUNA_PROTOCOL3233// Sending rumble on macOS blocks for a long time and eventually fails34#ifndef SDL_PLATFORM_MACOS35#define ENABLE_LUNA_BLUETOOTH_RUMBLE36#endif3738enum39{40SDL_GAMEPAD_BUTTON_LUNA_MICROPHONE = 11,41SDL_GAMEPAD_NUM_LUNA_BUTTONS,42};4344typedef struct45{46Uint8 last_state[USB_PACKET_LENGTH];47} SDL_DriverLuna_Context;4849static void HIDAPI_DriverLuna_RegisterHints(SDL_HintCallback callback, void *userdata)50{51SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_LUNA, callback, userdata);52}5354static void HIDAPI_DriverLuna_UnregisterHints(SDL_HintCallback callback, void *userdata)55{56SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_LUNA, callback, userdata);57}5859static bool HIDAPI_DriverLuna_IsEnabled(void)60{61return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_LUNA, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));62}6364static 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)65{66return SDL_IsJoystickAmazonLunaController(vendor_id, product_id);67}6869static bool HIDAPI_DriverLuna_InitDevice(SDL_HIDAPI_Device *device)70{71SDL_DriverLuna_Context *ctx;7273ctx = (SDL_DriverLuna_Context *)SDL_calloc(1, sizeof(*ctx));74if (!ctx) {75return false;76}77device->context = ctx;7879HIDAPI_SetDeviceName(device, "Amazon Luna Controller");8081return HIDAPI_JoystickConnected(device, NULL);82}8384static int HIDAPI_DriverLuna_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)85{86return -1;87}8889static void HIDAPI_DriverLuna_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)90{91}9293static bool HIDAPI_DriverLuna_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)94{95SDL_DriverLuna_Context *ctx = (SDL_DriverLuna_Context *)device->context;9697SDL_AssertJoysticksLocked();9899SDL_zeroa(ctx->last_state);100101// Initialize the joystick capabilities102joystick->nbuttons = SDL_GAMEPAD_NUM_LUNA_BUTTONS;103joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;104joystick->nhats = 1;105106return true;107}108109static bool HIDAPI_DriverLuna_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)110{111#ifdef ENABLE_LUNA_BLUETOOTH_RUMBLE112if (device->product_id == BLUETOOTH_PRODUCT_LUNA_CONTROLLER) {113// Same packet as on Xbox One controllers connected via Bluetooth114Uint8 rumble_packet[] = { 0x03, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xEB };115116// Magnitude is 1..100 so scale the 16-bit input here117rumble_packet[4] = (Uint8)(low_frequency_rumble / 655);118rumble_packet[5] = (Uint8)(high_frequency_rumble / 655);119120if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {121return SDL_SetError("Couldn't send rumble packet");122}123124return true;125}126#endif // ENABLE_LUNA_BLUETOOTH_RUMBLE127128// There is currently no rumble packet over USB129return SDL_Unsupported();130}131132static bool HIDAPI_DriverLuna_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)133{134return SDL_Unsupported();135}136137static Uint32 HIDAPI_DriverLuna_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)138{139Uint32 result = 0;140141#ifdef ENABLE_LUNA_BLUETOOTH_RUMBLE142if (device->product_id == BLUETOOTH_PRODUCT_LUNA_CONTROLLER) {143result |= SDL_JOYSTICK_CAP_RUMBLE;144}145#endif146147return result;148}149150static bool HIDAPI_DriverLuna_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)151{152return SDL_Unsupported();153}154155static bool HIDAPI_DriverLuna_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)156{157return SDL_Unsupported();158}159160static bool HIDAPI_DriverLuna_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)161{162return SDL_Unsupported();163}164165static void HIDAPI_DriverLuna_HandleUSBStatePacket(SDL_Joystick *joystick, SDL_DriverLuna_Context *ctx, Uint8 *data, int size)166{167Uint64 timestamp = SDL_GetTicksNS();168169if (ctx->last_state[1] != data[1]) {170SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[1] & 0x01) != 0));171SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[1] & 0x02) != 0));172SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[1] & 0x04) != 0));173SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[1] & 0x08) != 0));174SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[1] & 0x10) != 0));175SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[1] & 0x20) != 0));176SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[1] & 0x40) != 0));177SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[1] & 0x80) != 0));178}179if (ctx->last_state[2] != data[2]) {180SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[2] & 0x01) != 0));181SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LUNA_MICROPHONE, ((data[2] & 0x02) != 0));182SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[2] & 0x04) != 0));183SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x08) != 0));184}185186if (ctx->last_state[3] != data[3]) {187Uint8 hat;188189switch (data[3] & 0x0f) {190case 0:191hat = SDL_HAT_UP;192break;193case 1:194hat = SDL_HAT_RIGHTUP;195break;196case 2:197hat = SDL_HAT_RIGHT;198break;199case 3:200hat = SDL_HAT_RIGHTDOWN;201break;202case 4:203hat = SDL_HAT_DOWN;204break;205case 5:206hat = SDL_HAT_LEFTDOWN;207break;208case 6:209hat = SDL_HAT_LEFT;210break;211case 7:212hat = SDL_HAT_LEFTUP;213break;214default:215hat = SDL_HAT_CENTERED;216break;217}218SDL_SendJoystickHat(timestamp, joystick, 0, hat);219}220221#define READ_STICK_AXIS(offset) \222(data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)data[offset], 0x00, 0xff, SDL_MIN_SINT16, SDL_MAX_SINT16))223{224Sint16 axis = READ_STICK_AXIS(4);225SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);226axis = READ_STICK_AXIS(5);227SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);228axis = READ_STICK_AXIS(6);229SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);230axis = READ_STICK_AXIS(7);231SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);232}233#undef READ_STICK_AXIS234235#define READ_TRIGGER_AXIS(offset) \236(Sint16) HIDAPI_RemapVal((float)data[offset], 0x00, 0xff, SDL_MIN_SINT16, SDL_MAX_SINT16)237{238Sint16 axis = READ_TRIGGER_AXIS(8);239SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);240axis = READ_TRIGGER_AXIS(9);241SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);242}243#undef READ_TRIGGER_AXIS244245SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));246}247248static void HIDAPI_DriverLuna_HandleBluetoothStatePacket(SDL_Joystick *joystick, SDL_DriverLuna_Context *ctx, Uint8 *data, int size)249{250Uint64 timestamp = SDL_GetTicksNS();251252if (size >= 2 && data[0] == 0x02) {253// Home button has dedicated report254SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[1] & 0x1) != 0));255return;256}257258if (size >= 2 && data[0] == 0x04) {259// Battery level report260int percent = (int)SDL_roundf((data[1] / 255.0f) * 100.0f);261SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, percent);262return;263}264265if (size < 17 || data[0] != 0x01) {266// We don't know how to handle this report267return;268}269270if (ctx->last_state[13] != data[13]) {271Uint8 hat;272273switch (data[13] & 0x0f) {274case 1:275hat = SDL_HAT_UP;276break;277case 2:278hat = SDL_HAT_RIGHTUP;279break;280case 3:281hat = SDL_HAT_RIGHT;282break;283case 4:284hat = SDL_HAT_RIGHTDOWN;285break;286case 5:287hat = SDL_HAT_DOWN;288break;289case 6:290hat = SDL_HAT_LEFTDOWN;291break;292case 7:293hat = SDL_HAT_LEFT;294break;295case 8:296hat = SDL_HAT_LEFTUP;297break;298default:299hat = SDL_HAT_CENTERED;300break;301}302SDL_SendJoystickHat(timestamp, joystick, 0, hat);303}304305if (ctx->last_state[14] != data[14]) {306SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[14] & 0x01) != 0));307SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[14] & 0x02) != 0));308SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[14] & 0x08) != 0));309SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[14] & 0x10) != 0));310SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[14] & 0x40) != 0));311SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[14] & 0x80) != 0));312}313if (ctx->last_state[15] != data[15]) {314SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[15] & 0x08) != 0));315SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[15] & 0x20) != 0));316SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[15] & 0x40) != 0));317}318if (ctx->last_state[16] != data[16]) {319SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[16] & 0x01) != 0));320SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LUNA_MICROPHONE, ((data[16] & 0x02) != 0));321}322323#define READ_STICK_AXIS(offset) \324(data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)data[offset], 0x00, 0xff, SDL_MIN_SINT16, SDL_MAX_SINT16))325{326Sint16 axis = READ_STICK_AXIS(2);327SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);328axis = READ_STICK_AXIS(4);329SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);330axis = READ_STICK_AXIS(6);331SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);332axis = READ_STICK_AXIS(8);333SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);334}335#undef READ_STICK_AXIS336337#define READ_TRIGGER_AXIS(offset) \338(Sint16) HIDAPI_RemapVal((float)((int)(((data[offset] | (data[offset + 1] << 8)) & 0x3ff) - 0x200)), 0x00 - 0x200, 0x3ff - 0x200, SDL_MIN_SINT16, SDL_MAX_SINT16)339{340Sint16 axis = READ_TRIGGER_AXIS(9);341SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);342axis = READ_TRIGGER_AXIS(11);343SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);344}345#undef READ_TRIGGER_AXIS346347SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));348}349350static bool HIDAPI_DriverLuna_UpdateDevice(SDL_HIDAPI_Device *device)351{352SDL_DriverLuna_Context *ctx = (SDL_DriverLuna_Context *)device->context;353SDL_Joystick *joystick = NULL;354Uint8 data[USB_PACKET_LENGTH];355int size = 0;356357if (device->num_joysticks > 0) {358joystick = SDL_GetJoystickFromID(device->joysticks[0]);359} else {360return false;361}362363while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {364#ifdef DEBUG_LUNA_PROTOCOL365HIDAPI_DumpPacket("Amazon Luna packet: size = %d", data, size);366#endif367if (!joystick) {368continue;369}370371switch (size) {372case 10:373HIDAPI_DriverLuna_HandleUSBStatePacket(joystick, ctx, data, size);374break;375default:376HIDAPI_DriverLuna_HandleBluetoothStatePacket(joystick, ctx, data, size);377break;378}379}380381if (size < 0) {382// Read error, device is disconnected383HIDAPI_JoystickDisconnected(device, device->joysticks[0]);384}385return (size >= 0);386}387388static void HIDAPI_DriverLuna_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)389{390}391392static void HIDAPI_DriverLuna_FreeDevice(SDL_HIDAPI_Device *device)393{394}395396SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLuna = {397SDL_HINT_JOYSTICK_HIDAPI_LUNA,398true,399HIDAPI_DriverLuna_RegisterHints,400HIDAPI_DriverLuna_UnregisterHints,401HIDAPI_DriverLuna_IsEnabled,402HIDAPI_DriverLuna_IsSupportedDevice,403HIDAPI_DriverLuna_InitDevice,404HIDAPI_DriverLuna_GetDevicePlayerIndex,405HIDAPI_DriverLuna_SetDevicePlayerIndex,406HIDAPI_DriverLuna_UpdateDevice,407HIDAPI_DriverLuna_OpenJoystick,408HIDAPI_DriverLuna_RumbleJoystick,409HIDAPI_DriverLuna_RumbleJoystickTriggers,410HIDAPI_DriverLuna_GetJoystickCapabilities,411HIDAPI_DriverLuna_SetJoystickLED,412HIDAPI_DriverLuna_SendJoystickEffect,413HIDAPI_DriverLuna_SetJoystickSensorsEnabled,414HIDAPI_DriverLuna_CloseJoystick,415HIDAPI_DriverLuna_FreeDevice,416};417418#endif // SDL_JOYSTICK_HIDAPI_LUNA419420#endif // SDL_JOYSTICK_HIDAPI421422423