SDL_syshaptic.c (40020B)
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2014 Sam Lantinga <slouken@libsdl.org> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include "../../SDL_internal.h" 22 23#ifdef SDL_HAPTIC_IOKIT 24 25#include "SDL_assert.h" 26#include "SDL_stdinc.h" 27#include "SDL_haptic.h" 28#include "../SDL_syshaptic.h" 29#include "SDL_joystick.h" 30#include "../../joystick/SDL_sysjoystick.h" /* For the real SDL_Joystick */ 31#include "../../joystick/darwin/SDL_sysjoystick_c.h" /* For joystick hwdata */ 32#include "SDL_syshaptic_c.h" 33 34#include <IOKit/IOKitLib.h> 35#include <IOKit/hid/IOHIDKeys.h> 36#include <IOKit/hid/IOHIDUsageTables.h> 37#include <ForceFeedback/ForceFeedback.h> 38#include <ForceFeedback/ForceFeedbackConstants.h> 39 40#ifndef IO_OBJECT_NULL 41#define IO_OBJECT_NULL ((io_service_t)0) 42#endif 43 44/* 45 * List of available haptic devices. 46 */ 47typedef struct SDL_hapticlist_item 48{ 49 char name[256]; /* Name of the device. */ 50 51 io_service_t dev; /* Node we use to create the device. */ 52 SDL_Haptic *haptic; /* Haptic currently associated with it. */ 53 54 /* Usage pages for determining if it's a mouse or not. */ 55 long usage; 56 long usagePage; 57 58 struct SDL_hapticlist_item *next; 59} SDL_hapticlist_item; 60 61 62/* 63 * Haptic system hardware data. 64 */ 65struct haptic_hwdata 66{ 67 FFDeviceObjectReference device; /* Hardware device. */ 68 UInt8 axes[3]; 69}; 70 71 72/* 73 * Haptic system effect data. 74 */ 75struct haptic_hweffect 76{ 77 FFEffectObjectReference ref; /* Reference. */ 78 struct FFEFFECT effect; /* Hardware effect. */ 79}; 80 81/* 82 * Prototypes. 83 */ 84static void SDL_SYS_HapticFreeFFEFFECT(FFEFFECT * effect, int type); 85static int HIDGetDeviceProduct(io_service_t dev, char *name); 86 87static SDL_hapticlist_item *SDL_hapticlist = NULL; 88static SDL_hapticlist_item *SDL_hapticlist_tail = NULL; 89static int numhaptics = -1; 90 91/* 92 * Like strerror but for force feedback errors. 93 */ 94static const char * 95FFStrError(unsigned int err) 96{ 97 switch (err) { 98 case FFERR_DEVICEFULL: 99 return "device full"; 100 /* This should be valid, but for some reason isn't defined... */ 101 /* case FFERR_DEVICENOTREG: 102 return "device not registered"; */ 103 case FFERR_DEVICEPAUSED: 104 return "device paused"; 105 case FFERR_DEVICERELEASED: 106 return "device released"; 107 case FFERR_EFFECTPLAYING: 108 return "effect playing"; 109 case FFERR_EFFECTTYPEMISMATCH: 110 return "effect type mismatch"; 111 case FFERR_EFFECTTYPENOTSUPPORTED: 112 return "effect type not supported"; 113 case FFERR_GENERIC: 114 return "undetermined error"; 115 case FFERR_HASEFFECTS: 116 return "device has effects"; 117 case FFERR_INCOMPLETEEFFECT: 118 return "incomplete effect"; 119 case FFERR_INTERNAL: 120 return "internal fault"; 121 case FFERR_INVALIDDOWNLOADID: 122 return "invalid download id"; 123 case FFERR_INVALIDPARAM: 124 return "invalid parameter"; 125 case FFERR_MOREDATA: 126 return "more data"; 127 case FFERR_NOINTERFACE: 128 return "interface not supported"; 129 case FFERR_NOTDOWNLOADED: 130 return "effect is not downloaded"; 131 case FFERR_NOTINITIALIZED: 132 return "object has not been initialized"; 133 case FFERR_OUTOFMEMORY: 134 return "out of memory"; 135 case FFERR_UNPLUGGED: 136 return "device is unplugged"; 137 case FFERR_UNSUPPORTED: 138 return "function call unsupported"; 139 case FFERR_UNSUPPORTEDAXIS: 140 return "axis unsupported"; 141 142 default: 143 return "unknown error"; 144 } 145} 146 147 148/* 149 * Initializes the haptic subsystem. 150 */ 151int 152SDL_SYS_HapticInit(void) 153{ 154 IOReturn result; 155 io_iterator_t iter; 156 CFDictionaryRef match; 157 io_service_t device; 158 159 if (numhaptics != -1) { 160 return SDL_SetError("Haptic subsystem already initialized!"); 161 } 162 numhaptics = 0; 163 164 /* Get HID devices. */ 165 match = IOServiceMatching(kIOHIDDeviceKey); 166 if (match == NULL) { 167 return SDL_SetError("Haptic: Failed to get IOServiceMatching."); 168 } 169 170 /* Now search I/O Registry for matching devices. */ 171 result = IOServiceGetMatchingServices(kIOMasterPortDefault, match, &iter); 172 if (result != kIOReturnSuccess) { 173 return SDL_SetError("Haptic: Couldn't create a HID object iterator."); 174 } 175 /* IOServiceGetMatchingServices consumes dictionary. */ 176 177 if (!IOIteratorIsValid(iter)) { /* No iterator. */ 178 return 0; 179 } 180 181 while ((device = IOIteratorNext(iter)) != IO_OBJECT_NULL) { 182 MacHaptic_MaybeAddDevice(device); 183 /* always release as the AddDevice will retain IF it's a forcefeedback device */ 184 IOObjectRelease(device); 185 } 186 IOObjectRelease(iter); 187 188 return numhaptics; 189} 190 191int 192SDL_SYS_NumHaptics() 193{ 194 return numhaptics; 195} 196 197static SDL_hapticlist_item * 198HapticByDevIndex(int device_index) 199{ 200 SDL_hapticlist_item *item = SDL_hapticlist; 201 202 if ((device_index < 0) || (device_index >= numhaptics)) { 203 return NULL; 204 } 205 206 while (device_index > 0) { 207 SDL_assert(item != NULL); 208 --device_index; 209 item = item->next; 210 } 211 212 return item; 213} 214 215int 216MacHaptic_MaybeAddDevice( io_object_t device ) 217{ 218 IOReturn result; 219 CFMutableDictionaryRef hidProperties; 220 CFTypeRef refCF; 221 SDL_hapticlist_item *item; 222 223 if (numhaptics == -1) { 224 return -1; /* not initialized. We'll pick these up on enumeration if we init later. */ 225 } 226 227 /* Check for force feedback. */ 228 if (FFIsForceFeedback(device) != FF_OK) { 229 return -1; 230 } 231 232 /* Make sure we don't already have it */ 233 for (item = SDL_hapticlist; item ; item = item->next) 234 { 235 if (IOObjectIsEqualTo((io_object_t) item->dev, device)) { 236 /* Already added */ 237 return -1; 238 } 239 } 240 241 item = (SDL_hapticlist_item *)SDL_calloc(1, sizeof(SDL_hapticlist_item)); 242 if (item == NULL) { 243 return SDL_SetError("Could not allocate haptic storage"); 244 } 245 246 /* retain it as we are going to keep it around a while */ 247 IOObjectRetain(device); 248 249 /* Set basic device data. */ 250 HIDGetDeviceProduct(device, item->name); 251 item->dev = device; 252 253 /* Set usage pages. */ 254 hidProperties = 0; 255 refCF = 0; 256 result = IORegistryEntryCreateCFProperties(device, 257 &hidProperties, 258 kCFAllocatorDefault, 259 kNilOptions); 260 if ((result == KERN_SUCCESS) && hidProperties) { 261 refCF = CFDictionaryGetValue(hidProperties, 262 CFSTR(kIOHIDPrimaryUsagePageKey)); 263 if (refCF) { 264 if (!CFNumberGetValue(refCF, kCFNumberLongType, &item->usagePage)) { 265 SDL_SetError("Haptic: Recieving device's usage page."); 266 } 267 refCF = CFDictionaryGetValue(hidProperties, 268 CFSTR(kIOHIDPrimaryUsageKey)); 269 if (refCF) { 270 if (!CFNumberGetValue(refCF, kCFNumberLongType, &item->usage)) { 271 SDL_SetError("Haptic: Recieving device's usage."); 272 } 273 } 274 } 275 CFRelease(hidProperties); 276 } 277 278 if (SDL_hapticlist_tail == NULL) { 279 SDL_hapticlist = SDL_hapticlist_tail = item; 280 } else { 281 SDL_hapticlist_tail->next = item; 282 SDL_hapticlist_tail = item; 283 } 284 285 /* Device has been added. */ 286 ++numhaptics; 287 288 return numhaptics; 289} 290 291int 292MacHaptic_MaybeRemoveDevice( io_object_t device ) 293{ 294 SDL_hapticlist_item *item; 295 SDL_hapticlist_item *prev = NULL; 296 297 if (numhaptics == -1) { 298 return -1; /* not initialized. ignore this. */ 299 } 300 301 for (item = SDL_hapticlist; item != NULL; item = item->next) { 302 /* found it, remove it. */ 303 if (IOObjectIsEqualTo((io_object_t) item->dev, device)) { 304 const int retval = item->haptic ? item->haptic->index : -1; 305 306 if (prev != NULL) { 307 prev->next = item->next; 308 } else { 309 SDL_assert(SDL_hapticlist == item); 310 SDL_hapticlist = item->next; 311 } 312 if (item == SDL_hapticlist_tail) { 313 SDL_hapticlist_tail = prev; 314 } 315 316 /* Need to decrement the haptic count */ 317 --numhaptics; 318 /* !!! TODO: Send a haptic remove event? */ 319 320 IOObjectRelease(item->dev); 321 SDL_free(item); 322 return retval; 323 } 324 prev = item; 325 } 326 327 return -1; 328} 329 330/* 331 * Return the name of a haptic device, does not need to be opened. 332 */ 333const char * 334SDL_SYS_HapticName(int index) 335{ 336 SDL_hapticlist_item *item; 337 item = HapticByDevIndex(index); 338 return item->name; 339} 340 341/* 342 * Gets the device's product name. 343 */ 344static int 345HIDGetDeviceProduct(io_service_t dev, char *name) 346{ 347 CFMutableDictionaryRef hidProperties, usbProperties; 348 io_registry_entry_t parent1, parent2; 349 kern_return_t ret; 350 351 hidProperties = usbProperties = 0; 352 353 ret = IORegistryEntryCreateCFProperties(dev, &hidProperties, 354 kCFAllocatorDefault, kNilOptions); 355 if ((ret != KERN_SUCCESS) || !hidProperties) { 356 return SDL_SetError("Haptic: Unable to create CFProperties."); 357 } 358 359 /* Mac OS X currently is not mirroring all USB properties to HID page so need to look at USB device page also 360 * get dictionary for USB properties: step up two levels and get CF dictionary for USB properties 361 */ 362 if ((KERN_SUCCESS == 363 IORegistryEntryGetParentEntry(dev, kIOServicePlane, &parent1)) 364 && (KERN_SUCCESS == 365 IORegistryEntryGetParentEntry(parent1, kIOServicePlane, &parent2)) 366 && (KERN_SUCCESS == 367 IORegistryEntryCreateCFProperties(parent2, &usbProperties, 368 kCFAllocatorDefault, 369 kNilOptions))) { 370 if (usbProperties) { 371 CFTypeRef refCF = 0; 372 /* get device info 373 * try hid dictionary first, if fail then go to USB dictionary 374 */ 375 376 377 /* Get product name */ 378 refCF = CFDictionaryGetValue(hidProperties, CFSTR(kIOHIDProductKey)); 379 if (!refCF) { 380 refCF = CFDictionaryGetValue(usbProperties, 381 CFSTR("USB Product Name")); 382 } 383 if (refCF) { 384 if (!CFStringGetCString(refCF, name, 256, 385 CFStringGetSystemEncoding())) { 386 return SDL_SetError("Haptic: CFStringGetCString error retrieving pDevice->product."); 387 } 388 } 389 390 CFRelease(usbProperties); 391 } else { 392 return SDL_SetError("Haptic: IORegistryEntryCreateCFProperties failed to create usbProperties."); 393 } 394 395 /* Release stuff. */ 396 if (kIOReturnSuccess != IOObjectRelease(parent2)) { 397 SDL_SetError("Haptic: IOObjectRelease error with parent2."); 398 } 399 if (kIOReturnSuccess != IOObjectRelease(parent1)) { 400 SDL_SetError("Haptic: IOObjectRelease error with parent1."); 401 } 402 } else { 403 return SDL_SetError("Haptic: Error getting registry entries."); 404 } 405 406 return 0; 407} 408 409 410#define FF_TEST(ff, s) \ 411if (features.supportedEffects & (ff)) supported |= (s) 412/* 413 * Gets supported features. 414 */ 415static unsigned int 416GetSupportedFeatures(SDL_Haptic * haptic) 417{ 418 HRESULT ret; 419 FFDeviceObjectReference device; 420 FFCAPABILITIES features; 421 unsigned int supported; 422 Uint32 val; 423 424 device = haptic->hwdata->device; 425 426 ret = FFDeviceGetForceFeedbackCapabilities(device, &features); 427 if (ret != FF_OK) { 428 return SDL_SetError("Haptic: Unable to get device's supported features."); 429 } 430 431 supported = 0; 432 433 /* Get maximum effects. */ 434 haptic->neffects = features.storageCapacity; 435 haptic->nplaying = features.playbackCapacity; 436 437 /* Test for effects. */ 438 FF_TEST(FFCAP_ET_CONSTANTFORCE, SDL_HAPTIC_CONSTANT); 439 FF_TEST(FFCAP_ET_RAMPFORCE, SDL_HAPTIC_RAMP); 440 /* !!! FIXME: put this back when we have more bits in 2.1 */ 441 /* FF_TEST(FFCAP_ET_SQUARE, SDL_HAPTIC_SQUARE); */ 442 FF_TEST(FFCAP_ET_SINE, SDL_HAPTIC_SINE); 443 FF_TEST(FFCAP_ET_TRIANGLE, SDL_HAPTIC_TRIANGLE); 444 FF_TEST(FFCAP_ET_SAWTOOTHUP, SDL_HAPTIC_SAWTOOTHUP); 445 FF_TEST(FFCAP_ET_SAWTOOTHDOWN, SDL_HAPTIC_SAWTOOTHDOWN); 446 FF_TEST(FFCAP_ET_SPRING, SDL_HAPTIC_SPRING); 447 FF_TEST(FFCAP_ET_DAMPER, SDL_HAPTIC_DAMPER); 448 FF_TEST(FFCAP_ET_INERTIA, SDL_HAPTIC_INERTIA); 449 FF_TEST(FFCAP_ET_FRICTION, SDL_HAPTIC_FRICTION); 450 FF_TEST(FFCAP_ET_CUSTOMFORCE, SDL_HAPTIC_CUSTOM); 451 452 /* Check if supports gain. */ 453 ret = FFDeviceGetForceFeedbackProperty(device, FFPROP_FFGAIN, 454 &val, sizeof(val)); 455 if (ret == FF_OK) { 456 supported |= SDL_HAPTIC_GAIN; 457 } else if (ret != FFERR_UNSUPPORTED) { 458 return SDL_SetError("Haptic: Unable to get if device supports gain: %s.", 459 FFStrError(ret)); 460 } 461 462 /* Checks if supports autocenter. */ 463 ret = FFDeviceGetForceFeedbackProperty(device, FFPROP_AUTOCENTER, 464 &val, sizeof(val)); 465 if (ret == FF_OK) { 466 supported |= SDL_HAPTIC_AUTOCENTER; 467 } else if (ret != FFERR_UNSUPPORTED) { 468 return SDL_SetError 469 ("Haptic: Unable to get if device supports autocenter: %s.", 470 FFStrError(ret)); 471 } 472 473 /* Check for axes, we have an artificial limit on axes */ 474 haptic->naxes = ((features.numFfAxes) > 3) ? 3 : features.numFfAxes; 475 /* Actually store the axes we want to use */ 476 SDL_memcpy(haptic->hwdata->axes, features.ffAxes, 477 haptic->naxes * sizeof(Uint8)); 478 479 /* Always supported features. */ 480 supported |= SDL_HAPTIC_STATUS | SDL_HAPTIC_PAUSE; 481 482 haptic->supported = supported; 483 return 0; 484} 485 486 487/* 488 * Opens the haptic device from the file descriptor. 489 */ 490static int 491SDL_SYS_HapticOpenFromService(SDL_Haptic * haptic, io_service_t service) 492{ 493 HRESULT ret; 494 int ret2; 495 496 /* Allocate the hwdata */ 497 haptic->hwdata = (struct haptic_hwdata *) 498 SDL_malloc(sizeof(*haptic->hwdata)); 499 if (haptic->hwdata == NULL) { 500 SDL_OutOfMemory(); 501 goto creat_err; 502 } 503 SDL_memset(haptic->hwdata, 0, sizeof(*haptic->hwdata)); 504 505 /* Open the device */ 506 ret = FFCreateDevice(service, &haptic->hwdata->device); 507 if (ret != FF_OK) { 508 SDL_SetError("Haptic: Unable to create device from service: %s.", 509 FFStrError(ret)); 510 goto creat_err; 511 } 512 513 /* Get supported features. */ 514 ret2 = GetSupportedFeatures(haptic); 515 if (ret2 < 0) { 516 goto open_err; 517 } 518 519 520 /* Reset and then enable actuators. */ 521 ret = FFDeviceSendForceFeedbackCommand(haptic->hwdata->device, 522 FFSFFC_RESET); 523 if (ret != FF_OK) { 524 SDL_SetError("Haptic: Unable to reset device: %s.", FFStrError(ret)); 525 goto open_err; 526 } 527 ret = FFDeviceSendForceFeedbackCommand(haptic->hwdata->device, 528 FFSFFC_SETACTUATORSON); 529 if (ret != FF_OK) { 530 SDL_SetError("Haptic: Unable to enable actuators: %s.", 531 FFStrError(ret)); 532 goto open_err; 533 } 534 535 536 /* Allocate effects memory. */ 537 haptic->effects = (struct haptic_effect *) 538 SDL_malloc(sizeof(struct haptic_effect) * haptic->neffects); 539 if (haptic->effects == NULL) { 540 SDL_OutOfMemory(); 541 goto open_err; 542 } 543 /* Clear the memory */ 544 SDL_memset(haptic->effects, 0, 545 sizeof(struct haptic_effect) * haptic->neffects); 546 547 return 0; 548 549 /* Error handling */ 550 open_err: 551 FFReleaseDevice(haptic->hwdata->device); 552 creat_err: 553 if (haptic->hwdata != NULL) { 554 free(haptic->hwdata); 555 haptic->hwdata = NULL; 556 } 557 return -1; 558 559} 560 561 562/* 563 * Opens a haptic device for usage. 564 */ 565int 566SDL_SYS_HapticOpen(SDL_Haptic * haptic) 567{ 568 SDL_hapticlist_item *item; 569 item = HapticByDevIndex(haptic->index); 570 571 return SDL_SYS_HapticOpenFromService(haptic, item->dev); 572} 573 574 575/* 576 * Opens a haptic device from first mouse it finds for usage. 577 */ 578int 579SDL_SYS_HapticMouse(void) 580{ 581 int device_index = 0; 582 SDL_hapticlist_item *item; 583 584 for (item = SDL_hapticlist; item; item = item->next) { 585 if ((item->usagePage == kHIDPage_GenericDesktop) && 586 (item->usage == kHIDUsage_GD_Mouse)) { 587 return device_index; 588 } 589 ++device_index; 590 } 591 592 return -1; 593} 594 595 596/* 597 * Checks to see if a joystick has haptic features. 598 */ 599int 600SDL_SYS_JoystickIsHaptic(SDL_Joystick * joystick) 601{ 602 if (joystick->hwdata->ffservice != 0) { 603 return SDL_TRUE; 604 } 605 return SDL_FALSE; 606} 607 608 609/* 610 * Checks to see if the haptic device and joystick are in reality the same. 611 */ 612int 613SDL_SYS_JoystickSameHaptic(SDL_Haptic * haptic, SDL_Joystick * joystick) 614{ 615 if (IOObjectIsEqualTo((io_object_t) ((size_t)haptic->hwdata->device), 616 joystick->hwdata->ffservice)) { 617 return 1; 618 } 619 return 0; 620} 621 622 623/* 624 * Opens a SDL_Haptic from a SDL_Joystick. 625 */ 626int 627SDL_SYS_HapticOpenFromJoystick(SDL_Haptic * haptic, SDL_Joystick * joystick) 628{ 629 int device_index = 0; 630 SDL_hapticlist_item *item; 631 632 for (item = SDL_hapticlist; item; item = item->next) { 633 if (IOObjectIsEqualTo((io_object_t) item->dev, 634 joystick->hwdata->ffservice)) { 635 haptic->index = device_index; 636 break; 637 } 638 ++device_index; 639 } 640 641 return SDL_SYS_HapticOpenFromService(haptic, joystick->hwdata->ffservice); 642} 643 644 645/* 646 * Closes the haptic device. 647 */ 648void 649SDL_SYS_HapticClose(SDL_Haptic * haptic) 650{ 651 if (haptic->hwdata) { 652 653 /* Free Effects. */ 654 SDL_free(haptic->effects); 655 haptic->effects = NULL; 656 haptic->neffects = 0; 657 658 /* Clean up */ 659 FFReleaseDevice(haptic->hwdata->device); 660 661 /* Free */ 662 SDL_free(haptic->hwdata); 663 haptic->hwdata = NULL; 664 } 665} 666 667 668/* 669 * Clean up after system specific haptic stuff 670 */ 671void 672SDL_SYS_HapticQuit(void) 673{ 674 SDL_hapticlist_item *item; 675 SDL_hapticlist_item *next = NULL; 676 677 for (item = SDL_hapticlist; item; item = next) { 678 next = item->next; 679 /* Opened and not closed haptics are leaked, this is on purpose. 680 * Close your haptic devices after usage. */ 681 682 /* Free the io_service_t */ 683 IOObjectRelease(item->dev); 684 SDL_free(item); 685 } 686 numhaptics = -1; 687} 688 689 690/* 691 * Converts an SDL trigger button to an FFEFFECT trigger button. 692 */ 693static DWORD 694FFGetTriggerButton(Uint16 button) 695{ 696 DWORD dwTriggerButton; 697 698 dwTriggerButton = FFEB_NOTRIGGER; 699 700 if (button != 0) { 701 dwTriggerButton = FFJOFS_BUTTON(button - 1); 702 } 703 704 return dwTriggerButton; 705} 706 707 708/* 709 * Sets the direction. 710 */ 711static int 712SDL_SYS_SetDirection(FFEFFECT * effect, SDL_HapticDirection * dir, int naxes) 713{ 714 LONG *rglDir; 715 716 /* Handle no axes a part. */ 717 if (naxes == 0) { 718 effect->dwFlags |= FFEFF_SPHERICAL; /* Set as default. */ 719 effect->rglDirection = NULL; 720 return 0; 721 } 722 723 /* Has axes. */ 724 rglDir = SDL_malloc(sizeof(LONG) * naxes); 725 if (rglDir == NULL) { 726 return SDL_OutOfMemory(); 727 } 728 SDL_memset(rglDir, 0, sizeof(LONG) * naxes); 729 effect->rglDirection = rglDir; 730 731 switch (dir->type) { 732 case SDL_HAPTIC_POLAR: 733 effect->dwFlags |= FFEFF_POLAR; 734 rglDir[0] = dir->dir[0]; 735 return 0; 736 case SDL_HAPTIC_CARTESIAN: 737 effect->dwFlags |= FFEFF_CARTESIAN; 738 rglDir[0] = dir->dir[0]; 739 if (naxes > 1) { 740 rglDir[1] = dir->dir[1]; 741 } 742 if (naxes > 2) { 743 rglDir[2] = dir->dir[2]; 744 } 745 return 0; 746 case SDL_HAPTIC_SPHERICAL: 747 effect->dwFlags |= FFEFF_SPHERICAL; 748 rglDir[0] = dir->dir[0]; 749 if (naxes > 1) { 750 rglDir[1] = dir->dir[1]; 751 } 752 if (naxes > 2) { 753 rglDir[2] = dir->dir[2]; 754 } 755 return 0; 756 757 default: 758 return SDL_SetError("Haptic: Unknown direction type."); 759 } 760} 761 762 763/* Clamps and converts. */ 764#define CCONVERT(x) (((x) > 0x7FFF) ? 10000 : ((x)*10000) / 0x7FFF) 765/* Just converts. */ 766#define CONVERT(x) (((x)*10000) / 0x7FFF) 767/* 768 * Creates the FFEFFECT from a SDL_HapticEffect. 769 */ 770static int 771SDL_SYS_ToFFEFFECT(SDL_Haptic * haptic, FFEFFECT * dest, SDL_HapticEffect * src) 772{ 773 int i; 774 FFCONSTANTFORCE *constant; 775 FFPERIODIC *periodic; 776 FFCONDITION *condition; /* Actually an array of conditions - one per axis. */ 777 FFRAMPFORCE *ramp; 778 FFCUSTOMFORCE *custom; 779 FFENVELOPE *envelope; 780 SDL_HapticConstant *hap_constant; 781 SDL_HapticPeriodic *hap_periodic; 782 SDL_HapticCondition *hap_condition; 783 SDL_HapticRamp *hap_ramp; 784 SDL_HapticCustom *hap_custom; 785 DWORD *axes; 786 787 /* Set global stuff. */ 788 SDL_memset(dest, 0, sizeof(FFEFFECT)); 789 dest->dwSize = sizeof(FFEFFECT); /* Set the structure size. */ 790 dest->dwSamplePeriod = 0; /* Not used by us. */ 791 dest->dwGain = 10000; /* Gain is set globally, not locally. */ 792 dest->dwFlags = FFEFF_OBJECTOFFSETS; /* Seems obligatory. */ 793 794 /* Envelope. */ 795 envelope = SDL_malloc(sizeof(FFENVELOPE)); 796 if (envelope == NULL) { 797 return SDL_OutOfMemory(); 798 } 799 SDL_memset(envelope, 0, sizeof(FFENVELOPE)); 800 dest->lpEnvelope = envelope; 801 envelope->dwSize = sizeof(FFENVELOPE); /* Always should be this. */ 802 803 /* Axes. */ 804 dest->cAxes = haptic->naxes; 805 if (dest->cAxes > 0) { 806 axes = SDL_malloc(sizeof(DWORD) * dest->cAxes); 807 if (axes == NULL) { 808 return SDL_OutOfMemory(); 809 } 810 axes[0] = haptic->hwdata->axes[0]; /* Always at least one axis. */ 811 if (dest->cAxes > 1) { 812 axes[1] = haptic->hwdata->axes[1]; 813 } 814 if (dest->cAxes > 2) { 815 axes[2] = haptic->hwdata->axes[2]; 816 } 817 dest->rgdwAxes = axes; 818 } 819 820 821 /* The big type handling switch, even bigger then Linux's version. */ 822 switch (src->type) { 823 case SDL_HAPTIC_CONSTANT: 824 hap_constant = &src->constant; 825 constant = SDL_malloc(sizeof(FFCONSTANTFORCE)); 826 if (constant == NULL) { 827 return SDL_OutOfMemory(); 828 } 829 SDL_memset(constant, 0, sizeof(FFCONSTANTFORCE)); 830 831 /* Specifics */ 832 constant->lMagnitude = CONVERT(hap_constant->level); 833 dest->cbTypeSpecificParams = sizeof(FFCONSTANTFORCE); 834 dest->lpvTypeSpecificParams = constant; 835 836 /* Generics */ 837 dest->dwDuration = hap_constant->length * 1000; /* In microseconds. */ 838 dest->dwTriggerButton = FFGetTriggerButton(hap_constant->button); 839 dest->dwTriggerRepeatInterval = hap_constant->interval; 840 dest->dwStartDelay = hap_constant->delay * 1000; /* In microseconds. */ 841 842 /* Direction. */ 843 if (SDL_SYS_SetDirection(dest, &hap_constant->direction, dest->cAxes) 844 < 0) { 845 return -1; 846 } 847 848 /* Envelope */ 849 if ((hap_constant->attack_length == 0) 850 && (hap_constant->fade_length == 0)) { 851 SDL_free(envelope); 852 dest->lpEnvelope = NULL; 853 } else { 854 envelope->dwAttackLevel = CCONVERT(hap_constant->attack_level); 855 envelope->dwAttackTime = hap_constant->attack_length * 1000; 856 envelope->dwFadeLevel = CCONVERT(hap_constant->fade_level); 857 envelope->dwFadeTime = hap_constant->fade_length * 1000; 858 } 859 860 break; 861 862 case SDL_HAPTIC_SINE: 863 /* !!! FIXME: put this back when we have more bits in 2.1 */ 864 /* case SDL_HAPTIC_SQUARE: */ 865 case SDL_HAPTIC_TRIANGLE: 866 case SDL_HAPTIC_SAWTOOTHUP: 867 case SDL_HAPTIC_SAWTOOTHDOWN: 868 hap_periodic = &src->periodic; 869 periodic = SDL_malloc(sizeof(FFPERIODIC)); 870 if (periodic == NULL) { 871 return SDL_OutOfMemory(); 872 } 873 SDL_memset(periodic, 0, sizeof(FFPERIODIC)); 874 875 /* Specifics */ 876 periodic->dwMagnitude = CONVERT(SDL_abs(hap_periodic->magnitude)); 877 periodic->lOffset = CONVERT(hap_periodic->offset); 878 periodic->dwPhase = 879 (hap_periodic->phase + (hap_periodic->magnitude < 0 ? 18000 : 0)) % 36000; 880 periodic->dwPeriod = hap_periodic->period * 1000; 881 dest->cbTypeSpecificParams = sizeof(FFPERIODIC); 882 dest->lpvTypeSpecificParams = periodic; 883 884 /* Generics */ 885 dest->dwDuration = hap_periodic->length * 1000; /* In microseconds. */ 886 dest->dwTriggerButton = FFGetTriggerButton(hap_periodic->button); 887 dest->dwTriggerRepeatInterval = hap_periodic->interval; 888 dest->dwStartDelay = hap_periodic->delay * 1000; /* In microseconds. */ 889 890 /* Direction. */ 891 if (SDL_SYS_SetDirection(dest, &hap_periodic->direction, dest->cAxes) 892 < 0) { 893 return -1; 894 } 895 896 /* Envelope */ 897 if ((hap_periodic->attack_length == 0) 898 && (hap_periodic->fade_length == 0)) { 899 SDL_free(envelope); 900 dest->lpEnvelope = NULL; 901 } else { 902 envelope->dwAttackLevel = CCONVERT(hap_periodic->attack_level); 903 envelope->dwAttackTime = hap_periodic->attack_length * 1000; 904 envelope->dwFadeLevel = CCONVERT(hap_periodic->fade_level); 905 envelope->dwFadeTime = hap_periodic->fade_length * 1000; 906 } 907 908 break; 909 910 case SDL_HAPTIC_SPRING: 911 case SDL_HAPTIC_DAMPER: 912 case SDL_HAPTIC_INERTIA: 913 case SDL_HAPTIC_FRICTION: 914 hap_condition = &src->condition; 915 condition = SDL_malloc(sizeof(FFCONDITION) * dest->cAxes); 916 if (condition == NULL) { 917 return SDL_OutOfMemory(); 918 } 919 SDL_memset(condition, 0, sizeof(FFCONDITION)); 920 921 /* Specifics */ 922 for (i = 0; i < dest->cAxes; i++) { 923 condition[i].lOffset = CONVERT(hap_condition->center[i]); 924 condition[i].lPositiveCoefficient = 925 CONVERT(hap_condition->right_coeff[i]); 926 condition[i].lNegativeCoefficient = 927 CONVERT(hap_condition->left_coeff[i]); 928 condition[i].dwPositiveSaturation = 929 CCONVERT(hap_condition->right_sat[i] / 2); 930 condition[i].dwNegativeSaturation = 931 CCONVERT(hap_condition->left_sat[i] / 2); 932 condition[i].lDeadBand = CCONVERT(hap_condition->deadband[i] / 2); 933 } 934 dest->cbTypeSpecificParams = sizeof(FFCONDITION) * dest->cAxes; 935 dest->lpvTypeSpecificParams = condition; 936 937 /* Generics */ 938 dest->dwDuration = hap_condition->length * 1000; /* In microseconds. */ 939 dest->dwTriggerButton = FFGetTriggerButton(hap_condition->button); 940 dest->dwTriggerRepeatInterval = hap_condition->interval; 941 dest->dwStartDelay = hap_condition->delay * 1000; /* In microseconds. */ 942 943 /* Direction. */ 944 if (SDL_SYS_SetDirection(dest, &hap_condition->direction, dest->cAxes) 945 < 0) { 946 return -1; 947 } 948 949 /* Envelope - Not actually supported by most CONDITION implementations. */ 950 SDL_free(dest->lpEnvelope); 951 dest->lpEnvelope = NULL; 952 953 break; 954 955 case SDL_HAPTIC_RAMP: 956 hap_ramp = &src->ramp; 957 ramp = SDL_malloc(sizeof(FFRAMPFORCE)); 958 if (ramp == NULL) { 959 return SDL_OutOfMemory(); 960 } 961 SDL_memset(ramp, 0, sizeof(FFRAMPFORCE)); 962 963 /* Specifics */ 964 ramp->lStart = CONVERT(hap_ramp->start); 965 ramp->lEnd = CONVERT(hap_ramp->end); 966 dest->cbTypeSpecificParams = sizeof(FFRAMPFORCE); 967 dest->lpvTypeSpecificParams = ramp; 968 969 /* Generics */ 970 dest->dwDuration = hap_ramp->length * 1000; /* In microseconds. */ 971 dest->dwTriggerButton = FFGetTriggerButton(hap_ramp->button); 972 dest->dwTriggerRepeatInterval = hap_ramp->interval; 973 dest->dwStartDelay = hap_ramp->delay * 1000; /* In microseconds. */ 974 975 /* Direction. */ 976 if (SDL_SYS_SetDirection(dest, &hap_ramp->direction, dest->cAxes) < 0) { 977 return -1; 978 } 979 980 /* Envelope */ 981 if ((hap_ramp->attack_length == 0) && (hap_ramp->fade_length == 0)) { 982 SDL_free(envelope); 983 dest->lpEnvelope = NULL; 984 } else { 985 envelope->dwAttackLevel = CCONVERT(hap_ramp->attack_level); 986 envelope->dwAttackTime = hap_ramp->attack_length * 1000; 987 envelope->dwFadeLevel = CCONVERT(hap_ramp->fade_level); 988 envelope->dwFadeTime = hap_ramp->fade_length * 1000; 989 } 990 991 break; 992 993 case SDL_HAPTIC_CUSTOM: 994 hap_custom = &src->custom; 995 custom = SDL_malloc(sizeof(FFCUSTOMFORCE)); 996 if (custom == NULL) { 997 return SDL_OutOfMemory(); 998 } 999 SDL_memset(custom, 0, sizeof(FFCUSTOMFORCE)); 1000 1001 /* Specifics */ 1002 custom->cChannels = hap_custom->channels; 1003 custom->dwSamplePeriod = hap_custom->period * 1000; 1004 custom->cSamples = hap_custom->samples; 1005 custom->rglForceData = 1006 SDL_malloc(sizeof(LONG) * custom->cSamples * custom->cChannels); 1007 for (i = 0; i < hap_custom->samples * hap_custom->channels; i++) { /* Copy data. */ 1008 custom->rglForceData[i] = CCONVERT(hap_custom->data[i]); 1009 } 1010 dest->cbTypeSpecificParams = sizeof(FFCUSTOMFORCE); 1011 dest->lpvTypeSpecificParams = custom; 1012 1013 /* Generics */ 1014 dest->dwDuration = hap_custom->length * 1000; /* In microseconds. */ 1015 dest->dwTriggerButton = FFGetTriggerButton(hap_custom->button); 1016 dest->dwTriggerRepeatInterval = hap_custom->interval; 1017 dest->dwStartDelay = hap_custom->delay * 1000; /* In microseconds. */ 1018 1019 /* Direction. */ 1020 if (SDL_SYS_SetDirection(dest, &hap_custom->direction, dest->cAxes) < 1021 0) { 1022 return -1; 1023 } 1024 1025 /* Envelope */ 1026 if ((hap_custom->attack_length == 0) 1027 && (hap_custom->fade_length == 0)) { 1028 SDL_free(envelope); 1029 dest->lpEnvelope = NULL; 1030 } else { 1031 envelope->dwAttackLevel = CCONVERT(hap_custom->attack_level); 1032 envelope->dwAttackTime = hap_custom->attack_length * 1000; 1033 envelope->dwFadeLevel = CCONVERT(hap_custom->fade_level); 1034 envelope->dwFadeTime = hap_custom->fade_length * 1000; 1035 } 1036 1037 break; 1038 1039 1040 default: 1041 return SDL_SetError("Haptic: Unknown effect type."); 1042 } 1043 1044 return 0; 1045} 1046 1047 1048/* 1049 * Frees an FFEFFECT allocated by SDL_SYS_ToFFEFFECT. 1050 */ 1051static void 1052SDL_SYS_HapticFreeFFEFFECT(FFEFFECT * effect, int type) 1053{ 1054 FFCUSTOMFORCE *custom; 1055 1056 SDL_free(effect->lpEnvelope); 1057 effect->lpEnvelope = NULL; 1058 SDL_free(effect->rgdwAxes); 1059 effect->rgdwAxes = NULL; 1060 if (effect->lpvTypeSpecificParams != NULL) { 1061 if (type == SDL_HAPTIC_CUSTOM) { /* Must free the custom data. */ 1062 custom = (FFCUSTOMFORCE *) effect->lpvTypeSpecificParams; 1063 SDL_free(custom->rglForceData); 1064 custom->rglForceData = NULL; 1065 } 1066 SDL_free(effect->lpvTypeSpecificParams); 1067 effect->lpvTypeSpecificParams = NULL; 1068 } 1069 SDL_free(effect->rglDirection); 1070 effect->rglDirection = NULL; 1071} 1072 1073 1074/* 1075 * Gets the effect type from the generic SDL haptic effect wrapper. 1076 */ 1077CFUUIDRef 1078SDL_SYS_HapticEffectType(Uint16 type) 1079{ 1080 switch (type) { 1081 case SDL_HAPTIC_CONSTANT: 1082 return kFFEffectType_ConstantForce_ID; 1083 1084 case SDL_HAPTIC_RAMP: 1085 return kFFEffectType_RampForce_ID; 1086 1087 /* !!! FIXME: put this back when we have more bits in 2.1 */ 1088 /* case SDL_HAPTIC_SQUARE: 1089 return kFFEffectType_Square_ID; */ 1090 1091 case SDL_HAPTIC_SINE: 1092 return kFFEffectType_Sine_ID; 1093 1094 case SDL_HAPTIC_TRIANGLE: 1095 return kFFEffectType_Triangle_ID; 1096 1097 case SDL_HAPTIC_SAWTOOTHUP: 1098 return kFFEffectType_SawtoothUp_ID; 1099 1100 case SDL_HAPTIC_SAWTOOTHDOWN: 1101 return kFFEffectType_SawtoothDown_ID; 1102 1103 case SDL_HAPTIC_SPRING: 1104 return kFFEffectType_Spring_ID; 1105 1106 case SDL_HAPTIC_DAMPER: 1107 return kFFEffectType_Damper_ID; 1108 1109 case SDL_HAPTIC_INERTIA: 1110 return kFFEffectType_Inertia_ID; 1111 1112 case SDL_HAPTIC_FRICTION: 1113 return kFFEffectType_Friction_ID; 1114 1115 case SDL_HAPTIC_CUSTOM: 1116 return kFFEffectType_CustomForce_ID; 1117 1118 default: 1119 SDL_SetError("Haptic: Unknown effect type."); 1120 return NULL; 1121 } 1122} 1123 1124 1125/* 1126 * Creates a new haptic effect. 1127 */ 1128int 1129SDL_SYS_HapticNewEffect(SDL_Haptic * haptic, struct haptic_effect *effect, 1130 SDL_HapticEffect * base) 1131{ 1132 HRESULT ret; 1133 CFUUIDRef type; 1134 1135 /* Alloc the effect. */ 1136 effect->hweffect = (struct haptic_hweffect *) 1137 SDL_malloc(sizeof(struct haptic_hweffect)); 1138 if (effect->hweffect == NULL) { 1139 SDL_OutOfMemory(); 1140 goto err_hweffect; 1141 } 1142 1143 /* Get the type. */ 1144 type = SDL_SYS_HapticEffectType(base->type); 1145 if (type == NULL) { 1146 goto err_hweffect; 1147 } 1148 1149 /* Get the effect. */ 1150 if (SDL_SYS_ToFFEFFECT(haptic, &effect->hweffect->effect, base) < 0) { 1151 goto err_effectdone; 1152 } 1153 1154 /* Create the actual effect. */ 1155 ret = FFDeviceCreateEffect(haptic->hwdata->device, type, 1156 &effect->hweffect->effect, 1157 &effect->hweffect->ref); 1158 if (ret != FF_OK) { 1159 SDL_SetError("Haptic: Unable to create effect: %s.", FFStrError(ret)); 1160 goto err_effectdone; 1161 } 1162 1163 return 0; 1164 1165 err_effectdone: 1166 SDL_SYS_HapticFreeFFEFFECT(&effect->hweffect->effect, base->type); 1167 err_hweffect: 1168 SDL_free(effect->hweffect); 1169 effect->hweffect = NULL; 1170 return -1; 1171} 1172 1173 1174/* 1175 * Updates an effect. 1176 */ 1177int 1178SDL_SYS_HapticUpdateEffect(SDL_Haptic * haptic, 1179 struct haptic_effect *effect, 1180 SDL_HapticEffect * data) 1181{ 1182 HRESULT ret; 1183 FFEffectParameterFlag flags; 1184 FFEFFECT temp; 1185 1186 /* Get the effect. */ 1187 SDL_memset(&temp, 0, sizeof(FFEFFECT)); 1188 if (SDL_SYS_ToFFEFFECT(haptic, &temp, data) < 0) { 1189 goto err_update; 1190 } 1191 1192 /* Set the flags. Might be worthwhile to diff temp with loaded effect and 1193 * only change those parameters. */ 1194 flags = FFEP_DIRECTION | 1195 FFEP_DURATION | 1196 FFEP_ENVELOPE | 1197 FFEP_STARTDELAY | 1198 FFEP_TRIGGERBUTTON | 1199 FFEP_TRIGGERREPEATINTERVAL | FFEP_TYPESPECIFICPARAMS; 1200 1201 /* Create the actual effect. */ 1202 ret = FFEffectSetParameters(effect->hweffect->ref, &temp, flags); 1203 if (ret != FF_OK) { 1204 SDL_SetError("Haptic: Unable to update effect: %s.", FFStrError(ret)); 1205 goto err_update; 1206 } 1207 1208 /* Copy it over. */ 1209 SDL_SYS_HapticFreeFFEFFECT(&effect->hweffect->effect, data->type); 1210 SDL_memcpy(&effect->hweffect->effect, &temp, sizeof(FFEFFECT)); 1211 1212 return 0; 1213 1214 err_update: 1215 SDL_SYS_HapticFreeFFEFFECT(&temp, data->type); 1216 return -1; 1217} 1218 1219 1220/* 1221 * Runs an effect. 1222 */ 1223int 1224SDL_SYS_HapticRunEffect(SDL_Haptic * haptic, struct haptic_effect *effect, 1225 Uint32 iterations) 1226{ 1227 HRESULT ret; 1228 Uint32 iter; 1229 1230 /* Check if it's infinite. */ 1231 if (iterations == SDL_HAPTIC_INFINITY) { 1232 iter = FF_INFINITE; 1233 } else 1234 iter = iterations; 1235 1236 /* Run the effect. */ 1237 ret = FFEffectStart(effect->hweffect->ref, iter, 0); 1238 if (ret != FF_OK) { 1239 return SDL_SetError("Haptic: Unable to run the effect: %s.", 1240 FFStrError(ret)); 1241 } 1242 1243 return 0; 1244} 1245 1246 1247/* 1248 * Stops an effect. 1249 */ 1250int 1251SDL_SYS_HapticStopEffect(SDL_Haptic * haptic, struct haptic_effect *effect) 1252{ 1253 HRESULT ret; 1254 1255 ret = FFEffectStop(effect->hweffect->ref); 1256 if (ret != FF_OK) { 1257 return SDL_SetError("Haptic: Unable to stop the effect: %s.", 1258 FFStrError(ret)); 1259 } 1260 1261 return 0; 1262} 1263 1264 1265/* 1266 * Frees the effect. 1267 */ 1268void 1269SDL_SYS_HapticDestroyEffect(SDL_Haptic * haptic, struct haptic_effect *effect) 1270{ 1271 HRESULT ret; 1272 1273 ret = FFDeviceReleaseEffect(haptic->hwdata->device, effect->hweffect->ref); 1274 if (ret != FF_OK) { 1275 SDL_SetError("Haptic: Error removing the effect from the device: %s.", 1276 FFStrError(ret)); 1277 } 1278 SDL_SYS_HapticFreeFFEFFECT(&effect->hweffect->effect, 1279 effect->effect.type); 1280 SDL_free(effect->hweffect); 1281 effect->hweffect = NULL; 1282} 1283 1284 1285/* 1286 * Gets the status of a haptic effect. 1287 */ 1288int 1289SDL_SYS_HapticGetEffectStatus(SDL_Haptic * haptic, 1290 struct haptic_effect *effect) 1291{ 1292 HRESULT ret; 1293 FFEffectStatusFlag status; 1294 1295 ret = FFEffectGetEffectStatus(effect->hweffect->ref, &status); 1296 if (ret != FF_OK) { 1297 SDL_SetError("Haptic: Unable to get effect status: %s.", 1298 FFStrError(ret)); 1299 return -1; 1300 } 1301 1302 if (status == 0) { 1303 return SDL_FALSE; 1304 } 1305 return SDL_TRUE; /* Assume it's playing or emulated. */ 1306} 1307 1308 1309/* 1310 * Sets the gain. 1311 */ 1312int 1313SDL_SYS_HapticSetGain(SDL_Haptic * haptic, int gain) 1314{ 1315 HRESULT ret; 1316 Uint32 val; 1317 1318 val = gain * 100; /* Mac OS X uses 0 to 10,000 */ 1319 ret = FFDeviceSetForceFeedbackProperty(haptic->hwdata->device, 1320 FFPROP_FFGAIN, &val); 1321 if (ret != FF_OK) { 1322 return SDL_SetError("Haptic: Error setting gain: %s.", FFStrError(ret)); 1323 } 1324 1325 return 0; 1326} 1327 1328 1329/* 1330 * Sets the autocentering. 1331 */ 1332int 1333SDL_SYS_HapticSetAutocenter(SDL_Haptic * haptic, int autocenter) 1334{ 1335 HRESULT ret; 1336 Uint32 val; 1337 1338 /* Mac OS X only has 0 (off) and 1 (on) */ 1339 if (autocenter == 0) { 1340 val = 0; 1341 } else { 1342 val = 1; 1343 } 1344 1345 ret = FFDeviceSetForceFeedbackProperty(haptic->hwdata->device, 1346 FFPROP_AUTOCENTER, &val); 1347 if (ret != FF_OK) { 1348 return SDL_SetError("Haptic: Error setting autocenter: %s.", 1349 FFStrError(ret)); 1350 } 1351 1352 return 0; 1353} 1354 1355 1356/* 1357 * Pauses the device. 1358 */ 1359int 1360SDL_SYS_HapticPause(SDL_Haptic * haptic) 1361{ 1362 HRESULT ret; 1363 1364 ret = FFDeviceSendForceFeedbackCommand(haptic->hwdata->device, 1365 FFSFFC_PAUSE); 1366 if (ret != FF_OK) { 1367 return SDL_SetError("Haptic: Error pausing device: %s.", FFStrError(ret)); 1368 } 1369 1370 return 0; 1371} 1372 1373 1374/* 1375 * Unpauses the device. 1376 */ 1377int 1378SDL_SYS_HapticUnpause(SDL_Haptic * haptic) 1379{ 1380 HRESULT ret; 1381 1382 ret = FFDeviceSendForceFeedbackCommand(haptic->hwdata->device, 1383 FFSFFC_CONTINUE); 1384 if (ret != FF_OK) { 1385 return SDL_SetError("Haptic: Error pausing device: %s.", FFStrError(ret)); 1386 } 1387 1388 return 0; 1389} 1390 1391 1392/* 1393 * Stops all currently playing effects. 1394 */ 1395int 1396SDL_SYS_HapticStopAll(SDL_Haptic * haptic) 1397{ 1398 HRESULT ret; 1399 1400 ret = FFDeviceSendForceFeedbackCommand(haptic->hwdata->device, 1401 FFSFFC_STOPALL); 1402 if (ret != FF_OK) { 1403 return SDL_SetError("Haptic: Error stopping device: %s.", FFStrError(ret)); 1404 } 1405 1406 return 0; 1407} 1408 1409 1410#endif /* SDL_HAPTIC_IOKIT */