Path: blob/master/thirdparty/sdl/haptic/linux/SDL_syshaptic.c
9912 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_HAPTIC_LINUX2324#include "../SDL_syshaptic.h"25#include "../../joystick/SDL_sysjoystick.h" // For the real SDL_Joystick26#include "../../joystick/linux/SDL_sysjoystick_c.h" // For joystick hwdata27#include "../../core/linux/SDL_evdev_capabilities.h"28#include "../../core/linux/SDL_udev.h"2930#include <unistd.h> // close31#include <linux/input.h> // Force feedback linux stuff.32#include <fcntl.h> // O_RDWR33#include <limits.h> // INT_MAX34#include <errno.h> // errno35#include <string.h> // strerror36#include <sys/stat.h> // stat3738#define MAX_HAPTICS 32 // It's doubtful someone has more then 32 evdev3940static bool MaybeAddDevice(const char *path);41#ifdef SDL_USE_LIBUDEV42static bool MaybeRemoveDevice(const char *path);43static void haptic_udev_callback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath);44#endif // SDL_USE_LIBUDEV4546/*47* List of available haptic devices.48*/49typedef struct SDL_hapticlist_item50{51SDL_HapticID instance_id;52char *fname; // Dev path name (like /dev/input/event1)53SDL_Haptic *haptic; // Associated haptic.54dev_t dev_num;55struct SDL_hapticlist_item *next;56} SDL_hapticlist_item;5758/*59* Haptic system hardware data.60*/61struct haptic_hwdata62{63int fd; // File descriptor of the device.64char *fname; // Points to the name in SDL_hapticlist.65};6667/*68* Haptic system effect data.69*/70struct haptic_hweffect71{72struct ff_effect effect; // The linux kernel effect structure.73};7475static SDL_hapticlist_item *SDL_hapticlist = NULL;76static SDL_hapticlist_item *SDL_hapticlist_tail = NULL;77static int numhaptics = 0;7879#define EV_TEST(ev, f) \80if (test_bit((ev), features)) { \81ret |= (f); \82}83/*84* Test whether a device has haptic properties.85* Returns available properties or 0 if there are none.86*/87static Uint32 EV_IsHaptic(int fd)88{89unsigned long features[1 + FF_MAX / sizeof(unsigned long)];90Uint32 ret = 0;9192// Ask device for what it has.93if (ioctl(fd, EVIOCGBIT(EV_FF, sizeof(features)), features) < 0) {94SDL_SetError("Haptic: Unable to get device's features: %s", strerror(errno));95return 0;96}9798// Convert supported features to SDL_HAPTIC platform-neutral features.99EV_TEST(FF_CONSTANT, SDL_HAPTIC_CONSTANT);100EV_TEST(FF_SINE, SDL_HAPTIC_SINE);101EV_TEST(FF_SQUARE, SDL_HAPTIC_SQUARE);102EV_TEST(FF_TRIANGLE, SDL_HAPTIC_TRIANGLE);103EV_TEST(FF_SAW_UP, SDL_HAPTIC_SAWTOOTHUP);104EV_TEST(FF_SAW_DOWN, SDL_HAPTIC_SAWTOOTHDOWN);105EV_TEST(FF_RAMP, SDL_HAPTIC_RAMP);106EV_TEST(FF_SPRING, SDL_HAPTIC_SPRING);107EV_TEST(FF_FRICTION, SDL_HAPTIC_FRICTION);108EV_TEST(FF_DAMPER, SDL_HAPTIC_DAMPER);109EV_TEST(FF_INERTIA, SDL_HAPTIC_INERTIA);110EV_TEST(FF_CUSTOM, SDL_HAPTIC_CUSTOM);111EV_TEST(FF_GAIN, SDL_HAPTIC_GAIN);112EV_TEST(FF_AUTOCENTER, SDL_HAPTIC_AUTOCENTER);113EV_TEST(FF_RUMBLE, SDL_HAPTIC_LEFTRIGHT);114115// Return what it supports.116return ret;117}118119/*120* Tests whether a device is a mouse or not.121*/122static bool EV_IsMouse(int fd)123{124unsigned long argp[40];125126// Ask for supported features.127if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(argp)), argp) < 0) {128return false;129}130131// Currently we only test for BTN_MOUSE which can give fake positives.132if (test_bit(BTN_MOUSE, argp) != 0) {133return true;134}135136return true;137}138139/*140* Initializes the haptic subsystem by finding available devices.141*/142bool SDL_SYS_HapticInit(void)143{144const char joydev_pattern[] = "/dev/input/event%d";145char path[PATH_MAX];146int i, j;147148/*149* Limit amount of checks to MAX_HAPTICS since we may or may not have150* permission to some or all devices.151*/152i = 0;153for (j = 0; j < MAX_HAPTICS; ++j) {154(void)SDL_snprintf(path, PATH_MAX, joydev_pattern, i++);155MaybeAddDevice(path);156}157158#ifdef SDL_USE_LIBUDEV159if (!SDL_UDEV_Init()) {160return SDL_SetError("Could not initialize UDEV");161}162163if (!SDL_UDEV_AddCallback(haptic_udev_callback)) {164SDL_UDEV_Quit();165return SDL_SetError("Could not setup haptic <-> udev callback");166}167168// Force a scan to build the initial device list169SDL_UDEV_Scan();170#endif // SDL_USE_LIBUDEV171172return true;173}174175int SDL_SYS_NumHaptics(void)176{177return numhaptics;178}179180static SDL_hapticlist_item *HapticByDevIndex(int device_index)181{182SDL_hapticlist_item *item = SDL_hapticlist;183184if ((device_index < 0) || (device_index >= numhaptics)) {185return NULL;186}187188while (device_index > 0) {189SDL_assert(item != NULL);190--device_index;191item = item->next;192}193194return item;195}196197static SDL_hapticlist_item *HapticByInstanceID(SDL_HapticID instance_id)198{199SDL_hapticlist_item *item;200for (item = SDL_hapticlist; item; item = item->next) {201if (instance_id == item->instance_id) {202return item;203}204}205return NULL;206}207208#ifdef SDL_USE_LIBUDEV209static void haptic_udev_callback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath)210{211if (!devpath || !(udev_class & SDL_UDEV_DEVICE_JOYSTICK)) {212return;213}214215switch (udev_type) {216case SDL_UDEV_DEVICEADDED:217MaybeAddDevice(devpath);218break;219220case SDL_UDEV_DEVICEREMOVED:221MaybeRemoveDevice(devpath);222break;223224default:225break;226}227}228#endif // SDL_USE_LIBUDEV229230static bool MaybeAddDevice(const char *path)231{232struct stat sb;233int fd;234Uint32 supported;235SDL_hapticlist_item *item;236237if (!path) {238return false;239}240241// try to open242fd = open(path, O_RDWR | O_CLOEXEC, 0);243if (fd < 0) {244return false;245}246247// get file status248if (fstat(fd, &sb) != 0) {249close(fd);250return false;251}252253// check for duplicates254for (item = SDL_hapticlist; item; item = item->next) {255if (item->dev_num == sb.st_rdev) {256close(fd);257return false; // duplicate.258}259}260261#ifdef DEBUG_INPUT_EVENTS262printf("Checking %s\n", path);263#endif264265// see if it works266supported = EV_IsHaptic(fd);267close(fd);268if (!supported) {269return false;270}271272item = (SDL_hapticlist_item *)SDL_calloc(1, sizeof(SDL_hapticlist_item));273if (!item) {274return false;275}276277item->instance_id = SDL_GetNextObjectID();278item->fname = SDL_strdup(path);279if (!item->fname) {280SDL_free(item);281return false;282}283284item->dev_num = sb.st_rdev;285286// TODO: should we add instance IDs?287if (!SDL_hapticlist_tail) {288SDL_hapticlist = SDL_hapticlist_tail = item;289} else {290SDL_hapticlist_tail->next = item;291SDL_hapticlist_tail = item;292}293294++numhaptics;295296// !!! TODO: Send a haptic add event?297298return true;299}300301#ifdef SDL_USE_LIBUDEV302static bool MaybeRemoveDevice(const char *path)303{304SDL_hapticlist_item *item;305SDL_hapticlist_item *prev = NULL;306307if (!path) {308return false;309}310311for (item = SDL_hapticlist; item; item = item->next) {312// found it, remove it.313if (SDL_strcmp(path, item->fname) == 0) {314const bool result = item->haptic ? true : false;315316if (prev) {317prev->next = item->next;318} else {319SDL_assert(SDL_hapticlist == item);320SDL_hapticlist = item->next;321}322if (item == SDL_hapticlist_tail) {323SDL_hapticlist_tail = prev;324}325326// Need to decrement the haptic count327--numhaptics;328// !!! TODO: Send a haptic remove event?329330SDL_free(item->fname);331SDL_free(item);332return result;333}334prev = item;335}336337return false;338}339#endif // SDL_USE_LIBUDEV340341/*342* Return the instance ID of a haptic device, does not need to be opened.343*/344SDL_HapticID SDL_SYS_HapticInstanceID(int index)345{346SDL_hapticlist_item *item;347348item = HapticByDevIndex(index);349if (item) {350return item->instance_id;351}352return 0;353}354355/*356* Gets the name from a file descriptor.357*/358static const char *SDL_SYS_HapticNameFromFD(int fd)359{360static char namebuf[128];361362// We use the evdev name ioctl.363if (ioctl(fd, EVIOCGNAME(sizeof(namebuf)), namebuf) <= 0) {364return NULL;365}366367return namebuf;368}369370/*371* Return the name of a haptic device, does not need to be opened.372*/373const char *SDL_SYS_HapticName(int index)374{375SDL_hapticlist_item *item;376int fd;377const char *name = NULL;378379item = HapticByDevIndex(index);380if (item) {381// Open the haptic device.382fd = open(item->fname, O_RDONLY | O_CLOEXEC, 0);383384if (fd >= 0) {385386name = SDL_SYS_HapticNameFromFD(fd);387if (!name) {388// No name found, return device character device389name = item->fname;390}391close(fd);392}393}394return name;395}396397/*398* Opens the haptic device from the file descriptor.399*/400static bool SDL_SYS_HapticOpenFromFD(SDL_Haptic *haptic, int fd)401{402// Allocate the hwdata403haptic->hwdata = (struct haptic_hwdata *)404SDL_calloc(1, sizeof(*haptic->hwdata));405if (!haptic->hwdata) {406goto open_err;407}408409// Set the data.410haptic->hwdata->fd = fd;411haptic->supported = EV_IsHaptic(fd);412haptic->naxes = 2; // Hardcoded for now, not sure if it's possible to find out.413414// Set the effects415if (ioctl(fd, EVIOCGEFFECTS, &haptic->neffects) < 0) {416SDL_SetError("Haptic: Unable to query device memory: %s",417strerror(errno));418goto open_err;419}420haptic->nplaying = haptic->neffects; // Linux makes no distinction.421haptic->effects = (struct haptic_effect *)422SDL_malloc(sizeof(struct haptic_effect) * haptic->neffects);423if (!haptic->effects) {424goto open_err;425}426// Clear the memory427SDL_memset(haptic->effects, 0,428sizeof(struct haptic_effect) * haptic->neffects);429430return true;431432// Error handling433open_err:434close(fd);435if (haptic->hwdata) {436SDL_free(haptic->hwdata);437haptic->hwdata = NULL;438}439return false;440}441442/*443* Opens a haptic device for usage.444*/445bool SDL_SYS_HapticOpen(SDL_Haptic *haptic)446{447int fd;448SDL_hapticlist_item *item;449450item = HapticByInstanceID(haptic->instance_id);451// Open the character device452fd = open(item->fname, O_RDWR | O_CLOEXEC, 0);453if (fd < 0) {454return SDL_SetError("Haptic: Unable to open %s: %s",455item->fname, strerror(errno));456}457458// Try to create the haptic.459if (!SDL_SYS_HapticOpenFromFD(haptic, fd)) {460// Already closes on error.461return false;462}463464// Set the fname.465haptic->hwdata->fname = SDL_strdup(item->fname);466return true;467}468469/*470* Opens a haptic device from first mouse it finds for usage.471*/472int SDL_SYS_HapticMouse(void)473{474int fd;475int device_index = 0;476SDL_hapticlist_item *item;477478for (item = SDL_hapticlist; item; item = item->next) {479// Open the device.480fd = open(item->fname, O_RDWR | O_CLOEXEC, 0);481if (fd < 0) {482return SDL_SetError("Haptic: Unable to open %s: %s",483item->fname, strerror(errno));484}485486// Is it a mouse?487if (EV_IsMouse(fd)) {488close(fd);489return device_index;490}491492close(fd);493494++device_index;495}496497return -1;498}499500/*501* Checks to see if a joystick has haptic features.502*/503bool SDL_SYS_JoystickIsHaptic(SDL_Joystick *joystick)504{505#ifdef SDL_JOYSTICK_LINUX506SDL_AssertJoysticksLocked();507508if (joystick->driver != &SDL_LINUX_JoystickDriver) {509return false;510}511if (EV_IsHaptic(joystick->hwdata->fd)) {512return true;513}514#endif515return false;516}517518/*519* Checks to see if the haptic device and joystick are in reality the same.520*/521bool SDL_SYS_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick)522{523#ifdef SDL_JOYSTICK_LINUX524SDL_AssertJoysticksLocked();525526if (joystick->driver != &SDL_LINUX_JoystickDriver) {527return false;528}529/* We are assuming Linux is using evdev which should trump the old530* joystick methods. */531if (SDL_strcmp(joystick->hwdata->fname, haptic->hwdata->fname) == 0) {532return true;533}534#endif535return false;536}537538/*539* Opens a SDL_Haptic from a SDL_Joystick.540*/541bool SDL_SYS_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick)542{543#ifdef SDL_JOYSTICK_LINUX544int fd;545SDL_hapticlist_item *item;546const char *name;547548SDL_AssertJoysticksLocked();549550if (joystick->driver != &SDL_LINUX_JoystickDriver) {551return false;552}553// Find the joystick in the haptic list.554for (item = SDL_hapticlist; item; item = item->next) {555if (SDL_strcmp(item->fname, joystick->hwdata->fname) == 0) {556haptic->instance_id = item->instance_id;557break;558}559}560561fd = open(joystick->hwdata->fname, O_RDWR | O_CLOEXEC, 0);562if (fd < 0) {563return SDL_SetError("Haptic: Unable to open %s: %s",564joystick->hwdata->fname, strerror(errno));565}566if (!SDL_SYS_HapticOpenFromFD(haptic, fd)) {567// Already closes on error.568return false;569}570571haptic->hwdata->fname = SDL_strdup(joystick->hwdata->fname);572573name = SDL_SYS_HapticNameFromFD(fd);574if (name) {575haptic->name = SDL_strdup(name);576}577return true;578#else579return false;580#endif581}582583/*584* Closes the haptic device.585*/586void SDL_SYS_HapticClose(SDL_Haptic *haptic)587{588if (haptic->hwdata) {589590// Free effects.591SDL_free(haptic->effects);592haptic->effects = NULL;593haptic->neffects = 0;594595// Clean up596close(haptic->hwdata->fd);597598// Free599SDL_free(haptic->hwdata->fname);600SDL_free(haptic->hwdata);601haptic->hwdata = NULL;602}603604// Clear the rest.605SDL_memset(haptic, 0, sizeof(SDL_Haptic));606}607608/*609* Clean up after system specific haptic stuff610*/611void SDL_SYS_HapticQuit(void)612{613SDL_hapticlist_item *item = NULL;614SDL_hapticlist_item *next = NULL;615616for (item = SDL_hapticlist; item; item = next) {617next = item->next;618/* Opened and not closed haptics are leaked, this is on purpose.619* Close your haptic devices after usage. */620SDL_free(item->fname);621SDL_free(item);622}623624#ifdef SDL_USE_LIBUDEV625SDL_UDEV_DelCallback(haptic_udev_callback);626SDL_UDEV_Quit();627#endif // SDL_USE_LIBUDEV628629numhaptics = 0;630SDL_hapticlist = NULL;631SDL_hapticlist_tail = NULL;632}633634/*635* Converts an SDL button to a ff_trigger button.636*/637static Uint16 SDL_SYS_ToButton(Uint16 button)638{639Uint16 ff_button;640641ff_button = 0;642643/*644* Not sure what the proper syntax is because this actually isn't implemented645* in the current kernel from what I've seen (2.6.26).646*/647if (button != 0) {648ff_button = BTN_GAMEPAD + button - 1;649}650651return ff_button;652}653654/*655* Initializes the ff_effect usable direction from a SDL_HapticDirection.656*/657static bool SDL_SYS_ToDirection(Uint16 *dest, const SDL_HapticDirection *src)658{659Uint32 tmp;660661switch (src->type) {662case SDL_HAPTIC_POLAR:663tmp = ((src->dir[0] % 36000) * 0x8000) / 18000; // convert to range [0,0xFFFF]664*dest = (Uint16)tmp;665break;666667case SDL_HAPTIC_SPHERICAL:668/*669We convert to polar, because that's the only supported direction on Linux.670The first value of a spherical direction is practically the same as a671Polar direction, except that we have to add 90 degrees. It is the angle672from EAST {1,0} towards SOUTH {0,1}.673--> add 9000674--> finally convert to [0,0xFFFF] as in case SDL_HAPTIC_POLAR.675*/676tmp = ((src->dir[0]) + 9000) % 36000; // Convert to polars677tmp = (tmp * 0x8000) / 18000; // convert to range [0,0xFFFF]678*dest = (Uint16)tmp;679break;680681case SDL_HAPTIC_CARTESIAN:682if (!src->dir[1]) {683*dest = (src->dir[0] >= 0 ? 0x4000 : 0xC000);684} else if (!src->dir[0]) {685*dest = (src->dir[1] >= 0 ? 0x8000 : 0);686} else {687float f = SDL_atan2f(src->dir[1], src->dir[0]); // Ideally we'd use fixed point math instead of floats...688/*689SDL_atan2 takes the parameters: Y-axis-value and X-axis-value (in that order)690- Y-axis-value is the second coordinate (from center to SOUTH)691- X-axis-value is the first coordinate (from center to EAST)692We add 36000, because SDL_atan2 also returns negative values. Then we practically693have the first spherical value. Therefore we proceed as in case694SDL_HAPTIC_SPHERICAL and add another 9000 to get the polar value.695--> add 45000 in total696--> finally convert to [0,0xFFFF] as in case SDL_HAPTIC_POLAR.697*/698tmp = (((Sint32)(f * 18000.0 / SDL_PI_D)) + 45000) % 36000;699tmp = (tmp * 0x8000) / 18000; // convert to range [0,0xFFFF]700*dest = (Uint16)tmp;701}702break;703case SDL_HAPTIC_STEERING_AXIS:704*dest = 0x4000;705break;706default:707return SDL_SetError("Haptic: Unsupported direction type.");708}709710return true;711}712713#define CLAMP(x) (((x) > 32767) ? 32767 : x)714/*715* Initializes the Linux effect struct from a haptic_effect.716* Values above 32767 (for unsigned) are unspecified so we must clamp.717*/718static bool SDL_SYS_ToFFEffect(struct ff_effect *dest, const SDL_HapticEffect *src)719{720const SDL_HapticConstant *constant;721const SDL_HapticPeriodic *periodic;722const SDL_HapticCondition *condition;723const SDL_HapticRamp *ramp;724const SDL_HapticLeftRight *leftright;725726// Clear up727SDL_memset(dest, 0, sizeof(struct ff_effect));728729switch (src->type) {730case SDL_HAPTIC_CONSTANT:731constant = &src->constant;732733// Header734dest->type = FF_CONSTANT;735if (!SDL_SYS_ToDirection(&dest->direction, &constant->direction)) {736return false;737}738739// Replay740dest->replay.length = (constant->length == SDL_HAPTIC_INFINITY) ? 0 : CLAMP(constant->length);741dest->replay.delay = CLAMP(constant->delay);742743// Trigger744dest->trigger.button = SDL_SYS_ToButton(constant->button);745dest->trigger.interval = CLAMP(constant->interval);746747// Constant748dest->u.constant.level = constant->level;749750// Envelope751dest->u.constant.envelope.attack_length =752CLAMP(constant->attack_length);753dest->u.constant.envelope.attack_level =754CLAMP(constant->attack_level);755dest->u.constant.envelope.fade_length = CLAMP(constant->fade_length);756dest->u.constant.envelope.fade_level = CLAMP(constant->fade_level);757758break;759760case SDL_HAPTIC_SINE:761case SDL_HAPTIC_SQUARE:762case SDL_HAPTIC_TRIANGLE:763case SDL_HAPTIC_SAWTOOTHUP:764case SDL_HAPTIC_SAWTOOTHDOWN:765periodic = &src->periodic;766767// Header768dest->type = FF_PERIODIC;769if (!SDL_SYS_ToDirection(&dest->direction, &periodic->direction)) {770return false;771}772773// Replay774dest->replay.length = (periodic->length == SDL_HAPTIC_INFINITY) ? 0 : CLAMP(periodic->length);775dest->replay.delay = CLAMP(periodic->delay);776777// Trigger778dest->trigger.button = SDL_SYS_ToButton(periodic->button);779dest->trigger.interval = CLAMP(periodic->interval);780781// Periodic782if (periodic->type == SDL_HAPTIC_SINE) {783dest->u.periodic.waveform = FF_SINE;784} else if (periodic->type == SDL_HAPTIC_SQUARE) {785dest->u.periodic.waveform = FF_SQUARE;786} else if (periodic->type == SDL_HAPTIC_TRIANGLE) {787dest->u.periodic.waveform = FF_TRIANGLE;788} else if (periodic->type == SDL_HAPTIC_SAWTOOTHUP) {789dest->u.periodic.waveform = FF_SAW_UP;790} else if (periodic->type == SDL_HAPTIC_SAWTOOTHDOWN) {791dest->u.periodic.waveform = FF_SAW_DOWN;792}793dest->u.periodic.period = CLAMP(periodic->period);794dest->u.periodic.magnitude = periodic->magnitude;795dest->u.periodic.offset = periodic->offset;796// Linux phase is defined in interval "[0x0000, 0x10000[", corresponds with "[0deg, 360deg[" phase shift.797dest->u.periodic.phase = ((Uint32)periodic->phase * 0x10000U) / 36000;798799// Envelope800dest->u.periodic.envelope.attack_length =801CLAMP(periodic->attack_length);802dest->u.periodic.envelope.attack_level =803CLAMP(periodic->attack_level);804dest->u.periodic.envelope.fade_length = CLAMP(periodic->fade_length);805dest->u.periodic.envelope.fade_level = CLAMP(periodic->fade_level);806807break;808809case SDL_HAPTIC_SPRING:810case SDL_HAPTIC_DAMPER:811case SDL_HAPTIC_INERTIA:812case SDL_HAPTIC_FRICTION:813condition = &src->condition;814815// Header816if (condition->type == SDL_HAPTIC_SPRING) {817dest->type = FF_SPRING;818} else if (condition->type == SDL_HAPTIC_DAMPER) {819dest->type = FF_DAMPER;820} else if (condition->type == SDL_HAPTIC_INERTIA) {821dest->type = FF_INERTIA;822} else if (condition->type == SDL_HAPTIC_FRICTION) {823dest->type = FF_FRICTION;824}825826if (!SDL_SYS_ToDirection(&dest->direction, &condition->direction)) {827return false;828}829830// Replay831dest->replay.length = (condition->length == SDL_HAPTIC_INFINITY) ? 0 : CLAMP(condition->length);832dest->replay.delay = CLAMP(condition->delay);833834// Trigger835dest->trigger.button = SDL_SYS_ToButton(condition->button);836dest->trigger.interval = CLAMP(condition->interval);837838// Condition839// X axis840dest->u.condition[0].right_saturation = condition->right_sat[0];841dest->u.condition[0].left_saturation = condition->left_sat[0];842dest->u.condition[0].right_coeff = condition->right_coeff[0];843dest->u.condition[0].left_coeff = condition->left_coeff[0];844dest->u.condition[0].deadband = condition->deadband[0];845dest->u.condition[0].center = condition->center[0];846// Y axis847dest->u.condition[1].right_saturation = condition->right_sat[1];848dest->u.condition[1].left_saturation = condition->left_sat[1];849dest->u.condition[1].right_coeff = condition->right_coeff[1];850dest->u.condition[1].left_coeff = condition->left_coeff[1];851dest->u.condition[1].deadband = condition->deadband[1];852dest->u.condition[1].center = condition->center[1];853854/*855* There is no envelope in the linux force feedback api for conditions.856*/857858break;859860case SDL_HAPTIC_RAMP:861ramp = &src->ramp;862863// Header864dest->type = FF_RAMP;865if (!SDL_SYS_ToDirection(&dest->direction, &ramp->direction)) {866return false;867}868869// Replay870dest->replay.length = (ramp->length == SDL_HAPTIC_INFINITY) ? 0 : CLAMP(ramp->length);871dest->replay.delay = CLAMP(ramp->delay);872873// Trigger874dest->trigger.button = SDL_SYS_ToButton(ramp->button);875dest->trigger.interval = CLAMP(ramp->interval);876877// Ramp878dest->u.ramp.start_level = ramp->start;879dest->u.ramp.end_level = ramp->end;880881// Envelope882dest->u.ramp.envelope.attack_length = CLAMP(ramp->attack_length);883dest->u.ramp.envelope.attack_level = CLAMP(ramp->attack_level);884dest->u.ramp.envelope.fade_length = CLAMP(ramp->fade_length);885dest->u.ramp.envelope.fade_level = CLAMP(ramp->fade_level);886887break;888889case SDL_HAPTIC_LEFTRIGHT:890leftright = &src->leftright;891892// Header893dest->type = FF_RUMBLE;894dest->direction = 0x4000;895896// Replay897dest->replay.length = (leftright->length == SDL_HAPTIC_INFINITY) ? 0 : CLAMP(leftright->length);898899// Trigger900dest->trigger.button = 0;901dest->trigger.interval = 0;902903// Rumble (Linux expects 0-65535, so multiply by 2)904dest->u.rumble.strong_magnitude = CLAMP(leftright->large_magnitude) * 2;905dest->u.rumble.weak_magnitude = CLAMP(leftright->small_magnitude) * 2;906907break;908909default:910return SDL_SetError("Haptic: Unknown effect type.");911}912913return true;914}915916/*917* Creates a new haptic effect.918*/919bool SDL_SYS_HapticNewEffect(SDL_Haptic *haptic, struct haptic_effect *effect,920const SDL_HapticEffect *base)921{922struct ff_effect *linux_effect;923924// Allocate the hardware effect925effect->hweffect = (struct haptic_hweffect *)926SDL_calloc(1, sizeof(struct haptic_hweffect));927if (!effect->hweffect) {928return false;929}930931// Prepare the ff_effect932linux_effect = &effect->hweffect->effect;933if (!SDL_SYS_ToFFEffect(linux_effect, base)) {934goto new_effect_err;935}936linux_effect->id = -1; // Have the kernel give it an id937938// Upload the effect939if (ioctl(haptic->hwdata->fd, EVIOCSFF, linux_effect) < 0) {940SDL_SetError("Haptic: Error uploading effect to the device: %s",941strerror(errno));942goto new_effect_err;943}944945return true;946947new_effect_err:948SDL_free(effect->hweffect);949effect->hweffect = NULL;950return false;951}952953/*954* Updates an effect.955*956* Note: Dynamically updating the direction can in some cases force957* the effect to restart and run once.958*/959bool SDL_SYS_HapticUpdateEffect(SDL_Haptic *haptic,960struct haptic_effect *effect,961const SDL_HapticEffect *data)962{963struct ff_effect linux_effect;964965// Create the new effect966if (!SDL_SYS_ToFFEffect(&linux_effect, data)) {967return false;968}969linux_effect.id = effect->hweffect->effect.id;970971// See if it can be uploaded.972if (ioctl(haptic->hwdata->fd, EVIOCSFF, &linux_effect) < 0) {973return SDL_SetError("Haptic: Error updating the effect: %s",974strerror(errno));975}976977// Copy the new effect into memory.978SDL_memcpy(&effect->hweffect->effect, &linux_effect,979sizeof(struct ff_effect));980981return true;982}983984/*985* Runs an effect.986*/987bool SDL_SYS_HapticRunEffect(SDL_Haptic *haptic, struct haptic_effect *effect,988Uint32 iterations)989{990struct input_event run;991992// Prepare to run the effect993run.type = EV_FF;994run.code = effect->hweffect->effect.id;995// We don't actually have infinity here, so we just do INT_MAX which is pretty damn close.996run.value = (iterations > INT_MAX) ? INT_MAX : iterations;997998if (write(haptic->hwdata->fd, (const void *)&run, sizeof(run)) < 0) {999return SDL_SetError("Haptic: Unable to run the effect: %s", strerror(errno));1000}10011002return true;1003}10041005/*1006* Stops an effect.1007*/1008bool SDL_SYS_HapticStopEffect(SDL_Haptic *haptic, struct haptic_effect *effect)1009{1010struct input_event stop;10111012stop.type = EV_FF;1013stop.code = effect->hweffect->effect.id;1014stop.value = 0;10151016if (write(haptic->hwdata->fd, (const void *)&stop, sizeof(stop)) < 0) {1017return SDL_SetError("Haptic: Unable to stop the effect: %s",1018strerror(errno));1019}10201021return true;1022}10231024/*1025* Frees the effect.1026*/1027void SDL_SYS_HapticDestroyEffect(SDL_Haptic *haptic, struct haptic_effect *effect)1028{1029if (ioctl(haptic->hwdata->fd, EVIOCRMFF, effect->hweffect->effect.id) < 0) {1030SDL_SetError("Haptic: Error removing the effect from the device: %s",1031strerror(errno));1032}1033SDL_free(effect->hweffect);1034effect->hweffect = NULL;1035}10361037/*1038* Gets the status of a haptic effect.1039*/1040int SDL_SYS_HapticGetEffectStatus(SDL_Haptic *haptic,1041struct haptic_effect *effect)1042{1043#if 0 // Not supported atm.1044struct input_event ie;10451046ie.type = EV_FF;1047ie.type = EV_FF_STATUS;1048ie.code = effect->hweffect->effect.id;10491050if (write(haptic->hwdata->fd, &ie, sizeof(ie)) < 0) {1051SDL_SetError("Haptic: Error getting device status.");1052return -1;1053}10541055return 1;1056#endif10571058SDL_Unsupported();1059return -1;1060}10611062/*1063* Sets the gain.1064*/1065bool SDL_SYS_HapticSetGain(SDL_Haptic *haptic, int gain)1066{1067struct input_event ie;10681069ie.type = EV_FF;1070ie.code = FF_GAIN;1071ie.value = (0xFFFFUL * gain) / 100;10721073if (write(haptic->hwdata->fd, &ie, sizeof(ie)) < 0) {1074return SDL_SetError("Haptic: Error setting gain: %s", strerror(errno));1075}10761077return true;1078}10791080/*1081* Sets the autocentering.1082*/1083bool SDL_SYS_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter)1084{1085struct input_event ie;10861087ie.type = EV_FF;1088ie.code = FF_AUTOCENTER;1089ie.value = (0xFFFFUL * autocenter) / 100;10901091if (write(haptic->hwdata->fd, &ie, sizeof(ie)) < 0) {1092return SDL_SetError("Haptic: Error setting autocenter: %s", strerror(errno));1093}10941095return true;1096}10971098/*1099* Pausing is not supported atm by linux.1100*/1101bool SDL_SYS_HapticPause(SDL_Haptic *haptic)1102{1103return SDL_Unsupported();1104}11051106/*1107* Unpausing is not supported atm by linux.1108*/1109bool SDL_SYS_HapticResume(SDL_Haptic *haptic)1110{1111return SDL_Unsupported();1112}11131114/*1115* Stops all the currently playing effects.1116*/1117bool SDL_SYS_HapticStopAll(SDL_Haptic *haptic)1118{1119int i, ret;11201121// Linux does not support this natively so we have to loop.1122for (i = 0; i < haptic->neffects; i++) {1123if (haptic->effects[i].hweffect != NULL) {1124ret = SDL_SYS_HapticStopEffect(haptic, &haptic->effects[i]);1125if (ret < 0) {1126return SDL_SetError("Haptic: Error while trying to stop all playing effects.");1127}1128}1129}1130return true;1131}11321133#endif // SDL_HAPTIC_LINUX113411351136