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