#include "SDL_internal.h"
#include "SDL_syshaptic.h"
#include "SDL_haptic_c.h"
#include "../joystick/SDL_joystick_c.h"
#include "../SDL_hints_c.h"
typedef struct SDL_Haptic_VIDPID_Naxes {
Uint16 vid;
Uint16 pid;
Uint16 naxes;
} SDL_Haptic_VIDPID_Naxes;
static void SDL_Haptic_Load_Axes_List(SDL_Haptic_VIDPID_Naxes **entries, int *num_entries)
{
SDL_Haptic_VIDPID_Naxes entry;
const char *spot;
int length = 0;
spot = SDL_GetHint(SDL_HINT_JOYSTICK_HAPTIC_AXES);
if (!spot)
return;
while (SDL_sscanf(spot, "0x%hx/0x%hx/%hu%n", &entry.vid, &entry.pid, &entry.naxes, &length) == 3) {
SDL_assert(length > 0);
spot += length;
length = 0;
if ((*num_entries % 8) == 0) {
int new_max = *num_entries + 8;
SDL_Haptic_VIDPID_Naxes *new_entries =
(SDL_Haptic_VIDPID_Naxes *)SDL_realloc(*entries, new_max * sizeof(**entries));
if (!new_entries)
break;
*entries = new_entries;
}
(*entries)[(*num_entries)++] = entry;
if (spot[0] == ',')
spot++;
}
}
static int SDL_Haptic_Naxes_List_Index(struct SDL_Haptic_VIDPID_Naxes *entries, int num_entries, Uint16 vid, Uint16 pid)
{
if (!entries)
return -1;
int i;
for (i = 0; i < num_entries; ++i) {
if (entries[i].vid == vid && entries[i].pid == pid)
return i;
}
return -1;
}
static int SDL_Haptic_Get_Naxes(Uint16 vid, Uint16 pid)
{
int num_entries = 0, index = 0, naxes = -1;
SDL_Haptic_VIDPID_Naxes *naxes_list = NULL;
SDL_Haptic_Load_Axes_List(&naxes_list, &num_entries);
if (!num_entries || !naxes_list)
return -1;
index = SDL_Haptic_Naxes_List_Index(naxes_list, num_entries, 0xffff, 0xffff);
if (index >= 0)
naxes = naxes_list[index].naxes;
index = SDL_Haptic_Naxes_List_Index(naxes_list, num_entries, vid, pid);
if (index >= 0)
naxes = naxes_list[index].naxes;
SDL_free(naxes_list);
return naxes;
}
static SDL_Haptic *SDL_haptics = NULL;
#define CHECK_HAPTIC_MAGIC(haptic, result) \
if (!SDL_ObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC)) { \
SDL_InvalidParamError("haptic"); \
return result; \
}
bool SDL_InitHaptics(void)
{
return SDL_SYS_HapticInit();
}
static bool SDL_GetHapticIndex(SDL_HapticID instance_id, int *driver_index)
{
int num_haptics, device_index;
if (instance_id > 0) {
num_haptics = SDL_SYS_NumHaptics();
for (device_index = 0; device_index < num_haptics; ++device_index) {
SDL_HapticID haptic_id = SDL_SYS_HapticInstanceID(device_index);
if (haptic_id == instance_id) {
*driver_index = device_index;
return true;
}
}
}
SDL_SetError("Haptic device %" SDL_PRIu32 " not found", instance_id);
return false;
}
SDL_HapticID *SDL_GetHaptics(int *count)
{
int device_index;
int haptic_index = 0, num_haptics = 0;
SDL_HapticID *haptics;
num_haptics = SDL_SYS_NumHaptics();
haptics = (SDL_HapticID *)SDL_malloc((num_haptics + 1) * sizeof(*haptics));
if (haptics) {
if (count) {
*count = num_haptics;
}
for (device_index = 0; device_index < num_haptics; ++device_index) {
haptics[haptic_index] = SDL_SYS_HapticInstanceID(device_index);
SDL_assert(haptics[haptic_index] > 0);
++haptic_index;
}
haptics[haptic_index] = 0;
} else {
if (count) {
*count = 0;
}
}
return haptics;
}
const char *SDL_GetHapticNameForID(SDL_HapticID instance_id)
{
int device_index;
const char *name = NULL;
if (SDL_GetHapticIndex(instance_id, &device_index)) {
name = SDL_GetPersistentString(SDL_SYS_HapticName(device_index));
}
return name;
}
SDL_Haptic *SDL_OpenHaptic(SDL_HapticID instance_id)
{
SDL_Haptic *haptic;
SDL_Haptic *hapticlist;
const char *name;
int device_index = 0;
if (!SDL_GetHapticIndex(instance_id, &device_index)) {
return NULL;
}
hapticlist = SDL_haptics;
while (hapticlist) {
if (instance_id == hapticlist->instance_id) {
haptic = hapticlist;
++haptic->ref_count;
return haptic;
}
hapticlist = hapticlist->next;
}
haptic = (SDL_Haptic *)SDL_calloc(1, sizeof(*haptic));
if (!haptic) {
return NULL;
}
SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, true);
haptic->instance_id = instance_id;
haptic->rumble_id = -1;
if (!SDL_SYS_HapticOpen(haptic)) {
SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, false);
SDL_free(haptic);
return NULL;
}
if (!haptic->name) {
name = SDL_SYS_HapticName(device_index);
if (name) {
haptic->name = SDL_strdup(name);
}
}
++haptic->ref_count;
haptic->next = SDL_haptics;
SDL_haptics = haptic;
if (haptic->supported & SDL_HAPTIC_GAIN) {
SDL_SetHapticGain(haptic, 100);
}
if (haptic->supported & SDL_HAPTIC_AUTOCENTER) {
SDL_SetHapticAutocenter(haptic, 0);
}
return haptic;
}
SDL_Haptic *SDL_GetHapticFromID(SDL_HapticID instance_id)
{
SDL_Haptic *haptic;
for (haptic = SDL_haptics; haptic; haptic = haptic->next) {
if (instance_id == haptic->instance_id) {
break;
}
}
return haptic;
}
SDL_HapticID SDL_GetHapticID(SDL_Haptic *haptic)
{
CHECK_HAPTIC_MAGIC(haptic, 0);
return haptic->instance_id;
}
const char *SDL_GetHapticName(SDL_Haptic *haptic)
{
CHECK_HAPTIC_MAGIC(haptic, NULL);
return SDL_GetPersistentString(haptic->name);
}
bool SDL_IsMouseHaptic(void)
{
if (SDL_SYS_HapticMouse() < 0) {
return false;
}
return true;
}
SDL_Haptic *SDL_OpenHapticFromMouse(void)
{
int device_index;
device_index = SDL_SYS_HapticMouse();
if (device_index < 0) {
SDL_SetError("Haptic: Mouse isn't a haptic device.");
return NULL;
}
return SDL_OpenHaptic(device_index);
}
bool SDL_IsJoystickHaptic(SDL_Joystick *joystick)
{
bool result = false;
SDL_LockJoysticks();
{
if (SDL_IsJoystickValid(joystick) &&
!SDL_IsGamepad(SDL_GetJoystickID(joystick))) {
result = SDL_SYS_JoystickIsHaptic(joystick);
}
}
SDL_UnlockJoysticks();
return result;
}
SDL_Haptic *SDL_OpenHapticFromJoystick(SDL_Joystick *joystick)
{
SDL_Haptic *haptic;
SDL_Haptic *hapticlist;
SDL_LockJoysticks();
{
if (!SDL_IsJoystickValid(joystick)) {
SDL_SetError("Haptic: Joystick isn't valid.");
SDL_UnlockJoysticks();
return NULL;
}
if (SDL_IsGamepad(SDL_GetJoystickID(joystick)) ||
!SDL_SYS_JoystickIsHaptic(joystick)) {
SDL_SetError("Haptic: Joystick isn't a haptic device.");
SDL_UnlockJoysticks();
return NULL;
}
hapticlist = SDL_haptics;
while (hapticlist) {
if (SDL_SYS_JoystickSameHaptic(hapticlist, joystick)) {
haptic = hapticlist;
++haptic->ref_count;
SDL_UnlockJoysticks();
return haptic;
}
hapticlist = hapticlist->next;
}
haptic = (SDL_Haptic *)SDL_calloc(1, sizeof(*haptic));
if (!haptic) {
SDL_UnlockJoysticks();
return NULL;
}
SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, true);
haptic->rumble_id = -1;
if (!SDL_SYS_HapticOpenFromJoystick(haptic, joystick)) {
SDL_SetError("Haptic: SDL_SYS_HapticOpenFromJoystick failed.");
SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, false);
SDL_free(haptic);
SDL_UnlockJoysticks();
return NULL;
}
SDL_assert(haptic->instance_id != 0);
}
SDL_UnlockJoysticks();
Uint16 vid = SDL_GetJoystickVendor(joystick);
Uint16 pid = SDL_GetJoystickProduct(joystick);
int general_axes = SDL_GetNumJoystickAxes(joystick);
int naxes = SDL_Haptic_Get_Naxes(vid, pid);
if (naxes > 0)
haptic->naxes = naxes;
if (general_axes >= 0 && naxes > general_axes)
haptic->naxes = general_axes;
++haptic->ref_count;
haptic->next = SDL_haptics;
SDL_haptics = haptic;
return haptic;
}
void SDL_CloseHaptic(SDL_Haptic *haptic)
{
int i;
SDL_Haptic *hapticlist;
SDL_Haptic *hapticlistprev;
CHECK_HAPTIC_MAGIC(haptic,);
if (--haptic->ref_count > 0) {
return;
}
for (i = 0; i < haptic->neffects; i++) {
if (haptic->effects[i].hweffect != NULL) {
SDL_DestroyHapticEffect(haptic, i);
}
}
SDL_SYS_HapticClose(haptic);
SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, false);
hapticlist = SDL_haptics;
hapticlistprev = NULL;
while (hapticlist) {
if (haptic == hapticlist) {
if (hapticlistprev) {
hapticlistprev->next = hapticlist->next;
} else {
SDL_haptics = haptic->next;
}
break;
}
hapticlistprev = hapticlist;
hapticlist = hapticlist->next;
}
SDL_free(haptic->name);
SDL_free(haptic);
}
void SDL_QuitHaptics(void)
{
while (SDL_haptics) {
SDL_CloseHaptic(SDL_haptics);
}
SDL_SYS_HapticQuit();
}
int SDL_GetMaxHapticEffects(SDL_Haptic *haptic)
{
CHECK_HAPTIC_MAGIC(haptic, -1);
return haptic->neffects;
}
int SDL_GetMaxHapticEffectsPlaying(SDL_Haptic *haptic)
{
CHECK_HAPTIC_MAGIC(haptic, -1);
return haptic->nplaying;
}
Uint32 SDL_GetHapticFeatures(SDL_Haptic *haptic)
{
CHECK_HAPTIC_MAGIC(haptic, 0);
return haptic->supported;
}
int SDL_GetNumHapticAxes(SDL_Haptic *haptic)
{
CHECK_HAPTIC_MAGIC(haptic, -1);
return haptic->naxes;
}
bool SDL_HapticEffectSupported(SDL_Haptic *haptic, const SDL_HapticEffect *effect)
{
CHECK_HAPTIC_MAGIC(haptic, false);
if (!effect) {
return false;
}
if ((haptic->supported & effect->type) != 0) {
return true;
}
return false;
}
int SDL_CreateHapticEffect(SDL_Haptic *haptic, const SDL_HapticEffect *effect)
{
int i;
CHECK_HAPTIC_MAGIC(haptic, -1);
if (!effect) {
SDL_InvalidParamError("effect");
return -1;
}
if (SDL_HapticEffectSupported(haptic, effect) == false) {
SDL_SetError("Haptic: Effect not supported by haptic device.");
return -1;
}
for (i = 0; i < haptic->neffects; i++) {
if (haptic->effects[i].hweffect == NULL) {
if (!SDL_SYS_HapticNewEffect(haptic, &haptic->effects[i], effect)) {
return -1;
}
SDL_memcpy(&haptic->effects[i].effect, effect,
sizeof(SDL_HapticEffect));
return i;
}
}
SDL_SetError("Haptic: Device has no free space left.");
return -1;
}
static bool ValidEffect(SDL_Haptic *haptic, int effect)
{
if ((effect < 0) || (effect >= haptic->neffects)) {
SDL_SetError("Haptic: Invalid effect identifier.");
return false;
}
return true;
}
bool SDL_UpdateHapticEffect(SDL_Haptic *haptic, int effect, const SDL_HapticEffect *data)
{
CHECK_HAPTIC_MAGIC(haptic, false);
if (!ValidEffect(haptic, effect)) {
return false;
}
if (!data) {
return SDL_InvalidParamError("data");
}
if (data->type != haptic->effects[effect].effect.type) {
return SDL_SetError("Haptic: Updating effect type is illegal.");
}
if (!SDL_SYS_HapticUpdateEffect(haptic, &haptic->effects[effect], data)) {
return false;
}
SDL_memcpy(&haptic->effects[effect].effect, data,
sizeof(SDL_HapticEffect));
return true;
}
bool SDL_RunHapticEffect(SDL_Haptic *haptic, int effect, Uint32 iterations)
{
CHECK_HAPTIC_MAGIC(haptic, false);
if (!ValidEffect(haptic, effect)) {
return false;
}
if (!SDL_SYS_HapticRunEffect(haptic, &haptic->effects[effect], iterations)) {
return false;
}
return true;
}
bool SDL_StopHapticEffect(SDL_Haptic *haptic, int effect)
{
CHECK_HAPTIC_MAGIC(haptic, false);
if (!ValidEffect(haptic, effect)) {
return false;
}
if (!SDL_SYS_HapticStopEffect(haptic, &haptic->effects[effect])) {
return false;
}
return true;
}
void SDL_DestroyHapticEffect(SDL_Haptic *haptic, int effect)
{
CHECK_HAPTIC_MAGIC(haptic,);
if (!ValidEffect(haptic, effect)) {
return;
}
if (haptic->effects[effect].hweffect == NULL) {
return;
}
SDL_SYS_HapticDestroyEffect(haptic, &haptic->effects[effect]);
}
bool SDL_GetHapticEffectStatus(SDL_Haptic *haptic, int effect)
{
CHECK_HAPTIC_MAGIC(haptic, false);
if (!ValidEffect(haptic, effect)) {
return false;
}
if (!(haptic->supported & SDL_HAPTIC_STATUS)) {
return SDL_SetError("Haptic: Device does not support status queries.");
}
SDL_ClearError();
return (SDL_SYS_HapticGetEffectStatus(haptic, &haptic->effects[effect]) > 0);
}
bool SDL_SetHapticGain(SDL_Haptic *haptic, int gain)
{
const char *env;
int real_gain, max_gain;
CHECK_HAPTIC_MAGIC(haptic, false);
if (!(haptic->supported & SDL_HAPTIC_GAIN)) {
return SDL_SetError("Haptic: Device does not support setting gain.");
}
if ((gain < 0) || (gain > 100)) {
return SDL_SetError("Haptic: Gain must be between 0 and 100.");
}
env = SDL_getenv("SDL_HAPTIC_GAIN_MAX");
if (env) {
max_gain = SDL_atoi(env);
if (max_gain < 0) {
max_gain = 0;
} else if (max_gain > 100) {
max_gain = 100;
}
real_gain = (gain * max_gain) / 100;
} else {
real_gain = gain;
}
return SDL_SYS_HapticSetGain(haptic, real_gain);
}
bool SDL_SetHapticAutocenter(SDL_Haptic *haptic, int autocenter)
{
CHECK_HAPTIC_MAGIC(haptic, false);
if (!(haptic->supported & SDL_HAPTIC_AUTOCENTER)) {
return SDL_SetError("Haptic: Device does not support setting autocenter.");
}
if ((autocenter < 0) || (autocenter > 100)) {
return SDL_SetError("Haptic: Autocenter must be between 0 and 100.");
}
return SDL_SYS_HapticSetAutocenter(haptic, autocenter);
}
bool SDL_PauseHaptic(SDL_Haptic *haptic)
{
CHECK_HAPTIC_MAGIC(haptic, false);
if (!(haptic->supported & SDL_HAPTIC_PAUSE)) {
return SDL_SetError("Haptic: Device does not support setting pausing.");
}
return SDL_SYS_HapticPause(haptic);
}
bool SDL_ResumeHaptic(SDL_Haptic *haptic)
{
CHECK_HAPTIC_MAGIC(haptic, false);
if (!(haptic->supported & SDL_HAPTIC_PAUSE)) {
return true;
}
return SDL_SYS_HapticResume(haptic);
}
bool SDL_StopHapticEffects(SDL_Haptic *haptic)
{
CHECK_HAPTIC_MAGIC(haptic, false);
return SDL_SYS_HapticStopAll(haptic);
}
bool SDL_HapticRumbleSupported(SDL_Haptic *haptic)
{
CHECK_HAPTIC_MAGIC(haptic, false);
return (haptic->supported & (SDL_HAPTIC_SINE | SDL_HAPTIC_LEFTRIGHT)) != 0;
}
bool SDL_InitHapticRumble(SDL_Haptic *haptic)
{
SDL_HapticEffect *efx = &haptic->rumble_effect;
CHECK_HAPTIC_MAGIC(haptic, false);
if (haptic->rumble_id >= 0) {
return true;
}
SDL_zerop(efx);
if (haptic->supported & SDL_HAPTIC_SINE) {
efx->type = SDL_HAPTIC_SINE;
efx->periodic.direction.type = SDL_HAPTIC_CARTESIAN;
efx->periodic.period = 1000;
efx->periodic.magnitude = 0x4000;
efx->periodic.length = 5000;
efx->periodic.attack_length = 0;
efx->periodic.fade_length = 0;
} else if (haptic->supported & SDL_HAPTIC_LEFTRIGHT) {
efx->type = SDL_HAPTIC_LEFTRIGHT;
efx->leftright.length = 5000;
efx->leftright.large_magnitude = 0x4000;
efx->leftright.small_magnitude = 0x4000;
} else {
return SDL_SetError("Device doesn't support rumble");
}
haptic->rumble_id = SDL_CreateHapticEffect(haptic, &haptic->rumble_effect);
if (haptic->rumble_id >= 0) {
return true;
}
return false;
}
bool SDL_PlayHapticRumble(SDL_Haptic *haptic, float strength, Uint32 length)
{
SDL_HapticEffect *efx;
Sint16 magnitude;
CHECK_HAPTIC_MAGIC(haptic, false);
if (haptic->rumble_id < 0) {
return SDL_SetError("Haptic: Rumble effect not initialized on haptic device");
}
if (strength > 1.0f) {
strength = 1.0f;
} else if (strength < 0.0f) {
strength = 0.0f;
}
magnitude = (Sint16)(32767.0f * strength);
efx = &haptic->rumble_effect;
if (efx->type == SDL_HAPTIC_SINE) {
efx->periodic.magnitude = magnitude;
efx->periodic.length = length;
} else if (efx->type == SDL_HAPTIC_LEFTRIGHT) {
efx->leftright.small_magnitude = efx->leftright.large_magnitude = magnitude;
efx->leftright.length = length;
} else {
SDL_assert(!"This should have been caught elsewhere");
}
if (!SDL_UpdateHapticEffect(haptic, haptic->rumble_id, &haptic->rumble_effect)) {
return false;
}
return SDL_RunHapticEffect(haptic, haptic->rumble_id, 1);
}
bool SDL_StopHapticRumble(SDL_Haptic *haptic)
{
CHECK_HAPTIC_MAGIC(haptic, false);
if (haptic->rumble_id < 0) {
return SDL_SetError("Haptic: Rumble effect not initialized on haptic device");
}
return SDL_StopHapticEffect(haptic, haptic->rumble_id);
}