summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/haptic
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
committer3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
commit5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch)
tree8926ab44f168acf787d8e19608857b3af0f82758 /contrib/SDL-3.2.8/src/haptic
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/src/haptic')
-rw-r--r--contrib/SDL-3.2.8/src/haptic/SDL_haptic.c788
-rw-r--r--contrib/SDL-3.2.8/src/haptic/SDL_haptic_c.h28
-rw-r--r--contrib/SDL-3.2.8/src/haptic/SDL_syshaptic.h194
-rw-r--r--contrib/SDL-3.2.8/src/haptic/android/SDL_syshaptic.c307
-rw-r--r--contrib/SDL-3.2.8/src/haptic/android/SDL_syshaptic_c.h28
-rw-r--r--contrib/SDL-3.2.8/src/haptic/darwin/SDL_syshaptic.c1373
-rw-r--r--contrib/SDL-3.2.8/src/haptic/darwin/SDL_syshaptic_c.h29
-rw-r--r--contrib/SDL-3.2.8/src/haptic/dummy/SDL_syshaptic.c151
-rw-r--r--contrib/SDL-3.2.8/src/haptic/linux/SDL_syshaptic.c1134
-rw-r--r--contrib/SDL-3.2.8/src/haptic/windows/SDL_dinputhaptic.c1244
-rw-r--r--contrib/SDL-3.2.8/src/haptic/windows/SDL_dinputhaptic_c.h53
-rw-r--r--contrib/SDL-3.2.8/src/haptic/windows/SDL_windowshaptic.c369
-rw-r--r--contrib/SDL-3.2.8/src/haptic/windows/SDL_windowshaptic_c.h87
13 files changed, 5785 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/haptic/SDL_haptic.c b/contrib/SDL-3.2.8/src/haptic/SDL_haptic.c
new file mode 100644
index 0000000..1c11db6
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/haptic/SDL_haptic.c
@@ -0,0 +1,788 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 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#include "SDL_syshaptic.h"
24#include "SDL_haptic_c.h"
25#include "../joystick/SDL_joystick_c.h" // For SDL_IsJoystickValid
26#include "../SDL_hints_c.h"
27
28typedef struct SDL_Haptic_VIDPID_Naxes {
29 Uint16 vid;
30 Uint16 pid;
31 Uint16 naxes;
32} SDL_Haptic_VIDPID_Naxes;
33
34static void SDL_Haptic_Load_Axes_List(SDL_Haptic_VIDPID_Naxes **entries, int *num_entries)
35{
36 SDL_Haptic_VIDPID_Naxes entry;
37 const char *spot;
38 int length = 0;
39
40 spot = SDL_GetHint(SDL_HINT_JOYSTICK_HAPTIC_AXES);
41 if (!spot)
42 return;
43
44 while (SDL_sscanf(spot, "0x%hx/0x%hx/%hu%n", &entry.vid, &entry.pid, &entry.naxes, &length) == 3) {
45 SDL_assert(length > 0);
46 spot += length;
47 length = 0;
48
49 if ((*num_entries % 8) == 0) {
50 int new_max = *num_entries + 8;
51 SDL_Haptic_VIDPID_Naxes *new_entries =
52 (SDL_Haptic_VIDPID_Naxes *)SDL_realloc(*entries, new_max * sizeof(**entries));
53
54 // Out of memory, go with what we have already
55 if (!new_entries)
56 break;
57
58 *entries = new_entries;
59 }
60 (*entries)[(*num_entries)++] = entry;
61
62 if (spot[0] == ',')
63 spot++;
64 }
65}
66
67// /* Return -1 if not found */
68static int SDL_Haptic_Naxes_List_Index(struct SDL_Haptic_VIDPID_Naxes *entries, int num_entries, Uint16 vid, Uint16 pid)
69{
70 if (!entries)
71 return -1;
72
73 int i;
74 for (i = 0; i < num_entries; ++i) {
75 if (entries[i].vid == vid && entries[i].pid == pid)
76 return i;
77 }
78
79 return -1;
80}
81
82// Check if device needs a custom number of naxes
83static int SDL_Haptic_Get_Naxes(Uint16 vid, Uint16 pid)
84{
85 int num_entries = 0, index = 0, naxes = -1;
86 SDL_Haptic_VIDPID_Naxes *naxes_list = NULL;
87
88 SDL_Haptic_Load_Axes_List(&naxes_list, &num_entries);
89 if (!num_entries || !naxes_list)
90 return -1;
91
92 // Perform "wildcard" pass
93 index = SDL_Haptic_Naxes_List_Index(naxes_list, num_entries, 0xffff, 0xffff);
94 if (index >= 0)
95 naxes = naxes_list[index].naxes;
96
97 index = SDL_Haptic_Naxes_List_Index(naxes_list, num_entries, vid, pid);
98 if (index >= 0)
99 naxes = naxes_list[index].naxes;
100
101 SDL_free(naxes_list);
102 return naxes;
103}
104
105static SDL_Haptic *SDL_haptics = NULL;
106
107#define CHECK_HAPTIC_MAGIC(haptic, result) \
108 if (!SDL_ObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC)) { \
109 SDL_InvalidParamError("haptic"); \
110 return result; \
111 }
112
113bool SDL_InitHaptics(void)
114{
115 return SDL_SYS_HapticInit();
116}
117
118static bool SDL_GetHapticIndex(SDL_HapticID instance_id, int *driver_index)
119{
120 int num_haptics, device_index;
121
122 if (instance_id > 0) {
123 num_haptics = SDL_SYS_NumHaptics();
124 for (device_index = 0; device_index < num_haptics; ++device_index) {
125 SDL_HapticID haptic_id = SDL_SYS_HapticInstanceID(device_index);
126 if (haptic_id == instance_id) {
127 *driver_index = device_index;
128 return true;
129 }
130 }
131 }
132
133 SDL_SetError("Haptic device %" SDL_PRIu32 " not found", instance_id);
134 return false;
135}
136
137SDL_HapticID *SDL_GetHaptics(int *count)
138{
139 int device_index;
140 int haptic_index = 0, num_haptics = 0;
141 SDL_HapticID *haptics;
142
143 num_haptics = SDL_SYS_NumHaptics();
144
145 haptics = (SDL_HapticID *)SDL_malloc((num_haptics + 1) * sizeof(*haptics));
146 if (haptics) {
147 if (count) {
148 *count = num_haptics;
149 }
150
151 for (device_index = 0; device_index < num_haptics; ++device_index) {
152 haptics[haptic_index] = SDL_SYS_HapticInstanceID(device_index);
153 SDL_assert(haptics[haptic_index] > 0);
154 ++haptic_index;
155 }
156 haptics[haptic_index] = 0;
157 } else {
158 if (count) {
159 *count = 0;
160 }
161 }
162
163 return haptics;
164}
165
166const char *SDL_GetHapticNameForID(SDL_HapticID instance_id)
167{
168 int device_index;
169 const char *name = NULL;
170
171 if (SDL_GetHapticIndex(instance_id, &device_index)) {
172 name = SDL_GetPersistentString(SDL_SYS_HapticName(device_index));
173 }
174 return name;
175}
176
177SDL_Haptic *SDL_OpenHaptic(SDL_HapticID instance_id)
178{
179 SDL_Haptic *haptic;
180 SDL_Haptic *hapticlist;
181 const char *name;
182 int device_index = 0;
183
184 if (!SDL_GetHapticIndex(instance_id, &device_index)) {
185 return NULL;
186 }
187
188 hapticlist = SDL_haptics;
189 /* If the haptic device is already open, return it
190 * it is important that we have a single haptic device for each instance id
191 */
192 while (hapticlist) {
193 if (instance_id == hapticlist->instance_id) {
194 haptic = hapticlist;
195 ++haptic->ref_count;
196 return haptic;
197 }
198 hapticlist = hapticlist->next;
199 }
200
201 // Create the haptic device
202 haptic = (SDL_Haptic *)SDL_calloc(1, sizeof(*haptic));
203 if (!haptic) {
204 return NULL;
205 }
206
207 // Initialize the haptic device
208 SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, true);
209 haptic->instance_id = instance_id;
210 haptic->rumble_id = -1;
211 if (!SDL_SYS_HapticOpen(haptic)) {
212 SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, false);
213 SDL_free(haptic);
214 return NULL;
215 }
216
217 if (!haptic->name) {
218 name = SDL_SYS_HapticName(device_index);
219 if (name) {
220 haptic->name = SDL_strdup(name);
221 }
222 }
223
224 // Add haptic to list
225 ++haptic->ref_count;
226 // Link the haptic in the list
227 haptic->next = SDL_haptics;
228 SDL_haptics = haptic;
229
230 // Disable autocenter and set gain to max.
231 if (haptic->supported & SDL_HAPTIC_GAIN) {
232 SDL_SetHapticGain(haptic, 100);
233 }
234 if (haptic->supported & SDL_HAPTIC_AUTOCENTER) {
235 SDL_SetHapticAutocenter(haptic, 0);
236 }
237
238 return haptic;
239}
240
241SDL_Haptic *SDL_GetHapticFromID(SDL_HapticID instance_id)
242{
243 SDL_Haptic *haptic;
244
245 for (haptic = SDL_haptics; haptic; haptic = haptic->next) {
246 if (instance_id == haptic->instance_id) {
247 break;
248 }
249 }
250 return haptic;
251}
252
253SDL_HapticID SDL_GetHapticID(SDL_Haptic *haptic)
254{
255 CHECK_HAPTIC_MAGIC(haptic, 0);
256
257 return haptic->instance_id;
258}
259
260const char *SDL_GetHapticName(SDL_Haptic *haptic)
261{
262 CHECK_HAPTIC_MAGIC(haptic, NULL);
263
264 return SDL_GetPersistentString(haptic->name);
265}
266
267bool SDL_IsMouseHaptic(void)
268{
269 if (SDL_SYS_HapticMouse() < 0) {
270 return false;
271 }
272 return true;
273}
274
275SDL_Haptic *SDL_OpenHapticFromMouse(void)
276{
277 int device_index;
278
279 device_index = SDL_SYS_HapticMouse();
280
281 if (device_index < 0) {
282 SDL_SetError("Haptic: Mouse isn't a haptic device.");
283 return NULL;
284 }
285
286 return SDL_OpenHaptic(device_index);
287}
288
289bool SDL_IsJoystickHaptic(SDL_Joystick *joystick)
290{
291 bool result = false;
292
293 SDL_LockJoysticks();
294 {
295 // Must be a valid joystick
296 if (SDL_IsJoystickValid(joystick) &&
297 !SDL_IsGamepad(SDL_GetJoystickID(joystick))) {
298 result = SDL_SYS_JoystickIsHaptic(joystick);
299 }
300 }
301 SDL_UnlockJoysticks();
302
303 return result;
304}
305
306SDL_Haptic *SDL_OpenHapticFromJoystick(SDL_Joystick *joystick)
307{
308 SDL_Haptic *haptic;
309 SDL_Haptic *hapticlist;
310
311 SDL_LockJoysticks();
312 {
313 // Must be a valid joystick
314 if (!SDL_IsJoystickValid(joystick)) {
315 SDL_SetError("Haptic: Joystick isn't valid.");
316 SDL_UnlockJoysticks();
317 return NULL;
318 }
319
320 // Joystick must be haptic
321 if (SDL_IsGamepad(SDL_GetJoystickID(joystick)) ||
322 !SDL_SYS_JoystickIsHaptic(joystick)) {
323 SDL_SetError("Haptic: Joystick isn't a haptic device.");
324 SDL_UnlockJoysticks();
325 return NULL;
326 }
327
328 hapticlist = SDL_haptics;
329 // Check to see if joystick's haptic is already open
330 while (hapticlist) {
331 if (SDL_SYS_JoystickSameHaptic(hapticlist, joystick)) {
332 haptic = hapticlist;
333 ++haptic->ref_count;
334 SDL_UnlockJoysticks();
335 return haptic;
336 }
337 hapticlist = hapticlist->next;
338 }
339
340 // Create the haptic device
341 haptic = (SDL_Haptic *)SDL_calloc(1, sizeof(*haptic));
342 if (!haptic) {
343 SDL_UnlockJoysticks();
344 return NULL;
345 }
346
347 /* Initialize the haptic device
348 * This function should fill in the instance ID and name.
349 */
350 SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, true);
351 haptic->rumble_id = -1;
352 if (!SDL_SYS_HapticOpenFromJoystick(haptic, joystick)) {
353 SDL_SetError("Haptic: SDL_SYS_HapticOpenFromJoystick failed.");
354 SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, false);
355 SDL_free(haptic);
356 SDL_UnlockJoysticks();
357 return NULL;
358 }
359 SDL_assert(haptic->instance_id != 0);
360 }
361 SDL_UnlockJoysticks();
362
363 // Check if custom number of haptic axes was defined
364 Uint16 vid = SDL_GetJoystickVendor(joystick);
365 Uint16 pid = SDL_GetJoystickProduct(joystick);
366 int general_axes = SDL_GetNumJoystickAxes(joystick);
367
368 int naxes = SDL_Haptic_Get_Naxes(vid, pid);
369 if (naxes > 0)
370 haptic->naxes = naxes;
371
372 // Limit to the actual number of axes found on the device
373 if (general_axes >= 0 && naxes > general_axes)
374 haptic->naxes = general_axes;
375
376 // Add haptic to list
377 ++haptic->ref_count;
378 // Link the haptic in the list
379 haptic->next = SDL_haptics;
380 SDL_haptics = haptic;
381
382 return haptic;
383}
384
385void SDL_CloseHaptic(SDL_Haptic *haptic)
386{
387 int i;
388 SDL_Haptic *hapticlist;
389 SDL_Haptic *hapticlistprev;
390
391 CHECK_HAPTIC_MAGIC(haptic,);
392
393 // Check if it's still in use
394 if (--haptic->ref_count > 0) {
395 return;
396 }
397
398 // Close it, properly removing effects if needed
399 for (i = 0; i < haptic->neffects; i++) {
400 if (haptic->effects[i].hweffect != NULL) {
401 SDL_DestroyHapticEffect(haptic, i);
402 }
403 }
404 SDL_SYS_HapticClose(haptic);
405 SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, false);
406
407 // Remove from the list
408 hapticlist = SDL_haptics;
409 hapticlistprev = NULL;
410 while (hapticlist) {
411 if (haptic == hapticlist) {
412 if (hapticlistprev) {
413 // unlink this entry
414 hapticlistprev->next = hapticlist->next;
415 } else {
416 SDL_haptics = haptic->next;
417 }
418
419 break;
420 }
421 hapticlistprev = hapticlist;
422 hapticlist = hapticlist->next;
423 }
424
425 // Free the data associated with this device
426 SDL_free(haptic->name);
427 SDL_free(haptic);
428}
429
430void SDL_QuitHaptics(void)
431{
432 while (SDL_haptics) {
433 SDL_CloseHaptic(SDL_haptics);
434 }
435
436 SDL_SYS_HapticQuit();
437}
438
439int SDL_GetMaxHapticEffects(SDL_Haptic *haptic)
440{
441 CHECK_HAPTIC_MAGIC(haptic, -1);
442
443 return haptic->neffects;
444}
445
446int SDL_GetMaxHapticEffectsPlaying(SDL_Haptic *haptic)
447{
448 CHECK_HAPTIC_MAGIC(haptic, -1);
449
450 return haptic->nplaying;
451}
452
453Uint32 SDL_GetHapticFeatures(SDL_Haptic *haptic)
454{
455 CHECK_HAPTIC_MAGIC(haptic, 0);
456
457 return haptic->supported;
458}
459
460int SDL_GetNumHapticAxes(SDL_Haptic *haptic)
461{
462 CHECK_HAPTIC_MAGIC(haptic, -1);
463
464 return haptic->naxes;
465}
466
467bool SDL_HapticEffectSupported(SDL_Haptic *haptic, const SDL_HapticEffect *effect)
468{
469 CHECK_HAPTIC_MAGIC(haptic, false);
470
471 if (!effect) {
472 return false;
473 }
474
475 if ((haptic->supported & effect->type) != 0) {
476 return true;
477 }
478 return false;
479}
480
481int SDL_CreateHapticEffect(SDL_Haptic *haptic, const SDL_HapticEffect *effect)
482{
483 int i;
484
485 CHECK_HAPTIC_MAGIC(haptic, -1);
486
487 if (!effect) {
488 SDL_InvalidParamError("effect");
489 return -1;
490 }
491
492 // Check to see if effect is supported
493 if (SDL_HapticEffectSupported(haptic, effect) == false) {
494 SDL_SetError("Haptic: Effect not supported by haptic device.");
495 return -1;
496 }
497
498 // See if there's a free slot
499 for (i = 0; i < haptic->neffects; i++) {
500 if (haptic->effects[i].hweffect == NULL) {
501
502 // Now let the backend create the real effect
503 if (!SDL_SYS_HapticNewEffect(haptic, &haptic->effects[i], effect)) {
504 return -1; // Backend failed to create effect
505 }
506
507 SDL_memcpy(&haptic->effects[i].effect, effect,
508 sizeof(SDL_HapticEffect));
509 return i;
510 }
511 }
512
513 SDL_SetError("Haptic: Device has no free space left.");
514 return -1;
515}
516
517static bool ValidEffect(SDL_Haptic *haptic, int effect)
518{
519 if ((effect < 0) || (effect >= haptic->neffects)) {
520 SDL_SetError("Haptic: Invalid effect identifier.");
521 return false;
522 }
523 return true;
524}
525
526bool SDL_UpdateHapticEffect(SDL_Haptic *haptic, int effect, const SDL_HapticEffect *data)
527{
528 CHECK_HAPTIC_MAGIC(haptic, false);
529
530 if (!ValidEffect(haptic, effect)) {
531 return false;
532 }
533
534 if (!data) {
535 return SDL_InvalidParamError("data");
536 }
537
538 // Can't change type dynamically.
539 if (data->type != haptic->effects[effect].effect.type) {
540 return SDL_SetError("Haptic: Updating effect type is illegal.");
541 }
542
543 // Updates the effect
544 if (!SDL_SYS_HapticUpdateEffect(haptic, &haptic->effects[effect], data)) {
545 return false;
546 }
547
548 SDL_memcpy(&haptic->effects[effect].effect, data,
549 sizeof(SDL_HapticEffect));
550 return true;
551}
552
553bool SDL_RunHapticEffect(SDL_Haptic *haptic, int effect, Uint32 iterations)
554{
555 CHECK_HAPTIC_MAGIC(haptic, false);
556
557 if (!ValidEffect(haptic, effect)) {
558 return false;
559 }
560
561 // Run the effect
562 if (!SDL_SYS_HapticRunEffect(haptic, &haptic->effects[effect], iterations)) {
563 return false;
564 }
565
566 return true;
567}
568
569bool SDL_StopHapticEffect(SDL_Haptic *haptic, int effect)
570{
571 CHECK_HAPTIC_MAGIC(haptic, false);
572
573 if (!ValidEffect(haptic, effect)) {
574 return false;
575 }
576
577 // Stop the effect
578 if (!SDL_SYS_HapticStopEffect(haptic, &haptic->effects[effect])) {
579 return false;
580 }
581
582 return true;
583}
584
585void SDL_DestroyHapticEffect(SDL_Haptic *haptic, int effect)
586{
587 CHECK_HAPTIC_MAGIC(haptic,);
588
589 if (!ValidEffect(haptic, effect)) {
590 return;
591 }
592
593 // Not allocated
594 if (haptic->effects[effect].hweffect == NULL) {
595 return;
596 }
597
598 SDL_SYS_HapticDestroyEffect(haptic, &haptic->effects[effect]);
599}
600
601bool SDL_GetHapticEffectStatus(SDL_Haptic *haptic, int effect)
602{
603 CHECK_HAPTIC_MAGIC(haptic, false);
604
605 if (!ValidEffect(haptic, effect)) {
606 return false;
607 }
608
609 if (!(haptic->supported & SDL_HAPTIC_STATUS)) {
610 return SDL_SetError("Haptic: Device does not support status queries.");
611 }
612
613 SDL_ClearError();
614
615 return (SDL_SYS_HapticGetEffectStatus(haptic, &haptic->effects[effect]) > 0);
616}
617
618bool SDL_SetHapticGain(SDL_Haptic *haptic, int gain)
619{
620 const char *env;
621 int real_gain, max_gain;
622
623 CHECK_HAPTIC_MAGIC(haptic, false);
624
625 if (!(haptic->supported & SDL_HAPTIC_GAIN)) {
626 return SDL_SetError("Haptic: Device does not support setting gain.");
627 }
628
629 if ((gain < 0) || (gain > 100)) {
630 return SDL_SetError("Haptic: Gain must be between 0 and 100.");
631 }
632
633 // The user can use an environment variable to override the max gain.
634 env = SDL_getenv("SDL_HAPTIC_GAIN_MAX");
635 if (env) {
636 max_gain = SDL_atoi(env);
637
638 // Check for sanity.
639 if (max_gain < 0) {
640 max_gain = 0;
641 } else if (max_gain > 100) {
642 max_gain = 100;
643 }
644
645 // We'll scale it linearly with SDL_HAPTIC_GAIN_MAX
646 real_gain = (gain * max_gain) / 100;
647 } else {
648 real_gain = gain;
649 }
650
651 return SDL_SYS_HapticSetGain(haptic, real_gain);
652}
653
654bool SDL_SetHapticAutocenter(SDL_Haptic *haptic, int autocenter)
655{
656 CHECK_HAPTIC_MAGIC(haptic, false);
657
658 if (!(haptic->supported & SDL_HAPTIC_AUTOCENTER)) {
659 return SDL_SetError("Haptic: Device does not support setting autocenter.");
660 }
661
662 if ((autocenter < 0) || (autocenter > 100)) {
663 return SDL_SetError("Haptic: Autocenter must be between 0 and 100.");
664 }
665
666 return SDL_SYS_HapticSetAutocenter(haptic, autocenter);
667}
668
669bool SDL_PauseHaptic(SDL_Haptic *haptic)
670{
671 CHECK_HAPTIC_MAGIC(haptic, false);
672
673 if (!(haptic->supported & SDL_HAPTIC_PAUSE)) {
674 return SDL_SetError("Haptic: Device does not support setting pausing.");
675 }
676
677 return SDL_SYS_HapticPause(haptic);
678}
679
680bool SDL_ResumeHaptic(SDL_Haptic *haptic)
681{
682 CHECK_HAPTIC_MAGIC(haptic, false);
683
684 if (!(haptic->supported & SDL_HAPTIC_PAUSE)) {
685 return true; // Not going to be paused, so we pretend it's unpaused.
686 }
687
688 return SDL_SYS_HapticResume(haptic);
689}
690
691bool SDL_StopHapticEffects(SDL_Haptic *haptic)
692{
693 CHECK_HAPTIC_MAGIC(haptic, false);
694
695 return SDL_SYS_HapticStopAll(haptic);
696}
697
698bool SDL_HapticRumbleSupported(SDL_Haptic *haptic)
699{
700 CHECK_HAPTIC_MAGIC(haptic, false);
701
702 // Most things can use SINE, but XInput only has LEFTRIGHT.
703 return (haptic->supported & (SDL_HAPTIC_SINE | SDL_HAPTIC_LEFTRIGHT)) != 0;
704}
705
706bool SDL_InitHapticRumble(SDL_Haptic *haptic)
707{
708 SDL_HapticEffect *efx = &haptic->rumble_effect;
709
710 CHECK_HAPTIC_MAGIC(haptic, false);
711
712 // Already allocated.
713 if (haptic->rumble_id >= 0) {
714 return true;
715 }
716
717 SDL_zerop(efx);
718 if (haptic->supported & SDL_HAPTIC_SINE) {
719 efx->type = SDL_HAPTIC_SINE;
720 efx->periodic.direction.type = SDL_HAPTIC_CARTESIAN;
721 efx->periodic.period = 1000;
722 efx->periodic.magnitude = 0x4000;
723 efx->periodic.length = 5000;
724 efx->periodic.attack_length = 0;
725 efx->periodic.fade_length = 0;
726 } else if (haptic->supported & SDL_HAPTIC_LEFTRIGHT) { // XInput?
727 efx->type = SDL_HAPTIC_LEFTRIGHT;
728 efx->leftright.length = 5000;
729 efx->leftright.large_magnitude = 0x4000;
730 efx->leftright.small_magnitude = 0x4000;
731 } else {
732 return SDL_SetError("Device doesn't support rumble");
733 }
734
735 haptic->rumble_id = SDL_CreateHapticEffect(haptic, &haptic->rumble_effect);
736 if (haptic->rumble_id >= 0) {
737 return true;
738 }
739 return false;
740}
741
742bool SDL_PlayHapticRumble(SDL_Haptic *haptic, float strength, Uint32 length)
743{
744 SDL_HapticEffect *efx;
745 Sint16 magnitude;
746
747 CHECK_HAPTIC_MAGIC(haptic, false);
748
749 if (haptic->rumble_id < 0) {
750 return SDL_SetError("Haptic: Rumble effect not initialized on haptic device");
751 }
752
753 // Clamp strength.
754 if (strength > 1.0f) {
755 strength = 1.0f;
756 } else if (strength < 0.0f) {
757 strength = 0.0f;
758 }
759 magnitude = (Sint16)(32767.0f * strength);
760
761 efx = &haptic->rumble_effect;
762 if (efx->type == SDL_HAPTIC_SINE) {
763 efx->periodic.magnitude = magnitude;
764 efx->periodic.length = length;
765 } else if (efx->type == SDL_HAPTIC_LEFTRIGHT) {
766 efx->leftright.small_magnitude = efx->leftright.large_magnitude = magnitude;
767 efx->leftright.length = length;
768 } else {
769 SDL_assert(!"This should have been caught elsewhere");
770 }
771
772 if (!SDL_UpdateHapticEffect(haptic, haptic->rumble_id, &haptic->rumble_effect)) {
773 return false;
774 }
775
776 return SDL_RunHapticEffect(haptic, haptic->rumble_id, 1);
777}
778
779bool SDL_StopHapticRumble(SDL_Haptic *haptic)
780{
781 CHECK_HAPTIC_MAGIC(haptic, false);
782
783 if (haptic->rumble_id < 0) {
784 return SDL_SetError("Haptic: Rumble effect not initialized on haptic device");
785 }
786
787 return SDL_StopHapticEffect(haptic, haptic->rumble_id);
788}
diff --git a/contrib/SDL-3.2.8/src/haptic/SDL_haptic_c.h b/contrib/SDL-3.2.8/src/haptic/SDL_haptic_c.h
new file mode 100644
index 0000000..3f5ce87
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/haptic/SDL_haptic_c.h
@@ -0,0 +1,28 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 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
22#ifndef SDL_haptic_c_h_
23#define SDL_haptic_c_h_
24
25extern bool SDL_InitHaptics(void);
26extern void SDL_QuitHaptics(void);
27
28#endif // SDL_haptic_c_h_
diff --git a/contrib/SDL-3.2.8/src/haptic/SDL_syshaptic.h b/contrib/SDL-3.2.8/src/haptic/SDL_syshaptic.h
new file mode 100644
index 0000000..ec60a71
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/haptic/SDL_syshaptic.h
@@ -0,0 +1,194 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 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
22#include "SDL_internal.h"
23
24#ifndef SDL_syshaptic_h_
25#define SDL_syshaptic_h_
26
27// Set up for C function definitions, even when using C++
28#ifdef __cplusplus
29extern "C" {
30#endif
31
32struct haptic_effect
33{
34 SDL_HapticEffect effect; // The current event
35 struct haptic_hweffect *hweffect; // The hardware behind the event
36};
37
38/*
39 * The real SDL_Haptic struct.
40 */
41struct SDL_Haptic
42{
43 SDL_HapticID instance_id; // Device instance, monotonically increasing from 0
44 char *name; // Device name - system dependent
45
46 struct haptic_effect *effects; // Allocated effects
47 int neffects; // Maximum amount of effects
48 int nplaying; // Maximum amount of effects to play at the same time
49 Uint32 supported; // Supported effects and features
50 int naxes; // Number of axes on the device.
51
52 struct haptic_hwdata *hwdata; // Driver dependent
53 int ref_count; // Count for multiple opens
54
55 int rumble_id; // ID of rumble effect for simple rumble API.
56 SDL_HapticEffect rumble_effect; // Rumble effect.
57 struct SDL_Haptic *next; // pointer to next haptic we have allocated
58};
59
60/*
61 * Scans the system for haptic devices.
62 *
63 * Returns number of devices on success, -1 on error.
64 */
65extern bool SDL_SYS_HapticInit(void);
66
67// Function to return the number of haptic devices plugged in right now
68extern int SDL_SYS_NumHaptics(void);
69
70/*
71 * Gets the instance ID of the haptic device
72 */
73extern SDL_HapticID SDL_SYS_HapticInstanceID(int index);
74
75/*
76 * Gets the device dependent name of the haptic device
77 */
78extern const char *SDL_SYS_HapticName(int index);
79
80/*
81 * Opens the haptic device for usage. The haptic device should have
82 * the index value set previously.
83 */
84extern bool SDL_SYS_HapticOpen(SDL_Haptic *haptic);
85
86/*
87 * Returns the index of the haptic core pointer or -1 if none is found.
88 */
89extern int SDL_SYS_HapticMouse(void);
90
91/*
92 * Checks to see if the joystick has haptic capabilities.
93 */
94extern bool SDL_SYS_JoystickIsHaptic(SDL_Joystick *joystick);
95
96/*
97 * Opens the haptic device for usage using the same device as
98 * the joystick.
99 */
100extern bool SDL_SYS_HapticOpenFromJoystick(SDL_Haptic *haptic,
101 SDL_Joystick *joystick);
102/*
103 * Checks to see if haptic device and joystick device are the same.
104 *
105 * Returns true if they are the same, false if they aren't.
106 */
107extern bool SDL_SYS_JoystickSameHaptic(SDL_Haptic *haptic,
108 SDL_Joystick *joystick);
109
110/*
111 * Closes a haptic device after usage.
112 */
113extern void SDL_SYS_HapticClose(SDL_Haptic *haptic);
114
115/*
116 * Performs a cleanup on the haptic subsystem.
117 */
118extern void SDL_SYS_HapticQuit(void);
119
120/*
121 * Creates a new haptic effect on the haptic device using base
122 * as a template for the effect.
123 */
124extern bool SDL_SYS_HapticNewEffect(SDL_Haptic *haptic,
125 struct haptic_effect *effect,
126 const SDL_HapticEffect *base);
127
128/*
129 * Updates the haptic effect on the haptic device using data
130 * as a template.
131 */
132extern bool SDL_SYS_HapticUpdateEffect(SDL_Haptic *haptic,
133 struct haptic_effect *effect,
134 const SDL_HapticEffect *data);
135
136/*
137 * Runs the effect on the haptic device.
138 */
139extern bool SDL_SYS_HapticRunEffect(SDL_Haptic *haptic,
140 struct haptic_effect *effect,
141 Uint32 iterations);
142
143/*
144 * Stops the effect on the haptic device.
145 */
146extern bool SDL_SYS_HapticStopEffect(SDL_Haptic *haptic,
147 struct haptic_effect *effect);
148
149/*
150 * Cleanups up the effect on the haptic device.
151 */
152extern void SDL_SYS_HapticDestroyEffect(SDL_Haptic *haptic,
153 struct haptic_effect *effect);
154
155/*
156 * Queries the device for the status of effect.
157 *
158 * Returns 0 if device is stopped, >0 if device is playing and
159 * -1 on error.
160 */
161extern int SDL_SYS_HapticGetEffectStatus(SDL_Haptic *haptic,
162 struct haptic_effect *effect);
163
164/*
165 * Sets the global gain of the haptic device.
166 */
167extern bool SDL_SYS_HapticSetGain(SDL_Haptic *haptic, int gain);
168
169/*
170 * Sets the autocenter feature of the haptic device.
171 */
172extern bool SDL_SYS_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter);
173
174/*
175 * Pauses the haptic device.
176 */
177extern bool SDL_SYS_HapticPause(SDL_Haptic *haptic);
178
179/*
180 * Unpauses the haptic device.
181 */
182extern bool SDL_SYS_HapticResume(SDL_Haptic *haptic);
183
184/*
185 * Stops all the currently playing haptic effects on the device.
186 */
187extern bool SDL_SYS_HapticStopAll(SDL_Haptic *haptic);
188
189// Ends C function definitions when using C++
190#ifdef __cplusplus
191}
192#endif
193
194#endif // SDL_syshaptic_h_
diff --git a/contrib/SDL-3.2.8/src/haptic/android/SDL_syshaptic.c b/contrib/SDL-3.2.8/src/haptic/android/SDL_syshaptic.c
new file mode 100644
index 0000000..d155dbc
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/haptic/android/SDL_syshaptic.c
@@ -0,0 +1,307 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 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_ANDROID
24
25#include "SDL_syshaptic_c.h"
26#include "../SDL_syshaptic.h"
27#include "../../core/android/SDL_android.h"
28
29typedef struct SDL_hapticlist_item
30{
31 SDL_HapticID instance_id;
32 int device_id;
33 char *name;
34 SDL_Haptic *haptic;
35 struct SDL_hapticlist_item *next;
36} SDL_hapticlist_item;
37
38static SDL_hapticlist_item *SDL_hapticlist = NULL;
39static SDL_hapticlist_item *SDL_hapticlist_tail = NULL;
40static int numhaptics = 0;
41
42bool SDL_SYS_HapticInit(void)
43{
44 Android_JNI_PollHapticDevices();
45
46 return true;
47}
48
49int SDL_SYS_NumHaptics(void)
50{
51 return numhaptics;
52}
53
54static SDL_hapticlist_item *HapticByOrder(int index)
55{
56 SDL_hapticlist_item *item = SDL_hapticlist;
57 if ((index < 0) || (index >= numhaptics)) {
58 return NULL;
59 }
60 while (index > 0) {
61 SDL_assert(item != NULL);
62 --index;
63 item = item->next;
64 }
65 return item;
66}
67
68static SDL_hapticlist_item *HapticByInstanceID(SDL_HapticID instance_id)
69{
70 SDL_hapticlist_item *item;
71 for (item = SDL_hapticlist; item; item = item->next) {
72 if (instance_id == item->instance_id) {
73 return item;
74 }
75 }
76 return NULL;
77}
78
79SDL_HapticID SDL_SYS_HapticInstanceID(int index)
80{
81 SDL_hapticlist_item *item = HapticByOrder(index);
82 if (item) {
83 return item->instance_id;
84 }
85 return 0;
86}
87
88const char *SDL_SYS_HapticName(int index)
89{
90 SDL_hapticlist_item *item = HapticByOrder(index);
91 if (!item) {
92 SDL_SetError("No such device");
93 return NULL;
94 }
95 return item->name;
96}
97
98static SDL_hapticlist_item *OpenHaptic(SDL_Haptic *haptic, SDL_hapticlist_item *item)
99{
100 if (!item) {
101 SDL_SetError("No such device");
102 return NULL;
103 }
104 if (item->haptic) {
105 SDL_SetError("Haptic already opened");
106 return NULL;
107 }
108
109 haptic->hwdata = (struct haptic_hwdata *)item;
110 item->haptic = haptic;
111
112 haptic->instance_id = item->instance_id;
113 if (item->name) {
114 haptic->name = SDL_strdup(item->name);
115 }
116 haptic->supported = SDL_HAPTIC_LEFTRIGHT;
117 haptic->neffects = 1;
118 haptic->nplaying = haptic->neffects;
119 haptic->effects = (struct haptic_effect *)SDL_calloc(haptic->neffects, sizeof(struct haptic_effect));
120 if (!haptic->effects) {
121 return NULL;
122 }
123 return item;
124}
125
126static SDL_hapticlist_item *OpenHapticByInstanceID(SDL_Haptic *haptic, SDL_HapticID instance_id)
127{
128 return OpenHaptic(haptic, HapticByInstanceID(instance_id));
129}
130
131bool SDL_SYS_HapticOpen(SDL_Haptic *haptic)
132{
133 return OpenHapticByInstanceID(haptic, haptic->instance_id) != NULL;
134}
135
136int SDL_SYS_HapticMouse(void)
137{
138 return -1;
139}
140
141bool SDL_SYS_JoystickIsHaptic(SDL_Joystick *joystick)
142{
143 return false;
144}
145
146bool SDL_SYS_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick)
147{
148 return SDL_Unsupported();
149}
150
151bool SDL_SYS_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick)
152{
153 return false;
154}
155
156void SDL_SYS_HapticClose(SDL_Haptic *haptic)
157{
158 ((SDL_hapticlist_item *)haptic->hwdata)->haptic = NULL;
159 haptic->hwdata = NULL;
160}
161
162void SDL_SYS_HapticQuit(void)
163{
164/* We don't have any way to scan for joysticks (and their vibrators) at init, so don't wipe the list
165 * of joysticks here in case this is a reinit.
166 */
167#if 0
168 SDL_hapticlist_item *item = NULL;
169 SDL_hapticlist_item *next = NULL;
170
171 for (item = SDL_hapticlist; item; item = next) {
172 next = item->next;
173 SDL_free(item);
174 }
175
176 SDL_hapticlist = SDL_hapticlist_tail = NULL;
177 numhaptics = 0;
178 return;
179#endif
180}
181
182bool SDL_SYS_HapticNewEffect(SDL_Haptic *haptic,
183 struct haptic_effect *effect, const SDL_HapticEffect *base)
184{
185 return true;
186}
187
188bool SDL_SYS_HapticUpdateEffect(SDL_Haptic *haptic,
189 struct haptic_effect *effect,
190 const SDL_HapticEffect *data)
191{
192 return true;
193}
194
195bool SDL_SYS_HapticRunEffect(SDL_Haptic *haptic, struct haptic_effect *effect,
196 Uint32 iterations)
197{
198 float large = effect->effect.leftright.large_magnitude / 32767.0f;
199 float small = effect->effect.leftright.small_magnitude / 32767.0f;
200
201 float total = (large * 0.6f) + (small * 0.4f);
202
203 Android_JNI_HapticRun(((SDL_hapticlist_item *)haptic->hwdata)->device_id, total, effect->effect.leftright.length);
204 return true;
205}
206
207bool SDL_SYS_HapticStopEffect(SDL_Haptic *haptic, struct haptic_effect *effect)
208{
209 Android_JNI_HapticStop(((SDL_hapticlist_item *)haptic->hwdata)->device_id);
210 return true;
211}
212
213void SDL_SYS_HapticDestroyEffect(SDL_Haptic *haptic, struct haptic_effect *effect)
214{
215}
216
217int SDL_SYS_HapticGetEffectStatus(SDL_Haptic *haptic, struct haptic_effect *effect)
218{
219 return 0;
220}
221
222bool SDL_SYS_HapticSetGain(SDL_Haptic *haptic, int gain)
223{
224 return true;
225}
226
227bool SDL_SYS_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter)
228{
229 return true;
230}
231
232bool SDL_SYS_HapticPause(SDL_Haptic *haptic)
233{
234 return true;
235}
236
237bool SDL_SYS_HapticResume(SDL_Haptic *haptic)
238{
239 return true;
240}
241
242bool SDL_SYS_HapticStopAll(SDL_Haptic *haptic)
243{
244 return true;
245}
246
247bool Android_AddHaptic(int device_id, const char *name)
248{
249 SDL_hapticlist_item *item;
250 item = (SDL_hapticlist_item *)SDL_calloc(1, sizeof(SDL_hapticlist_item));
251 if (!item) {
252 return false;
253 }
254
255 item->instance_id = SDL_GetNextObjectID();
256 item->device_id = device_id;
257 item->name = SDL_strdup(name);
258 if (!item->name) {
259 SDL_free(item);
260 return false;
261 }
262
263 if (!SDL_hapticlist_tail) {
264 SDL_hapticlist = SDL_hapticlist_tail = item;
265 } else {
266 SDL_hapticlist_tail->next = item;
267 SDL_hapticlist_tail = item;
268 }
269
270 ++numhaptics;
271 return true;
272}
273
274bool Android_RemoveHaptic(int device_id)
275{
276 SDL_hapticlist_item *item;
277 SDL_hapticlist_item *prev = NULL;
278
279 for (item = SDL_hapticlist; item; item = item->next) {
280 // found it, remove it.
281 if (device_id == item->device_id) {
282 const bool result = item->haptic ? true : false;
283
284 if (prev) {
285 prev->next = item->next;
286 } else {
287 SDL_assert(SDL_hapticlist == item);
288 SDL_hapticlist = item->next;
289 }
290 if (item == SDL_hapticlist_tail) {
291 SDL_hapticlist_tail = prev;
292 }
293
294 // Need to decrement the haptic count
295 --numhaptics;
296 // !!! TODO: Send a haptic remove event?
297
298 SDL_free(item->name);
299 SDL_free(item);
300 return result;
301 }
302 prev = item;
303 }
304 return false;
305}
306
307#endif // SDL_HAPTIC_ANDROID
diff --git a/contrib/SDL-3.2.8/src/haptic/android/SDL_syshaptic_c.h b/contrib/SDL-3.2.8/src/haptic/android/SDL_syshaptic_c.h
new file mode 100644
index 0000000..4f58f4f
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/haptic/android/SDL_syshaptic_c.h
@@ -0,0 +1,28 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 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_ANDROID
24
25extern bool Android_AddHaptic(int device_id, const char *name);
26extern bool Android_RemoveHaptic(int device_id);
27
28#endif // SDL_HAPTIC_ANDROID
diff --git a/contrib/SDL-3.2.8/src/haptic/darwin/SDL_syshaptic.c b/contrib/SDL-3.2.8/src/haptic/darwin/SDL_syshaptic.c
new file mode 100644
index 0000000..b48a3fe
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/haptic/darwin/SDL_syshaptic.c
@@ -0,0 +1,1373 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 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_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
diff --git a/contrib/SDL-3.2.8/src/haptic/darwin/SDL_syshaptic_c.h b/contrib/SDL-3.2.8/src/haptic/darwin/SDL_syshaptic_c.h
new file mode 100644
index 0000000..03c67ec
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/haptic/darwin/SDL_syshaptic_c.h
@@ -0,0 +1,29 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 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
22// Things named "Master" were renamed to "Main" in macOS 12.0's SDK.
23#include <AvailabilityMacros.h>
24#if MAC_OS_X_VERSION_MIN_REQUIRED < 120000
25#define kIOMainPortDefault kIOMasterPortDefault
26#endif
27
28extern bool MacHaptic_MaybeAddDevice(io_object_t device);
29extern bool MacHaptic_MaybeRemoveDevice(io_object_t device);
diff --git a/contrib/SDL-3.2.8/src/haptic/dummy/SDL_syshaptic.c b/contrib/SDL-3.2.8/src/haptic/dummy/SDL_syshaptic.c
new file mode 100644
index 0000000..81f711f
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/haptic/dummy/SDL_syshaptic.c
@@ -0,0 +1,151 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 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#if defined(SDL_HAPTIC_DUMMY) || defined(SDL_HAPTIC_DISABLED)
24
25#include "../SDL_syshaptic.h"
26
27static bool SDL_SYS_LogicError(void)
28{
29 return SDL_SetError("Logic error: No haptic devices available.");
30}
31
32bool SDL_SYS_HapticInit(void)
33{
34 return true;
35}
36
37int SDL_SYS_NumHaptics(void)
38{
39 return 0;
40}
41
42SDL_HapticID SDL_SYS_HapticInstanceID(int index)
43{
44 SDL_SYS_LogicError();
45 return 0;
46}
47
48const char *SDL_SYS_HapticName(int index)
49{
50 SDL_SYS_LogicError();
51 return NULL;
52}
53
54bool SDL_SYS_HapticOpen(SDL_Haptic *haptic)
55{
56 return SDL_SYS_LogicError();
57}
58
59int SDL_SYS_HapticMouse(void)
60{
61 return -1;
62}
63
64bool SDL_SYS_JoystickIsHaptic(SDL_Joystick *joystick)
65{
66 return false;
67}
68
69bool SDL_SYS_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick)
70{
71 return SDL_SYS_LogicError();
72}
73
74bool SDL_SYS_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick)
75{
76 return false;
77}
78
79void SDL_SYS_HapticClose(SDL_Haptic *haptic)
80{
81 return;
82}
83
84void SDL_SYS_HapticQuit(void)
85{
86 return;
87}
88
89bool SDL_SYS_HapticNewEffect(SDL_Haptic *haptic,
90 struct haptic_effect *effect, const SDL_HapticEffect *base)
91{
92 return SDL_SYS_LogicError();
93}
94
95bool SDL_SYS_HapticUpdateEffect(SDL_Haptic *haptic,
96 struct haptic_effect *effect,
97 const SDL_HapticEffect *data)
98{
99 return SDL_SYS_LogicError();
100}
101
102bool SDL_SYS_HapticRunEffect(SDL_Haptic *haptic, struct haptic_effect *effect,
103 Uint32 iterations)
104{
105 return SDL_SYS_LogicError();
106}
107
108bool SDL_SYS_HapticStopEffect(SDL_Haptic *haptic, struct haptic_effect *effect)
109{
110 return SDL_SYS_LogicError();
111}
112
113void SDL_SYS_HapticDestroyEffect(SDL_Haptic *haptic, struct haptic_effect *effect)
114{
115 SDL_SYS_LogicError();
116 return;
117}
118
119int SDL_SYS_HapticGetEffectStatus(SDL_Haptic *haptic,
120 struct haptic_effect *effect)
121{
122 SDL_SYS_LogicError();
123 return -1;
124}
125
126bool SDL_SYS_HapticSetGain(SDL_Haptic *haptic, int gain)
127{
128 return SDL_SYS_LogicError();
129}
130
131bool SDL_SYS_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter)
132{
133 return SDL_SYS_LogicError();
134}
135
136bool SDL_SYS_HapticPause(SDL_Haptic *haptic)
137{
138 return SDL_SYS_LogicError();
139}
140
141bool SDL_SYS_HapticResume(SDL_Haptic *haptic)
142{
143 return SDL_SYS_LogicError();
144}
145
146bool SDL_SYS_HapticStopAll(SDL_Haptic *haptic)
147{
148 return SDL_SYS_LogicError();
149}
150
151#endif // SDL_HAPTIC_DUMMY || SDL_HAPTIC_DISABLED
diff --git a/contrib/SDL-3.2.8/src/haptic/linux/SDL_syshaptic.c b/contrib/SDL-3.2.8/src/haptic/linux/SDL_syshaptic.c
new file mode 100644
index 0000000..842f577
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/haptic/linux/SDL_syshaptic.c
@@ -0,0 +1,1134 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 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_LINUX
24
25#include "../SDL_syshaptic.h"
26#include "../../joystick/SDL_sysjoystick.h" // For the real SDL_Joystick
27#include "../../joystick/linux/SDL_sysjoystick_c.h" // For joystick hwdata
28#include "../../core/linux/SDL_evdev_capabilities.h"
29#include "../../core/linux/SDL_udev.h"
30
31#include <unistd.h> // close
32#include <linux/input.h> // Force feedback linux stuff.
33#include <fcntl.h> // O_RDWR
34#include <limits.h> // INT_MAX
35#include <errno.h> // errno
36#include <string.h> // strerror
37#include <sys/stat.h> // stat
38
39#define MAX_HAPTICS 32 // It's doubtful someone has more then 32 evdev
40
41static bool MaybeAddDevice(const char *path);
42#ifdef SDL_USE_LIBUDEV
43static bool MaybeRemoveDevice(const char *path);
44static void haptic_udev_callback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath);
45#endif // SDL_USE_LIBUDEV
46
47/*
48 * List of available haptic devices.
49 */
50typedef struct SDL_hapticlist_item
51{
52 SDL_HapticID instance_id;
53 char *fname; // Dev path name (like /dev/input/event1)
54 SDL_Haptic *haptic; // Associated haptic.
55 dev_t dev_num;
56 struct SDL_hapticlist_item *next;
57} SDL_hapticlist_item;
58
59/*
60 * Haptic system hardware data.
61 */
62struct haptic_hwdata
63{
64 int fd; // File descriptor of the device.
65 char *fname; // Points to the name in SDL_hapticlist.
66};
67
68/*
69 * Haptic system effect data.
70 */
71struct haptic_hweffect
72{
73 struct ff_effect effect; // The linux kernel effect structure.
74};
75
76static SDL_hapticlist_item *SDL_hapticlist = NULL;
77static SDL_hapticlist_item *SDL_hapticlist_tail = NULL;
78static int numhaptics = 0;
79
80#define EV_TEST(ev, f) \
81 if (test_bit((ev), features)) { \
82 ret |= (f); \
83 }
84/*
85 * Test whether a device has haptic properties.
86 * Returns available properties or 0 if there are none.
87 */
88static Uint32 EV_IsHaptic(int fd)
89{
90 unsigned long features[1 + FF_MAX / sizeof(unsigned long)];
91 Uint32 ret = 0;
92
93 // Ask device for what it has.
94 if (ioctl(fd, EVIOCGBIT(EV_FF, sizeof(features)), features) < 0) {
95 SDL_SetError("Haptic: Unable to get device's features: %s", strerror(errno));
96 return 0;
97 }
98
99 // Convert supported features to SDL_HAPTIC platform-neutral features.
100 EV_TEST(FF_CONSTANT, SDL_HAPTIC_CONSTANT);
101 EV_TEST(FF_SINE, SDL_HAPTIC_SINE);
102 EV_TEST(FF_SQUARE, SDL_HAPTIC_SQUARE);
103 EV_TEST(FF_TRIANGLE, SDL_HAPTIC_TRIANGLE);
104 EV_TEST(FF_SAW_UP, SDL_HAPTIC_SAWTOOTHUP);
105 EV_TEST(FF_SAW_DOWN, SDL_HAPTIC_SAWTOOTHDOWN);
106 EV_TEST(FF_RAMP, SDL_HAPTIC_RAMP);
107 EV_TEST(FF_SPRING, SDL_HAPTIC_SPRING);
108 EV_TEST(FF_FRICTION, SDL_HAPTIC_FRICTION);
109 EV_TEST(FF_DAMPER, SDL_HAPTIC_DAMPER);
110 EV_TEST(FF_INERTIA, SDL_HAPTIC_INERTIA);
111 EV_TEST(FF_CUSTOM, SDL_HAPTIC_CUSTOM);
112 EV_TEST(FF_GAIN, SDL_HAPTIC_GAIN);
113 EV_TEST(FF_AUTOCENTER, SDL_HAPTIC_AUTOCENTER);
114 EV_TEST(FF_RUMBLE, SDL_HAPTIC_LEFTRIGHT);
115
116 // Return what it supports.
117 return ret;
118}
119
120/*
121 * Tests whether a device is a mouse or not.
122 */
123static bool EV_IsMouse(int fd)
124{
125 unsigned long argp[40];
126
127 // Ask for supported features.
128 if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(argp)), argp) < 0) {
129 return false;
130 }
131
132 // Currently we only test for BTN_MOUSE which can give fake positives.
133 if (test_bit(BTN_MOUSE, argp) != 0) {
134 return true;
135 }
136
137 return true;
138}
139
140/*
141 * Initializes the haptic subsystem by finding available devices.
142 */
143bool SDL_SYS_HapticInit(void)
144{
145 const char joydev_pattern[] = "/dev/input/event%d";
146 char path[PATH_MAX];
147 int i, j;
148
149 /*
150 * Limit amount of checks to MAX_HAPTICS since we may or may not have
151 * permission to some or all devices.
152 */
153 i = 0;
154 for (j = 0; j < MAX_HAPTICS; ++j) {
155 (void)SDL_snprintf(path, PATH_MAX, joydev_pattern, i++);
156 MaybeAddDevice(path);
157 }
158
159#ifdef SDL_USE_LIBUDEV
160 if (!SDL_UDEV_Init()) {
161 return SDL_SetError("Could not initialize UDEV");
162 }
163
164 if (!SDL_UDEV_AddCallback(haptic_udev_callback)) {
165 SDL_UDEV_Quit();
166 return SDL_SetError("Could not setup haptic <-> udev callback");
167 }
168
169 // Force a scan to build the initial device list
170 SDL_UDEV_Scan();
171#endif // SDL_USE_LIBUDEV
172
173 return true;
174}
175
176int SDL_SYS_NumHaptics(void)
177{
178 return numhaptics;
179}
180
181static SDL_hapticlist_item *HapticByDevIndex(int device_index)
182{
183 SDL_hapticlist_item *item = SDL_hapticlist;
184
185 if ((device_index < 0) || (device_index >= numhaptics)) {
186 return NULL;
187 }
188
189 while (device_index > 0) {
190 SDL_assert(item != NULL);
191 --device_index;
192 item = item->next;
193 }
194
195 return item;
196}
197
198static SDL_hapticlist_item *HapticByInstanceID(SDL_HapticID instance_id)
199{
200 SDL_hapticlist_item *item;
201 for (item = SDL_hapticlist; item; item = item->next) {
202 if (instance_id == item->instance_id) {
203 return item;
204 }
205 }
206 return NULL;
207}
208
209#ifdef SDL_USE_LIBUDEV
210static void haptic_udev_callback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath)
211{
212 if (!devpath || !(udev_class & SDL_UDEV_DEVICE_JOYSTICK)) {
213 return;
214 }
215
216 switch (udev_type) {
217 case SDL_UDEV_DEVICEADDED:
218 MaybeAddDevice(devpath);
219 break;
220
221 case SDL_UDEV_DEVICEREMOVED:
222 MaybeRemoveDevice(devpath);
223 break;
224
225 default:
226 break;
227 }
228}
229#endif // SDL_USE_LIBUDEV
230
231static bool MaybeAddDevice(const char *path)
232{
233 struct stat sb;
234 int fd;
235 Uint32 supported;
236 SDL_hapticlist_item *item;
237
238 if (!path) {
239 return false;
240 }
241
242 // try to open
243 fd = open(path, O_RDWR | O_CLOEXEC, 0);
244 if (fd < 0) {
245 return false;
246 }
247
248 // get file status
249 if (fstat(fd, &sb) != 0) {
250 close(fd);
251 return false;
252 }
253
254 // check for duplicates
255 for (item = SDL_hapticlist; item; item = item->next) {
256 if (item->dev_num == sb.st_rdev) {
257 close(fd);
258 return false; // duplicate.
259 }
260 }
261
262#ifdef DEBUG_INPUT_EVENTS
263 printf("Checking %s\n", path);
264#endif
265
266 // see if it works
267 supported = EV_IsHaptic(fd);
268 close(fd);
269 if (!supported) {
270 return false;
271 }
272
273 item = (SDL_hapticlist_item *)SDL_calloc(1, sizeof(SDL_hapticlist_item));
274 if (!item) {
275 return false;
276 }
277
278 item->instance_id = SDL_GetNextObjectID();
279 item->fname = SDL_strdup(path);
280 if (!item->fname) {
281 SDL_free(item);
282 return false;
283 }
284
285 item->dev_num = sb.st_rdev;
286
287 // TODO: should we add instance IDs?
288 if (!SDL_hapticlist_tail) {
289 SDL_hapticlist = SDL_hapticlist_tail = item;
290 } else {
291 SDL_hapticlist_tail->next = item;
292 SDL_hapticlist_tail = item;
293 }
294
295 ++numhaptics;
296
297 // !!! TODO: Send a haptic add event?
298
299 return true;
300}
301
302#ifdef SDL_USE_LIBUDEV
303static bool MaybeRemoveDevice(const char *path)
304{
305 SDL_hapticlist_item *item;
306 SDL_hapticlist_item *prev = NULL;
307
308 if (!path) {
309 return false;
310 }
311
312 for (item = SDL_hapticlist; item; item = item->next) {
313 // found it, remove it.
314 if (SDL_strcmp(path, item->fname) == 0) {
315 const bool result = item->haptic ? true : false;
316
317 if (prev) {
318 prev->next = item->next;
319 } else {
320 SDL_assert(SDL_hapticlist == item);
321 SDL_hapticlist = item->next;
322 }
323 if (item == SDL_hapticlist_tail) {
324 SDL_hapticlist_tail = prev;
325 }
326
327 // Need to decrement the haptic count
328 --numhaptics;
329 // !!! TODO: Send a haptic remove event?
330
331 SDL_free(item->fname);
332 SDL_free(item);
333 return result;
334 }
335 prev = item;
336 }
337
338 return false;
339}
340#endif // SDL_USE_LIBUDEV
341
342/*
343 * Return the instance ID of a haptic device, does not need to be opened.
344 */
345SDL_HapticID SDL_SYS_HapticInstanceID(int index)
346{
347 SDL_hapticlist_item *item;
348
349 item = HapticByDevIndex(index);
350 if (item) {
351 return item->instance_id;
352 }
353 return 0;
354}
355
356/*
357 * Gets the name from a file descriptor.
358 */
359static const char *SDL_SYS_HapticNameFromFD(int fd)
360{
361 static char namebuf[128];
362
363 // We use the evdev name ioctl.
364 if (ioctl(fd, EVIOCGNAME(sizeof(namebuf)), namebuf) <= 0) {
365 return NULL;
366 }
367
368 return namebuf;
369}
370
371/*
372 * Return the name of a haptic device, does not need to be opened.
373 */
374const char *SDL_SYS_HapticName(int index)
375{
376 SDL_hapticlist_item *item;
377 int fd;
378 const char *name = NULL;
379
380 item = HapticByDevIndex(index);
381 if (item) {
382 // Open the haptic device.
383 fd = open(item->fname, O_RDONLY | O_CLOEXEC, 0);
384
385 if (fd >= 0) {
386
387 name = SDL_SYS_HapticNameFromFD(fd);
388 if (!name) {
389 // No name found, return device character device
390 name = item->fname;
391 }
392 close(fd);
393 }
394 }
395 return name;
396}
397
398/*
399 * Opens the haptic device from the file descriptor.
400 */
401static bool SDL_SYS_HapticOpenFromFD(SDL_Haptic *haptic, int fd)
402{
403 // Allocate the hwdata
404 haptic->hwdata = (struct haptic_hwdata *)
405 SDL_calloc(1, sizeof(*haptic->hwdata));
406 if (!haptic->hwdata) {
407 goto open_err;
408 }
409
410 // Set the data.
411 haptic->hwdata->fd = fd;
412 haptic->supported = EV_IsHaptic(fd);
413 haptic->naxes = 2; // Hardcoded for now, not sure if it's possible to find out.
414
415 // Set the effects
416 if (ioctl(fd, EVIOCGEFFECTS, &haptic->neffects) < 0) {
417 SDL_SetError("Haptic: Unable to query device memory: %s",
418 strerror(errno));
419 goto open_err;
420 }
421 haptic->nplaying = haptic->neffects; // Linux makes no distinction.
422 haptic->effects = (struct haptic_effect *)
423 SDL_malloc(sizeof(struct haptic_effect) * haptic->neffects);
424 if (!haptic->effects) {
425 goto open_err;
426 }
427 // Clear the memory
428 SDL_memset(haptic->effects, 0,
429 sizeof(struct haptic_effect) * haptic->neffects);
430
431 return true;
432
433 // Error handling
434open_err:
435 close(fd);
436 if (haptic->hwdata) {
437 SDL_free(haptic->hwdata);
438 haptic->hwdata = NULL;
439 }
440 return false;
441}
442
443/*
444 * Opens a haptic device for usage.
445 */
446bool SDL_SYS_HapticOpen(SDL_Haptic *haptic)
447{
448 int fd;
449 SDL_hapticlist_item *item;
450
451 item = HapticByInstanceID(haptic->instance_id);
452 // Open the character device
453 fd = open(item->fname, O_RDWR | O_CLOEXEC, 0);
454 if (fd < 0) {
455 return SDL_SetError("Haptic: Unable to open %s: %s",
456 item->fname, strerror(errno));
457 }
458
459 // Try to create the haptic.
460 if (!SDL_SYS_HapticOpenFromFD(haptic, fd)) {
461 // Already closes on error.
462 return false;
463 }
464
465 // Set the fname.
466 haptic->hwdata->fname = SDL_strdup(item->fname);
467 return true;
468}
469
470/*
471 * Opens a haptic device from first mouse it finds for usage.
472 */
473int SDL_SYS_HapticMouse(void)
474{
475 int fd;
476 int device_index = 0;
477 SDL_hapticlist_item *item;
478
479 for (item = SDL_hapticlist; item; item = item->next) {
480 // Open the device.
481 fd = open(item->fname, O_RDWR | O_CLOEXEC, 0);
482 if (fd < 0) {
483 return SDL_SetError("Haptic: Unable to open %s: %s",
484 item->fname, strerror(errno));
485 }
486
487 // Is it a mouse?
488 if (EV_IsMouse(fd)) {
489 close(fd);
490 return device_index;
491 }
492
493 close(fd);
494
495 ++device_index;
496 }
497
498 return -1;
499}
500
501/*
502 * Checks to see if a joystick has haptic features.
503 */
504bool SDL_SYS_JoystickIsHaptic(SDL_Joystick *joystick)
505{
506#ifdef SDL_JOYSTICK_LINUX
507 SDL_AssertJoysticksLocked();
508
509 if (joystick->driver != &SDL_LINUX_JoystickDriver) {
510 return false;
511 }
512 if (EV_IsHaptic(joystick->hwdata->fd)) {
513 return true;
514 }
515#endif
516 return false;
517}
518
519/*
520 * Checks to see if the haptic device and joystick are in reality the same.
521 */
522bool SDL_SYS_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick)
523{
524#ifdef SDL_JOYSTICK_LINUX
525 SDL_AssertJoysticksLocked();
526
527 if (joystick->driver != &SDL_LINUX_JoystickDriver) {
528 return false;
529 }
530 /* We are assuming Linux is using evdev which should trump the old
531 * joystick methods. */
532 if (SDL_strcmp(joystick->hwdata->fname, haptic->hwdata->fname) == 0) {
533 return true;
534 }
535#endif
536 return false;
537}
538
539/*
540 * Opens a SDL_Haptic from a SDL_Joystick.
541 */
542bool SDL_SYS_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick)
543{
544#ifdef SDL_JOYSTICK_LINUX
545 int fd;
546 SDL_hapticlist_item *item;
547 const char *name;
548
549 SDL_AssertJoysticksLocked();
550
551 if (joystick->driver != &SDL_LINUX_JoystickDriver) {
552 return false;
553 }
554 // Find the joystick in the haptic list.
555 for (item = SDL_hapticlist; item; item = item->next) {
556 if (SDL_strcmp(item->fname, joystick->hwdata->fname) == 0) {
557 haptic->instance_id = item->instance_id;
558 break;
559 }
560 }
561
562 fd = open(joystick->hwdata->fname, O_RDWR | O_CLOEXEC, 0);
563 if (fd < 0) {
564 return SDL_SetError("Haptic: Unable to open %s: %s",
565 joystick->hwdata->fname, strerror(errno));
566 }
567 if (!SDL_SYS_HapticOpenFromFD(haptic, fd)) {
568 // Already closes on error.
569 return false;
570 }
571
572 haptic->hwdata->fname = SDL_strdup(joystick->hwdata->fname);
573
574 name = SDL_SYS_HapticNameFromFD(fd);
575 if (name) {
576 haptic->name = SDL_strdup(name);
577 }
578 return true;
579#else
580 return false;
581#endif
582}
583
584/*
585 * Closes the haptic device.
586 */
587void SDL_SYS_HapticClose(SDL_Haptic *haptic)
588{
589 if (haptic->hwdata) {
590
591 // Free effects.
592 SDL_free(haptic->effects);
593 haptic->effects = NULL;
594 haptic->neffects = 0;
595
596 // Clean up
597 close(haptic->hwdata->fd);
598
599 // Free
600 SDL_free(haptic->hwdata->fname);
601 SDL_free(haptic->hwdata);
602 haptic->hwdata = NULL;
603 }
604
605 // Clear the rest.
606 SDL_memset(haptic, 0, sizeof(SDL_Haptic));
607}
608
609/*
610 * Clean up after system specific haptic stuff
611 */
612void SDL_SYS_HapticQuit(void)
613{
614 SDL_hapticlist_item *item = NULL;
615 SDL_hapticlist_item *next = NULL;
616
617 for (item = SDL_hapticlist; item; item = next) {
618 next = item->next;
619 /* Opened and not closed haptics are leaked, this is on purpose.
620 * Close your haptic devices after usage. */
621 SDL_free(item->fname);
622 SDL_free(item);
623 }
624
625#ifdef SDL_USE_LIBUDEV
626 SDL_UDEV_DelCallback(haptic_udev_callback);
627 SDL_UDEV_Quit();
628#endif // SDL_USE_LIBUDEV
629
630 numhaptics = 0;
631 SDL_hapticlist = NULL;
632 SDL_hapticlist_tail = NULL;
633}
634
635/*
636 * Converts an SDL button to a ff_trigger button.
637 */
638static Uint16 SDL_SYS_ToButton(Uint16 button)
639{
640 Uint16 ff_button;
641
642 ff_button = 0;
643
644 /*
645 * Not sure what the proper syntax is because this actually isn't implemented
646 * in the current kernel from what I've seen (2.6.26).
647 */
648 if (button != 0) {
649 ff_button = BTN_GAMEPAD + button - 1;
650 }
651
652 return ff_button;
653}
654
655/*
656 * Initializes the ff_effect usable direction from a SDL_HapticDirection.
657 */
658static bool SDL_SYS_ToDirection(Uint16 *dest, const SDL_HapticDirection *src)
659{
660 Uint32 tmp;
661
662 switch (src->type) {
663 case SDL_HAPTIC_POLAR:
664 tmp = ((src->dir[0] % 36000) * 0x8000) / 18000; // convert to range [0,0xFFFF]
665 *dest = (Uint16)tmp;
666 break;
667
668 case SDL_HAPTIC_SPHERICAL:
669 /*
670 We convert to polar, because that's the only supported direction on Linux.
671 The first value of a spherical direction is practically the same as a
672 Polar direction, except that we have to add 90 degrees. It is the angle
673 from EAST {1,0} towards SOUTH {0,1}.
674 --> add 9000
675 --> finally convert to [0,0xFFFF] as in case SDL_HAPTIC_POLAR.
676 */
677 tmp = ((src->dir[0]) + 9000) % 36000; // Convert to polars
678 tmp = (tmp * 0x8000) / 18000; // convert to range [0,0xFFFF]
679 *dest = (Uint16)tmp;
680 break;
681
682 case SDL_HAPTIC_CARTESIAN:
683 if (!src->dir[1]) {
684 *dest = (src->dir[0] >= 0 ? 0x4000 : 0xC000);
685 } else if (!src->dir[0]) {
686 *dest = (src->dir[1] >= 0 ? 0x8000 : 0);
687 } else {
688 float f = SDL_atan2f(src->dir[1], src->dir[0]); // Ideally we'd use fixed point math instead of floats...
689 /*
690 SDL_atan2 takes the parameters: Y-axis-value and X-axis-value (in that order)
691 - Y-axis-value is the second coordinate (from center to SOUTH)
692 - X-axis-value is the first coordinate (from center to EAST)
693 We add 36000, because SDL_atan2 also returns negative values. Then we practically
694 have the first spherical value. Therefore we proceed as in case
695 SDL_HAPTIC_SPHERICAL and add another 9000 to get the polar value.
696 --> add 45000 in total
697 --> finally convert to [0,0xFFFF] as in case SDL_HAPTIC_POLAR.
698 */
699 tmp = (((Sint32)(f * 18000.0 / SDL_PI_D)) + 45000) % 36000;
700 tmp = (tmp * 0x8000) / 18000; // convert to range [0,0xFFFF]
701 *dest = (Uint16)tmp;
702 }
703 break;
704 case SDL_HAPTIC_STEERING_AXIS:
705 *dest = 0x4000;
706 break;
707 default:
708 return SDL_SetError("Haptic: Unsupported direction type.");
709 }
710
711 return true;
712}
713
714#define CLAMP(x) (((x) > 32767) ? 32767 : x)
715/*
716 * Initializes the Linux effect struct from a haptic_effect.
717 * Values above 32767 (for unsigned) are unspecified so we must clamp.
718 */
719static bool SDL_SYS_ToFFEffect(struct ff_effect *dest, const SDL_HapticEffect *src)
720{
721 const SDL_HapticConstant *constant;
722 const SDL_HapticPeriodic *periodic;
723 const SDL_HapticCondition *condition;
724 const SDL_HapticRamp *ramp;
725 const SDL_HapticLeftRight *leftright;
726
727 // Clear up
728 SDL_memset(dest, 0, sizeof(struct ff_effect));
729
730 switch (src->type) {
731 case SDL_HAPTIC_CONSTANT:
732 constant = &src->constant;
733
734 // Header
735 dest->type = FF_CONSTANT;
736 if (!SDL_SYS_ToDirection(&dest->direction, &constant->direction)) {
737 return false;
738 }
739
740 // Replay
741 dest->replay.length = (constant->length == SDL_HAPTIC_INFINITY) ? 0 : CLAMP(constant->length);
742 dest->replay.delay = CLAMP(constant->delay);
743
744 // Trigger
745 dest->trigger.button = SDL_SYS_ToButton(constant->button);
746 dest->trigger.interval = CLAMP(constant->interval);
747
748 // Constant
749 dest->u.constant.level = constant->level;
750
751 // Envelope
752 dest->u.constant.envelope.attack_length =
753 CLAMP(constant->attack_length);
754 dest->u.constant.envelope.attack_level =
755 CLAMP(constant->attack_level);
756 dest->u.constant.envelope.fade_length = CLAMP(constant->fade_length);
757 dest->u.constant.envelope.fade_level = CLAMP(constant->fade_level);
758
759 break;
760
761 case SDL_HAPTIC_SINE:
762 case SDL_HAPTIC_SQUARE:
763 case SDL_HAPTIC_TRIANGLE:
764 case SDL_HAPTIC_SAWTOOTHUP:
765 case SDL_HAPTIC_SAWTOOTHDOWN:
766 periodic = &src->periodic;
767
768 // Header
769 dest->type = FF_PERIODIC;
770 if (!SDL_SYS_ToDirection(&dest->direction, &periodic->direction)) {
771 return false;
772 }
773
774 // Replay
775 dest->replay.length = (periodic->length == SDL_HAPTIC_INFINITY) ? 0 : CLAMP(periodic->length);
776 dest->replay.delay = CLAMP(periodic->delay);
777
778 // Trigger
779 dest->trigger.button = SDL_SYS_ToButton(periodic->button);
780 dest->trigger.interval = CLAMP(periodic->interval);
781
782 // Periodic
783 if (periodic->type == SDL_HAPTIC_SINE) {
784 dest->u.periodic.waveform = FF_SINE;
785 } else if (periodic->type == SDL_HAPTIC_SQUARE) {
786 dest->u.periodic.waveform = FF_SQUARE;
787 } else if (periodic->type == SDL_HAPTIC_TRIANGLE) {
788 dest->u.periodic.waveform = FF_TRIANGLE;
789 } else if (periodic->type == SDL_HAPTIC_SAWTOOTHUP) {
790 dest->u.periodic.waveform = FF_SAW_UP;
791 } else if (periodic->type == SDL_HAPTIC_SAWTOOTHDOWN) {
792 dest->u.periodic.waveform = FF_SAW_DOWN;
793 }
794 dest->u.periodic.period = CLAMP(periodic->period);
795 dest->u.periodic.magnitude = periodic->magnitude;
796 dest->u.periodic.offset = periodic->offset;
797 // Linux phase is defined in interval "[0x0000, 0x10000[", corresponds with "[0deg, 360deg[" phase shift.
798 dest->u.periodic.phase = ((Uint32)periodic->phase * 0x10000U) / 36000;
799
800 // Envelope
801 dest->u.periodic.envelope.attack_length =
802 CLAMP(periodic->attack_length);
803 dest->u.periodic.envelope.attack_level =
804 CLAMP(periodic->attack_level);
805 dest->u.periodic.envelope.fade_length = CLAMP(periodic->fade_length);
806 dest->u.periodic.envelope.fade_level = CLAMP(periodic->fade_level);
807
808 break;
809
810 case SDL_HAPTIC_SPRING:
811 case SDL_HAPTIC_DAMPER:
812 case SDL_HAPTIC_INERTIA:
813 case SDL_HAPTIC_FRICTION:
814 condition = &src->condition;
815
816 // Header
817 if (condition->type == SDL_HAPTIC_SPRING) {
818 dest->type = FF_SPRING;
819 } else if (condition->type == SDL_HAPTIC_DAMPER) {
820 dest->type = FF_DAMPER;
821 } else if (condition->type == SDL_HAPTIC_INERTIA) {
822 dest->type = FF_INERTIA;
823 } else if (condition->type == SDL_HAPTIC_FRICTION) {
824 dest->type = FF_FRICTION;
825 }
826
827 if (!SDL_SYS_ToDirection(&dest->direction, &condition->direction)) {
828 return false;
829 }
830
831 // Replay
832 dest->replay.length = (condition->length == SDL_HAPTIC_INFINITY) ? 0 : CLAMP(condition->length);
833 dest->replay.delay = CLAMP(condition->delay);
834
835 // Trigger
836 dest->trigger.button = SDL_SYS_ToButton(condition->button);
837 dest->trigger.interval = CLAMP(condition->interval);
838
839 // Condition
840 // X axis
841 dest->u.condition[0].right_saturation = condition->right_sat[0];
842 dest->u.condition[0].left_saturation = condition->left_sat[0];
843 dest->u.condition[0].right_coeff = condition->right_coeff[0];
844 dest->u.condition[0].left_coeff = condition->left_coeff[0];
845 dest->u.condition[0].deadband = condition->deadband[0];
846 dest->u.condition[0].center = condition->center[0];
847 // Y axis
848 dest->u.condition[1].right_saturation = condition->right_sat[1];
849 dest->u.condition[1].left_saturation = condition->left_sat[1];
850 dest->u.condition[1].right_coeff = condition->right_coeff[1];
851 dest->u.condition[1].left_coeff = condition->left_coeff[1];
852 dest->u.condition[1].deadband = condition->deadband[1];
853 dest->u.condition[1].center = condition->center[1];
854
855 /*
856 * There is no envelope in the linux force feedback api for conditions.
857 */
858
859 break;
860
861 case SDL_HAPTIC_RAMP:
862 ramp = &src->ramp;
863
864 // Header
865 dest->type = FF_RAMP;
866 if (!SDL_SYS_ToDirection(&dest->direction, &ramp->direction)) {
867 return false;
868 }
869
870 // Replay
871 dest->replay.length = (ramp->length == SDL_HAPTIC_INFINITY) ? 0 : CLAMP(ramp->length);
872 dest->replay.delay = CLAMP(ramp->delay);
873
874 // Trigger
875 dest->trigger.button = SDL_SYS_ToButton(ramp->button);
876 dest->trigger.interval = CLAMP(ramp->interval);
877
878 // Ramp
879 dest->u.ramp.start_level = ramp->start;
880 dest->u.ramp.end_level = ramp->end;
881
882 // Envelope
883 dest->u.ramp.envelope.attack_length = CLAMP(ramp->attack_length);
884 dest->u.ramp.envelope.attack_level = CLAMP(ramp->attack_level);
885 dest->u.ramp.envelope.fade_length = CLAMP(ramp->fade_length);
886 dest->u.ramp.envelope.fade_level = CLAMP(ramp->fade_level);
887
888 break;
889
890 case SDL_HAPTIC_LEFTRIGHT:
891 leftright = &src->leftright;
892
893 // Header
894 dest->type = FF_RUMBLE;
895 dest->direction = 0x4000;
896
897 // Replay
898 dest->replay.length = (leftright->length == SDL_HAPTIC_INFINITY) ? 0 : CLAMP(leftright->length);
899
900 // Trigger
901 dest->trigger.button = 0;
902 dest->trigger.interval = 0;
903
904 // Rumble (Linux expects 0-65535, so multiply by 2)
905 dest->u.rumble.strong_magnitude = CLAMP(leftright->large_magnitude) * 2;
906 dest->u.rumble.weak_magnitude = CLAMP(leftright->small_magnitude) * 2;
907
908 break;
909
910 default:
911 return SDL_SetError("Haptic: Unknown effect type.");
912 }
913
914 return true;
915}
916
917/*
918 * Creates a new haptic effect.
919 */
920bool SDL_SYS_HapticNewEffect(SDL_Haptic *haptic, struct haptic_effect *effect,
921 const SDL_HapticEffect *base)
922{
923 struct ff_effect *linux_effect;
924
925 // Allocate the hardware effect
926 effect->hweffect = (struct haptic_hweffect *)
927 SDL_calloc(1, sizeof(struct haptic_hweffect));
928 if (!effect->hweffect) {
929 return false;
930 }
931
932 // Prepare the ff_effect
933 linux_effect = &effect->hweffect->effect;
934 if (!SDL_SYS_ToFFEffect(linux_effect, base)) {
935 goto new_effect_err;
936 }
937 linux_effect->id = -1; // Have the kernel give it an id
938
939 // Upload the effect
940 if (ioctl(haptic->hwdata->fd, EVIOCSFF, linux_effect) < 0) {
941 SDL_SetError("Haptic: Error uploading effect to the device: %s",
942 strerror(errno));
943 goto new_effect_err;
944 }
945
946 return true;
947
948new_effect_err:
949 SDL_free(effect->hweffect);
950 effect->hweffect = NULL;
951 return false;
952}
953
954/*
955 * Updates an effect.
956 *
957 * Note: Dynamically updating the direction can in some cases force
958 * the effect to restart and run once.
959 */
960bool SDL_SYS_HapticUpdateEffect(SDL_Haptic *haptic,
961 struct haptic_effect *effect,
962 const SDL_HapticEffect *data)
963{
964 struct ff_effect linux_effect;
965
966 // Create the new effect
967 if (!SDL_SYS_ToFFEffect(&linux_effect, data)) {
968 return false;
969 }
970 linux_effect.id = effect->hweffect->effect.id;
971
972 // See if it can be uploaded.
973 if (ioctl(haptic->hwdata->fd, EVIOCSFF, &linux_effect) < 0) {
974 return SDL_SetError("Haptic: Error updating the effect: %s",
975 strerror(errno));
976 }
977
978 // Copy the new effect into memory.
979 SDL_memcpy(&effect->hweffect->effect, &linux_effect,
980 sizeof(struct ff_effect));
981
982 return true;
983}
984
985/*
986 * Runs an effect.
987 */
988bool SDL_SYS_HapticRunEffect(SDL_Haptic *haptic, struct haptic_effect *effect,
989 Uint32 iterations)
990{
991 struct input_event run;
992
993 // Prepare to run the effect
994 run.type = EV_FF;
995 run.code = effect->hweffect->effect.id;
996 // We don't actually have infinity here, so we just do INT_MAX which is pretty damn close.
997 run.value = (iterations > INT_MAX) ? INT_MAX : iterations;
998
999 if (write(haptic->hwdata->fd, (const void *)&run, sizeof(run)) < 0) {
1000 return SDL_SetError("Haptic: Unable to run the effect: %s", strerror(errno));
1001 }
1002
1003 return true;
1004}
1005
1006/*
1007 * Stops an effect.
1008 */
1009bool SDL_SYS_HapticStopEffect(SDL_Haptic *haptic, struct haptic_effect *effect)
1010{
1011 struct input_event stop;
1012
1013 stop.type = EV_FF;
1014 stop.code = effect->hweffect->effect.id;
1015 stop.value = 0;
1016
1017 if (write(haptic->hwdata->fd, (const void *)&stop, sizeof(stop)) < 0) {
1018 return SDL_SetError("Haptic: Unable to stop the effect: %s",
1019 strerror(errno));
1020 }
1021
1022 return true;
1023}
1024
1025/*
1026 * Frees the effect.
1027 */
1028void SDL_SYS_HapticDestroyEffect(SDL_Haptic *haptic, struct haptic_effect *effect)
1029{
1030 if (ioctl(haptic->hwdata->fd, EVIOCRMFF, effect->hweffect->effect.id) < 0) {
1031 SDL_SetError("Haptic: Error removing the effect from the device: %s",
1032 strerror(errno));
1033 }
1034 SDL_free(effect->hweffect);
1035 effect->hweffect = NULL;
1036}
1037
1038/*
1039 * Gets the status of a haptic effect.
1040 */
1041int SDL_SYS_HapticGetEffectStatus(SDL_Haptic *haptic,
1042 struct haptic_effect *effect)
1043{
1044#if 0 // Not supported atm.
1045 struct input_event ie;
1046
1047 ie.type = EV_FF;
1048 ie.type = EV_FF_STATUS;
1049 ie.code = effect->hweffect->effect.id;
1050
1051 if (write(haptic->hwdata->fd, &ie, sizeof(ie)) < 0) {
1052 SDL_SetError("Haptic: Error getting device status.");
1053 return -1;
1054 }
1055
1056 return 1;
1057#endif
1058
1059 SDL_Unsupported();
1060 return -1;
1061}
1062
1063/*
1064 * Sets the gain.
1065 */
1066bool SDL_SYS_HapticSetGain(SDL_Haptic *haptic, int gain)
1067{
1068 struct input_event ie;
1069
1070 ie.type = EV_FF;
1071 ie.code = FF_GAIN;
1072 ie.value = (0xFFFFUL * gain) / 100;
1073
1074 if (write(haptic->hwdata->fd, &ie, sizeof(ie)) < 0) {
1075 return SDL_SetError("Haptic: Error setting gain: %s", strerror(errno));
1076 }
1077
1078 return true;
1079}
1080
1081/*
1082 * Sets the autocentering.
1083 */
1084bool SDL_SYS_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter)
1085{
1086 struct input_event ie;
1087
1088 ie.type = EV_FF;
1089 ie.code = FF_AUTOCENTER;
1090 ie.value = (0xFFFFUL * autocenter) / 100;
1091
1092 if (write(haptic->hwdata->fd, &ie, sizeof(ie)) < 0) {
1093 return SDL_SetError("Haptic: Error setting autocenter: %s", strerror(errno));
1094 }
1095
1096 return true;
1097}
1098
1099/*
1100 * Pausing is not supported atm by linux.
1101 */
1102bool SDL_SYS_HapticPause(SDL_Haptic *haptic)
1103{
1104 return SDL_Unsupported();
1105}
1106
1107/*
1108 * Unpausing is not supported atm by linux.
1109 */
1110bool SDL_SYS_HapticResume(SDL_Haptic *haptic)
1111{
1112 return SDL_Unsupported();
1113}
1114
1115/*
1116 * Stops all the currently playing effects.
1117 */
1118bool SDL_SYS_HapticStopAll(SDL_Haptic *haptic)
1119{
1120 int i, ret;
1121
1122 // Linux does not support this natively so we have to loop.
1123 for (i = 0; i < haptic->neffects; i++) {
1124 if (haptic->effects[i].hweffect != NULL) {
1125 ret = SDL_SYS_HapticStopEffect(haptic, &haptic->effects[i]);
1126 if (ret < 0) {
1127 return SDL_SetError("Haptic: Error while trying to stop all playing effects.");
1128 }
1129 }
1130 }
1131 return true;
1132}
1133
1134#endif // SDL_HAPTIC_LINUX
diff --git a/contrib/SDL-3.2.8/src/haptic/windows/SDL_dinputhaptic.c b/contrib/SDL-3.2.8/src/haptic/windows/SDL_dinputhaptic.c
new file mode 100644
index 0000000..255aac0
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/haptic/windows/SDL_dinputhaptic.c
@@ -0,0 +1,1244 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 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#include "../SDL_syshaptic.h"
24
25#ifdef SDL_HAPTIC_DINPUT
26
27#include "SDL_windowshaptic_c.h"
28#include "SDL_dinputhaptic_c.h"
29#include "../../joystick/windows/SDL_windowsjoystick_c.h"
30
31/*
32 * External stuff.
33 */
34#ifdef SDL_VIDEO_DRIVER_WINDOWS
35extern HWND SDL_HelperWindow;
36#else
37static const HWND SDL_HelperWindow = NULL;
38#endif
39
40/*
41 * Internal stuff.
42 */
43static bool coinitialized = false;
44static LPDIRECTINPUT8 dinput = NULL;
45
46/*
47 * Like SDL_SetError but for DX error codes.
48 */
49static bool DI_SetError(const char *str, HRESULT err)
50{
51 return SDL_SetError("Haptic error %s", str);
52}
53
54/*
55 * Callback to find the haptic devices.
56 */
57static BOOL CALLBACK EnumHapticsCallback(const DIDEVICEINSTANCE *pdidInstance, VOID *pContext)
58{
59 (void)pContext;
60 SDL_DINPUT_HapticMaybeAddDevice(pdidInstance);
61 return DIENUM_CONTINUE; // continue enumerating
62}
63
64bool SDL_DINPUT_HapticInit(void)
65{
66 HRESULT ret;
67 HINSTANCE instance;
68 DWORD devClass;
69
70 if (dinput != NULL) { // Already open.
71 return SDL_SetError("Haptic: SubSystem already open.");
72 }
73
74 if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_DIRECTINPUT, true)) {
75 // In some environments, IDirectInput8_Initialize / _EnumDevices can take a minute even with no controllers.
76 return true;
77 }
78
79 ret = WIN_CoInitialize();
80 if (FAILED(ret)) {
81 return DI_SetError("Coinitialize", ret);
82 }
83
84 coinitialized = true;
85
86 ret = CoCreateInstance(&CLSID_DirectInput8, NULL, CLSCTX_INPROC_SERVER,
87 &IID_IDirectInput8, (LPVOID *)&dinput);
88 if (FAILED(ret)) {
89 SDL_SYS_HapticQuit();
90 return DI_SetError("CoCreateInstance", ret);
91 }
92
93 // Because we used CoCreateInstance, we need to Initialize it, first.
94 instance = GetModuleHandle(NULL);
95 if (!instance) {
96 SDL_SYS_HapticQuit();
97 return SDL_SetError("GetModuleHandle() failed with error code %lu.",
98 GetLastError());
99 }
100 ret = IDirectInput8_Initialize(dinput, instance, DIRECTINPUT_VERSION);
101 if (FAILED(ret)) {
102 SDL_SYS_HapticQuit();
103 return DI_SetError("Initializing DirectInput device", ret);
104 }
105
106 // Look for haptic devices.
107 for (devClass = DI8DEVCLASS_DEVICE; devClass <= DI8DEVCLASS_GAMECTRL; devClass++) {
108 if (devClass == DI8DEVCLASS_GAMECTRL && SDL_WasInit(SDL_INIT_JOYSTICK)) {
109 // The joystick subsystem will manage adding DInput joystick haptic devices
110 continue;
111 }
112
113 ret = IDirectInput8_EnumDevices(dinput,
114 devClass,
115 EnumHapticsCallback,
116 NULL,
117 DIEDFL_FORCEFEEDBACK |
118 DIEDFL_ATTACHEDONLY);
119 if (FAILED(ret)) {
120 SDL_SYS_HapticQuit();
121 return DI_SetError("Enumerating DirectInput devices", ret);
122 }
123 }
124
125 return true;
126}
127
128bool SDL_DINPUT_HapticMaybeAddDevice(const DIDEVICEINSTANCE *pdidInstance)
129{
130 HRESULT ret;
131 LPDIRECTINPUTDEVICE8 device;
132 const DWORD needflags = DIDC_ATTACHED | DIDC_FORCEFEEDBACK;
133 DIDEVCAPS capabilities;
134 SDL_hapticlist_item *item = NULL;
135
136 if (!dinput) {
137 return false; // not initialized. We'll pick these up on enumeration if we init later.
138 }
139
140 // Make sure we don't already have it
141 for (item = SDL_hapticlist; item; item = item->next) {
142 if (SDL_memcmp(&item->instance, pdidInstance, sizeof(*pdidInstance)) == 0) {
143 return false; // Already added
144 }
145 }
146
147 // Open the device
148 ret = IDirectInput8_CreateDevice(dinput, &pdidInstance->guidInstance, &device, NULL);
149 if (FAILED(ret)) {
150 // DI_SetError("Creating DirectInput device",ret);
151 return false;
152 }
153
154 // Get capabilities.
155 SDL_zero(capabilities);
156 capabilities.dwSize = sizeof(DIDEVCAPS);
157 ret = IDirectInputDevice8_GetCapabilities(device, &capabilities);
158 IDirectInputDevice8_Release(device);
159 if (FAILED(ret)) {
160 // DI_SetError("Getting device capabilities",ret);
161 return false;
162 }
163
164 if ((capabilities.dwFlags & needflags) != needflags) {
165 return false; // not a device we can use.
166 }
167
168 item = (SDL_hapticlist_item *)SDL_calloc(1, sizeof(SDL_hapticlist_item));
169 if (!item) {
170 return false;
171 }
172
173 item->instance_id = SDL_GetNextObjectID();
174 item->name = WIN_StringToUTF8(pdidInstance->tszProductName);
175 if (!item->name) {
176 SDL_free(item);
177 return false;
178 }
179
180 // Copy the instance over, useful for creating devices.
181 SDL_memcpy(&item->instance, pdidInstance, sizeof(DIDEVICEINSTANCE));
182 SDL_memcpy(&item->capabilities, &capabilities, sizeof(capabilities));
183
184 return SDL_SYS_AddHapticDevice(item);
185}
186
187bool SDL_DINPUT_HapticMaybeRemoveDevice(const DIDEVICEINSTANCE *pdidInstance)
188{
189 SDL_hapticlist_item *item;
190 SDL_hapticlist_item *prev = NULL;
191
192 if (!dinput) {
193 return false; // not initialized, ignore this.
194 }
195
196 for (item = SDL_hapticlist; item; item = item->next) {
197 if (SDL_memcmp(&item->instance, pdidInstance, sizeof(*pdidInstance)) == 0) {
198 // found it, remove it.
199 return SDL_SYS_RemoveHapticDevice(prev, item);
200 }
201 prev = item;
202 }
203 return false;
204}
205
206/*
207 * Callback to get supported axes.
208 */
209static BOOL CALLBACK DI_DeviceObjectCallback(LPCDIDEVICEOBJECTINSTANCE dev, LPVOID pvRef)
210{
211 SDL_Haptic *haptic = (SDL_Haptic *)pvRef;
212
213 if ((dev->dwType & DIDFT_AXIS) && (dev->dwFlags & DIDOI_FFACTUATOR)) {
214 const GUID *guid = &dev->guidType;
215 DWORD offset = 0;
216 if (WIN_IsEqualGUID(guid, &GUID_XAxis)) {
217 offset = DIJOFS_X;
218 } else if (WIN_IsEqualGUID(guid, &GUID_YAxis)) {
219 offset = DIJOFS_Y;
220 } else if (WIN_IsEqualGUID(guid, &GUID_ZAxis)) {
221 offset = DIJOFS_Z;
222 } else if (WIN_IsEqualGUID(guid, &GUID_RxAxis)) {
223 offset = DIJOFS_RX;
224 } else if (WIN_IsEqualGUID(guid, &GUID_RyAxis)) {
225 offset = DIJOFS_RY;
226 } else if (WIN_IsEqualGUID(guid, &GUID_RzAxis)) {
227 offset = DIJOFS_RZ;
228 } else {
229 return DIENUM_CONTINUE; // can't use this, go on.
230 }
231
232 haptic->hwdata->axes[haptic->naxes] = offset;
233 haptic->naxes++;
234
235 // Currently using the artificial limit of 3 axes.
236 if (haptic->naxes >= 3) {
237 return DIENUM_STOP;
238 }
239 }
240
241 return DIENUM_CONTINUE;
242}
243
244/*
245 * Callback to get all supported effects.
246 */
247#define EFFECT_TEST(e, s) \
248 if (WIN_IsEqualGUID(&pei->guid, &(e))) \
249 haptic->supported |= (s)
250static BOOL CALLBACK DI_EffectCallback(LPCDIEFFECTINFO pei, LPVOID pv)
251{
252 // Prepare the haptic device.
253 SDL_Haptic *haptic = (SDL_Haptic *)pv;
254
255 // Get supported.
256 EFFECT_TEST(GUID_Spring, SDL_HAPTIC_SPRING);
257 EFFECT_TEST(GUID_Damper, SDL_HAPTIC_DAMPER);
258 EFFECT_TEST(GUID_Inertia, SDL_HAPTIC_INERTIA);
259 EFFECT_TEST(GUID_Friction, SDL_HAPTIC_FRICTION);
260 EFFECT_TEST(GUID_ConstantForce, SDL_HAPTIC_CONSTANT);
261 EFFECT_TEST(GUID_CustomForce, SDL_HAPTIC_CUSTOM);
262 EFFECT_TEST(GUID_Sine, SDL_HAPTIC_SINE);
263 EFFECT_TEST(GUID_Square, SDL_HAPTIC_SQUARE);
264 EFFECT_TEST(GUID_Triangle, SDL_HAPTIC_TRIANGLE);
265 EFFECT_TEST(GUID_SawtoothUp, SDL_HAPTIC_SAWTOOTHUP);
266 EFFECT_TEST(GUID_SawtoothDown, SDL_HAPTIC_SAWTOOTHDOWN);
267 EFFECT_TEST(GUID_RampForce, SDL_HAPTIC_RAMP);
268
269 // Check for more.
270 return DIENUM_CONTINUE;
271}
272
273/*
274 * Opens the haptic device.
275 *
276 * Steps:
277 * - Set cooperative level.
278 * - Set data format.
279 * - Acquire exclusiveness.
280 * - Reset actuators.
281 * - Get supported features.
282 */
283static bool SDL_DINPUT_HapticOpenFromDevice(SDL_Haptic *haptic, LPDIRECTINPUTDEVICE8 device8, bool is_joystick)
284{
285 HRESULT ret;
286 DIPROPDWORD dipdw;
287
288 // Allocate the hwdata
289 haptic->hwdata = (struct haptic_hwdata *)SDL_calloc(1, sizeof(*haptic->hwdata));
290 if (!haptic->hwdata) {
291 return false;
292 }
293
294 // We'll use the device8 from now on.
295 haptic->hwdata->device = device8;
296 haptic->hwdata->is_joystick = is_joystick;
297
298 /* !!! FIXME: opening a haptic device here first will make an attempt to
299 !!! FIXME: SDL_OpenJoystick() that same device fail later, since we
300 !!! FIXME: have it open in exclusive mode. But this will allow
301 !!! FIXME: SDL_OpenJoystick() followed by SDL_OpenHapticFromJoystick()
302 !!! FIXME: to work, and that's probably the common case. Still,
303 !!! FIXME: ideally, We need to unify the opening code. */
304
305 if (!is_joystick) { // if is_joystick, we already set this up elsewhere.
306 // Grab it exclusively to use force feedback stuff.
307 ret = IDirectInputDevice8_SetCooperativeLevel(haptic->hwdata->device,
308 SDL_HelperWindow,
309 DISCL_EXCLUSIVE |
310 DISCL_BACKGROUND);
311 if (FAILED(ret)) {
312 DI_SetError("Setting cooperative level to exclusive", ret);
313 goto acquire_err;
314 }
315
316 // Set data format.
317 ret = IDirectInputDevice8_SetDataFormat(haptic->hwdata->device,
318 &SDL_c_dfDIJoystick2);
319 if (FAILED(ret)) {
320 DI_SetError("Setting data format", ret);
321 goto acquire_err;
322 }
323
324 // Acquire the device.
325 ret = IDirectInputDevice8_Acquire(haptic->hwdata->device);
326 if (FAILED(ret)) {
327 DI_SetError("Acquiring DirectInput device", ret);
328 goto acquire_err;
329 }
330 }
331
332 // Get number of axes.
333 ret = IDirectInputDevice8_EnumObjects(haptic->hwdata->device,
334 DI_DeviceObjectCallback,
335 haptic, DIDFT_AXIS);
336 if (FAILED(ret)) {
337 DI_SetError("Getting device axes", ret);
338 goto acquire_err;
339 }
340
341 // Reset all actuators - just in case.
342 ret = IDirectInputDevice8_SendForceFeedbackCommand(haptic->hwdata->device,
343 DISFFC_RESET);
344 if (FAILED(ret)) {
345 DI_SetError("Resetting device", ret);
346 goto acquire_err;
347 }
348
349 // Enabling actuators.
350 ret = IDirectInputDevice8_SendForceFeedbackCommand(haptic->hwdata->device,
351 DISFFC_SETACTUATORSON);
352 if (FAILED(ret)) {
353 DI_SetError("Enabling actuators", ret);
354 goto acquire_err;
355 }
356
357 // Get supported effects.
358 ret = IDirectInputDevice8_EnumEffects(haptic->hwdata->device,
359 DI_EffectCallback, haptic,
360 DIEFT_ALL);
361 if (FAILED(ret)) {
362 DI_SetError("Enumerating supported effects", ret);
363 goto acquire_err;
364 }
365 if (haptic->supported == 0) { // Error since device supports nothing.
366 SDL_SetError("Haptic: Internal error on finding supported effects.");
367 goto acquire_err;
368 }
369
370 // Check autogain and autocenter.
371 dipdw.diph.dwSize = sizeof(DIPROPDWORD);
372 dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
373 dipdw.diph.dwObj = 0;
374 dipdw.diph.dwHow = DIPH_DEVICE;
375 dipdw.dwData = 10000;
376 ret = IDirectInputDevice8_SetProperty(haptic->hwdata->device,
377 DIPROP_FFGAIN, &dipdw.diph);
378 if (!FAILED(ret)) { // Gain is supported.
379 haptic->supported |= SDL_HAPTIC_GAIN;
380 }
381 dipdw.diph.dwObj = 0;
382 dipdw.diph.dwHow = DIPH_DEVICE;
383 dipdw.dwData = DIPROPAUTOCENTER_OFF;
384 ret = IDirectInputDevice8_SetProperty(haptic->hwdata->device,
385 DIPROP_AUTOCENTER, &dipdw.diph);
386 if (!FAILED(ret)) { // Autocenter is supported.
387 haptic->supported |= SDL_HAPTIC_AUTOCENTER;
388 }
389
390 // Status is always supported.
391 haptic->supported |= SDL_HAPTIC_STATUS | SDL_HAPTIC_PAUSE;
392
393 // Check maximum effects.
394 haptic->neffects = 128; /* This is not actually supported as thus under windows,
395 there is no way to tell the number of EFFECTS that a
396 device can hold, so we'll just use a "random" number
397 instead and put warnings in SDL_haptic.h */
398 haptic->nplaying = 128; // Even more impossible to get this then neffects.
399
400 // Prepare effects memory.
401 haptic->effects = (struct haptic_effect *)
402 SDL_malloc(sizeof(struct haptic_effect) * haptic->neffects);
403 if (!haptic->effects) {
404 goto acquire_err;
405 }
406 // Clear the memory
407 SDL_memset(haptic->effects, 0,
408 sizeof(struct haptic_effect) * haptic->neffects);
409
410 return true;
411
412 // Error handling
413acquire_err:
414 IDirectInputDevice8_Unacquire(haptic->hwdata->device);
415 return false;
416}
417
418bool SDL_DINPUT_HapticOpen(SDL_Haptic *haptic, SDL_hapticlist_item *item)
419{
420 HRESULT ret;
421 LPDIRECTINPUTDEVICE8 device;
422
423 // Open the device
424 ret = IDirectInput8_CreateDevice(dinput, &item->instance.guidInstance,
425 &device, NULL);
426 if (FAILED(ret)) {
427 DI_SetError("Creating DirectInput device", ret);
428 return false;
429 }
430
431 if (!SDL_DINPUT_HapticOpenFromDevice(haptic, device, false)) {
432 IDirectInputDevice8_Release(device);
433 return false;
434 }
435 return true;
436}
437
438bool SDL_DINPUT_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick)
439{
440 HRESULT ret;
441 DIDEVICEINSTANCE hap_instance, joy_instance;
442
443 hap_instance.dwSize = sizeof(DIDEVICEINSTANCE);
444 joy_instance.dwSize = sizeof(DIDEVICEINSTANCE);
445
446 // Get the device instances.
447 ret = IDirectInputDevice8_GetDeviceInfo(haptic->hwdata->device,
448 &hap_instance);
449 if (FAILED(ret)) {
450 return false;
451 }
452 ret = IDirectInputDevice8_GetDeviceInfo(joystick->hwdata->InputDevice,
453 &joy_instance);
454 if (FAILED(ret)) {
455 return false;
456 }
457
458 return (WIN_IsEqualGUID(&hap_instance.guidInstance, &joy_instance.guidInstance) == TRUE);
459}
460
461bool SDL_DINPUT_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick)
462{
463 SDL_hapticlist_item *item;
464 HRESULT ret;
465 DIDEVICEINSTANCE joy_instance;
466
467 joy_instance.dwSize = sizeof(DIDEVICEINSTANCE);
468 ret = IDirectInputDevice8_GetDeviceInfo(joystick->hwdata->InputDevice, &joy_instance);
469 if (FAILED(ret)) {
470 return false;
471 }
472
473 // Since it comes from a joystick we have to try to match it with a haptic device on our haptic list.
474 for (item = SDL_hapticlist; item; item = item->next) {
475 if (WIN_IsEqualGUID(&item->instance.guidInstance, &joy_instance.guidInstance)) {
476 haptic->instance_id = item->instance_id;
477 haptic->name = SDL_strdup(item->name);
478 return SDL_DINPUT_HapticOpenFromDevice(haptic, joystick->hwdata->InputDevice, true);
479 }
480 }
481
482 return SDL_SetError("Couldn't find joystick in haptic device list");
483}
484
485void SDL_DINPUT_HapticClose(SDL_Haptic *haptic)
486{
487 IDirectInputDevice8_Unacquire(haptic->hwdata->device);
488
489 // Only release if isn't grabbed by a joystick.
490 if (haptic->hwdata->is_joystick == 0) {
491 IDirectInputDevice8_Release(haptic->hwdata->device);
492 }
493}
494
495void SDL_DINPUT_HapticQuit(void)
496{
497 if (dinput != NULL) {
498 IDirectInput8_Release(dinput);
499 dinput = NULL;
500 }
501
502 if (coinitialized) {
503 WIN_CoUninitialize();
504 coinitialized = false;
505 }
506}
507
508/*
509 * Converts an SDL trigger button to an DIEFFECT trigger button.
510 */
511static DWORD DIGetTriggerButton(Uint16 button)
512{
513 DWORD dwTriggerButton;
514
515 dwTriggerButton = DIEB_NOTRIGGER;
516
517 if (button != 0) {
518 dwTriggerButton = DIJOFS_BUTTON(button - 1);
519 }
520
521 return dwTriggerButton;
522}
523
524/*
525 * Sets the direction.
526 */
527static bool SDL_SYS_SetDirection(DIEFFECT *effect, const SDL_HapticDirection *dir, int naxes)
528{
529 LONG *rglDir;
530
531 // Handle no axes a part.
532 if (naxes == 0) {
533 effect->dwFlags |= DIEFF_SPHERICAL; // Set as default.
534 effect->rglDirection = NULL;
535 return true;
536 }
537
538 // Has axes.
539 rglDir = (LONG *)SDL_malloc(sizeof(LONG) * naxes);
540 if (!rglDir) {
541 return false;
542 }
543 SDL_memset(rglDir, 0, sizeof(LONG) * naxes);
544 effect->rglDirection = rglDir;
545
546 switch (dir->type) {
547 case SDL_HAPTIC_POLAR:
548 effect->dwFlags |= DIEFF_POLAR;
549 rglDir[0] = dir->dir[0];
550 return true;
551 case SDL_HAPTIC_CARTESIAN:
552 effect->dwFlags |= DIEFF_CARTESIAN;
553 rglDir[0] = dir->dir[0];
554 if (naxes > 1) {
555 rglDir[1] = dir->dir[1];
556 }
557 if (naxes > 2) {
558 rglDir[2] = dir->dir[2];
559 }
560 return true;
561 case SDL_HAPTIC_SPHERICAL:
562 effect->dwFlags |= DIEFF_SPHERICAL;
563 rglDir[0] = dir->dir[0];
564 if (naxes > 1) {
565 rglDir[1] = dir->dir[1];
566 }
567 if (naxes > 2) {
568 rglDir[2] = dir->dir[2];
569 }
570 return true;
571 case SDL_HAPTIC_STEERING_AXIS:
572 effect->dwFlags |= DIEFF_CARTESIAN;
573 rglDir[0] = 0;
574 return true;
575
576 default:
577 return SDL_SetError("Haptic: Unknown direction type.");
578 }
579}
580
581// Clamps and converts.
582#define CCONVERT(x) (((x) > 0x7FFF) ? 10000 : ((x)*10000) / 0x7FFF)
583// Just converts.
584#define CONVERT(x) (((x)*10000) / 0x7FFF)
585/*
586 * Creates the DIEFFECT from a SDL_HapticEffect.
587 */
588static bool SDL_SYS_ToDIEFFECT(SDL_Haptic *haptic, DIEFFECT *dest,
589 const SDL_HapticEffect *src)
590{
591 int i;
592 DICONSTANTFORCE *constant;
593 DIPERIODIC *periodic;
594 DICONDITION *condition; // Actually an array of conditions - one per axis.
595 DIRAMPFORCE *ramp;
596 DICUSTOMFORCE *custom;
597 DIENVELOPE *envelope;
598 const SDL_HapticConstant *hap_constant;
599 const SDL_HapticPeriodic *hap_periodic;
600 const SDL_HapticCondition *hap_condition;
601 const SDL_HapticRamp *hap_ramp;
602 const SDL_HapticCustom *hap_custom;
603 DWORD *axes;
604
605 // Set global stuff.
606 SDL_memset(dest, 0, sizeof(DIEFFECT));
607 dest->dwSize = sizeof(DIEFFECT); // Set the structure size.
608 dest->dwSamplePeriod = 0; // Not used by us.
609 dest->dwGain = 10000; // Gain is set globally, not locally.
610 dest->dwFlags = DIEFF_OBJECTOFFSETS; // Seems obligatory.
611
612 // Envelope.
613 envelope = (DIENVELOPE *)SDL_calloc(1, sizeof(DIENVELOPE));
614 if (!envelope) {
615 return false;
616 }
617 dest->lpEnvelope = envelope;
618 envelope->dwSize = sizeof(DIENVELOPE); // Always should be this.
619
620 // Axes.
621 if (src->constant.direction.type == SDL_HAPTIC_STEERING_AXIS) {
622 dest->cAxes = 1;
623 } else {
624 dest->cAxes = haptic->naxes;
625 }
626 if (dest->cAxes > 0) {
627 axes = (DWORD *)SDL_malloc(sizeof(DWORD) * dest->cAxes);
628 if (!axes) {
629 return false;
630 }
631 axes[0] = haptic->hwdata->axes[0]; // Always at least one axis.
632 if (dest->cAxes > 1) {
633 axes[1] = haptic->hwdata->axes[1];
634 }
635 if (dest->cAxes > 2) {
636 axes[2] = haptic->hwdata->axes[2];
637 }
638 dest->rgdwAxes = axes;
639 }
640
641 // The big type handling switch, even bigger than Linux's version.
642 switch (src->type) {
643 case SDL_HAPTIC_CONSTANT:
644 hap_constant = &src->constant;
645 constant = (DICONSTANTFORCE *)SDL_calloc(1, sizeof(DICONSTANTFORCE));
646 if (!constant) {
647 return false;
648 }
649
650 // Specifics
651 constant->lMagnitude = CONVERT(hap_constant->level);
652 dest->cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
653 dest->lpvTypeSpecificParams = constant;
654
655 // Generics
656 dest->dwDuration = hap_constant->length * 1000UL; // In microseconds.
657 dest->dwTriggerButton = DIGetTriggerButton(hap_constant->button);
658 dest->dwTriggerRepeatInterval = hap_constant->interval;
659 dest->dwStartDelay = hap_constant->delay * 1000UL; // In microseconds.
660
661 // Direction.
662 if (!SDL_SYS_SetDirection(dest, &hap_constant->direction, dest->cAxes)) {
663 return false;
664 }
665
666 // Envelope
667 if ((hap_constant->attack_length == 0) && (hap_constant->fade_length == 0)) {
668 SDL_free(dest->lpEnvelope);
669 dest->lpEnvelope = NULL;
670 } else {
671 envelope->dwAttackLevel = CCONVERT(hap_constant->attack_level);
672 envelope->dwAttackTime = hap_constant->attack_length * 1000UL;
673 envelope->dwFadeLevel = CCONVERT(hap_constant->fade_level);
674 envelope->dwFadeTime = hap_constant->fade_length * 1000UL;
675 }
676
677 break;
678
679 case SDL_HAPTIC_SINE:
680 case SDL_HAPTIC_SQUARE:
681 case SDL_HAPTIC_TRIANGLE:
682 case SDL_HAPTIC_SAWTOOTHUP:
683 case SDL_HAPTIC_SAWTOOTHDOWN:
684 hap_periodic = &src->periodic;
685 periodic = (DIPERIODIC *)SDL_calloc(1, sizeof(DIPERIODIC));
686 if (!periodic) {
687 return false;
688 }
689
690 // Specifics
691 periodic->dwMagnitude = CONVERT(SDL_abs(hap_periodic->magnitude));
692 periodic->lOffset = CONVERT(hap_periodic->offset);
693 periodic->dwPhase =
694 (hap_periodic->phase + (hap_periodic->magnitude < 0 ? 18000 : 0)) % 36000;
695 periodic->dwPeriod = hap_periodic->period * 1000;
696 dest->cbTypeSpecificParams = sizeof(DIPERIODIC);
697 dest->lpvTypeSpecificParams = periodic;
698
699 // Generics
700 dest->dwDuration = hap_periodic->length * 1000UL; // In microseconds.
701 dest->dwTriggerButton = DIGetTriggerButton(hap_periodic->button);
702 dest->dwTriggerRepeatInterval = hap_periodic->interval;
703 dest->dwStartDelay = hap_periodic->delay * 1000UL; // In microseconds.
704
705 // Direction.
706 if (!SDL_SYS_SetDirection(dest, &hap_periodic->direction, dest->cAxes)) {
707 return false;
708 }
709
710 // Envelope
711 if ((hap_periodic->attack_length == 0) && (hap_periodic->fade_length == 0)) {
712 SDL_free(dest->lpEnvelope);
713 dest->lpEnvelope = NULL;
714 } else {
715 envelope->dwAttackLevel = CCONVERT(hap_periodic->attack_level);
716 envelope->dwAttackTime = hap_periodic->attack_length * 1000UL;
717 envelope->dwFadeLevel = CCONVERT(hap_periodic->fade_level);
718 envelope->dwFadeTime = hap_periodic->fade_length * 1000UL;
719 }
720
721 break;
722
723 case SDL_HAPTIC_SPRING:
724 case SDL_HAPTIC_DAMPER:
725 case SDL_HAPTIC_INERTIA:
726 case SDL_HAPTIC_FRICTION:
727 hap_condition = &src->condition;
728 condition = (DICONDITION *)SDL_calloc(dest->cAxes, sizeof(DICONDITION));
729 if (!condition) {
730 return false;
731 }
732
733 // Specifics
734 for (i = 0; i < (int)dest->cAxes; i++) {
735 condition[i].lOffset = CONVERT(hap_condition->center[i]);
736 condition[i].lPositiveCoefficient =
737 CONVERT(hap_condition->right_coeff[i]);
738 condition[i].lNegativeCoefficient =
739 CONVERT(hap_condition->left_coeff[i]);
740 condition[i].dwPositiveSaturation =
741 CCONVERT(hap_condition->right_sat[i] / 2);
742 condition[i].dwNegativeSaturation =
743 CCONVERT(hap_condition->left_sat[i] / 2);
744 condition[i].lDeadBand = CCONVERT(hap_condition->deadband[i] / 2);
745 }
746 dest->cbTypeSpecificParams = sizeof(DICONDITION) * dest->cAxes;
747 dest->lpvTypeSpecificParams = condition;
748
749 // Generics
750 dest->dwDuration = hap_condition->length * 1000UL; // In microseconds.
751 dest->dwTriggerButton = DIGetTriggerButton(hap_condition->button);
752 dest->dwTriggerRepeatInterval = hap_condition->interval;
753 dest->dwStartDelay = hap_condition->delay * 1000UL; // In microseconds.
754
755 // Direction.
756 if (!SDL_SYS_SetDirection(dest, &hap_condition->direction, dest->cAxes)) {
757 return false;
758 }
759
760 // Envelope - Not actually supported by most CONDITION implementations.
761 SDL_free(dest->lpEnvelope);
762 dest->lpEnvelope = NULL;
763
764 break;
765
766 case SDL_HAPTIC_RAMP:
767 hap_ramp = &src->ramp;
768 ramp = (DIRAMPFORCE *)SDL_calloc(1, sizeof(DIRAMPFORCE));
769 if (!ramp) {
770 return false;
771 }
772
773 // Specifics
774 ramp->lStart = CONVERT(hap_ramp->start);
775 ramp->lEnd = CONVERT(hap_ramp->end);
776 dest->cbTypeSpecificParams = sizeof(DIRAMPFORCE);
777 dest->lpvTypeSpecificParams = ramp;
778
779 // Generics
780 dest->dwDuration = hap_ramp->length * 1000UL; // In microseconds.
781 dest->dwTriggerButton = DIGetTriggerButton(hap_ramp->button);
782 dest->dwTriggerRepeatInterval = hap_ramp->interval;
783 dest->dwStartDelay = hap_ramp->delay * 1000UL; // In microseconds.
784
785 // Direction.
786 if (!SDL_SYS_SetDirection(dest, &hap_ramp->direction, dest->cAxes)) {
787 return false;
788 }
789
790 // Envelope
791 if ((hap_ramp->attack_length == 0) && (hap_ramp->fade_length == 0)) {
792 SDL_free(dest->lpEnvelope);
793 dest->lpEnvelope = NULL;
794 } else {
795 envelope->dwAttackLevel = CCONVERT(hap_ramp->attack_level);
796 envelope->dwAttackTime = hap_ramp->attack_length * 1000UL;
797 envelope->dwFadeLevel = CCONVERT(hap_ramp->fade_level);
798 envelope->dwFadeTime = hap_ramp->fade_length * 1000UL;
799 }
800
801 break;
802
803 case SDL_HAPTIC_CUSTOM:
804 hap_custom = &src->custom;
805 custom = (DICUSTOMFORCE *)SDL_calloc(1, sizeof(DICUSTOMFORCE));
806 if (!custom) {
807 return false;
808 }
809
810 // Specifics
811 custom->cChannels = hap_custom->channels;
812 custom->dwSamplePeriod = hap_custom->period * 1000UL;
813 custom->cSamples = hap_custom->samples;
814 custom->rglForceData = (LPLONG)SDL_malloc(sizeof(LONG) * custom->cSamples * custom->cChannels);
815 for (i = 0; i < hap_custom->samples * hap_custom->channels; i++) { // Copy data.
816 custom->rglForceData[i] = CCONVERT(hap_custom->data[i]);
817 }
818 dest->cbTypeSpecificParams = sizeof(DICUSTOMFORCE);
819 dest->lpvTypeSpecificParams = custom;
820
821 // Generics
822 dest->dwDuration = hap_custom->length * 1000UL; // In microseconds.
823 dest->dwTriggerButton = DIGetTriggerButton(hap_custom->button);
824 dest->dwTriggerRepeatInterval = hap_custom->interval;
825 dest->dwStartDelay = hap_custom->delay * 1000UL; // In microseconds.
826
827 // Direction.
828 if (!SDL_SYS_SetDirection(dest, &hap_custom->direction, dest->cAxes)) {
829 return false;
830 }
831
832 // Envelope
833 if ((hap_custom->attack_length == 0) && (hap_custom->fade_length == 0)) {
834 SDL_free(dest->lpEnvelope);
835 dest->lpEnvelope = NULL;
836 } else {
837 envelope->dwAttackLevel = CCONVERT(hap_custom->attack_level);
838 envelope->dwAttackTime = hap_custom->attack_length * 1000UL;
839 envelope->dwFadeLevel = CCONVERT(hap_custom->fade_level);
840 envelope->dwFadeTime = hap_custom->fade_length * 1000UL;
841 }
842
843 break;
844
845 default:
846 return SDL_SetError("Haptic: Unknown effect type.");
847 }
848
849 return true;
850}
851
852/*
853 * Frees an DIEFFECT allocated by SDL_SYS_ToDIEFFECT.
854 */
855static void SDL_SYS_HapticFreeDIEFFECT(DIEFFECT *effect, int type)
856{
857 DICUSTOMFORCE *custom;
858
859 SDL_free(effect->lpEnvelope);
860 effect->lpEnvelope = NULL;
861 SDL_free(effect->rgdwAxes);
862 effect->rgdwAxes = NULL;
863 if (effect->lpvTypeSpecificParams) {
864 if (type == SDL_HAPTIC_CUSTOM) { // Must free the custom data.
865 custom = (DICUSTOMFORCE *)effect->lpvTypeSpecificParams;
866 SDL_free(custom->rglForceData);
867 custom->rglForceData = NULL;
868 }
869 SDL_free(effect->lpvTypeSpecificParams);
870 effect->lpvTypeSpecificParams = NULL;
871 }
872 SDL_free(effect->rglDirection);
873 effect->rglDirection = NULL;
874}
875
876/*
877 * Gets the effect type from the generic SDL haptic effect wrapper.
878 */
879// NOLINTNEXTLINE(readability-const-return-type): Can't fix Windows' headers
880static REFGUID SDL_SYS_HapticEffectType(const SDL_HapticEffect *effect)
881{
882 switch (effect->type) {
883 case SDL_HAPTIC_CONSTANT:
884 return &GUID_ConstantForce;
885
886 case SDL_HAPTIC_RAMP:
887 return &GUID_RampForce;
888
889 case SDL_HAPTIC_SQUARE:
890 return &GUID_Square;
891
892 case SDL_HAPTIC_SINE:
893 return &GUID_Sine;
894
895 case SDL_HAPTIC_TRIANGLE:
896 return &GUID_Triangle;
897
898 case SDL_HAPTIC_SAWTOOTHUP:
899 return &GUID_SawtoothUp;
900
901 case SDL_HAPTIC_SAWTOOTHDOWN:
902 return &GUID_SawtoothDown;
903
904 case SDL_HAPTIC_SPRING:
905 return &GUID_Spring;
906
907 case SDL_HAPTIC_DAMPER:
908 return &GUID_Damper;
909
910 case SDL_HAPTIC_INERTIA:
911 return &GUID_Inertia;
912
913 case SDL_HAPTIC_FRICTION:
914 return &GUID_Friction;
915
916 case SDL_HAPTIC_CUSTOM:
917 return &GUID_CustomForce;
918
919 default:
920 return NULL;
921 }
922}
923bool SDL_DINPUT_HapticNewEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *base)
924{
925 HRESULT ret;
926 REFGUID type = SDL_SYS_HapticEffectType(base);
927
928 if (!type) {
929 return SDL_SetError("Haptic: Unknown effect type.");
930 }
931
932 // Get the effect.
933 if (!SDL_SYS_ToDIEFFECT(haptic, &effect->hweffect->effect, base)) {
934 goto err_effectdone;
935 }
936
937 // Create the actual effect.
938 ret = IDirectInputDevice8_CreateEffect(haptic->hwdata->device, type,
939 &effect->hweffect->effect,
940 &effect->hweffect->ref, NULL);
941 if (FAILED(ret)) {
942 DI_SetError("Unable to create effect", ret);
943 goto err_effectdone;
944 }
945
946 return true;
947
948err_effectdone:
949 SDL_SYS_HapticFreeDIEFFECT(&effect->hweffect->effect, base->type);
950 return false;
951}
952
953bool SDL_DINPUT_HapticUpdateEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *data)
954{
955 HRESULT ret;
956 DWORD flags;
957 DIEFFECT temp;
958
959 // Get the effect.
960 SDL_memset(&temp, 0, sizeof(DIEFFECT));
961 if (!SDL_SYS_ToDIEFFECT(haptic, &temp, data)) {
962 goto err_update;
963 }
964
965 /* Set the flags. Might be worthwhile to diff temp with loaded effect and
966 * only change those parameters. */
967 flags = DIEP_DIRECTION |
968 DIEP_DURATION |
969 DIEP_ENVELOPE |
970 DIEP_STARTDELAY |
971 DIEP_TRIGGERBUTTON |
972 DIEP_TRIGGERREPEATINTERVAL | DIEP_TYPESPECIFICPARAMS;
973
974 // Create the actual effect.
975 ret =
976 IDirectInputEffect_SetParameters(effect->hweffect->ref, &temp, flags);
977 if (ret == DIERR_NOTEXCLUSIVEACQUIRED) {
978 IDirectInputDevice8_Unacquire(haptic->hwdata->device);
979 ret = IDirectInputDevice8_SetCooperativeLevel(haptic->hwdata->device, SDL_HelperWindow, DISCL_EXCLUSIVE | DISCL_BACKGROUND);
980 if (SUCCEEDED(ret)) {
981 ret = DIERR_NOTACQUIRED;
982 }
983 }
984 if (ret == DIERR_INPUTLOST || ret == DIERR_NOTACQUIRED) {
985 ret = IDirectInputDevice8_Acquire(haptic->hwdata->device);
986 if (SUCCEEDED(ret)) {
987 ret = IDirectInputEffect_SetParameters(effect->hweffect->ref, &temp, flags);
988 }
989 }
990 if (FAILED(ret)) {
991 DI_SetError("Unable to update effect", ret);
992 goto err_update;
993 }
994
995 // Copy it over.
996 SDL_SYS_HapticFreeDIEFFECT(&effect->hweffect->effect, data->type);
997 SDL_memcpy(&effect->hweffect->effect, &temp, sizeof(DIEFFECT));
998
999 return true;
1000
1001err_update:
1002 SDL_SYS_HapticFreeDIEFFECT(&temp, data->type);
1003 return false;
1004}
1005
1006bool SDL_DINPUT_HapticRunEffect(SDL_Haptic *haptic, struct haptic_effect *effect, Uint32 iterations)
1007{
1008 HRESULT ret;
1009 DWORD iter;
1010
1011 // Check if it's infinite.
1012 if (iterations == SDL_HAPTIC_INFINITY) {
1013 iter = INFINITE;
1014 } else {
1015 iter = iterations;
1016 }
1017
1018 // Run the effect.
1019 ret = IDirectInputEffect_Start(effect->hweffect->ref, iter, 0);
1020 if (FAILED(ret)) {
1021 return DI_SetError("Running the effect", ret);
1022 }
1023 return true;
1024}
1025
1026bool SDL_DINPUT_HapticStopEffect(SDL_Haptic *haptic, struct haptic_effect *effect)
1027{
1028 HRESULT ret;
1029
1030 ret = IDirectInputEffect_Stop(effect->hweffect->ref);
1031 if (FAILED(ret)) {
1032 return DI_SetError("Unable to stop effect", ret);
1033 }
1034 return true;
1035}
1036
1037void SDL_DINPUT_HapticDestroyEffect(SDL_Haptic *haptic, struct haptic_effect *effect)
1038{
1039 HRESULT ret;
1040
1041 ret = IDirectInputEffect_Unload(effect->hweffect->ref);
1042 if (FAILED(ret)) {
1043 DI_SetError("Removing effect from the device", ret);
1044 }
1045 SDL_SYS_HapticFreeDIEFFECT(&effect->hweffect->effect, effect->effect.type);
1046}
1047
1048int SDL_DINPUT_HapticGetEffectStatus(SDL_Haptic *haptic, struct haptic_effect *effect)
1049{
1050 HRESULT ret;
1051 DWORD status;
1052
1053 ret = IDirectInputEffect_GetEffectStatus(effect->hweffect->ref, &status);
1054 if (FAILED(ret)) {
1055 DI_SetError("Getting effect status", ret);
1056 return -1;
1057 }
1058
1059 if (status == 0) {
1060 return 0;
1061 }
1062 return 1;
1063}
1064
1065bool SDL_DINPUT_HapticSetGain(SDL_Haptic *haptic, int gain)
1066{
1067 HRESULT ret;
1068 DIPROPDWORD dipdw;
1069
1070 // Create the weird structure thingy.
1071 dipdw.diph.dwSize = sizeof(DIPROPDWORD);
1072 dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
1073 dipdw.diph.dwObj = 0;
1074 dipdw.diph.dwHow = DIPH_DEVICE;
1075 dipdw.dwData = (DWORD)gain * 100; // 0 to 10,000
1076
1077 // Try to set the autocenter.
1078 ret = IDirectInputDevice8_SetProperty(haptic->hwdata->device,
1079 DIPROP_FFGAIN, &dipdw.diph);
1080 if (FAILED(ret)) {
1081 return DI_SetError("Setting gain", ret);
1082 }
1083 return true;
1084}
1085
1086bool SDL_DINPUT_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter)
1087{
1088 HRESULT ret;
1089 DIPROPDWORD dipdw;
1090
1091 // Create the weird structure thingy.
1092 dipdw.diph.dwSize = sizeof(DIPROPDWORD);
1093 dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
1094 dipdw.diph.dwObj = 0;
1095 dipdw.diph.dwHow = DIPH_DEVICE;
1096 dipdw.dwData = (autocenter == 0) ? DIPROPAUTOCENTER_OFF : DIPROPAUTOCENTER_ON;
1097
1098 // Try to set the autocenter.
1099 ret = IDirectInputDevice8_SetProperty(haptic->hwdata->device,
1100 DIPROP_AUTOCENTER, &dipdw.diph);
1101 if (FAILED(ret)) {
1102 return DI_SetError("Setting autocenter", ret);
1103 }
1104 return true;
1105}
1106
1107bool SDL_DINPUT_HapticPause(SDL_Haptic *haptic)
1108{
1109 HRESULT ret;
1110
1111 // Pause the device.
1112 ret = IDirectInputDevice8_SendForceFeedbackCommand(haptic->hwdata->device,
1113 DISFFC_PAUSE);
1114 if (FAILED(ret)) {
1115 return DI_SetError("Pausing the device", ret);
1116 }
1117 return true;
1118}
1119
1120bool SDL_DINPUT_HapticResume(SDL_Haptic *haptic)
1121{
1122 HRESULT ret;
1123
1124 // Unpause the device.
1125 ret = IDirectInputDevice8_SendForceFeedbackCommand(haptic->hwdata->device,
1126 DISFFC_CONTINUE);
1127 if (FAILED(ret)) {
1128 return DI_SetError("Pausing the device", ret);
1129 }
1130 return true;
1131}
1132
1133bool SDL_DINPUT_HapticStopAll(SDL_Haptic *haptic)
1134{
1135 HRESULT ret;
1136
1137 // Try to stop the effects.
1138 ret = IDirectInputDevice8_SendForceFeedbackCommand(haptic->hwdata->device,
1139 DISFFC_STOPALL);
1140 if (FAILED(ret)) {
1141 return DI_SetError("Stopping the device", ret);
1142 }
1143 return true;
1144}
1145
1146#else // !SDL_HAPTIC_DINPUT
1147
1148typedef struct DIDEVICEINSTANCE DIDEVICEINSTANCE;
1149typedef struct SDL_hapticlist_item SDL_hapticlist_item;
1150
1151bool SDL_DINPUT_HapticInit(void)
1152{
1153 return true;
1154}
1155
1156bool SDL_DINPUT_HapticMaybeAddDevice(const DIDEVICEINSTANCE *pdidInstance)
1157{
1158 return SDL_Unsupported();
1159}
1160
1161bool SDL_DINPUT_HapticMaybeRemoveDevice(const DIDEVICEINSTANCE *pdidInstance)
1162{
1163 return SDL_Unsupported();
1164}
1165
1166bool SDL_DINPUT_HapticOpen(SDL_Haptic *haptic, SDL_hapticlist_item *item)
1167{
1168 return SDL_Unsupported();
1169}
1170
1171bool SDL_DINPUT_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick)
1172{
1173 return false;
1174}
1175
1176bool SDL_DINPUT_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick)
1177{
1178 return SDL_Unsupported();
1179}
1180
1181void SDL_DINPUT_HapticClose(SDL_Haptic *haptic)
1182{
1183}
1184
1185void SDL_DINPUT_HapticQuit(void)
1186{
1187}
1188
1189bool SDL_DINPUT_HapticNewEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *base)
1190{
1191 return SDL_Unsupported();
1192}
1193
1194bool SDL_DINPUT_HapticUpdateEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *data)
1195{
1196 return SDL_Unsupported();
1197}
1198
1199bool SDL_DINPUT_HapticRunEffect(SDL_Haptic *haptic, struct haptic_effect *effect, Uint32 iterations)
1200{
1201 return SDL_Unsupported();
1202}
1203
1204bool SDL_DINPUT_HapticStopEffect(SDL_Haptic *haptic, struct haptic_effect *effect)
1205{
1206 return SDL_Unsupported();
1207}
1208
1209void SDL_DINPUT_HapticDestroyEffect(SDL_Haptic *haptic, struct haptic_effect *effect)
1210{
1211}
1212
1213int SDL_DINPUT_HapticGetEffectStatus(SDL_Haptic *haptic, struct haptic_effect *effect)
1214{
1215 SDL_Unsupported();
1216 return -1;
1217}
1218
1219bool SDL_DINPUT_HapticSetGain(SDL_Haptic *haptic, int gain)
1220{
1221 return SDL_Unsupported();
1222}
1223
1224bool SDL_DINPUT_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter)
1225{
1226 return SDL_Unsupported();
1227}
1228
1229bool SDL_DINPUT_HapticPause(SDL_Haptic *haptic)
1230{
1231 return SDL_Unsupported();
1232}
1233
1234bool SDL_DINPUT_HapticResume(SDL_Haptic *haptic)
1235{
1236 return SDL_Unsupported();
1237}
1238
1239bool SDL_DINPUT_HapticStopAll(SDL_Haptic *haptic)
1240{
1241 return SDL_Unsupported();
1242}
1243
1244#endif // SDL_HAPTIC_DINPUT
diff --git a/contrib/SDL-3.2.8/src/haptic/windows/SDL_dinputhaptic_c.h b/contrib/SDL-3.2.8/src/haptic/windows/SDL_dinputhaptic_c.h
new file mode 100644
index 0000000..d6265c9
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/haptic/windows/SDL_dinputhaptic_c.h
@@ -0,0 +1,53 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 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#include "SDL_windowshaptic_c.h"
24
25// Set up for C function definitions, even when using C++
26#ifdef __cplusplus
27extern "C" {
28#endif
29
30extern bool SDL_DINPUT_HapticInit(void);
31extern bool SDL_DINPUT_HapticMaybeAddDevice(const DIDEVICEINSTANCE *pdidInstance);
32extern bool SDL_DINPUT_HapticMaybeRemoveDevice(const DIDEVICEINSTANCE *pdidInstance);
33extern bool SDL_DINPUT_HapticOpen(SDL_Haptic *haptic, SDL_hapticlist_item *item);
34extern bool SDL_DINPUT_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick);
35extern bool SDL_DINPUT_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick);
36extern void SDL_DINPUT_HapticClose(SDL_Haptic *haptic);
37extern void SDL_DINPUT_HapticQuit(void);
38extern bool SDL_DINPUT_HapticNewEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *base);
39extern bool SDL_DINPUT_HapticUpdateEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *data);
40extern bool SDL_DINPUT_HapticRunEffect(SDL_Haptic *haptic, struct haptic_effect *effect, Uint32 iterations);
41extern bool SDL_DINPUT_HapticStopEffect(SDL_Haptic *haptic, struct haptic_effect *effect);
42extern void SDL_DINPUT_HapticDestroyEffect(SDL_Haptic *haptic, struct haptic_effect *effect);
43extern int SDL_DINPUT_HapticGetEffectStatus(SDL_Haptic *haptic, struct haptic_effect *effect);
44extern bool SDL_DINPUT_HapticSetGain(SDL_Haptic *haptic, int gain);
45extern bool SDL_DINPUT_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter);
46extern bool SDL_DINPUT_HapticPause(SDL_Haptic *haptic);
47extern bool SDL_DINPUT_HapticResume(SDL_Haptic *haptic);
48extern bool SDL_DINPUT_HapticStopAll(SDL_Haptic *haptic);
49
50// Ends C function definitions when using C++
51#ifdef __cplusplus
52}
53#endif
diff --git a/contrib/SDL-3.2.8/src/haptic/windows/SDL_windowshaptic.c b/contrib/SDL-3.2.8/src/haptic/windows/SDL_windowshaptic.c
new file mode 100644
index 0000000..e21ab91
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/haptic/windows/SDL_windowshaptic.c
@@ -0,0 +1,369 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 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_DINPUT
24
25#include "../SDL_syshaptic.h"
26#include "../../joystick/SDL_sysjoystick.h" // For the real SDL_Joystick
27#include "../../joystick/windows/SDL_windowsjoystick_c.h" // For joystick hwdata
28#include "../../joystick/windows/SDL_xinputjoystick_c.h" // For xinput rumble
29
30#include "SDL_windowshaptic_c.h"
31#include "SDL_dinputhaptic_c.h"
32
33// Set up for C function definitions, even when using C++
34#ifdef __cplusplus
35extern "C" {
36#endif
37
38/*
39 * Internal stuff.
40 */
41SDL_hapticlist_item *SDL_hapticlist = NULL;
42static SDL_hapticlist_item *SDL_hapticlist_tail = NULL;
43static int numhaptics = 0;
44
45/*
46 * Initializes the haptic subsystem.
47 */
48bool SDL_SYS_HapticInit(void)
49{
50 JoyStick_DeviceData *device;
51
52 if (!SDL_DINPUT_HapticInit()) {
53 return false;
54 }
55
56 /* The joystick subsystem will usually be initialized before haptics,
57 * so the initial HapticMaybeAddDevice() calls from the joystick
58 * subsystem will arrive too early to create haptic devices. We will
59 * invoke those callbacks again here to pick up any joysticks that
60 * were added prior to haptics initialization. */
61 for (device = SYS_Joystick; device; device = device->pNext) {
62 SDL_DINPUT_HapticMaybeAddDevice(&device->dxdevice);
63 }
64
65 return true;
66}
67
68bool SDL_SYS_AddHapticDevice(SDL_hapticlist_item *item)
69{
70 if (!SDL_hapticlist_tail) {
71 SDL_hapticlist = SDL_hapticlist_tail = item;
72 } else {
73 SDL_hapticlist_tail->next = item;
74 SDL_hapticlist_tail = item;
75 }
76
77 // Device has been added.
78 ++numhaptics;
79
80 return true;
81}
82
83bool SDL_SYS_RemoveHapticDevice(SDL_hapticlist_item *prev, SDL_hapticlist_item *item)
84{
85 const bool result = item->haptic ? true : false;
86 if (prev) {
87 prev->next = item->next;
88 } else {
89 SDL_assert(SDL_hapticlist == item);
90 SDL_hapticlist = item->next;
91 }
92 if (item == SDL_hapticlist_tail) {
93 SDL_hapticlist_tail = prev;
94 }
95 --numhaptics;
96 // !!! TODO: Send a haptic remove event?
97 SDL_free(item);
98 return result;
99}
100
101int SDL_SYS_NumHaptics(void)
102{
103 return numhaptics;
104}
105
106static SDL_hapticlist_item *HapticByDevIndex(int device_index)
107{
108 SDL_hapticlist_item *item = SDL_hapticlist;
109
110 if ((device_index < 0) || (device_index >= numhaptics)) {
111 return NULL;
112 }
113
114 while (device_index > 0) {
115 SDL_assert(item != NULL);
116 --device_index;
117 item = item->next;
118 }
119 return item;
120}
121
122static SDL_hapticlist_item *HapticByInstanceID(SDL_HapticID instance_id)
123{
124 SDL_hapticlist_item *item;
125 for (item = SDL_hapticlist; item; item = item->next) {
126 if (instance_id == item->instance_id) {
127 return item;
128 }
129 }
130 return NULL;
131}
132
133SDL_HapticID SDL_SYS_HapticInstanceID(int index)
134{
135 SDL_hapticlist_item *item = HapticByDevIndex(index);
136 if (item) {
137 return item->instance_id;
138 }
139 return 0;
140}
141
142/*
143 * Return the name of a haptic device, does not need to be opened.
144 */
145const char *SDL_SYS_HapticName(int index)
146{
147 SDL_hapticlist_item *item = HapticByDevIndex(index);
148 return item->name;
149}
150
151/*
152 * Opens a haptic device for usage.
153 */
154bool SDL_SYS_HapticOpen(SDL_Haptic *haptic)
155{
156 SDL_hapticlist_item *item = HapticByInstanceID(haptic->instance_id);
157 return SDL_DINPUT_HapticOpen(haptic, item);
158}
159
160/*
161 * Opens a haptic device from first mouse it finds for usage.
162 */
163int SDL_SYS_HapticMouse(void)
164{
165#ifdef SDL_HAPTIC_DINPUT
166 SDL_hapticlist_item *item;
167 int index = 0;
168
169 // Grab the first mouse haptic device we find.
170 for (item = SDL_hapticlist; item; item = item->next) {
171 if (item->capabilities.dwDevType == DI8DEVCLASS_POINTER) {
172 return index;
173 }
174 ++index;
175 }
176#endif // SDL_HAPTIC_DINPUT
177 return -1;
178}
179
180/*
181 * Checks to see if a joystick has haptic features.
182 */
183bool SDL_SYS_JoystickIsHaptic(SDL_Joystick *joystick)
184{
185 if (joystick->driver != &SDL_WINDOWS_JoystickDriver) {
186 return false;
187 }
188 if (joystick->hwdata->Capabilities.dwFlags & DIDC_FORCEFEEDBACK) {
189 return true;
190 }
191 return false;
192}
193
194/*
195 * Checks to see if the haptic device and joystick are in reality the same.
196 */
197bool SDL_SYS_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick)
198{
199 if (joystick->driver != &SDL_WINDOWS_JoystickDriver) {
200 return false;
201 }
202 return SDL_DINPUT_JoystickSameHaptic(haptic, joystick);
203}
204
205/*
206 * Opens a SDL_Haptic from a SDL_Joystick.
207 */
208bool SDL_SYS_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick)
209{
210 SDL_assert(joystick->driver == &SDL_WINDOWS_JoystickDriver);
211
212 return SDL_DINPUT_HapticOpenFromJoystick(haptic, joystick);
213}
214
215/*
216 * Closes the haptic device.
217 */
218void SDL_SYS_HapticClose(SDL_Haptic *haptic)
219{
220 if (haptic->hwdata) {
221
222 // Free effects.
223 SDL_free(haptic->effects);
224 haptic->effects = NULL;
225 haptic->neffects = 0;
226
227 // Clean up
228 SDL_DINPUT_HapticClose(haptic);
229
230 // Free
231 SDL_free(haptic->hwdata);
232 haptic->hwdata = NULL;
233 }
234}
235
236/*
237 * Clean up after system specific haptic stuff
238 */
239void SDL_SYS_HapticQuit(void)
240{
241 SDL_hapticlist_item *item;
242 SDL_hapticlist_item *next = NULL;
243
244 for (item = SDL_hapticlist; item; item = next) {
245 /* Opened and not closed haptics are leaked, this is on purpose.
246 * Close your haptic devices after usage. */
247 // !!! FIXME: (...is leaking on purpose a good idea?) - No, of course not.
248 next = item->next;
249 SDL_free(item->name);
250 SDL_free(item);
251 }
252
253 SDL_DINPUT_HapticQuit();
254
255 numhaptics = 0;
256 SDL_hapticlist = NULL;
257 SDL_hapticlist_tail = NULL;
258}
259
260/*
261 * Creates a new haptic effect.
262 */
263bool SDL_SYS_HapticNewEffect(SDL_Haptic *haptic, struct haptic_effect *effect,
264 const SDL_HapticEffect *base)
265{
266 bool result;
267
268 // Alloc the effect.
269 effect->hweffect = (struct haptic_hweffect *) SDL_calloc(1, sizeof(struct haptic_hweffect));
270 if (!effect->hweffect) {
271 return false;
272 }
273
274 result = SDL_DINPUT_HapticNewEffect(haptic, effect, base);
275 if (!result) {
276 SDL_free(effect->hweffect);
277 effect->hweffect = NULL;
278 }
279 return result;
280}
281
282/*
283 * Updates an effect.
284 */
285bool SDL_SYS_HapticUpdateEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *data)
286{
287 return SDL_DINPUT_HapticUpdateEffect(haptic, effect, data);
288}
289
290/*
291 * Runs an effect.
292 */
293bool SDL_SYS_HapticRunEffect(SDL_Haptic *haptic, struct haptic_effect *effect, Uint32 iterations)
294{
295 return SDL_DINPUT_HapticRunEffect(haptic, effect, iterations);
296}
297
298/*
299 * Stops an effect.
300 */
301bool SDL_SYS_HapticStopEffect(SDL_Haptic *haptic, struct haptic_effect *effect)
302{
303 return SDL_DINPUT_HapticStopEffect(haptic, effect);
304}
305
306/*
307 * Frees the effect.
308 */
309void SDL_SYS_HapticDestroyEffect(SDL_Haptic *haptic, struct haptic_effect *effect)
310{
311 SDL_DINPUT_HapticDestroyEffect(haptic, effect);
312 SDL_free(effect->hweffect);
313 effect->hweffect = NULL;
314}
315
316/*
317 * Gets the status of a haptic effect.
318 */
319int SDL_SYS_HapticGetEffectStatus(SDL_Haptic *haptic, struct haptic_effect *effect)
320{
321 return SDL_DINPUT_HapticGetEffectStatus(haptic, effect);
322}
323
324/*
325 * Sets the gain.
326 */
327bool SDL_SYS_HapticSetGain(SDL_Haptic *haptic, int gain)
328{
329 return SDL_DINPUT_HapticSetGain(haptic, gain);
330}
331
332/*
333 * Sets the autocentering.
334 */
335bool SDL_SYS_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter)
336{
337 return SDL_DINPUT_HapticSetAutocenter(haptic, autocenter);
338}
339
340/*
341 * Pauses the device.
342 */
343bool SDL_SYS_HapticPause(SDL_Haptic *haptic)
344{
345 return SDL_DINPUT_HapticPause(haptic);
346}
347
348/*
349 * Pauses the device.
350 */
351bool SDL_SYS_HapticResume(SDL_Haptic *haptic)
352{
353 return SDL_DINPUT_HapticResume(haptic);
354}
355
356/*
357 * Stops all the playing effects on the device.
358 */
359bool SDL_SYS_HapticStopAll(SDL_Haptic *haptic)
360{
361 return SDL_DINPUT_HapticStopAll(haptic);
362}
363
364// Ends C function definitions when using C++
365#ifdef __cplusplus
366}
367#endif
368
369#endif // SDL_HAPTIC_DINPUT
diff --git a/contrib/SDL-3.2.8/src/haptic/windows/SDL_windowshaptic_c.h b/contrib/SDL-3.2.8/src/haptic/windows/SDL_windowshaptic_c.h
new file mode 100644
index 0000000..c4b6928
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/haptic/windows/SDL_windowshaptic_c.h
@@ -0,0 +1,87 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 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#ifndef SDL_windowshaptic_c_h_
24#define SDL_windowshaptic_c_h_
25
26#include "../SDL_syshaptic.h"
27#include "../../core/windows/SDL_directx.h"
28#include "../../core/windows/SDL_xinput.h"
29
30// Set up for C function definitions, even when using C++
31#ifdef __cplusplus
32extern "C" {
33#endif
34
35/*
36 * Haptic system hardware data.
37 */
38struct haptic_hwdata
39{
40#ifdef SDL_HAPTIC_DINPUT
41 LPDIRECTINPUTDEVICE8 device;
42#endif
43 DWORD axes[3]; // Axes to use.
44 bool is_joystick; // Device is loaded as joystick.
45 SDL_Thread *thread;
46 SDL_Mutex *mutex;
47 Uint64 stopTicks;
48 SDL_AtomicInt stopThread;
49};
50
51/*
52 * Haptic system effect data.
53 */
54#ifdef SDL_HAPTIC_DINPUT
55struct haptic_hweffect
56{
57 DIEFFECT effect;
58 LPDIRECTINPUTEFFECT ref;
59};
60#endif
61
62/*
63 * List of available haptic devices.
64 */
65typedef struct SDL_hapticlist_item
66{
67 SDL_HapticID instance_id;
68 char *name;
69 SDL_Haptic *haptic;
70#ifdef SDL_HAPTIC_DINPUT
71 DIDEVICEINSTANCE instance;
72 DIDEVCAPS capabilities;
73#endif
74 struct SDL_hapticlist_item *next;
75} SDL_hapticlist_item;
76
77extern SDL_hapticlist_item *SDL_hapticlist;
78
79extern bool SDL_SYS_AddHapticDevice(SDL_hapticlist_item *item);
80extern bool SDL_SYS_RemoveHapticDevice(SDL_hapticlist_item *prev, SDL_hapticlist_item *item);
81
82// Ends C function definitions when using C++
83#ifdef __cplusplus
84}
85#endif
86
87#endif // SDL_windowshaptic_c_h_