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