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