Path: blob/master/thirdparty/sdl/haptic/SDL_haptic.c
10278 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#include "SDL_syshaptic.h"23#include "SDL_haptic_c.h"24#include "../joystick/SDL_joystick_c.h" // For SDL_IsJoystickValid25#include "../SDL_hints_c.h"2627typedef struct SDL_Haptic_VIDPID_Naxes {28Uint16 vid;29Uint16 pid;30Uint16 naxes;31} SDL_Haptic_VIDPID_Naxes;3233static void SDL_Haptic_Load_Axes_List(SDL_Haptic_VIDPID_Naxes **entries, int *num_entries)34{35SDL_Haptic_VIDPID_Naxes entry;36const char *spot;37int length = 0;3839spot = SDL_GetHint(SDL_HINT_JOYSTICK_HAPTIC_AXES);40if (!spot)41return;4243while (SDL_sscanf(spot, "0x%hx/0x%hx/%hu%n", &entry.vid, &entry.pid, &entry.naxes, &length) == 3) {44SDL_assert(length > 0);45spot += length;46length = 0;4748if ((*num_entries % 8) == 0) {49int new_max = *num_entries + 8;50SDL_Haptic_VIDPID_Naxes *new_entries =51(SDL_Haptic_VIDPID_Naxes *)SDL_realloc(*entries, new_max * sizeof(**entries));5253// Out of memory, go with what we have already54if (!new_entries)55break;5657*entries = new_entries;58}59(*entries)[(*num_entries)++] = entry;6061if (spot[0] == ',')62spot++;63}64}6566// /* Return -1 if not found */67static int SDL_Haptic_Naxes_List_Index(struct SDL_Haptic_VIDPID_Naxes *entries, int num_entries, Uint16 vid, Uint16 pid)68{69if (!entries)70return -1;7172int i;73for (i = 0; i < num_entries; ++i) {74if (entries[i].vid == vid && entries[i].pid == pid)75return i;76}7778return -1;79}8081// Check if device needs a custom number of naxes82static int SDL_Haptic_Get_Naxes(Uint16 vid, Uint16 pid)83{84int num_entries = 0, index = 0, naxes = -1;85SDL_Haptic_VIDPID_Naxes *naxes_list = NULL;8687SDL_Haptic_Load_Axes_List(&naxes_list, &num_entries);88if (!num_entries || !naxes_list)89return -1;9091// Perform "wildcard" pass92index = SDL_Haptic_Naxes_List_Index(naxes_list, num_entries, 0xffff, 0xffff);93if (index >= 0)94naxes = naxes_list[index].naxes;9596index = SDL_Haptic_Naxes_List_Index(naxes_list, num_entries, vid, pid);97if (index >= 0)98naxes = naxes_list[index].naxes;99100SDL_free(naxes_list);101return naxes;102}103104static SDL_Haptic *SDL_haptics = NULL;105106#define CHECK_HAPTIC_MAGIC(haptic, result) \107if (!SDL_ObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC)) { \108SDL_InvalidParamError("haptic"); \109return result; \110}111112bool SDL_InitHaptics(void)113{114return SDL_SYS_HapticInit();115}116117static bool SDL_GetHapticIndex(SDL_HapticID instance_id, int *driver_index)118{119int num_haptics, device_index;120121if (instance_id > 0) {122num_haptics = SDL_SYS_NumHaptics();123for (device_index = 0; device_index < num_haptics; ++device_index) {124SDL_HapticID haptic_id = SDL_SYS_HapticInstanceID(device_index);125if (haptic_id == instance_id) {126*driver_index = device_index;127return true;128}129}130}131132SDL_SetError("Haptic device %" SDL_PRIu32 " not found", instance_id);133return false;134}135136SDL_HapticID *SDL_GetHaptics(int *count)137{138int device_index;139int haptic_index = 0, num_haptics = 0;140SDL_HapticID *haptics;141142num_haptics = SDL_SYS_NumHaptics();143144haptics = (SDL_HapticID *)SDL_malloc((num_haptics + 1) * sizeof(*haptics));145if (haptics) {146if (count) {147*count = num_haptics;148}149150for (device_index = 0; device_index < num_haptics; ++device_index) {151haptics[haptic_index] = SDL_SYS_HapticInstanceID(device_index);152SDL_assert(haptics[haptic_index] > 0);153++haptic_index;154}155haptics[haptic_index] = 0;156} else {157if (count) {158*count = 0;159}160}161162return haptics;163}164165const char *SDL_GetHapticNameForID(SDL_HapticID instance_id)166{167int device_index;168const char *name = NULL;169170if (SDL_GetHapticIndex(instance_id, &device_index)) {171name = SDL_GetPersistentString(SDL_SYS_HapticName(device_index));172}173return name;174}175176SDL_Haptic *SDL_OpenHaptic(SDL_HapticID instance_id)177{178SDL_Haptic *haptic;179SDL_Haptic *hapticlist;180const char *name;181int device_index = 0;182183if (!SDL_GetHapticIndex(instance_id, &device_index)) {184return NULL;185}186187hapticlist = SDL_haptics;188/* If the haptic device is already open, return it189* it is important that we have a single haptic device for each instance id190*/191while (hapticlist) {192if (instance_id == hapticlist->instance_id) {193haptic = hapticlist;194++haptic->ref_count;195return haptic;196}197hapticlist = hapticlist->next;198}199200// Create the haptic device201haptic = (SDL_Haptic *)SDL_calloc(1, sizeof(*haptic));202if (!haptic) {203return NULL;204}205206// Initialize the haptic device207SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, true);208haptic->instance_id = instance_id;209haptic->rumble_id = -1;210if (!SDL_SYS_HapticOpen(haptic)) {211SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, false);212SDL_free(haptic);213return NULL;214}215216if (!haptic->name) {217name = SDL_SYS_HapticName(device_index);218if (name) {219haptic->name = SDL_strdup(name);220}221}222223// Add haptic to list224++haptic->ref_count;225// Link the haptic in the list226haptic->next = SDL_haptics;227SDL_haptics = haptic;228229// Disable autocenter and set gain to max.230if (haptic->supported & SDL_HAPTIC_GAIN) {231SDL_SetHapticGain(haptic, 100);232}233if (haptic->supported & SDL_HAPTIC_AUTOCENTER) {234SDL_SetHapticAutocenter(haptic, 0);235}236237return haptic;238}239240SDL_Haptic *SDL_GetHapticFromID(SDL_HapticID instance_id)241{242SDL_Haptic *haptic;243244for (haptic = SDL_haptics; haptic; haptic = haptic->next) {245if (instance_id == haptic->instance_id) {246break;247}248}249return haptic;250}251252SDL_HapticID SDL_GetHapticID(SDL_Haptic *haptic)253{254CHECK_HAPTIC_MAGIC(haptic, 0);255256return haptic->instance_id;257}258259const char *SDL_GetHapticName(SDL_Haptic *haptic)260{261CHECK_HAPTIC_MAGIC(haptic, NULL);262263return SDL_GetPersistentString(haptic->name);264}265266bool SDL_IsMouseHaptic(void)267{268if (SDL_SYS_HapticMouse() < 0) {269return false;270}271return true;272}273274SDL_Haptic *SDL_OpenHapticFromMouse(void)275{276int device_index;277278device_index = SDL_SYS_HapticMouse();279280if (device_index < 0) {281SDL_SetError("Haptic: Mouse isn't a haptic device.");282return NULL;283}284285return SDL_OpenHaptic(device_index);286}287288bool SDL_IsJoystickHaptic(SDL_Joystick *joystick)289{290bool result = false;291292SDL_LockJoysticks();293{294// Must be a valid joystick295if (SDL_IsJoystickValid(joystick) &&296!SDL_IsGamepad(SDL_GetJoystickID(joystick))) {297result = SDL_SYS_JoystickIsHaptic(joystick);298}299}300SDL_UnlockJoysticks();301302return result;303}304305SDL_Haptic *SDL_OpenHapticFromJoystick(SDL_Joystick *joystick)306{307SDL_Haptic *haptic;308SDL_Haptic *hapticlist;309310SDL_LockJoysticks();311{312// Must be a valid joystick313if (!SDL_IsJoystickValid(joystick)) {314SDL_SetError("Haptic: Joystick isn't valid.");315SDL_UnlockJoysticks();316return NULL;317}318319// Joystick must be haptic320if (SDL_IsGamepad(SDL_GetJoystickID(joystick)) ||321!SDL_SYS_JoystickIsHaptic(joystick)) {322SDL_SetError("Haptic: Joystick isn't a haptic device.");323SDL_UnlockJoysticks();324return NULL;325}326327hapticlist = SDL_haptics;328// Check to see if joystick's haptic is already open329while (hapticlist) {330if (SDL_SYS_JoystickSameHaptic(hapticlist, joystick)) {331haptic = hapticlist;332++haptic->ref_count;333SDL_UnlockJoysticks();334return haptic;335}336hapticlist = hapticlist->next;337}338339// Create the haptic device340haptic = (SDL_Haptic *)SDL_calloc(1, sizeof(*haptic));341if (!haptic) {342SDL_UnlockJoysticks();343return NULL;344}345346/* Initialize the haptic device347* This function should fill in the instance ID and name.348*/349SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, true);350haptic->rumble_id = -1;351if (!SDL_SYS_HapticOpenFromJoystick(haptic, joystick)) {352SDL_SetError("Haptic: SDL_SYS_HapticOpenFromJoystick failed.");353SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, false);354SDL_free(haptic);355SDL_UnlockJoysticks();356return NULL;357}358SDL_assert(haptic->instance_id != 0);359}360SDL_UnlockJoysticks();361362// Check if custom number of haptic axes was defined363Uint16 vid = SDL_GetJoystickVendor(joystick);364Uint16 pid = SDL_GetJoystickProduct(joystick);365int general_axes = SDL_GetNumJoystickAxes(joystick);366367int naxes = SDL_Haptic_Get_Naxes(vid, pid);368if (naxes > 0)369haptic->naxes = naxes;370371// Limit to the actual number of axes found on the device372if (general_axes >= 0 && naxes > general_axes)373haptic->naxes = general_axes;374375// Add haptic to list376++haptic->ref_count;377// Link the haptic in the list378haptic->next = SDL_haptics;379SDL_haptics = haptic;380381return haptic;382}383384void SDL_CloseHaptic(SDL_Haptic *haptic)385{386int i;387SDL_Haptic *hapticlist;388SDL_Haptic *hapticlistprev;389390CHECK_HAPTIC_MAGIC(haptic,);391392// Check if it's still in use393if (--haptic->ref_count > 0) {394return;395}396397// Close it, properly removing effects if needed398for (i = 0; i < haptic->neffects; i++) {399if (haptic->effects[i].hweffect != NULL) {400SDL_DestroyHapticEffect(haptic, i);401}402}403SDL_SYS_HapticClose(haptic);404SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, false);405406// Remove from the list407hapticlist = SDL_haptics;408hapticlistprev = NULL;409while (hapticlist) {410if (haptic == hapticlist) {411if (hapticlistprev) {412// unlink this entry413hapticlistprev->next = hapticlist->next;414} else {415SDL_haptics = haptic->next;416}417418break;419}420hapticlistprev = hapticlist;421hapticlist = hapticlist->next;422}423424// Free the data associated with this device425SDL_free(haptic->name);426SDL_free(haptic);427}428429void SDL_QuitHaptics(void)430{431while (SDL_haptics) {432SDL_CloseHaptic(SDL_haptics);433}434435SDL_SYS_HapticQuit();436}437438int SDL_GetMaxHapticEffects(SDL_Haptic *haptic)439{440CHECK_HAPTIC_MAGIC(haptic, -1);441442return haptic->neffects;443}444445int SDL_GetMaxHapticEffectsPlaying(SDL_Haptic *haptic)446{447CHECK_HAPTIC_MAGIC(haptic, -1);448449return haptic->nplaying;450}451452Uint32 SDL_GetHapticFeatures(SDL_Haptic *haptic)453{454CHECK_HAPTIC_MAGIC(haptic, 0);455456return haptic->supported;457}458459int SDL_GetNumHapticAxes(SDL_Haptic *haptic)460{461CHECK_HAPTIC_MAGIC(haptic, -1);462463return haptic->naxes;464}465466bool SDL_HapticEffectSupported(SDL_Haptic *haptic, const SDL_HapticEffect *effect)467{468CHECK_HAPTIC_MAGIC(haptic, false);469470if (!effect) {471return false;472}473474if ((haptic->supported & effect->type) != 0) {475return true;476}477return false;478}479480int SDL_CreateHapticEffect(SDL_Haptic *haptic, const SDL_HapticEffect *effect)481{482int i;483484CHECK_HAPTIC_MAGIC(haptic, -1);485486if (!effect) {487SDL_InvalidParamError("effect");488return -1;489}490491// Check to see if effect is supported492if (SDL_HapticEffectSupported(haptic, effect) == false) {493SDL_SetError("Haptic: Effect not supported by haptic device.");494return -1;495}496497// See if there's a free slot498for (i = 0; i < haptic->neffects; i++) {499if (haptic->effects[i].hweffect == NULL) {500501// Now let the backend create the real effect502if (!SDL_SYS_HapticNewEffect(haptic, &haptic->effects[i], effect)) {503return -1; // Backend failed to create effect504}505506SDL_memcpy(&haptic->effects[i].effect, effect,507sizeof(SDL_HapticEffect));508return i;509}510}511512SDL_SetError("Haptic: Device has no free space left.");513return -1;514}515516static bool ValidEffect(SDL_Haptic *haptic, int effect)517{518if ((effect < 0) || (effect >= haptic->neffects)) {519SDL_SetError("Haptic: Invalid effect identifier.");520return false;521}522return true;523}524525bool SDL_UpdateHapticEffect(SDL_Haptic *haptic, int effect, const SDL_HapticEffect *data)526{527CHECK_HAPTIC_MAGIC(haptic, false);528529if (!ValidEffect(haptic, effect)) {530return false;531}532533if (!data) {534return SDL_InvalidParamError("data");535}536537// Can't change type dynamically.538if (data->type != haptic->effects[effect].effect.type) {539return SDL_SetError("Haptic: Updating effect type is illegal.");540}541542// Updates the effect543if (!SDL_SYS_HapticUpdateEffect(haptic, &haptic->effects[effect], data)) {544return false;545}546547SDL_memcpy(&haptic->effects[effect].effect, data,548sizeof(SDL_HapticEffect));549return true;550}551552bool SDL_RunHapticEffect(SDL_Haptic *haptic, int effect, Uint32 iterations)553{554CHECK_HAPTIC_MAGIC(haptic, false);555556if (!ValidEffect(haptic, effect)) {557return false;558}559560// Run the effect561if (!SDL_SYS_HapticRunEffect(haptic, &haptic->effects[effect], iterations)) {562return false;563}564565return true;566}567568bool SDL_StopHapticEffect(SDL_Haptic *haptic, int effect)569{570CHECK_HAPTIC_MAGIC(haptic, false);571572if (!ValidEffect(haptic, effect)) {573return false;574}575576// Stop the effect577if (!SDL_SYS_HapticStopEffect(haptic, &haptic->effects[effect])) {578return false;579}580581return true;582}583584void SDL_DestroyHapticEffect(SDL_Haptic *haptic, int effect)585{586CHECK_HAPTIC_MAGIC(haptic,);587588if (!ValidEffect(haptic, effect)) {589return;590}591592// Not allocated593if (haptic->effects[effect].hweffect == NULL) {594return;595}596597SDL_SYS_HapticDestroyEffect(haptic, &haptic->effects[effect]);598}599600bool SDL_GetHapticEffectStatus(SDL_Haptic *haptic, int effect)601{602CHECK_HAPTIC_MAGIC(haptic, false);603604if (!ValidEffect(haptic, effect)) {605return false;606}607608if (!(haptic->supported & SDL_HAPTIC_STATUS)) {609return SDL_SetError("Haptic: Device does not support status queries.");610}611612SDL_ClearError();613614return (SDL_SYS_HapticGetEffectStatus(haptic, &haptic->effects[effect]) > 0);615}616617bool SDL_SetHapticGain(SDL_Haptic *haptic, int gain)618{619const char *env;620int real_gain, max_gain;621622CHECK_HAPTIC_MAGIC(haptic, false);623624if (!(haptic->supported & SDL_HAPTIC_GAIN)) {625return SDL_SetError("Haptic: Device does not support setting gain.");626}627628if ((gain < 0) || (gain > 100)) {629return SDL_SetError("Haptic: Gain must be between 0 and 100.");630}631632// The user can use an environment variable to override the max gain.633env = SDL_getenv("SDL_HAPTIC_GAIN_MAX");634if (env) {635max_gain = SDL_atoi(env);636637// Check for sanity.638if (max_gain < 0) {639max_gain = 0;640} else if (max_gain > 100) {641max_gain = 100;642}643644// We'll scale it linearly with SDL_HAPTIC_GAIN_MAX645real_gain = (gain * max_gain) / 100;646} else {647real_gain = gain;648}649650return SDL_SYS_HapticSetGain(haptic, real_gain);651}652653bool SDL_SetHapticAutocenter(SDL_Haptic *haptic, int autocenter)654{655CHECK_HAPTIC_MAGIC(haptic, false);656657if (!(haptic->supported & SDL_HAPTIC_AUTOCENTER)) {658return SDL_SetError("Haptic: Device does not support setting autocenter.");659}660661if ((autocenter < 0) || (autocenter > 100)) {662return SDL_SetError("Haptic: Autocenter must be between 0 and 100.");663}664665return SDL_SYS_HapticSetAutocenter(haptic, autocenter);666}667668bool SDL_PauseHaptic(SDL_Haptic *haptic)669{670CHECK_HAPTIC_MAGIC(haptic, false);671672if (!(haptic->supported & SDL_HAPTIC_PAUSE)) {673return SDL_SetError("Haptic: Device does not support setting pausing.");674}675676return SDL_SYS_HapticPause(haptic);677}678679bool SDL_ResumeHaptic(SDL_Haptic *haptic)680{681CHECK_HAPTIC_MAGIC(haptic, false);682683if (!(haptic->supported & SDL_HAPTIC_PAUSE)) {684return true; // Not going to be paused, so we pretend it's unpaused.685}686687return SDL_SYS_HapticResume(haptic);688}689690bool SDL_StopHapticEffects(SDL_Haptic *haptic)691{692CHECK_HAPTIC_MAGIC(haptic, false);693694return SDL_SYS_HapticStopAll(haptic);695}696697bool SDL_HapticRumbleSupported(SDL_Haptic *haptic)698{699CHECK_HAPTIC_MAGIC(haptic, false);700701// Most things can use SINE, but XInput only has LEFTRIGHT.702return (haptic->supported & (SDL_HAPTIC_SINE | SDL_HAPTIC_LEFTRIGHT)) != 0;703}704705bool SDL_InitHapticRumble(SDL_Haptic *haptic)706{707SDL_HapticEffect *efx = &haptic->rumble_effect;708709CHECK_HAPTIC_MAGIC(haptic, false);710711// Already allocated.712if (haptic->rumble_id >= 0) {713return true;714}715716SDL_zerop(efx);717if (haptic->supported & SDL_HAPTIC_SINE) {718efx->type = SDL_HAPTIC_SINE;719efx->periodic.direction.type = SDL_HAPTIC_CARTESIAN;720efx->periodic.period = 1000;721efx->periodic.magnitude = 0x4000;722efx->periodic.length = 5000;723efx->periodic.attack_length = 0;724efx->periodic.fade_length = 0;725} else if (haptic->supported & SDL_HAPTIC_LEFTRIGHT) { // XInput?726efx->type = SDL_HAPTIC_LEFTRIGHT;727efx->leftright.length = 5000;728efx->leftright.large_magnitude = 0x4000;729efx->leftright.small_magnitude = 0x4000;730} else {731return SDL_SetError("Device doesn't support rumble");732}733734haptic->rumble_id = SDL_CreateHapticEffect(haptic, &haptic->rumble_effect);735if (haptic->rumble_id >= 0) {736return true;737}738return false;739}740741bool SDL_PlayHapticRumble(SDL_Haptic *haptic, float strength, Uint32 length)742{743SDL_HapticEffect *efx;744Sint16 magnitude;745746CHECK_HAPTIC_MAGIC(haptic, false);747748if (haptic->rumble_id < 0) {749return SDL_SetError("Haptic: Rumble effect not initialized on haptic device");750}751752// Clamp strength.753if (strength > 1.0f) {754strength = 1.0f;755} else if (strength < 0.0f) {756strength = 0.0f;757}758magnitude = (Sint16)(32767.0f * strength);759760efx = &haptic->rumble_effect;761if (efx->type == SDL_HAPTIC_SINE) {762efx->periodic.magnitude = magnitude;763efx->periodic.length = length;764} else if (efx->type == SDL_HAPTIC_LEFTRIGHT) {765efx->leftright.small_magnitude = efx->leftright.large_magnitude = magnitude;766efx->leftright.length = length;767} else {768SDL_assert(!"This should have been caught elsewhere");769}770771if (!SDL_UpdateHapticEffect(haptic, haptic->rumble_id, &haptic->rumble_effect)) {772return false;773}774775return SDL_RunHapticEffect(haptic, haptic->rumble_id, 1);776}777778bool SDL_StopHapticRumble(SDL_Haptic *haptic)779{780CHECK_HAPTIC_MAGIC(haptic, false);781782if (haptic->rumble_id < 0) {783return SDL_SetError("Haptic: Rumble effect not initialized on haptic device");784}785786return SDL_StopHapticEffect(haptic, haptic->rumble_id);787}788789790