1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2021 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_PrivateJoystickValid */
26
27/* Global for SDL_windowshaptic.c */
28#if (defined(SDL_HAPTIC_DINPUT) && SDL_HAPTIC_DINPUT) || (defined(SDL_HAPTIC_XINPUT) && SDL_HAPTIC_XINPUT)
29SDL_Haptic *SDL_haptics = NULL;
30#else
31static SDL_Haptic *SDL_haptics = NULL;
32#endif
33
34/*
35 * Initializes the Haptic devices.
36 */
37int
38SDL_HapticInit(void)
39{
40 int status;
41
42 status = SDL_SYS_HapticInit();
43 if (status >= 0) {
44 status = 0;
45 }
46
47 return status;
48}
49
50
51/*
52 * Checks to see if the haptic device is valid
53 */
54static int
55ValidHaptic(SDL_Haptic * haptic)
56{
57 int valid;
58 SDL_Haptic *hapticlist;
59
60 valid = 0;
61 if (haptic != NULL) {
62 hapticlist = SDL_haptics;
63 while ( hapticlist )
64 {
65 if (hapticlist == haptic) {
66 valid = 1;
67 break;
68 }
69 hapticlist = hapticlist->next;
70 }
71 }
72
73 /* Create the error here. */
74 if (valid == 0) {
75 SDL_SetError("Haptic: Invalid haptic device identifier");
76 }
77
78 return valid;
79}
80
81
82/*
83 * Returns the number of available devices.
84 */
85int
86SDL_NumHaptics(void)
87{
88 return SDL_SYS_NumHaptics();
89}
90
91
92/*
93 * Gets the name of a Haptic device by index.
94 */
95const char *
96SDL_HapticName(int device_index)
97{
98 if ((device_index < 0) || (device_index >= SDL_NumHaptics())) {
99 SDL_SetError("Haptic: There are %d haptic devices available",
100 SDL_NumHaptics());
101 return NULL;
102 }
103 return SDL_SYS_HapticName(device_index);
104}
105
106
107/*
108 * Opens a Haptic device.
109 */
110SDL_Haptic *
111SDL_HapticOpen(int device_index)
112{
113 SDL_Haptic *haptic;
114 SDL_Haptic *hapticlist;
115
116 if ((device_index < 0) || (device_index >= SDL_NumHaptics())) {
117 SDL_SetError("Haptic: There are %d haptic devices available",
118 SDL_NumHaptics());
119 return NULL;
120 }
121
122 hapticlist = SDL_haptics;
123 /* If the haptic is already open, return it
124 * TODO: Should we create haptic instance IDs like the Joystick API?
125 */
126 while ( hapticlist )
127 {
128 if (device_index == hapticlist->index) {
129 haptic = hapticlist;
130 ++haptic->ref_count;
131 return haptic;
132 }
133 hapticlist = hapticlist->next;
134 }
135
136 /* Create the haptic device */
137 haptic = (SDL_Haptic *) SDL_malloc((sizeof *haptic));
138 if (haptic == NULL) {
139 SDL_OutOfMemory();
140 return NULL;
141 }
142
143 /* Initialize the haptic device */
144 SDL_memset(haptic, 0, (sizeof *haptic));
145 haptic->rumble_id = -1;
146 haptic->index = device_index;
147 if (SDL_SYS_HapticOpen(haptic) < 0) {
148 SDL_free(haptic);
149 return NULL;
150 }
151
152 /* Add haptic to list */
153 ++haptic->ref_count;
154 /* Link the haptic in the list */
155 haptic->next = SDL_haptics;
156 SDL_haptics = haptic;
157
158 /* Disable autocenter and set gain to max. */
159 if (haptic->supported & SDL_HAPTIC_GAIN)
160 SDL_HapticSetGain(haptic, 100);
161 if (haptic->supported & SDL_HAPTIC_AUTOCENTER)
162 SDL_HapticSetAutocenter(haptic, 0);
163
164 return haptic;
165}
166
167
168/*
169 * Returns 1 if the device has been opened.
170 */
171int
172SDL_HapticOpened(int device_index)
173{
174 int opened;
175 SDL_Haptic *hapticlist;
176
177 /* Make sure it's valid. */
178 if ((device_index < 0) || (device_index >= SDL_NumHaptics())) {
179 SDL_SetError("Haptic: There are %d haptic devices available",
180 SDL_NumHaptics());
181 return 0;
182 }
183
184 opened = 0;
185 hapticlist = SDL_haptics;
186 /* TODO Should this use an instance ID? */
187 while ( hapticlist )
188 {
189 if (hapticlist->index == (Uint8) device_index) {
190 opened = 1;
191 break;
192 }
193 hapticlist = hapticlist->next;
194 }
195 return opened;
196}
197
198
199/*
200 * Returns the index to a haptic device.
201 */
202int
203SDL_HapticIndex(SDL_Haptic * haptic)
204{
205 if (!ValidHaptic(haptic)) {
206 return -1;
207 }
208
209 return haptic->index;
210}
211
212
213/*
214 * Returns SDL_TRUE if mouse is haptic, SDL_FALSE if it isn't.
215 */
216int
217SDL_MouseIsHaptic(void)
218{
219 if (SDL_SYS_HapticMouse() < 0)
220 return SDL_FALSE;
221 return SDL_TRUE;
222}
223
224
225/*
226 * Returns the haptic device if mouse is haptic or NULL elsewise.
227 */
228SDL_Haptic *
229SDL_HapticOpenFromMouse(void)
230{
231 int device_index;
232
233 device_index = SDL_SYS_HapticMouse();
234
235 if (device_index < 0) {
236 SDL_SetError("Haptic: Mouse isn't a haptic device.");
237 return NULL;
238 }
239
240 return SDL_HapticOpen(device_index);
241}
242
243
244/*
245 * Returns SDL_TRUE if joystick has haptic features.
246 */
247int
248SDL_JoystickIsHaptic(SDL_Joystick * joystick)
249{
250 int ret;
251
252 /* Must be a valid joystick */
253 if (!SDL_PrivateJoystickValid(joystick)) {
254 return -1;
255 }
256
257 ret = SDL_SYS_JoystickIsHaptic(joystick);
258
259 if (ret > 0)
260 return SDL_TRUE;
261 else if (ret == 0)
262 return SDL_FALSE;
263 else
264 return -1;
265}
266
267
268/*
269 * Opens a haptic device from a joystick.
270 */
271SDL_Haptic *
272SDL_HapticOpenFromJoystick(SDL_Joystick * joystick)
273{
274 SDL_Haptic *haptic;
275 SDL_Haptic *hapticlist;
276
277 /* Make sure there is room. */
278 if (SDL_NumHaptics() <= 0) {
279 SDL_SetError("Haptic: There are %d haptic devices available",
280 SDL_NumHaptics());
281 return NULL;
282 }
283
284 /* Must be a valid joystick */
285 if (!SDL_PrivateJoystickValid(joystick)) {
286 SDL_SetError("Haptic: Joystick isn't valid.");
287 return NULL;
288 }
289
290 /* Joystick must be haptic */
291 if (SDL_SYS_JoystickIsHaptic(joystick) <= 0) {
292 SDL_SetError("Haptic: Joystick isn't a haptic device.");
293 return NULL;
294 }
295
296 hapticlist = SDL_haptics;
297 /* Check to see if joystick's haptic is already open */
298 while ( hapticlist )
299 {
300 if (SDL_SYS_JoystickSameHaptic(hapticlist, joystick)) {
301 haptic = hapticlist;
302 ++haptic->ref_count;
303 return haptic;
304 }
305 hapticlist = hapticlist->next;
306 }
307
308 /* Create the haptic device */
309 haptic = (SDL_Haptic *) SDL_malloc((sizeof *haptic));
310 if (haptic == NULL) {
311 SDL_OutOfMemory();
312 return NULL;
313 }
314
315 /* Initialize the haptic device */
316 SDL_memset(haptic, 0, sizeof(SDL_Haptic));
317 haptic->rumble_id = -1;
318 if (SDL_SYS_HapticOpenFromJoystick(haptic, joystick) < 0) {
319 SDL_SetError("Haptic: SDL_SYS_HapticOpenFromJoystick failed.");
320 SDL_free(haptic);
321 return NULL;
322 }
323
324 /* Add haptic to list */
325 ++haptic->ref_count;
326 /* Link the haptic in the list */
327 haptic->next = SDL_haptics;
328 SDL_haptics = haptic;
329
330 return haptic;
331}
332
333
334/*
335 * Closes a SDL_Haptic device.
336 */
337void
338SDL_HapticClose(SDL_Haptic * haptic)
339{
340 int i;
341 SDL_Haptic *hapticlist;
342 SDL_Haptic *hapticlistprev;
343
344 /* Must be valid */
345 if (!ValidHaptic(haptic)) {
346 return;
347 }
348
349 /* Check if it's still in use */
350 if (--haptic->ref_count > 0) {
351 return;
352 }
353
354 /* Close it, properly removing effects if needed */
355 for (i = 0; i < haptic->neffects; i++) {
356 if (haptic->effects[i].hweffect != NULL) {
357 SDL_HapticDestroyEffect(haptic, i);
358 }
359 }
360 SDL_SYS_HapticClose(haptic);
361
362 /* Remove from the list */
363 hapticlist = SDL_haptics;
364 hapticlistprev = NULL;
365 while ( hapticlist )
366 {
367 if (haptic == hapticlist)
368 {
369 if ( hapticlistprev )
370 {
371 /* unlink this entry */
372 hapticlistprev->next = hapticlist->next;
373 }
374 else
375 {
376 SDL_haptics = haptic->next;
377 }
378
379 break;
380 }
381 hapticlistprev = hapticlist;
382 hapticlist = hapticlist->next;
383 }
384
385 /* Free */
386 SDL_free(haptic);
387}
388
389/*
390 * Cleans up after the subsystem.
391 */
392void
393SDL_HapticQuit(void)
394{
395 while (SDL_haptics) {
396 SDL_HapticClose(SDL_haptics);
397 }
398
399 SDL_SYS_HapticQuit();
400}
401
402/*
403 * Returns the number of effects a haptic device has.
404 */
405int
406SDL_HapticNumEffects(SDL_Haptic * haptic)
407{
408 if (!ValidHaptic(haptic)) {
409 return -1;
410 }
411
412 return haptic->neffects;
413}
414
415
416/*
417 * Returns the number of effects a haptic device can play.
418 */
419int
420SDL_HapticNumEffectsPlaying(SDL_Haptic * haptic)
421{
422 if (!ValidHaptic(haptic)) {
423 return -1;
424 }
425
426 return haptic->nplaying;
427}
428
429
430/*
431 * Returns supported effects by the device.
432 */
433unsigned int
434SDL_HapticQuery(SDL_Haptic * haptic)
435{
436 if (!ValidHaptic(haptic)) {
437 return 0; /* same as if no effects were supported */
438 }
439
440 return haptic->supported;
441}
442
443
444/*
445 * Returns the number of axis on the device.
446 */
447int
448SDL_HapticNumAxes(SDL_Haptic * haptic)
449{
450 if (!ValidHaptic(haptic)) {
451 return -1;
452 }
453
454 return haptic->naxes;
455}
456
457/*
458 * Checks to see if the device can support the effect.
459 */
460int
461SDL_HapticEffectSupported(SDL_Haptic * haptic, SDL_HapticEffect * effect)
462{
463 if (!ValidHaptic(haptic)) {
464 return -1;
465 }
466
467 if ((haptic->supported & effect->type) != 0)
468 return SDL_TRUE;
469 return SDL_FALSE;
470}
471
472/*
473 * Creates a new haptic effect.
474 */
475int
476SDL_HapticNewEffect(SDL_Haptic * haptic, SDL_HapticEffect * effect)
477{
478 int i;
479
480 /* Check for device validity. */
481 if (!ValidHaptic(haptic)) {
482 return -1;
483 }
484
485 /* Check to see if effect is supported */
486 if (SDL_HapticEffectSupported(haptic, effect) == SDL_FALSE) {
487 return SDL_SetError("Haptic: Effect not supported by haptic device.");
488 }
489
490 /* See if there's a free slot */
491 for (i = 0; i < haptic->neffects; i++) {
492 if (haptic->effects[i].hweffect == NULL) {
493
494 /* Now let the backend create the real effect */
495 if (SDL_SYS_HapticNewEffect(haptic, &haptic->effects[i], effect)
496 != 0) {
497 return -1; /* Backend failed to create effect */
498 }
499
500 SDL_memcpy(&haptic->effects[i].effect, effect,
501 sizeof(SDL_HapticEffect));
502 return i;
503 }
504 }
505
506 return SDL_SetError("Haptic: Device has no free space left.");
507}
508
509/*
510 * Checks to see if an effect is valid.
511 */
512static int
513ValidEffect(SDL_Haptic * haptic, int effect)
514{
515 if ((effect < 0) || (effect >= haptic->neffects)) {
516 SDL_SetError("Haptic: Invalid effect identifier.");
517 return 0;
518 }
519 return 1;
520}
521
522/*
523 * Updates an effect.
524 */
525int
526SDL_HapticUpdateEffect(SDL_Haptic * haptic, int effect,
527 SDL_HapticEffect * data)
528{
529 if (!ValidHaptic(haptic) || !ValidEffect(haptic, effect)) {
530 return -1;
531 }
532
533 /* Can't change type dynamically. */
534 if (data->type != haptic->effects[effect].effect.type) {
535 return SDL_SetError("Haptic: Updating effect type is illegal.");
536 }
537
538 /* Updates the effect */
539 if (SDL_SYS_HapticUpdateEffect(haptic, &haptic->effects[effect], data) <
540 0) {
541 return -1;
542 }
543
544 SDL_memcpy(&haptic->effects[effect].effect, data,
545 sizeof(SDL_HapticEffect));
546 return 0;
547}
548
549
550/*
551 * Runs the haptic effect on the device.
552 */
553int
554SDL_HapticRunEffect(SDL_Haptic * haptic, int effect, Uint32 iterations)
555{
556 if (!ValidHaptic(haptic) || !ValidEffect(haptic, effect)) {
557 return -1;
558 }
559
560 /* Run the effect */
561 if (SDL_SYS_HapticRunEffect(haptic, &haptic->effects[effect], iterations)
562 < 0) {
563 return -1;
564 }
565
566 return 0;
567}
568
569/*
570 * Stops the haptic effect on the device.
571 */
572int
573SDL_HapticStopEffect(SDL_Haptic * haptic, int effect)
574{
575 if (!ValidHaptic(haptic) || !ValidEffect(haptic, effect)) {
576 return -1;
577 }
578
579 /* Stop the effect */
580 if (SDL_SYS_HapticStopEffect(haptic, &haptic->effects[effect]) < 0) {
581 return -1;
582 }
583
584 return 0;
585}
586
587/*
588 * Gets rid of a haptic effect.
589 */
590void
591SDL_HapticDestroyEffect(SDL_Haptic * haptic, int effect)
592{
593 if (!ValidHaptic(haptic) || !ValidEffect(haptic, effect)) {
594 return;
595 }
596
597 /* Not allocated */
598 if (haptic->effects[effect].hweffect == NULL) {
599 return;
600 }
601
602 SDL_SYS_HapticDestroyEffect(haptic, &haptic->effects[effect]);
603}
604
605/*
606 * Gets the status of a haptic effect.
607 */
608int
609SDL_HapticGetEffectStatus(SDL_Haptic * haptic, int effect)
610{
611 if (!ValidHaptic(haptic) || !ValidEffect(haptic, effect)) {
612 return -1;
613 }
614
615 if ((haptic->supported & SDL_HAPTIC_STATUS) == 0) {
616 return SDL_SetError("Haptic: Device does not support status queries.");
617 }
618
619 return SDL_SYS_HapticGetEffectStatus(haptic, &haptic->effects[effect]);
620}
621
622/*
623 * Sets the global gain of the device.
624 */
625int
626SDL_HapticSetGain(SDL_Haptic * haptic, int gain)
627{
628 const char *env;
629 int real_gain, max_gain;
630
631 if (!ValidHaptic(haptic)) {
632 return -1;
633 }
634
635 if ((haptic->supported & SDL_HAPTIC_GAIN) == 0) {
636 return SDL_SetError("Haptic: Device does not support setting gain.");
637 }
638
639 if ((gain < 0) || (gain > 100)) {
640 return SDL_SetError("Haptic: Gain must be between 0 and 100.");
641 }
642
643 /* We use the envvar to get the maximum gain. */
644 env = SDL_getenv("SDL_HAPTIC_GAIN_MAX");
645 if (env != NULL) {
646 max_gain = SDL_atoi(env);
647
648 /* Check for sanity. */
649 if (max_gain < 0)
650 max_gain = 0;
651 else if (max_gain > 100)
652 max_gain = 100;
653
654 /* We'll scale it linearly with SDL_HAPTIC_GAIN_MAX */
655 real_gain = (gain * max_gain) / 100;
656 } else {
657 real_gain = gain;
658 }
659
660 if (SDL_SYS_HapticSetGain(haptic, real_gain) < 0) {
661 return -1;
662 }
663
664 return 0;
665}
666
667/*
668 * Makes the device autocenter, 0 disables.
669 */
670int
671SDL_HapticSetAutocenter(SDL_Haptic * haptic, int autocenter)
672{
673 if (!ValidHaptic(haptic)) {
674 return -1;
675 }
676
677 if ((haptic->supported & SDL_HAPTIC_AUTOCENTER) == 0) {
678 return SDL_SetError("Haptic: Device does not support setting autocenter.");
679 }
680
681 if ((autocenter < 0) || (autocenter > 100)) {
682 return SDL_SetError("Haptic: Autocenter must be between 0 and 100.");
683 }
684
685 if (SDL_SYS_HapticSetAutocenter(haptic, autocenter) < 0) {
686 return -1;
687 }
688
689 return 0;
690}
691
692/*
693 * Pauses the haptic device.
694 */
695int
696SDL_HapticPause(SDL_Haptic * haptic)
697{
698 if (!ValidHaptic(haptic)) {
699 return -1;
700 }
701
702 if ((haptic->supported & SDL_HAPTIC_PAUSE) == 0) {
703 return SDL_SetError("Haptic: Device does not support setting pausing.");
704 }
705
706 return SDL_SYS_HapticPause(haptic);
707}
708
709/*
710 * Unpauses the haptic device.
711 */
712int
713SDL_HapticUnpause(SDL_Haptic * haptic)
714{
715 if (!ValidHaptic(haptic)) {
716 return -1;
717 }
718
719 if ((haptic->supported & SDL_HAPTIC_PAUSE) == 0) {
720 return 0; /* Not going to be paused, so we pretend it's unpaused. */
721 }
722
723 return SDL_SYS_HapticUnpause(haptic);
724}
725
726/*
727 * Stops all the currently playing effects.
728 */
729int
730SDL_HapticStopAll(SDL_Haptic * haptic)
731{
732 if (!ValidHaptic(haptic)) {
733 return -1;
734 }
735
736 return SDL_SYS_HapticStopAll(haptic);
737}
738
739/*
740 * Checks to see if rumble is supported.
741 */
742int
743SDL_HapticRumbleSupported(SDL_Haptic * haptic)
744{
745 if (!ValidHaptic(haptic)) {
746 return -1;
747 }
748
749 /* Most things can use SINE, but XInput only has LEFTRIGHT. */
750 return ((haptic->supported & (SDL_HAPTIC_SINE|SDL_HAPTIC_LEFTRIGHT)) != 0);
751}
752
753/*
754 * Initializes the haptic device for simple rumble playback.
755 */
756int
757SDL_HapticRumbleInit(SDL_Haptic * haptic)
758{
759 SDL_HapticEffect *efx = &haptic->rumble_effect;
760
761 if (!ValidHaptic(haptic)) {
762 return -1;
763 }
764
765 /* Already allocated. */
766 if (haptic->rumble_id >= 0) {
767 return 0;
768 }
769
770 SDL_zerop(efx);
771 if (haptic->supported & SDL_HAPTIC_SINE) {
772 efx->type = SDL_HAPTIC_SINE;
773 efx->periodic.direction.type = SDL_HAPTIC_CARTESIAN;
774 efx->periodic.period = 1000;
775 efx->periodic.magnitude = 0x4000;
776 efx->periodic.length = 5000;
777 efx->periodic.attack_length = 0;
778 efx->periodic.fade_length = 0;
779 } else if (haptic->supported & SDL_HAPTIC_LEFTRIGHT) { /* XInput? */
780 efx->type = SDL_HAPTIC_LEFTRIGHT;
781 efx->leftright.length = 5000;
782 efx->leftright.large_magnitude = 0x4000;
783 efx->leftright.small_magnitude = 0x4000;
784 } else {
785 return SDL_SetError("Device doesn't support rumble");
786 }
787
788 haptic->rumble_id = SDL_HapticNewEffect(haptic, &haptic->rumble_effect);
789 if (haptic->rumble_id >= 0) {
790 return 0;
791 }
792 return -1;
793}
794
795/*
796 * Runs simple rumble on a haptic device
797 */
798int
799SDL_HapticRumblePlay(SDL_Haptic * haptic, float strength, Uint32 length)
800{
801 SDL_HapticEffect *efx;
802 Sint16 magnitude;
803
804 if (!ValidHaptic(haptic)) {
805 return -1;
806 }
807
808 if (haptic->rumble_id < 0) {
809 return SDL_SetError("Haptic: Rumble effect not initialized on haptic device");
810 }
811
812 /* Clamp strength. */
813 if (strength > 1.0f) {
814 strength = 1.0f;
815 } else if (strength < 0.0f) {
816 strength = 0.0f;
817 }
818 magnitude = (Sint16)(32767.0f*strength);
819
820 efx = &haptic->rumble_effect;
821 if (efx->type == SDL_HAPTIC_SINE) {
822 efx->periodic.magnitude = magnitude;
823 efx->periodic.length = length;
824 } else if (efx->type == SDL_HAPTIC_LEFTRIGHT) {
825 efx->leftright.small_magnitude = efx->leftright.large_magnitude = magnitude;
826 efx->leftright.length = length;
827 } else {
828 SDL_assert(0 && "This should have been caught elsewhere");
829 }
830
831 if (SDL_HapticUpdateEffect(haptic, haptic->rumble_id, &haptic->rumble_effect) < 0) {
832 return -1;
833 }
834
835 return SDL_HapticRunEffect(haptic, haptic->rumble_id, 1);
836}
837
838/*
839 * Stops the simple rumble on a haptic device.
840 */
841int
842SDL_HapticRumbleStop(SDL_Haptic * haptic)
843{
844 if (!ValidHaptic(haptic)) {
845 return -1;
846 }
847
848 if (haptic->rumble_id < 0) {
849 return SDL_SetError("Haptic: Rumble effect not initialized on haptic device");
850 }
851
852 return SDL_HapticStopEffect(haptic, haptic->rumble_id);
853}
854
855/* vi: set ts=4 sw=4 expandtab: */
856