1 /*         ______   ___    ___
2  *        /\  _  \ /\_ \  /\_ \
3  *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___
4  *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
5  *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
6  *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
7  *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
8  *                                           /\____/
9  *                                           \_/__/
10  *
11  *      Windows haptic (force-feedback) device driver.
12  *
13  *      By Beoran.
14  *
15  *      See LICENSE.txt for copyright information.
16  */
17 
18 
19 
20 #define ALLEGRO_NO_COMPATIBILITY
21 
22 #define DIRECTINPUT_VERSION 0x0800
23 
24 /* For waitable timers */
25 #define _WIN32_WINNT 0x0501
26 
27 #include "allegro5/allegro.h"
28 #include "allegro5/internal/aintern.h"
29 #include "allegro5/platform/aintwin.h"
30 #include "allegro5/internal/aintern_haptic.h"
31 #include "allegro5/internal/aintern_events.h"
32 #include "allegro5/internal/aintern_joystick.h"
33 #include "allegro5/internal/aintern_bitmap.h"
34 
35 #ifndef ALLEGRO_WINDOWS
36 #error something is wrong with the makefile
37 #endif
38 
39 #ifdef ALLEGRO_MINGW32
40 #undef MAKEFOURCC
41 #endif
42 
43 #include <initguid.h>
44 #include <stdio.h>
45 #include <mmsystem.h>
46 #include <process.h>
47 #include <math.h>
48 #include <dinput.h>
49 /* #include <sys/time.h> */
50 
51 #include "allegro5/internal/aintern_wjoydxnu.h"
52 
53 ALLEGRO_DEBUG_CHANNEL("whaptic")
54 
55 /* Support at most 32 haptic devices. */
56 #define HAPTICS_MAX             32
57 /* Support at most 16 effects per device. */
58 #define HAPTICS_EFFECTS_MAX     16
59 /* Support at most 3 axes per device. */
60 #define HAPTICS_AXES_MAX        3
61 
62 /** This union is needed to avoid
63    dynamical memory allocation. */
64 typedef union
65 {
66    DICONSTANTFORCE constant;
67    DIRAMPFORCE ramp;
68    DIPERIODIC periodic;
69    DICONDITION condition;
70    DICUSTOMFORCE custom;
71 } ALLEGRO_HAPTIC_PARAMETER_WINDOWS;
72 
73 
74 /*
75  * Haptic effect system data.
76  */
77 typedef struct
78 {
79    int id;
80    bool active;
81    DIEFFECT effect;
82    DIENVELOPE envelope;
83    LPDIRECTINPUTEFFECT ref;
84    DWORD axes[HAPTICS_AXES_MAX];
85    LONG directions[HAPTICS_AXES_MAX];
86    ALLEGRO_HAPTIC_PARAMETER_WINDOWS parameter;
87    const GUID *guid;
88 } ALLEGRO_HAPTIC_EFFECT_WINDOWS;
89 
90 
91 
92 typedef struct
93 {
94    struct ALLEGRO_HAPTIC parent;        /* must be first */
95    bool active;
96    LPDIRECTINPUTDEVICE2 device;
97    GUID guid;
98    DIDEVICEINSTANCE instance;
99    DIDEVCAPS capabilities;
100    LPDIRECTINPUTDEVICE8 device8;
101    ALLEGRO_DISPLAY_WIN *display;
102    int flags;
103    ALLEGRO_HAPTIC_EFFECT_WINDOWS effects[HAPTICS_EFFECTS_MAX];
104    DWORD axes[HAPTICS_AXES_MAX];
105    int naxes;
106 } ALLEGRO_HAPTIC_WINDOWS;
107 
108 
109 #define LONG_BITS    (sizeof(long) * 8)
110 #define NLONGS(x)    (((x) + LONG_BITS - 1) / LONG_BITS)
111 /* Tests if a bit in an array of longs is set. */
112 #define TEST_BIT(nr, addr) \
113    ((1UL << ((nr) % LONG_BITS)) & (addr)[(nr) / LONG_BITS])
114 
115 
116 /* forward declarations */
117 static bool whap_init_haptic(void);
118 static void whap_exit_haptic(void);
119 
120 static bool whap_is_mouse_haptic(ALLEGRO_MOUSE *dev);
121 static bool whap_is_joystick_haptic(ALLEGRO_JOYSTICK *);
122 static bool whap_is_keyboard_haptic(ALLEGRO_KEYBOARD *dev);
123 static bool whap_is_display_haptic(ALLEGRO_DISPLAY *dev);
124 static bool whap_is_touch_input_haptic(ALLEGRO_TOUCH_INPUT *dev);
125 
126 static ALLEGRO_HAPTIC *whap_get_from_mouse(ALLEGRO_MOUSE *dev);
127 static ALLEGRO_HAPTIC *whap_get_from_joystick(ALLEGRO_JOYSTICK *dev);
128 static ALLEGRO_HAPTIC *whap_get_from_keyboard(ALLEGRO_KEYBOARD *dev);
129 static ALLEGRO_HAPTIC *whap_get_from_display(ALLEGRO_DISPLAY *dev);
130 static ALLEGRO_HAPTIC *whap_get_from_touch_input(ALLEGRO_TOUCH_INPUT *dev);
131 
132 static bool whap_release(ALLEGRO_HAPTIC *haptic);
133 
134 static bool whap_get_active(ALLEGRO_HAPTIC *hap);
135 static int whap_get_capabilities(ALLEGRO_HAPTIC *dev);
136 static double whap_get_gain(ALLEGRO_HAPTIC *dev);
137 static bool whap_set_gain(ALLEGRO_HAPTIC *dev, double);
138 static int whap_get_max_effects(ALLEGRO_HAPTIC *dev);
139 
140 static bool whap_is_effect_ok(ALLEGRO_HAPTIC *dev,
141                               ALLEGRO_HAPTIC_EFFECT *eff);
142 static bool whap_upload_effect(ALLEGRO_HAPTIC *dev,
143                                ALLEGRO_HAPTIC_EFFECT *eff,
144                                ALLEGRO_HAPTIC_EFFECT_ID *id);
145 static bool whap_play_effect(ALLEGRO_HAPTIC_EFFECT_ID *id, int loop);
146 static bool whap_stop_effect(ALLEGRO_HAPTIC_EFFECT_ID *id);
147 static bool whap_is_effect_playing(ALLEGRO_HAPTIC_EFFECT_ID *id);
148 static bool whap_release_effect(ALLEGRO_HAPTIC_EFFECT_ID *id);
149 
150 static double whap_get_autocenter(ALLEGRO_HAPTIC *dev);
151 static bool whap_set_autocenter(ALLEGRO_HAPTIC *dev, double);
152 
153 ALLEGRO_HAPTIC_DRIVER _al_hapdrv_directx = {
154    AL_HAPTIC_TYPE_DIRECTX,
155    "",
156    "",
157    "Windows haptic(s)",
158    whap_init_haptic,
159    whap_exit_haptic,
160 
161    whap_is_mouse_haptic,
162    whap_is_joystick_haptic,
163    whap_is_keyboard_haptic,
164    whap_is_display_haptic,
165    whap_is_touch_input_haptic,
166 
167    whap_get_from_mouse,
168    whap_get_from_joystick,
169    whap_get_from_keyboard,
170    whap_get_from_display,
171    whap_get_from_touch_input,
172 
173    whap_get_active,
174    whap_get_capabilities,
175    whap_get_gain,
176    whap_set_gain,
177    whap_get_max_effects,
178 
179    whap_is_effect_ok,
180    whap_upload_effect,
181    whap_play_effect,
182    whap_stop_effect,
183    whap_is_effect_playing,
184    whap_release_effect,
185 
186    whap_release,
187 
188    whap_get_autocenter,
189    whap_set_autocenter
190 };
191 
192 
193 static ALLEGRO_HAPTIC_WINDOWS haptics[HAPTICS_MAX];
194 static ALLEGRO_MUTEX *haptic_mutex = NULL;
195 
196 /* Capability map between directinput effects and allegro effect types. */
197 struct CAP_MAP
198 {
199    GUID guid;
200    int allegro_bit;
201 };
202 
203 /* GUID values are borrowed from Wine */
204 #define DEFINE_PRIVATE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
205    static const GUID name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
206 
207 DEFINE_PRIVATE_GUID(_al_GUID_None, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
208 
209 
210 static const struct CAP_MAP cap_map[] = {
211    { GUID_ConstantForce, ALLEGRO_HAPTIC_CONSTANT },
212    { GUID_Spring, ALLEGRO_HAPTIC_SPRING },
213    { GUID_Spring, ALLEGRO_HAPTIC_FRICTION },
214    { GUID_Damper, ALLEGRO_HAPTIC_DAMPER },
215    { GUID_Inertia, ALLEGRO_HAPTIC_INERTIA },
216    { GUID_RampForce, ALLEGRO_HAPTIC_RAMP },
217    { GUID_Square, ALLEGRO_HAPTIC_SQUARE },
218    { GUID_Triangle, ALLEGRO_HAPTIC_TRIANGLE },
219    { GUID_Sine, ALLEGRO_HAPTIC_SINE },
220    { GUID_SawtoothUp, ALLEGRO_HAPTIC_SAW_UP },
221    { GUID_SawtoothDown, ALLEGRO_HAPTIC_SAW_DOWN },
222    { GUID_CustomForce, ALLEGRO_HAPTIC_CUSTOM },
223    /*{ { _al_GUID_None    },      -1 } */
224 };
225 
226 
whap_init_haptic(void)227 static bool whap_init_haptic(void)
228 {
229    int i;
230 
231    ASSERT(haptic_mutex == NULL);
232    haptic_mutex = al_create_mutex();
233    if (!haptic_mutex)
234       return false;
235 
236    for (i = 0; i < HAPTICS_MAX; i++) {
237       haptics[i].active = false;
238    }
239 
240    return true;
241 }
242 
243 
whap_get_available_haptic(void)244 static ALLEGRO_HAPTIC_WINDOWS *whap_get_available_haptic(void)
245 {
246    int i;
247 
248    for (i = 0; i < HAPTICS_MAX; i++) {
249       if (!haptics[i].active) {
250          haptics[i].active = true;
251          return &haptics[i];
252       }
253    }
254 
255    return NULL;
256 }
257 
258 /* Look for a free haptic effect slot for a device and return it,
259  * or NULL if exhausted. Also initializes the effect
260  * reference to NULL. */
261 static ALLEGRO_HAPTIC_EFFECT_WINDOWS
whap_get_available_effect(ALLEGRO_HAPTIC_WINDOWS * whap)262 *whap_get_available_effect(ALLEGRO_HAPTIC_WINDOWS *whap)
263 {
264    ALLEGRO_HAPTIC_EFFECT_WINDOWS *weff;
265    int i;
266    for (i = 0; i < al_get_max_haptic_effects(&whap->parent); i++) {
267       if (!whap->effects[i].active) {
268          weff = whap->effects + i;
269          weff->id = i;
270          weff->active = true;
271          weff->ref = NULL;
272          return weff;
273       }
274    }
275    return NULL;
276 }
277 
278 
279 /* Releases a windows haptics effect and unloads it from the device. */
whap_release_effect_windows(ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff)280 static bool whap_release_effect_windows(ALLEGRO_HAPTIC_EFFECT_WINDOWS *weff)
281 {
282    bool result = true;
283    if (!weff)
284       return false;             /* make it easy to handle all cases later on. */
285    if (!weff->active)
286       return false;             /* already not in use, bail out. */
287 
288    /* Unload the effect from the device. */
289    if (weff->ref) {
290       HRESULT ret;
291       ret = IDirectInputEffect_Unload(weff->ref);
292       if (FAILED(ret)) {
293          ALLEGRO_WARN("Could not unload effect.");
294          result = false;
295       }
296    }
297    /* Custom force needs to clean up it's data. */
298    if (weff->guid == &GUID_CustomForce) {
299       al_free(weff->parameter.custom.rglForceData);
300       weff->parameter.custom.rglForceData = NULL;
301    }
302    weff->active = false;        /* not in use */
303    weff->ref = NULL;            /* No reference to effect anymore. */
304    return result;
305 }
306 
307 
308 /* Converts a generic haptic device to a Windows-specific one. */
whap_from_al(ALLEGRO_HAPTIC * hap)309 static ALLEGRO_HAPTIC_WINDOWS *whap_from_al(ALLEGRO_HAPTIC *hap)
310 {
311    return (ALLEGRO_HAPTIC_WINDOWS *)hap;
312 }
313 
whap_exit_haptic(void)314 static void whap_exit_haptic(void)
315 {
316    ASSERT(haptic_mutex);
317    al_destroy_mutex(haptic_mutex);
318    haptic_mutex = NULL;
319 }
320 
321 /* Convert the type of the periodic allegro effect to the windows effect*/
whap_periodictype2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff,ALLEGRO_HAPTIC_EFFECT * effect)322 static bool whap_periodictype2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS *weff,
323                                   ALLEGRO_HAPTIC_EFFECT *effect)
324 {
325    switch (effect->data.periodic.waveform) {
326       case ALLEGRO_HAPTIC_SINE:
327          weff->guid = &GUID_Sine;
328          return true;
329 
330       case ALLEGRO_HAPTIC_SQUARE:
331          weff->guid = &GUID_Square;
332          return true;
333 
334       case ALLEGRO_HAPTIC_TRIANGLE:
335          weff->guid = &GUID_Triangle;
336          return true;
337 
338       case ALLEGRO_HAPTIC_SAW_UP:
339          weff->guid = &GUID_SawtoothUp;
340          return true;
341 
342       case ALLEGRO_HAPTIC_SAW_DOWN:
343          weff->guid = &GUID_SawtoothDown;
344          return true;
345 
346       case ALLEGRO_HAPTIC_CUSTOM:
347          weff->guid = &GUID_CustomForce;
348          return true;
349       default:
350          return false;
351    }
352 }
353 
354 
355 
356 /* Convert the type of the allegro effect to the windows effect*/
whap_type2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff,ALLEGRO_HAPTIC_EFFECT * effect)357 static bool whap_type2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS *weff,
358                           ALLEGRO_HAPTIC_EFFECT *effect)
359 {
360    switch (effect->type) {
361       case ALLEGRO_HAPTIC_RUMBLE:
362          weff->guid = &GUID_Sine;
363          return true;
364       case ALLEGRO_HAPTIC_PERIODIC:
365          return whap_periodictype2win(weff, effect);
366       case ALLEGRO_HAPTIC_CONSTANT:
367          weff->guid = &GUID_ConstantForce;
368          return true;
369       case ALLEGRO_HAPTIC_SPRING:
370          weff->guid = &GUID_Spring;
371          return true;
372       case ALLEGRO_HAPTIC_FRICTION:
373          weff->guid = &GUID_Friction;
374          return true;
375       case ALLEGRO_HAPTIC_DAMPER:
376          weff->guid = &GUID_Damper;
377          return true;
378       case ALLEGRO_HAPTIC_INERTIA:
379          weff->guid = &GUID_Inertia;
380          return true;
381       case ALLEGRO_HAPTIC_RAMP:
382          weff->guid = &GUID_RampForce;
383          return true;
384       default:
385          return NULL;
386    }
387    return true;
388 }
389 
390 /* Convert the direction of the allegro effect to the windows effect*/
whap_direction2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff,ALLEGRO_HAPTIC_EFFECT * effect,ALLEGRO_HAPTIC_WINDOWS * whap)391 static bool whap_direction2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS *weff,
392                                ALLEGRO_HAPTIC_EFFECT *effect,
393                                ALLEGRO_HAPTIC_WINDOWS *whap)
394 {
395    unsigned int index;
396    double calc_x, calc_y;
397 
398    /* It seems that (at least for sine motions), 1 or 2 axes work, but 0 or 3 don't
399       for my test joystick. Also, while polar or spherical coordinates are accepted,
400       they don't cause any vibration for a periodic effect. All in
401       all it seems that cartesian is the only well-supported axis system,
402       at least for periodic effects. Need more tests with other joysticks to see
403       their behavior.
404       Annoyingly there seems to be no way to discover
405       what kind of axis system is supported without trying to upload the
406       effect... Hence, use a 1 or 2 axis cartesian system and hope for the
407       best for non-periodic effects.
408     */
409 
410 
411    /* Use CARTESIAN coordinates since those seem to be the only well suppported
412       ones. */
413    weff->effect.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
414    /* Prepare axes. If whap naxes is > 2, use 2 because more than 2
415       axes isn't always supported. */
416    weff->effect.cAxes = whap->naxes;
417    if (weff->effect.cAxes > 2) {
418       weff->effect.cAxes = 2;
419    }
420    memset((void *)weff->axes, 0, sizeof(weff->axes));
421    for (index = 0; index < weff->effect.cAxes; index++) {
422       weff->axes[index] = whap->axes[index];
423    }
424    weff->effect.rgdwAxes = weff->axes;
425    /* Set up directions as well.. */
426    memset((void *)weff->directions, 0, sizeof(weff->directions));
427    /* Calculate the X and Y coordinates of the effect based on the angle.
428       That is map angular coordinates to cartesian ones. */
429    calc_x =
430       sin(effect->direction.angle) * effect->direction.radius *
431       DI_FFNOMINALMAX;
432    calc_y =
433       cos(effect->direction.angle) * effect->direction.radius *
434       DI_FFNOMINALMAX;
435 
436    /* Set X if there is 1 axis and also y if there are more .
437     */
438    if (weff->effect.cAxes > 1) {
439       weff->directions[0] = (long)calc_x;
440    }
441    if (whap->naxes > 2) {
442       weff->directions[1] = (long)calc_y;
443    }
444    weff->effect.rglDirection = weff->directions;
445    return true;
446 }
447 
448 /* Converts the time in seconds to a Windows-compatible time.
449  * Return false if out of bounds.
450  */
whap_time2win(DWORD * res,double sec)451 static bool whap_time2win(DWORD *res, double sec)
452 {
453    ASSERT(res);
454 
455    if (sec < 0.0 || sec >= 4294.967296)
456       return false;
457    (*res) = (DWORD)floor(sec * DI_SECONDS);
458    return true;
459 }
460 
461 /* Converts the level in range 0.0 to 1.0 to a Windows-compatible level.
462  * Returns false if out of bounds.
463  */
whap_level2win(DWORD * res,double level)464 static bool whap_level2win(DWORD *res, double level)
465 {
466    ASSERT(res);
467 
468    if (level < 0.0 || level > 1.0)
469       return false;
470    *res = (DWORD)floor(level * DI_FFNOMINALMAX);
471    return true;
472 }
473 
474 
475 /* Converts the level in range -1.0 to 1.0 to a Windows-compatible level.
476  * Returns false if out of bounds.
477  */
whap_slevel2win(LONG * res,double level)478 static bool whap_slevel2win(LONG *res, double level)
479 {
480    ASSERT(res);
481 
482    if (level < -1.0 || level > 1.0)
483       return false;
484    *res = (LONG)(level * DI_FFNOMINALMAX);
485    return true;
486 }
487 
488 /* Converts a phase in range 0.0 to 1.0 to a Windows-compatible level.
489  * Returns false if out of bounds.
490  */
whap_phase2win(DWORD * res,double phase)491 static bool whap_phase2win(DWORD *res, double phase)
492 {
493    ASSERT(res);
494 
495    if (phase < 0.0 || phase > 1.0)
496       return false;
497    *res = (DWORD)(phase * 35999);
498    return true;
499 }
500 
501 
502 /* Converts replay data to Widows-compatible data. */
whap_replay2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff,ALLEGRO_HAPTIC_EFFECT * effect)503 static bool whap_replay2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS *weff,
504                             ALLEGRO_HAPTIC_EFFECT *effect)
505 {
506    return whap_time2win(&weff->effect.dwStartDelay, effect->replay.delay)
507           && whap_time2win(&weff->effect.dwDuration, effect->replay.length);
508 }
509 
510 
511 /* Converts an Allegro haptic effect envelope to DirectInput API. */
whap_envelope2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff,ALLEGRO_HAPTIC_ENVELOPE * aenv)512 static bool whap_envelope2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS *weff,
513                               ALLEGRO_HAPTIC_ENVELOPE *aenv)
514 {
515    /* Prepare envelope. */
516    DIENVELOPE *wenv = &weff->envelope;
517 
518    /* Do not set any envelope if all values are 0.0  */
519    if ((aenv->attack_length == 0.0) &&
520        (aenv->fade_length == 0.0) &&
521        (aenv->attack_level == 0.0) && (aenv->fade_level == 0.0)) {
522       return true;
523    }
524 
525    /* Prepare the envelope. */
526    memset((void *)wenv, 0, sizeof(DIENVELOPE));
527    weff->envelope.dwSize = sizeof(DIENVELOPE);
528    weff->effect.lpEnvelope = wenv;
529 
530    /* Set the values. */
531    return whap_time2win(&wenv->dwAttackTime, aenv->attack_length)
532           && whap_time2win(&wenv->dwFadeTime, aenv->fade_length)
533           && whap_level2win(&wenv->dwAttackLevel, aenv->attack_level)
534           && whap_level2win(&wenv->dwFadeLevel, aenv->fade_level);
535 }
536 
537 /* Converts a constant effect to directinput API. */
whap_constant2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff,ALLEGRO_HAPTIC_EFFECT * effect)538 static bool whap_constant2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS *weff,
539                               ALLEGRO_HAPTIC_EFFECT *effect)
540 {
541    weff->effect.cbTypeSpecificParams = sizeof(weff->parameter.constant);
542    weff->effect.lpvTypeSpecificParams = &weff->parameter.constant;
543    return whap_envelope2win(weff, &effect->data.constant.envelope)
544           && whap_slevel2win(&weff->parameter.constant.lMagnitude,
545                              effect->data.constant.level);
546 }
547 
548 
549 /* Converts a ramp effect to directinput input API. */
whap_ramp2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff,ALLEGRO_HAPTIC_EFFECT * effect)550 static bool whap_ramp2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS *weff,
551                           ALLEGRO_HAPTIC_EFFECT *effect)
552 {
553    weff->effect.cbTypeSpecificParams = sizeof(weff->parameter.ramp);
554    weff->effect.lpvTypeSpecificParams = &weff->parameter.ramp;
555 
556    return whap_envelope2win(weff, &effect->data.ramp.envelope)
557           && whap_slevel2win(&weff->parameter.ramp.lStart,
558                              effect->data.ramp.start_level)
559           && whap_slevel2win(&weff->parameter.ramp.lEnd,
560                              effect->data.ramp.end_level);
561 }
562 
563 /* Converts a condition effect to directinput input API. */
whap_condition2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff,ALLEGRO_HAPTIC_EFFECT * effect)564 static bool whap_condition2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS *weff,
565                                ALLEGRO_HAPTIC_EFFECT *effect)
566 {
567    weff->effect.cbTypeSpecificParams = sizeof(weff->parameter.condition);
568    weff->effect.lpvTypeSpecificParams = &weff->parameter.condition;
569    /* XXX: no envelope here ???  */
570 
571    return whap_level2win(&weff->parameter.condition.dwNegativeSaturation,
572                          effect->data.condition.left_saturation)
573           && whap_level2win(&weff->parameter.condition.dwPositiveSaturation,
574                             effect->data.condition.right_saturation)
575           && whap_slevel2win(&weff->parameter.condition.lNegativeCoefficient,
576                              effect->data.condition.left_coeff)
577           && whap_slevel2win(&weff->parameter.condition.lPositiveCoefficient,
578                              effect->data.condition.right_coeff)
579           && whap_slevel2win(&weff->parameter.condition.lDeadBand,
580                              effect->data.condition.deadband)
581           && whap_slevel2win(&weff->parameter.condition.lOffset,
582                              effect->data.condition.center);
583 }
584 
585 /* Converts a custom effect to directinput input API. */
whap_custom2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff,ALLEGRO_HAPTIC_EFFECT * effect)586 static bool whap_custom2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS *weff,
587                             ALLEGRO_HAPTIC_EFFECT *effect)
588 {
589    int index;
590    weff->effect.cbTypeSpecificParams = sizeof(weff->parameter.custom);
591    weff->effect.lpvTypeSpecificParams = &weff->parameter.custom;
592    weff->parameter.custom.cChannels = 1;
593    weff->parameter.custom.cSamples = effect->data.periodic.custom_len;
594    /* Use al malloc only in this case since the custom_data can be arbitrarily long. */
595    weff->parameter.custom.rglForceData =
596       (LONG *)al_malloc(sizeof(LONG) * effect->data.periodic.custom_len);
597    if (!weff->parameter.custom.rglForceData)
598       return false;
599    /* Gotta copy this to long values, and scale them too... */
600    for (index = 0; index < effect->data.periodic.custom_len; index++) {
601       weff->parameter.custom.rglForceData[index] =
602          (LONG)(effect->data.periodic.custom_data[index] *
603                 ((double)(1 << 31)));
604    }
605    return true;
606 }
607 
608 
609 /* Converts a periodic effect to directinput input API. */
whap_periodic2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff,ALLEGRO_HAPTIC_EFFECT * effect)610 static bool whap_periodic2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS *weff,
611                               ALLEGRO_HAPTIC_EFFECT *effect)
612 {
613    if (effect->data.periodic.waveform == ALLEGRO_HAPTIC_CUSTOM) {
614       return whap_custom2win(weff, effect);
615    }
616 
617    weff->effect.cbTypeSpecificParams = sizeof(weff->parameter.periodic);
618    weff->effect.lpvTypeSpecificParams = &weff->parameter.periodic;
619 
620    return whap_envelope2win(weff, &effect->data.periodic.envelope)
621           && whap_level2win(&weff->parameter.periodic.dwMagnitude,
622                             effect->data.periodic.magnitude)
623           && whap_phase2win(&weff->parameter.periodic.dwPhase,
624                             effect->data.periodic.phase)
625           && whap_time2win(&weff->parameter.periodic.dwPeriod,
626                            effect->data.periodic.period)
627           && whap_slevel2win(&weff->parameter.periodic.lOffset,
628                              effect->data.periodic.offset);
629 }
630 
631 
632 /* Converts a periodic effect to directinput input API. */
whap_rumble2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff,ALLEGRO_HAPTIC_EFFECT * effect)633 static bool whap_rumble2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS *weff,
634                             ALLEGRO_HAPTIC_EFFECT *effect)
635 {
636    weff->effect.cbTypeSpecificParams = sizeof(weff->parameter.periodic);
637    weff->effect.lpvTypeSpecificParams = &weff->parameter.periodic;
638 
639    return whap_level2win(&weff->parameter.periodic.dwMagnitude,
640                          effect->data.rumble.strong_magnitude)
641           && whap_phase2win(&weff->parameter.periodic.dwPhase, 0)
642           && whap_time2win(&weff->parameter.periodic.dwPeriod, 0.01)
643           && whap_slevel2win(&weff->parameter.periodic.lOffset, 0);
644 }
645 
646 /* Converts Allegro haptic effect to dinput API. */
whap_effect2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff,ALLEGRO_HAPTIC_EFFECT * effect,ALLEGRO_HAPTIC_WINDOWS * whap)647 static bool whap_effect2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS *weff,
648                             ALLEGRO_HAPTIC_EFFECT *effect,
649                             ALLEGRO_HAPTIC_WINDOWS *whap)
650 {
651    /* Generic setup */
652    memset((void *)weff, 0, sizeof(*weff));
653    /* Set global stuff. */
654    weff->effect.dwSize = sizeof(DIEFFECT);
655    weff->effect.dwGain = DI_FFNOMINALMAX;
656    weff->effect.dwSamplePeriod = 0;
657    weff->effect.dwFlags = DIEFF_OBJECTOFFSETS;
658    weff->effect.lpEnvelope = NULL;
659    /* Gain of the effect must be set to max, otherwise it won't be felt
660       (enough) as the per effect gain multiplies with the per-device gain. */
661    weff->effect.dwGain = DI_FFNOMINALMAX;
662    /* This effect is not mapped to a trigger, and must be played explicitly. */
663    weff->effect.dwTriggerButton = DIEB_NOTRIGGER;
664 
665    if (!whap_type2win(weff, effect)) {
666       return false;
667    }
668 
669    if (!whap_direction2win(weff, effect, whap)) {
670       return false;
671    }
672 
673    if (!whap_replay2win(weff, effect)) {
674       return false;
675    }
676 
677 
678    switch (effect->type) {
679       case ALLEGRO_HAPTIC_RUMBLE:
680          return whap_rumble2win(weff, effect);
681       case ALLEGRO_HAPTIC_PERIODIC:
682          return whap_periodic2win(weff, effect);
683       case ALLEGRO_HAPTIC_CONSTANT:
684          return whap_constant2win(weff, effect);
685       case ALLEGRO_HAPTIC_RAMP:
686          return whap_ramp2win(weff, effect);
687       case ALLEGRO_HAPTIC_SPRING:
688       case ALLEGRO_HAPTIC_FRICTION:
689       case ALLEGRO_HAPTIC_DAMPER:
690       case ALLEGRO_HAPTIC_INERTIA:
691          return whap_condition2win(weff, effect);
692       default:
693          return false;
694    }
695 }
696 
whap_get_active(ALLEGRO_HAPTIC * haptic)697 static bool whap_get_active(ALLEGRO_HAPTIC *haptic)
698 {
699    ALLEGRO_HAPTIC_WINDOWS *whap = whap_from_al(haptic);
700    return whap->active;
701 }
702 
703 
whap_is_dinput_device_haptic(LPDIRECTINPUTDEVICE2 device)704 static bool whap_is_dinput_device_haptic(LPDIRECTINPUTDEVICE2 device)
705 {
706    HRESULT ret;
707    DIDEVCAPS dicaps;
708    /* Get capabilities. */
709    ALLEGRO_DEBUG("IDirectInputDevice_GetCapabilities on %p\n", device);
710    dicaps.dwSize = sizeof(dicaps);
711    ret = IDirectInputDevice_GetCapabilities(device, &dicaps);
712    if (FAILED(ret)) {
713       ALLEGRO_ERROR("IDirectInputDevice_GetCapabilities failed on %p\n",
714                     device);
715       return false;
716    }
717    /** Is it a haptic device? */
718    bool ishaptic = (dicaps.dwFlags & DIDC_FORCEFEEDBACK);
719    ALLEGRO_DEBUG("dicaps.dwFlags: %lu, %d, %d\n", dicaps.dwFlags,
720                  DIDC_FORCEFEEDBACK, ishaptic);
721    return(ishaptic);
722 }
723 
724 
725 
whap_is_mouse_haptic(ALLEGRO_MOUSE * mouse)726 static bool whap_is_mouse_haptic(ALLEGRO_MOUSE *mouse)
727 {
728    (void)mouse;
729    return false;
730 }
731 
732 
whap_is_joystick_haptic(ALLEGRO_JOYSTICK * joy)733 static bool whap_is_joystick_haptic(ALLEGRO_JOYSTICK *joy)
734 {
735    ALLEGRO_JOYSTICK_DIRECTX *joydx = (ALLEGRO_JOYSTICK_DIRECTX *)joy;
736    (void)joydx;
737    if (!al_is_joystick_installed())
738       return false;
739    if (!al_get_joystick_active(joy))
740       return false;
741    ALLEGRO_DEBUG("Checking capabilities of joystick %s\n", joydx->name);
742    return whap_is_dinput_device_haptic(joydx->device);
743 }
744 
745 
whap_is_display_haptic(ALLEGRO_DISPLAY * dev)746 static bool whap_is_display_haptic(ALLEGRO_DISPLAY *dev)
747 {
748    (void)dev;
749    return false;
750 }
751 
752 
whap_is_keyboard_haptic(ALLEGRO_KEYBOARD * dev)753 static bool whap_is_keyboard_haptic(ALLEGRO_KEYBOARD *dev)
754 {
755    (void)dev;
756    return false;
757 }
758 
759 
whap_is_touch_input_haptic(ALLEGRO_TOUCH_INPUT * dev)760 static bool whap_is_touch_input_haptic(ALLEGRO_TOUCH_INPUT *dev)
761 {
762    (void)dev;
763    return false;
764 }
765 
766 
whap_get_from_mouse(ALLEGRO_MOUSE * mouse)767 static ALLEGRO_HAPTIC *whap_get_from_mouse(ALLEGRO_MOUSE *mouse)
768 {
769    (void)mouse;
770    return NULL;
771 }
772 
773 
774 /* Sets the force feedback gain on a directinput device.
775    Returns true on success and false on failure.  */
whap_set_dinput_device_gain(LPDIRECTINPUTDEVICE2 device,double gain)776 static bool whap_set_dinput_device_gain(LPDIRECTINPUTDEVICE2 device,
777                                         double gain)
778 {
779    HRESULT ret;
780    DIPROPDWORD dipdw;
781    if (gain < 0.0)
782       return false;
783    if (gain > 1.0)
784       return false;
785 
786    dipdw.diph.dwSize = sizeof(DIPROPDWORD);
787    dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
788    dipdw.diph.dwObj = 0;
789    dipdw.diph.dwHow = DIPH_DEVICE;
790    dipdw.dwData = gain * DI_FFNOMINALMAX;
791 
792    ret = IDirectInputDevice_SetProperty(device, DIPROP_FFGAIN, &dipdw.diph);
793    return(!FAILED(ret));
794 }
795 
796 /* Sets the force feedback autocentering intensity on a directinput device.
797    Returns true on success and false on failure. */
whap_set_dinput_device_autocenter(LPDIRECTINPUTDEVICE2 device,double intensity)798 static bool whap_set_dinput_device_autocenter(LPDIRECTINPUTDEVICE2 device,
799                                               double intensity)
800 {
801    HRESULT ret;
802    DIPROPDWORD dipdw;
803    if (intensity < 0.0)
804       return false;
805    if (intensity > 1.0)
806       return false;
807 
808    dipdw.diph.dwSize = sizeof(DIPROPDWORD);
809    dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
810    dipdw.diph.dwObj = 0;
811    dipdw.diph.dwHow = DIPH_DEVICE;
812    if (intensity < 0.5) {
813       dipdw.dwData = DIPROPAUTOCENTER_OFF;
814    }
815    else {
816       dipdw.dwData = DIPROPAUTOCENTER_ON;
817    }
818    /* Try to set the autocenter. */
819    ret = IDirectInputDevice_SetProperty(device, DIPROP_AUTOCENTER, &dipdw.diph);
820    return(!FAILED(ret));
821 }
822 
823 
824 /* Callback to check which effect types are supported. */
825 static BOOL CALLBACK
whap_check_effect_callback(LPCDIEFFECTINFO info,LPVOID data)826 whap_check_effect_callback(LPCDIEFFECTINFO info, LPVOID data)
827 {
828    ALLEGRO_HAPTIC *haptic = (ALLEGRO_HAPTIC *)data;
829    ALLEGRO_HAPTIC_WINDOWS *whap = whap_from_al(haptic);
830 
831    const CAP_MAP *map;
832    for (map = cap_map; map->allegro_bit != -1; map++) {
833       if (GUID_EQUAL(info->guid, map->guid)) {
834          whap->flags |= map->allegro_bit;
835       }
836    }
837    /* Check for more supported effect types. */
838    return DIENUM_CONTINUE;
839 }
840 
841 
842 /* Callback to check which axes are supported. */
843 static BOOL CALLBACK
whap_check_axes_callback(LPCDIDEVICEOBJECTINSTANCE dev,LPVOID data)844 whap_check_axes_callback(LPCDIDEVICEOBJECTINSTANCE dev, LPVOID data)
845 {
846    ALLEGRO_HAPTIC *haptic = (ALLEGRO_HAPTIC *)data;
847    ALLEGRO_HAPTIC_WINDOWS *whap = whap_from_al(haptic);
848 
849    if ((dev->dwType & DIDFT_AXIS) && (dev->dwFlags & DIDOI_FFACTUATOR)) {
850       whap->axes[whap->naxes] = dev->dwOfs;
851       whap->naxes++;
852 
853       /* Stop if the axes limit is reached */
854       if (whap->naxes >= HAPTICS_AXES_MAX) {
855          return DIENUM_STOP;
856       }
857    }
858    return DIENUM_CONTINUE;
859 }
860 
861 /* Acquires an exclusive lock on the device. */
whap_acquire_lock(ALLEGRO_HAPTIC_WINDOWS * whap)862 static bool whap_acquire_lock(ALLEGRO_HAPTIC_WINDOWS *whap)
863 {
864    HRESULT ret;
865 
866    /* Release previous acquire lock on device if any */
867    ret = IDirectInputDevice_Unacquire(whap->device);
868    if (FAILED(ret)) {
869       ALLEGRO_WARN
870          ("IDirectInputDevice_Unacquire failed for haptic device.\n");
871       return false;
872    }
873 
874    /* Need a display to lock the cooperative level of the haptic device on */
875    whap->display = (ALLEGRO_DISPLAY_WIN *)al_get_current_display();
876    if (!whap->display) {
877       ALLEGRO_WARN("No active window available to lock the haptic device on.");
878       return false;
879    }
880 
881    /* Must set the cooperative level to exclusive now to enable force feedback.
882     */
883    ret =
884       IDirectInputDevice_SetCooperativeLevel(whap->device,
885                                              whap->display->window,
886                                              DISCL_BACKGROUND |
887                                              DISCL_EXCLUSIVE);
888    if (FAILED(ret)) {
889       ALLEGRO_WARN
890          ("IDirectInputDevice_SetCooperativeLevel failed for haptic device.\n");
891       return false;
892    }
893 
894    /* Get acquire lock on device */
895    ret = IDirectInputDevice_Acquire(whap->device);
896    if (FAILED(ret)) {
897       ALLEGRO_WARN("IDirectInputDevice_Acquire failed for haptic device.\n");
898       return false;
899    }
900    return true;
901 }
902 
903 
904 /* Initializes the haptic device for use with DirectInput */
whap_initialize_dinput(ALLEGRO_HAPTIC_WINDOWS * whap)905 static bool whap_initialize_dinput(ALLEGRO_HAPTIC_WINDOWS *whap)
906 {
907    HRESULT ret;
908    ALLEGRO_HAPTIC *haptic = &whap->parent;
909    /* Set number of axes to zero, and then ... */
910    whap->naxes = 0;
911    /* ... get number of axes. */
912    ret = IDirectInputDevice_EnumObjects(whap->device,
913                                         whap_check_axes_callback,
914                                         haptic, DIDFT_AXIS);
915    if (FAILED(ret)) {
916       ALLEGRO_WARN("Could not get haptic device axes \n");
917       return false;
918    }
919 
920    /* Support angle and radius if we have at least 2 axes.
921     * Axis support on DirectInput is a big mess, so Azimuth is unlikely to be
922     * supported.
923     */
924    if (whap->naxes >= 1) {
925       whap->flags |= ALLEGRO_HAPTIC_ANGLE;
926       whap->flags |= ALLEGRO_HAPTIC_RADIUS;
927    }
928 
929    if (!whap_acquire_lock(whap)) {
930       ALLEGRO_WARN("Could not lock haptic device \n");
931       return false;
932    }
933    /* Reset all actuators in case some where active */
934    ret = IDirectInputDevice8_SendForceFeedbackCommand(whap->device,
935                                                       DISFFC_RESET);
936    if (FAILED(ret)) {
937       ALLEGRO_WARN("Could not reset haptic device \n");
938    }
939 
940    /* Enable all actuators. */
941    ret = IDirectInputDevice8_SendForceFeedbackCommand(whap->device,
942                                                       DISFFC_SETACTUATORSON);
943    if (FAILED(ret)) {
944       ALLEGRO_WARN("Could not enable haptic device actuators\n");
945       return false;
946    }
947 
948    /* Get known supported effects. */
949    ret = IDirectInputDevice8_EnumEffects(whap->device,
950                                          whap_check_effect_callback, haptic,
951                                          DIEFT_ALL);
952    if (FAILED(ret)) {
953       ALLEGRO_WARN("Could not get haptic device supported effects\n");
954       return false;
955    }
956 
957    /* Check if any periodic effects are supported. */
958    bool periodic_ok = al_is_haptic_capable(haptic, ALLEGRO_HAPTIC_SINE);
959    periodic_ok |= al_is_haptic_capable(haptic, ALLEGRO_HAPTIC_SQUARE);
960    periodic_ok |= al_is_haptic_capable(haptic, ALLEGRO_HAPTIC_TRIANGLE);
961    periodic_ok |= al_is_haptic_capable(haptic, ALLEGRO_HAPTIC_SAW_DOWN);
962    periodic_ok |= al_is_haptic_capable(haptic, ALLEGRO_HAPTIC_SAW_UP);
963 
964    if (periodic_ok) {
965       /* If we have any of the effects above, we can use
966          periodic and rumble effects. */
967       whap->flags |= (ALLEGRO_HAPTIC_PERIODIC | ALLEGRO_HAPTIC_RUMBLE);
968    }
969 
970    if (whap_set_dinput_device_gain(whap->device, 1.0)) {
971       whap->flags |= ALLEGRO_HAPTIC_GAIN;
972    }
973 
974    /* Check autocenter and turn it off in one go. */
975    if (whap_set_dinput_device_autocenter(whap->device, 0.0)) {
976       whap->flags |= ALLEGRO_HAPTIC_AUTOCENTER;
977    }
978 
979    return true;
980 }
981 
982 
983 
984 
whap_get_from_joystick(ALLEGRO_JOYSTICK * joy)985 static ALLEGRO_HAPTIC *whap_get_from_joystick(ALLEGRO_JOYSTICK *joy)
986 {
987    ALLEGRO_JOYSTICK_DIRECTX *joydx = (ALLEGRO_JOYSTICK_DIRECTX *)joy;
988    ALLEGRO_HAPTIC_WINDOWS *whap;
989 
990    int i;
991 
992    if (!al_is_joystick_haptic(joy))
993       return NULL;
994 
995    al_lock_mutex(haptic_mutex);
996 
997    whap = whap_get_available_haptic();
998 
999    if (!whap) {
1000       al_unlock_mutex(haptic_mutex);
1001       return NULL;
1002    }
1003 
1004    whap->parent.driver = &_al_hapdrv_directx;
1005    whap->parent.device = joy;
1006    whap->parent.from = _AL_HAPTIC_FROM_JOYSTICK;
1007 
1008    whap->guid = joydx->guid;
1009    whap->device = joydx->device;
1010    whap->active = true;
1011    for (i = 0; i < HAPTICS_EFFECTS_MAX; i++) {
1012       whap->effects[i].active = false;  /* not in use */
1013    }
1014    whap->parent.gain = 1.0;
1015    whap->parent.autocenter = 0.0;
1016 
1017    /* result is ok if init functions returns true. */
1018    if (!whap_initialize_dinput(whap)) {
1019       al_release_haptic(&whap->parent);
1020       al_unlock_mutex(haptic_mutex);
1021       return NULL;
1022    }
1023 
1024    al_unlock_mutex(haptic_mutex);
1025 
1026    return &whap->parent;
1027 }
1028 
1029 
whap_get_from_display(ALLEGRO_DISPLAY * dev)1030 static ALLEGRO_HAPTIC *whap_get_from_display(ALLEGRO_DISPLAY *dev)
1031 {
1032    (void)dev;
1033    return NULL;
1034 }
1035 
1036 
whap_get_from_keyboard(ALLEGRO_KEYBOARD * dev)1037 static ALLEGRO_HAPTIC *whap_get_from_keyboard(ALLEGRO_KEYBOARD *dev)
1038 {
1039    (void)dev;
1040    return NULL;
1041 }
1042 
1043 
whap_get_from_touch_input(ALLEGRO_TOUCH_INPUT * dev)1044 static ALLEGRO_HAPTIC *whap_get_from_touch_input(ALLEGRO_TOUCH_INPUT *dev)
1045 {
1046    (void)dev;
1047    return NULL;
1048 }
1049 
1050 
whap_get_capabilities(ALLEGRO_HAPTIC * dev)1051 static int whap_get_capabilities(ALLEGRO_HAPTIC *dev)
1052 {
1053    ALLEGRO_HAPTIC_WINDOWS *whap = whap_from_al(dev);
1054    return whap->flags;
1055 }
1056 
1057 
whap_get_gain(ALLEGRO_HAPTIC * dev)1058 static double whap_get_gain(ALLEGRO_HAPTIC *dev)
1059 {
1060    ALLEGRO_HAPTIC_WINDOWS *whap = whap_from_al(dev);
1061    /* Just return the stored gain, it's easier than querying. */
1062    return whap->parent.gain;
1063 }
1064 
1065 
whap_set_gain(ALLEGRO_HAPTIC * dev,double gain)1066 static bool whap_set_gain(ALLEGRO_HAPTIC *dev, double gain)
1067 {
1068    ALLEGRO_HAPTIC_WINDOWS *whap = whap_from_al(dev);
1069    bool ok = whap_set_dinput_device_gain(whap->device, gain);
1070    if (ok) {
1071       whap->parent.gain = gain;
1072    }
1073    else {
1074       whap->parent.gain = 1.0;
1075    }
1076    return ok;
1077 }
1078 
1079 
whap_get_autocenter(ALLEGRO_HAPTIC * dev)1080 double whap_get_autocenter(ALLEGRO_HAPTIC *dev)
1081 {
1082    ALLEGRO_HAPTIC_WINDOWS *whap = whap_from_al(dev);
1083    /* Return the stored autocenter value. It's easiest like that. */
1084    return whap->parent.autocenter;
1085 }
1086 
1087 
whap_set_autocenter(ALLEGRO_HAPTIC * dev,double intensity)1088 static bool whap_set_autocenter(ALLEGRO_HAPTIC *dev, double intensity)
1089 {
1090    ALLEGRO_HAPTIC_WINDOWS *whap = whap_from_al(dev);
1091    bool ok = whap_set_dinput_device_autocenter(whap->device, intensity);
1092    if (ok) {
1093       whap->parent.autocenter = intensity;
1094    }
1095    else {
1096       whap->parent.autocenter = 0.0;
1097    }
1098    return ok;
1099 }
1100 
whap_get_max_effects(ALLEGRO_HAPTIC * dev)1101 static int whap_get_max_effects(ALLEGRO_HAPTIC *dev)
1102 {
1103    ALLEGRO_HAPTIC_WINDOWS *whap = whap_from_al(dev);
1104    int n_effects;
1105    (void)n_effects, (void)whap;
1106 
1107    return HAPTICS_EFFECTS_MAX;
1108 }
1109 
1110 
whap_is_effect_ok(ALLEGRO_HAPTIC * haptic,ALLEGRO_HAPTIC_EFFECT * effect)1111 static bool whap_is_effect_ok(ALLEGRO_HAPTIC *haptic,
1112                               ALLEGRO_HAPTIC_EFFECT *effect)
1113 {
1114    int caps;
1115 
1116    caps = al_get_haptic_capabilities(haptic);
1117    if (caps & effect->type) {
1118       return true;
1119    }
1120    /* XXX: should do more checking here? */
1121    return false;
1122 }
1123 
1124 
1125 struct dinput_error_pair
1126 {
1127    HRESULT error;
1128    const char *text;
1129 };
1130 
1131 #define DIMKEP(ERROR) { ((HRESULT)ERROR), # ERROR }
1132 
1133 struct dinput_error_pair dinput_errors[] = {
1134    DIMKEP(DI_BUFFEROVERFLOW),
1135    DIMKEP(DI_DOWNLOADSKIPPED),
1136    DIMKEP(DI_EFFECTRESTARTED),
1137    DIMKEP(DI_NOEFFECT),
1138    DIMKEP(DI_NOTATTACHED),
1139    DIMKEP(DI_OK),
1140    DIMKEP(DI_POLLEDDEVICE),
1141    DIMKEP(DI_PROPNOEFFECT),
1142    DIMKEP(DI_SETTINGSNOTSAVED),
1143    DIMKEP(DI_TRUNCATED),
1144    DIMKEP(DI_TRUNCATEDANDRESTARTED),
1145    DIMKEP(DI_WRITEPROTECT),
1146    DIMKEP(DIERR_ACQUIRED),
1147    DIMKEP(DIERR_ALREADYINITIALIZED),
1148    DIMKEP(DIERR_BADDRIVERVER),
1149    DIMKEP(DIERR_BETADIRECTINPUTVERSION),
1150    DIMKEP(DIERR_DEVICEFULL),
1151    DIMKEP(DIERR_DEVICENOTREG),
1152    DIMKEP(DIERR_EFFECTPLAYING),
1153    DIMKEP(DIERR_GENERIC),
1154    DIMKEP(DIERR_HANDLEEXISTS),
1155    DIMKEP(DIERR_HASEFFECTS),
1156    DIMKEP(DIERR_INCOMPLETEEFFECT),
1157    DIMKEP(DIERR_INPUTLOST),
1158    DIMKEP(DIERR_INVALIDPARAM),
1159    DIMKEP(DIERR_MAPFILEFAIL),
1160    DIMKEP(DIERR_MOREDATA),
1161    DIMKEP(DIERR_NOAGGREGATION),
1162    DIMKEP(DIERR_NOINTERFACE),
1163    DIMKEP(DIERR_NOTACQUIRED),
1164    DIMKEP(DIERR_NOTBUFFERED),
1165    DIMKEP(DIERR_NOTDOWNLOADED),
1166    DIMKEP(DIERR_NOTEXCLUSIVEACQUIRED),
1167    DIMKEP(DIERR_NOTFOUND),
1168    DIMKEP(DIERR_NOTINITIALIZED),
1169    DIMKEP(DIERR_OBJECTNOTFOUND),
1170    DIMKEP(DIERR_OLDDIRECTINPUTVERSION),
1171    DIMKEP(DIERR_OTHERAPPHASPRIO),
1172    DIMKEP(DIERR_OUTOFMEMORY),
1173    DIMKEP(DIERR_READONLY),
1174    DIMKEP(DIERR_REPORTFULL),
1175    DIMKEP(DIERR_UNPLUGGED),
1176    DIMKEP(DIERR_UNSUPPORTED),
1177    DIMKEP(E_HANDLE),
1178    DIMKEP(E_PENDING),
1179    DIMKEP(E_POINTER),
1180    { 0, NULL }
1181 };
1182 
warn_on_error(HRESULT hr)1183 static void warn_on_error(HRESULT hr)
1184 {
1185    struct dinput_error_pair *pair = dinput_errors;
1186    while (pair->text) {
1187       if (hr == pair->error) {
1188          ALLEGRO_WARN("HRESULT error: %s\n", pair->text);
1189       }
1190       pair++;
1191    }
1192    ALLEGRO_WARN("Unknown HRESULT error: %u\n", (unsigned int)hr);
1193 }
1194 
1195 
1196 
whap_upload_effect_helper(ALLEGRO_HAPTIC_WINDOWS * whap,ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff,ALLEGRO_HAPTIC_EFFECT * effect)1197 static bool whap_upload_effect_helper
1198    (ALLEGRO_HAPTIC_WINDOWS *whap,
1199    ALLEGRO_HAPTIC_EFFECT_WINDOWS *weff, ALLEGRO_HAPTIC_EFFECT *effect)
1200 {
1201    HRESULT ret;
1202 
1203    if (!whap_effect2win(weff, effect, whap)) {
1204       ALLEGRO_WARN("Could not convert haptic effect.\n");
1205       return false;
1206    }
1207 
1208    /* Create the effect. */
1209    ret = IDirectInputDevice8_CreateEffect(whap->device, (*weff->guid),
1210                                           &weff->effect,
1211                                           &weff->ref, NULL);
1212 
1213    /* XXX Need to re-lock since the joystick driver steals my thunder
1214     * by calling Unacquire on the device.
1215     * The better way would be to fix this in that driver somehow.
1216     */
1217    if (!whap_acquire_lock(whap)) {
1218       ALLEGRO_WARN("Could not lock haptic device.\n");
1219       return false;
1220    }
1221 
1222 
1223    /* Create the effect. */
1224    ret = IDirectInputDevice8_CreateEffect(whap->device, (*weff->guid),
1225                                           &weff->effect, &weff->ref, NULL);
1226 
1227    if (FAILED(ret)) {
1228       ALLEGRO_WARN("Could not create haptic effect.\n");
1229       warn_on_error(ret);
1230       return false;
1231    }
1232 
1233    /* Upload the effect to the device. */
1234    ret = IDirectInputEffect_Download(weff->ref);
1235    if (FAILED(ret)) {
1236       ALLEGRO_WARN("Could not upload haptic effect.\n");
1237       warn_on_error(ret);
1238       return false;
1239    }
1240 
1241    return true;
1242 }
1243 
whap_upload_effect(ALLEGRO_HAPTIC * dev,ALLEGRO_HAPTIC_EFFECT * effect,ALLEGRO_HAPTIC_EFFECT_ID * id)1244 static bool whap_upload_effect(ALLEGRO_HAPTIC *dev,
1245                                ALLEGRO_HAPTIC_EFFECT *effect,
1246                                ALLEGRO_HAPTIC_EFFECT_ID *id)
1247 {
1248    bool ok = FALSE;
1249    ALLEGRO_HAPTIC_WINDOWS *whap = whap_from_al(dev);
1250    ALLEGRO_HAPTIC_EFFECT_WINDOWS *weff = NULL;
1251 
1252    ASSERT(dev);
1253    ASSERT(id);
1254    ASSERT(effect);
1255 
1256    /* Set id's values to indicate failure beforehand. */
1257    id->_haptic = NULL;
1258    id->_id = -1;
1259    id->_pointer = NULL;
1260    id->_playing = false;
1261    id->_effect_duration = 0.0;
1262    id->_start_time = 0.0;
1263    id->_end_time = 0.0;
1264 
1265    al_lock_mutex(haptic_mutex);
1266 
1267    /* Look for a free haptic effect slot. */
1268    weff = whap_get_available_effect(whap);
1269    /* Returns NULL if there is no more space for an effect. */
1270    if (weff) {
1271       if (whap_upload_effect_helper(whap, weff, effect)) {
1272          /* set ID handle to signify success */
1273          id->_haptic = dev;
1274          id->_pointer = weff;
1275          id->_id = weff->id;
1276          id->_effect_duration = al_get_haptic_effect_duration(effect);
1277          ok = true;
1278       }
1279       else {
1280          ALLEGRO_WARN("Could not upload effect.");
1281       }
1282    }
1283    else {
1284       ALLEGRO_WARN("No free effect slot.");
1285    }
1286 
1287    al_unlock_mutex(haptic_mutex);
1288    return ok;
1289 }
1290 
1291 
whap_play_effect(ALLEGRO_HAPTIC_EFFECT_ID * id,int loops)1292 static bool whap_play_effect(ALLEGRO_HAPTIC_EFFECT_ID *id, int loops)
1293 {
1294    HRESULT res;
1295    ALLEGRO_HAPTIC_WINDOWS *whap = (ALLEGRO_HAPTIC_WINDOWS *)id->_haptic;
1296    ALLEGRO_HAPTIC_EFFECT_WINDOWS *weff;
1297    if ((!whap) || (id->_id < 0))
1298       return false;
1299    weff = whap->effects + id->_id;
1300 
1301    /* Need to re-lock since the joystick driver steals the haptics' thunder
1302     * by calling Unacquire on the device.
1303     * The better way would be to fix this in that driver somehow.
1304     */
1305    if (!whap_acquire_lock(whap)) {
1306       ALLEGRO_WARN("Could not lock haptic device \n");
1307       return false;
1308    }
1309 
1310    res = IDirectInputEffect_Start(weff->ref, loops, 0);
1311    if (FAILED(res)) {
1312       ALLEGRO_WARN("Failed to play an effect.");
1313       warn_on_error(res);
1314       return false;
1315    }
1316    id->_playing = true;
1317    id->_start_time = al_get_time();
1318    id->_end_time = id->_start_time;
1319    id->_end_time += id->_effect_duration * (double)loops;
1320    return true;
1321 }
1322 
1323 
whap_stop_effect(ALLEGRO_HAPTIC_EFFECT_ID * id)1324 static bool whap_stop_effect(ALLEGRO_HAPTIC_EFFECT_ID *id)
1325 {
1326    HRESULT res;
1327    ALLEGRO_HAPTIC_WINDOWS *whap = (ALLEGRO_HAPTIC_WINDOWS *)id->_haptic;
1328    ALLEGRO_HAPTIC_EFFECT_WINDOWS *weff;
1329 
1330    if ((!whap) || (id->_id < 0))
1331       return false;
1332 
1333    weff = whap->effects + id->_id;
1334 
1335    res = IDirectInputEffect_Stop(weff->ref);
1336    if (FAILED(res)) {
1337       ALLEGRO_WARN("Failed to play an effect.");
1338       return false;
1339    }
1340    id->_playing = false;
1341 
1342 
1343    return true;
1344 }
1345 
1346 
whap_is_effect_playing(ALLEGRO_HAPTIC_EFFECT_ID * id)1347 static bool whap_is_effect_playing(ALLEGRO_HAPTIC_EFFECT_ID *id)
1348 {
1349    ASSERT(id);
1350    HRESULT res;
1351    DWORD flags = 0;
1352    ALLEGRO_HAPTIC_WINDOWS *whap = (ALLEGRO_HAPTIC_WINDOWS *)id->_haptic;
1353    ALLEGRO_HAPTIC_EFFECT_WINDOWS *weff;
1354 
1355    if ((!whap) || (id->_id < 0) || (!id->_playing))
1356       return false;
1357 
1358    weff = whap->effects + id->_id;
1359 
1360    res = IDirectInputEffect_GetEffectStatus(weff->ref, &flags);
1361    if (FAILED(res)) {
1362       ALLEGRO_WARN("Failed to get the status of effect.");
1363       /* If we get here, then use the play time in stead to
1364        * see if the effect should still be playing.
1365        * Do this because in case GeteffectStatus fails, we can't
1366        * assume the sample isn't playing. In fact, if the play command
1367        * was successful, it should still be playing as long as the play
1368        * time has not passed.
1369        */
1370       return(al_get_time() < id->_end_time);
1371    }
1372    if (flags & DIEGES_PLAYING)
1373       return true;
1374    /* WINE is bugged here, it doesn't set flags, but it also
1375     * just returns DI_OK. Thats why here, don't believe the API
1376     * when it the playing flag isn't set if the effect's duration
1377     * has not passed. On real Windows it should probably always be the
1378     * case that the effect will have played completely when
1379     * the play time has ended.
1380     */
1381    return(al_get_time() < id->_end_time);
1382 }
1383 
1384 
1385 
whap_release_effect(ALLEGRO_HAPTIC_EFFECT_ID * id)1386 static bool whap_release_effect(ALLEGRO_HAPTIC_EFFECT_ID *id)
1387 {
1388    ALLEGRO_HAPTIC_WINDOWS *whap = (ALLEGRO_HAPTIC_WINDOWS *)id->_haptic;
1389    ALLEGRO_HAPTIC_EFFECT_WINDOWS *weff;
1390    if ((!whap) || (id->_id < 0))
1391       return false;
1392 
1393    whap_stop_effect(id);
1394 
1395    weff = whap->effects + id->_id;
1396    return whap_release_effect_windows(weff);
1397 }
1398 
1399 
whap_release(ALLEGRO_HAPTIC * haptic)1400 static bool whap_release(ALLEGRO_HAPTIC *haptic)
1401 {
1402    ALLEGRO_HAPTIC_WINDOWS *whap = whap_from_al(haptic);
1403    int index;
1404    HRESULT res;
1405 
1406    ASSERT(haptic);
1407 
1408    if (!whap->active)
1409       return false;
1410 
1411    /* Release all effects for this device. */
1412    for (index = 0; index < HAPTICS_EFFECTS_MAX; index++) {
1413       whap_release_effect_windows(whap->effects + index);
1414    }
1415 
1416    /* Release the acquire lock on the device */
1417    IDirectInputDevice_Unacquire(whap->device);
1418 
1419    /* Reset the cooperative level to nonexclusive.
1420     */
1421    res =
1422       IDirectInputDevice_SetCooperativeLevel(whap->device,
1423                                              whap->display->window,
1424                                              DISCL_FOREGROUND |
1425                                              DISCL_NONEXCLUSIVE);
1426    if (FAILED(res)) {
1427       ALLEGRO_WARN
1428          ("IDirectInputDevice8_SetCooperativeLevel NONEXCLUSIVE failed for haptic device.\n");
1429    }
1430 
1431    whap->display = NULL;
1432    whap->active = false;
1433    whap->device = NULL;
1434    return true;
1435 }
1436 
1437 
1438 
1439 /* vim: set sts=3 sw=3 et: */
1440