Re: [AD] Windows DirectInput haptic driver and some small haptic enhancements

[ Thread Index | Date Index | More lists.liballeg.org/allegro-developers Archives ]


I finally got around to debugging my Windows DirectInput haptic driver
though a VM. I found that Windows is more severe when it comes to
locking the active display window than I had thought, and also, that
only Cartesian coordinates with 1 or 2 axes are well supported, at
least, on my joystick. I also used indent to format the code according
to the Allegro code standard.

I hope this is acceptable this time around, and anyone who has a
DirectInput compatible device (not XInput, though), please test this
and report your results.

Kind Regards,

B.
diff --git a/cmake/FileList.cmake b/cmake/FileList.cmake
index af72772..31ca116 100644
--- a/cmake/FileList.cmake
+++ b/cmake/FileList.cmake
@@ -53,6 +53,8 @@ set(ALLEGRO_SRC_FILES
     )
 
 set(ALLEGRO_SRC_WIN_FILES
+    src/win/whaptic.c
+    src/win/whaptic.cpp
     src/win/wjoydrv.c
     src/win/wjoydxnu.cpp
     src/win/wkeyboard.c
@@ -95,6 +97,7 @@ set(ALLEGRO_SRC_UNIX_FILES
     src/unix/udrvlist.c
     src/unix/ufdwatch.c
     src/unix/ugfxdrv.c
+    src/unix/uhapdrv.c
     src/unix/ujoydrv.c
     src/unix/ukeybd.c
     src/unix/umouse.c
diff --git a/docs/src/refman/haptic.txt b/docs/src/refman/haptic.txt
index 18cac76..aa72961 100644
--- a/docs/src/refman/haptic.txt
+++ b/docs/src/refman/haptic.txt
@@ -7,6 +7,11 @@ These functions are declared in the main Allegro header file:
  #include <allegro5/allegro.h>
 ~~~~
 
+Currently force feedback is fully supported on Linux and on Windows for 
+DirectInput compatible devices. There is also minimal support for
+Android. It is not yet supported on OSX, iOS, or on Windows for XInput 
+compatible devices.
+
 ## API: ALLEGRO_HAPTIC
 
 This is an abstract data type representing a haptic device that supports
@@ -95,9 +100,11 @@ direction
 
     In Allegro's coordinate system, the value in `direction.angle` determines
     the planar angle between the effect and the direction of the user who
-    holds the device, expressed in radians.  So, an effect with an angle 0.0
-    takes place in the direction of the user of the haptic device, and an
-    angle of π means in the direction away from the user.
+    holds the device, expressed in radians. This angle increases clockwise
+    away from the user. So, an effect with an angle 0.0 takes place in the 
+    direction of the user of the haptic device, an angle of π/2 is
+    to the left of the user, an angle of π means the direction away from 
+    the user, and an angle of 3π/2 means to the right of the user.
 
     If [al_get_haptic_capabilities] has the flag ALLEGRO_HAPTIC_ANGLE set,
     then setting `direction.angle` is supported. Otherwise, it is
@@ -117,9 +124,9 @@ direction
     The value in `direction.azimuth` determines the elevation angle between
     the effect and the plane in which the user is holding the device,
     expressed in radians. An effect with an azimuth 0.0 plays back in the
-    plane in which the user is holding the device, an azimuth +π/2 means the
-    effect plays back vertically above the user plane, and an azimuth -π/2
-    means the effect plays back vertically below the user plane.
+    plane in which the user is holding the device, an azimuth +π/2 means 
+    the effect plays back vertically above the user plane, and an azimuth 
+    -π/2 means the effect plays back vertically below the user plane.
 
     If [al_get_haptic_capabilities] has the flag ALLEGRO_HAPTIC_AZIMUTH set,
     then setting `direction.azimuth` is supported. Otherwise, it is
@@ -254,13 +261,31 @@ Since: 5.1.8
 
 Installs the haptic (force feedback) device subsystem. This must be called
 before using any other haptic-related functions. Returns true if the haptics
-subsystem could be initialized correctly, false if not.
+subsystem could be initialized correctly, false if not. 
+
+For portability you should first open a display before calling 
+[al_install_haptic]. On some platforms, such as DirectInput under 
+Windows, [al_install_haptic] will only work if at least one active 
+display is available. This display must stay available until
+[al_uninstall_haptic] is called.
+
+If you need to close and reopen your active display, e.g. then you should 
+call [al_uninstall_haptic] before closing the display, and 
+[al_install_haptic] after opening it again.
+
+on Windows 
+and DirectInput
 
 Since: 5.1.8
 
 ## API: al_uninstall_haptic
 
-Uninstalls the haptic device subsystem.
+Uninstalls the haptic device subsystem. This is useful since on some
+platforms haptic effects are bound to the active display.
+
+If you need to close and reopen your active display, e.g. then you should 
+call [al_uninstall_haptic] before closing the display, and 
+[al_install_haptic] after opening it again.
 
 Since: 5.1.8
 
@@ -327,7 +352,8 @@ Since: 5.1.8
 
 If the joystick has haptic capabilities, returns the associated haptic device
 handle.  Otherwise returns NULL. It's neccesary to call this again every time
-the joystick configuration changes, such as though hot plugging.
+the joystick configuration changes, such as through hot plugging. In that case,
+the old haptic device must be released using [al_release_haptic].
 
 Since: 5.1.8
 
@@ -342,7 +368,9 @@ Since: 5.1.8
 
 Releases the haptic device and it's resources when it's not needed anymore.
 Should also be used in case the joystick configuration changed, such as when
-a joystick is hot plugged.
+a joystick is hot plugged. This function also automatically releases all 
+haptic effects that are still uploaded to the device and that have not been
+released manually using [al_release_haptic_effect].
 
 Since: 5.1.8
 
@@ -359,6 +387,16 @@ if set, indicate that the haptic device supports the given feature.
 
 Since: 5.1.8
 
+## API: al_is_haptic_capable
+
+Returns true if the haptic device is supports the feature indicated by 
+the query parameter, false if the feature is not supported. 
+The query parameter must be one of the values of [ALLEGRO_HAPTIC_CONSTANTS]. 
+
+Since: 5.1.9
+
+See also: [al_get_haptic_capabilities]
+
 ## API: al_set_haptic_gain
 
 Sets the gain of the haptic device if supported. Gain is much like volume for
@@ -381,10 +419,48 @@ gain influence.
 
 Since: 5.1.8
 
+## API: al_set_haptic_autocenter
+
+Turns on or off the automatic centering feature of the haptic device if 
+supported. Depending on the device automatic centering may ensure that the 
+axes of the device are  centered again automatically after playing 
+a haptic effect. The intensity parameter should be passed with a value 
+between 0.0 and 1.0. The value 0.0 means automatic centering is 
+disabled, and 1.0 means full strength automatic centering. Any value
+in between those two extremes will result in partial automatic 
+centering. Some platforms do not support partial automatic 
+centering. If that is the case, a value of less than 0.5 will turn 
+it off, while a value equal to or higher to 0.5 will turn it on.
+Returns true if set sucessfully, false if not.
+Can only work if [al_get_haptic_capabilities] returns a value that has
+[ALLEGRO_HAPTIC_AUTOCENTER] set.  If not, this function returns false.
+
+Since: 5.1.9
+
+## API: al_get_haptic_autocenter
+
+Returns the current automatic centering intensity of the device. 
+Depending on the device automatic centering may ensure that the 
+axes of the device are  centered again automatically after playing 
+a haptic effect. The return value can be between 0.0 and 1.0. 
+The value 0.0 means automatic centering is disabled, and 1.0 means 
+automatic centering is enabled at full strength. Any value
+in between those two extremes means partial automatic 
+centering is enabled. Some platforms do not support partial automatic 
+centering. If that is the case, a value of less than 0.5 means it is turned 
+off, while a value equal to or higher to 0.5 means it is turned on.
+Can only work if [al_get_haptic_capabilities] returns a 
+value that has [ALLEGRO_HAPTIC_AUTOCENTER] set. If not, this function 
+returns 0.0.
+
+Since: 5.1.9
+
+
 ## API: al_get_num_haptic_effects
 
 Returns the maximum amount of haptic effects that can be uploaded to the
-device.  This depends on the platform and the device.
+device.  This depends on the operating system, driver, platform and the 
+device itself. 
 
 Since: 5.1.8
 
@@ -401,11 +477,15 @@ Uploads the haptic effect to the device.  The haptic effect must have been
 filled in completely and correctly.  You must also pass in a pointer to a
 user allocated [ALLEGRO_HAPTIC_EFFECT_ID].  This `id` can be used to control
 playback of the effect.  Returns true if the effect was successfully uploaded,
-false if not.
+false if not. 
+
+The function [al_get_num_haptic_effects] returns how many effects can 
+be uploaded to the device at the same time.
 
 The same haptic effect can be uploaded several times, as long as care is
 taken to pass in a different [ALLEGRO_HAPTIC_EFFECT_ID].
 
+
 Since: 5.1.8
 
 ## API: al_play_haptic_effect
@@ -426,7 +506,14 @@ Since: 5.1.8
 ## API: al_upload_and_play_haptic_effect
 
 Uploads and immediately plays back the haptic effect to the device. Returns
-true if the upload and playback were successful, false if either failed.
+true if the upload and playback were successful, false if either failed. 
+
+In case false is returned, the haptic effect will be automatically 
+released as if [al_release_haptic_effect] had been called, so there 
+is no need to call it again manually in this case. However, 
+if true is returned, it is necessary to call [al_release_haptic_effect]
+when the effect isn't needed anymore, to prevent the amount of available 
+effects on the haptic devicefrom running out.
 
 Since: 5.1.8
 
@@ -450,13 +537,27 @@ from [al_upload_haptic_effect], [al_upload_and_play_haptic_effect] or
 
 Since: 5.1.8
 
+## API: al_get_haptic_effect_duration
+
+Returns the estimated duration of the given haptic effect. This is merely 
+a convenience function for now, as it returns the sum of 
+`effect.replay.length` and `effect.replay.data`.  
+
+Since: 5.1.9
+
 ## API: al_release_haptic_effect
 
 Releases a previously uploaded haptic effect from the device it has been
-uploaded to, allowing for other effects to be uploaded. The play_id must be a
-valid [ALLEGRO_HAPTIC_EFFECT_ID] obtained from [al_upload_haptic_effect],
+uploaded to, allowing for other effects to be uploaded. The play_id must be 
+a valid [ALLEGRO_HAPTIC_EFFECT_ID] obtained from [al_upload_haptic_effect],
 [al_upload_and_play_haptic_effect] or [al_rumble_haptic].
 
+This function is called automatically when you call [al_release_haptic] 
+on a [ALLEGRO_HAPTIC] for all effects that are still uploaded to the device.
+Therefore this function is most useful if you want to upload and release
+haptic effects dynamically, for example as a way to circumvent the limit 
+imposed by [al_get_num_haptic_effects].
+
 Since: 5.1.8
 
 ## API: al_rumble_haptic
@@ -471,4 +572,11 @@ You must also pass in a pointer to a user allocated
 control playback of the effect.  Returns true if the rumble effect was
 successfully uploaded and started, false if not.
 
+In case false is returned, the rumble effect will be automatically 
+released as if [al_release_haptic_effect] had been called, so there 
+is no need to call it again manually in this case. However, 
+if true is returned, it is necessary to call [al_release_haptic_effect]
+when the effect isn't needed anymore, to prevent the amount of available 
+effects on the haptic device from running out.
+
 Since: 5.1.8
diff --git a/examples/ex_haptic.c b/examples/ex_haptic.c
index 2a93ad2..0089ce6 100644
--- a/examples/ex_haptic.c
+++ b/examples/ex_haptic.c
@@ -22,6 +22,12 @@ static void test_haptic_joystick(ALLEGRO_JOYSTICK *joy)
       al_get_joystick_name(joy));
 
    haptic = al_get_haptic_from_joystick(joy);
+
+   if(!haptic) {
+      log_printf("Could not initialize haptic device!\n");
+      return;
+   }
+
    log_printf("Can play back %d haptic effects.\n",
       al_get_num_haptic_effects(haptic));
 
@@ -45,7 +51,7 @@ static void test_haptic_joystick(ALLEGRO_JOYSTICK *joy)
       al_upload_haptic_effect(haptic, &effect, &id));
 
    log_printf("Playing effect: %d.\n",
-      al_play_haptic_effect(&id, 5));
+      al_play_haptic_effect(&id, 3));
 
    do {
       al_rest(0.1);
diff --git a/examples/ex_haptic2.cpp b/examples/ex_haptic2.cpp
index 1960492..8c7cdf2 100644
--- a/examples/ex_haptic2.cpp
+++ b/examples/ex_haptic2.cpp
@@ -28,6 +28,45 @@ struct Haptic
 
 static Haptic haptics[EX_MAX_HAPTICS];
 static int num_haptics = 0;
+static ALLEGRO_EVENT_QUEUE * joystick_queue;
+
+static void release_all_haptics() {
+   for (int i = 0; i < num_haptics; i++) {    
+      al_release_haptic(haptics[i].haptic);
+      haptics[i].haptic = NULL;
+   }  
+}
+
+static void get_all_haptics() {
+   num_haptics = 0;
+   ALLEGRO_DISPLAY * display = al_get_current_display();
+
+   if (al_is_display_haptic(display)) {
+      haptics[num_haptics].haptic = al_get_haptic_from_display(display);
+      if (haptics[num_haptics].haptic) {
+         haptics[num_haptics].name = (const char *)"display";
+         haptics[num_haptics].playing = false;
+         num_haptics++;
+      }
+   }
+
+   for (int i = 0;
+      num_haptics < EX_MAX_HAPTICS && i < al_get_num_joysticks();
+      i++)
+   {
+      ALLEGRO_JOYSTICK *joy = al_get_joystick(i);
+      if (al_is_joystick_haptic(joy)) {
+         haptics[num_haptics].haptic = al_get_haptic_from_joystick(joy);
+         if (haptics[num_haptics].haptic) {
+            const char *name = al_get_joystick_name(joy);
+            haptics[num_haptics].name = (const char *)name;
+            haptics[num_haptics].playing = false;
+            num_haptics++;
+         }
+      }
+   }
+
+}
 
 struct CapacityName
 {
@@ -205,6 +244,9 @@ class Prog: public CanStopAndPlay
 
    HSlider gain_slider;
    Label gain_label;
+   
+   HSlider autocenter_slider;
+   Label autocenter_label;
 
    Label message_label;
    Label message_label_label;
@@ -293,6 +335,8 @@ Prog::Prog(const Theme & theme, ALLEGRO_DISPLAY *display) :
    weak_magnitude_label("Weak Magnitude", false),
    gain_slider(10, 10),
    gain_label("Gain"),
+   autocenter_slider(0, 10),
+   autocenter_label("Autocenter"),
    message_label("Ready.", false),
    message_label_label("Status", false),
    play_button(this),
@@ -328,6 +372,10 @@ Prog::Prog(const Theme & theme, ALLEGRO_DISPLAY *display) :
    d.add(loops_slider, 7, 12, 6, 1);
    d.add(gain_label, 13, 11, 7, 1);
    d.add(gain_slider, 13, 12, 7, 1);
+   
+   d.add(autocenter_label, 13, 13, 7, 1);
+   d.add(autocenter_slider, 13, 14, 7, 1);
+
 
    d.add(envelope_label, 0, 15, 9, 1);
    d.add(attack_length_label, 0, 16, 3, 1);
@@ -392,6 +440,7 @@ Prog::Prog(const Theme & theme, ALLEGRO_DISPLAY *display) :
 
    d.add(play_button, 6, 38, 3, 2);
    d.add(stop_button, 12, 38, 3, 2);
+   
 }
 
 void Prog::update()
@@ -407,19 +456,42 @@ void Prog::update()
          log_printf("Play done on %s\n", last_haptic->name);
       }
    }
+   
+   ALLEGRO_EVENT e;
+   
+   /* Check for hot plugging*/
+   while(al_get_next_event(joystick_queue, &e)) {
+     /* clear, reconfigure and fetch haptics again. */
+     if (e.type == ALLEGRO_EVENT_JOYSTICK_CONFIGURATION) {
+       al_reconfigure_joysticks(); 
+       release_all_haptics();
+       get_all_haptics();
+       device_list.clear_items();
+       for (int i = 0; i < num_haptics; i++) {
+          device_list.append_item(haptics[i].name);
+       }
+       log_printf("Hot plugging detected...\n");  
+       message_label.set_text("Hot Plugging...");
+       play_button.set_disabled(false);
+       d.request_draw();
+     }
+   }
 
    /* Update availability of controls based on capabilities. */
    int devno = device_list.get_cur_value();
    Haptic *dev = haptics + devno;
    if (dev && dev->haptic) {
       if (dev != show_haptic) {
+         play_button.set_disabled(false);
          update_controls(dev);
          show_haptic = dev;
-      }
-   }
-   else {
+         message_label.set_text("Haptic Device Found.");
+         d.request_draw();
+      }     
+   } else {
       play_button.set_disabled(true);
       message_label.set_text("No Haptic Device.");
+      d.request_draw();
    }
 }
 
@@ -447,12 +519,20 @@ void Prog::run()
 void Prog::update_controls(Haptic *dev)
 {
    /* Take a deep breath, here we go... */
-   bool condition, envelope, periodic;
-   int cap = al_get_haptic_capabilities(dev->haptic);
+   bool condition, envelope, periodic;      
+   int cap = 0;
+   if (dev) { 
+     cap = al_get_haptic_capabilities(dev->haptic);
+   }
 
    /* Gain capability */
    gain_slider.set_disabled(!TEST_CAP(cap, ALLEGRO_HAPTIC_GAIN));
    gain_label.set_disabled(!TEST_CAP(cap, ALLEGRO_HAPTIC_GAIN));
+   
+   /* Autocenter capability */
+   autocenter_slider.set_disabled(!TEST_CAP(cap, ALLEGRO_HAPTIC_AUTOCENTER));
+   autocenter_label.set_disabled(!TEST_CAP(cap, ALLEGRO_HAPTIC_AUTOCENTER));
+   
 
    /* Envelope related capabilities and sliders. */
    envelope = TEST_CAP(cap, ALLEGRO_HAPTIC_PERIODIC) ||
@@ -625,6 +705,11 @@ void Prog::on_play()
    /* First set gain. */
    double gain = slider_to_magnitude(gain_slider);
    al_set_haptic_gain(haptic->haptic, gain);
+   
+   /* Set autocentering. */
+   double autocenter = slider_to_magnitude(autocenter_slider);
+   al_set_haptic_autocenter(haptic->haptic, autocenter);
+
 
    /* Now fill in the effect struct. */
    int type = name_to_cap(type_list.get_selected_item_text());
@@ -776,33 +861,16 @@ int main(int argc, char *argv[])
          abort_example("Could not create builtin font.\n");
       }
    }
-
-   num_haptics = 0;
-
-   if (al_is_display_haptic(display)) {
-      haptics[num_haptics].haptic = al_get_haptic_from_display(display);
-      if (haptics[num_haptics].haptic) {
-         haptics[num_haptics].name = (const char *)"display";
-         haptics[num_haptics].playing = false;
-         num_haptics++;
-      }
+   
+   joystick_queue = al_create_event_queue();
+   if (!joystick_queue) {
+      abort_example("Could not create joystick event queue font.\n");
    }
+   
+   al_register_event_source(joystick_queue, al_get_joystick_event_source());
 
-   for (int i = 0;
-      num_haptics < EX_MAX_HAPTICS && i < al_get_num_joysticks();
-      i++)
-   {
-      ALLEGRO_JOYSTICK *joy = al_get_joystick(i);
-      if (al_is_joystick_haptic(joy)) {
-         haptics[num_haptics].haptic = al_get_haptic_from_joystick(joy);
-         if (haptics[num_haptics].haptic) {
-            const char *name = al_get_joystick_name(joy);
-            haptics[num_haptics].name = (const char *)name;
-            haptics[num_haptics].playing = false;
-            num_haptics++;
-         }
-      }
-   }
+   
+   get_all_haptics();
 
    /* Don't remove these braces. */
    {
@@ -810,13 +878,12 @@ int main(int argc, char *argv[])
       Prog prog(theme, display);
       prog.run();
    }
-
-   for (int i = 0; i < num_haptics; i++) {
-      al_release_haptic(haptics[i].haptic);
-   }
+   
+   release_all_haptics();
+   al_destroy_event_queue(joystick_queue); 
 
    close_log(false);
-
+   
    al_destroy_font(font);
 
    return 0;
diff --git a/include/allegro5/haptic.h b/include/allegro5/haptic.h
index 58302d1..f97d4df 100644
--- a/include/allegro5/haptic.h
+++ b/include/allegro5/haptic.h
@@ -52,7 +52,8 @@ enum ALLEGRO_HAPTIC_CONSTANTS
    ALLEGRO_HAPTIC_GAIN     = 1 << 14,
    ALLEGRO_HAPTIC_ANGLE    = 1 << 15,
    ALLEGRO_HAPTIC_RADIUS   = 1 << 16,
-   ALLEGRO_HAPTIC_AZIMUTH  = 1 << 17
+   ALLEGRO_HAPTIC_AZIMUTH  = 1 << 17,
+   ALLEGRO_HAPTIC_AUTOCENTER= 1 << 18,
 };
 
 
@@ -185,6 +186,7 @@ struct ALLEGRO_HAPTIC_EFFECT_ID
    ALLEGRO_HAPTIC *_haptic;
    int _id;
    int _handle;
+   void * _pointer;
    double _effect_duration;
    bool _playing;
    double _start_time;
@@ -212,18 +214,25 @@ AL_FUNC(bool, al_release_haptic, (ALLEGRO_HAPTIC *));
 
 AL_FUNC(bool, al_get_haptic_active, (ALLEGRO_HAPTIC *));
 AL_FUNC(int, al_get_haptic_capabilities, (ALLEGRO_HAPTIC *));
-AL_FUNC(bool, al_set_haptic_gain, (ALLEGRO_HAPTIC *, double gain));
+AL_FUNC(bool, al_is_haptic_capable, (ALLEGRO_HAPTIC *, int));
+
+AL_FUNC(bool, al_set_haptic_gain, (ALLEGRO_HAPTIC *, double));
 AL_FUNC(double, al_get_haptic_gain, (ALLEGRO_HAPTIC *));
 
+AL_FUNC(bool, al_set_haptic_autocenter, (ALLEGRO_HAPTIC *, double));
+AL_FUNC(double, al_get_haptic_autocenter, (ALLEGRO_HAPTIC *));
+
+
 AL_FUNC(int, al_get_num_haptic_effects, (ALLEGRO_HAPTIC *));
 AL_FUNC(bool, al_is_haptic_effect_ok, (ALLEGRO_HAPTIC *, ALLEGRO_HAPTIC_EFFECT *));
-AL_FUNC(bool, al_upload_haptic_effect, (ALLEGRO_HAPTIC *, ALLEGRO_HAPTIC_EFFECT *, ALLEGRO_HAPTIC_EFFECT_ID *play_id));
-AL_FUNC(bool, al_play_haptic_effect, (ALLEGRO_HAPTIC_EFFECT_ID *, int loop));
-AL_FUNC(bool, al_upload_and_play_haptic_effect, (ALLEGRO_HAPTIC *, ALLEGRO_HAPTIC_EFFECT *, int loop, ALLEGRO_HAPTIC_EFFECT_ID *play_id));
+AL_FUNC(bool, al_upload_haptic_effect, (ALLEGRO_HAPTIC *, ALLEGRO_HAPTIC_EFFECT *, ALLEGRO_HAPTIC_EFFECT_ID *));
+AL_FUNC(bool, al_play_haptic_effect, (ALLEGRO_HAPTIC_EFFECT_ID *, int));
+AL_FUNC(bool, al_upload_and_play_haptic_effect, (ALLEGRO_HAPTIC *, ALLEGRO_HAPTIC_EFFECT *, int, ALLEGRO_HAPTIC_EFFECT_ID *));
 AL_FUNC(bool, al_stop_haptic_effect, (ALLEGRO_HAPTIC_EFFECT_ID *));
 AL_FUNC(bool, al_is_haptic_effect_playing, (ALLEGRO_HAPTIC_EFFECT_ID *));
 AL_FUNC(bool, al_release_haptic_effect, (ALLEGRO_HAPTIC_EFFECT_ID *));
-AL_FUNC(bool, al_rumble_haptic, (ALLEGRO_HAPTIC *, double intensity, double duration, ALLEGRO_HAPTIC_EFFECT_ID *));
+AL_FUNC(double, al_get_haptic_effect_duration, (ALLEGRO_HAPTIC_EFFECT *));
+AL_FUNC(bool, al_rumble_haptic, (ALLEGRO_HAPTIC *, double, double, ALLEGRO_HAPTIC_EFFECT_ID *));
 
 
 #ifdef __cplusplus
diff --git a/include/allegro5/internal/aintern_haptic.h b/include/allegro5/internal/aintern_haptic.h
index e21532d..f799ab0 100644
--- a/include/allegro5/internal/aintern_haptic.h
+++ b/include/allegro5/internal/aintern_haptic.h
@@ -7,67 +7,101 @@
 #include "allegro5/internal/aintern_events.h"
 
 #ifdef __cplusplus
-   extern "C" {
+extern "C"
+{
 #endif
 
 
-typedef struct ALLEGRO_HAPTIC_DRIVER
-{
-   int hapdrv_id;
-   const char *hapdrv_name;
-   const char *hapdrv_desc;
-   const char *hapdrv_ascii_name;
-   AL_METHOD(bool, init_haptic, (void));
-   AL_METHOD(void, exit_haptic, (void));
-
-   AL_METHOD(bool, is_mouse_haptic, (ALLEGRO_MOUSE *));
-   AL_METHOD(bool, is_joystick_haptic, (ALLEGRO_JOYSTICK *));
-   AL_METHOD(bool, is_keyboard_haptic, (ALLEGRO_KEYBOARD *));
-   AL_METHOD(bool, is_display_haptic, (ALLEGRO_DISPLAY *));
-   AL_METHOD(bool, is_touch_input_haptic, (ALLEGRO_TOUCH_INPUT *));
-
-   AL_METHOD(ALLEGRO_HAPTIC *, get_from_mouse, (ALLEGRO_MOUSE *));
-   AL_METHOD(ALLEGRO_HAPTIC *, get_from_joystick, (ALLEGRO_JOYSTICK *));
-   AL_METHOD(ALLEGRO_HAPTIC *, get_from_keyboard, (ALLEGRO_KEYBOARD *));
-   AL_METHOD(ALLEGRO_HAPTIC *, get_from_display, (ALLEGRO_DISPLAY *));
-   AL_METHOD(ALLEGRO_HAPTIC *, get_from_touch_input, (ALLEGRO_TOUCH_INPUT *));
-
-   AL_METHOD(bool, get_active, (ALLEGRO_HAPTIC *));
-   AL_METHOD(int, get_capabilities, (ALLEGRO_HAPTIC *));
-   AL_METHOD(double, get_gain, (ALLEGRO_HAPTIC *));
-   AL_METHOD(bool, set_gain, (ALLEGRO_HAPTIC *, double));
-   AL_METHOD(int, get_num_effects, (ALLEGRO_HAPTIC *));
-
-   AL_METHOD(bool, is_effect_ok, (ALLEGRO_HAPTIC *, ALLEGRO_HAPTIC_EFFECT *));
-   AL_METHOD(bool, upload_effect, (ALLEGRO_HAPTIC *, ALLEGRO_HAPTIC_EFFECT *,
-                                   ALLEGRO_HAPTIC_EFFECT_ID *));
-   AL_METHOD(bool, play_effect, (ALLEGRO_HAPTIC_EFFECT_ID *, int));
-   AL_METHOD(bool, stop_effect, (ALLEGRO_HAPTIC_EFFECT_ID *));
-   AL_METHOD(bool, is_effect_playing, (ALLEGRO_HAPTIC_EFFECT_ID *));
-   AL_METHOD(bool, release_effect, (ALLEGRO_HAPTIC_EFFECT_ID *));
-   AL_METHOD(bool, release, (ALLEGRO_HAPTIC *));
-} ALLEGRO_HAPTIC_DRIVER;
-
-
-enum ALLEGRO_HAPTIC_PARENT {
-   _AL_HAPTIC_FROM_JOYSTICK = 1,
-   _AL_HAPTIC_FROM_MOUSE,
-   _AL_HAPTIC_FROM_KEYBOARD,
-   _AL_HAPTIC_FROM_DISPLAY,
-   _AL_HAPTIC_FROM_TOUCH_INPUT
-};
-
-
-struct ALLEGRO_HAPTIC
-{
-   enum ALLEGRO_HAPTIC_PARENT from;
-   void *device;
-   double gain;
-};
+   typedef struct ALLEGRO_HAPTIC_DRIVER
+   {
+      int hapdrv_id;
+      const char *hapdrv_name;
+      const char *hapdrv_desc;
+      const char *hapdrv_ascii_name;
+       AL_METHOD(bool, init_haptic, (void));
+       AL_METHOD(void, exit_haptic, (void));
+
+       AL_METHOD(bool, is_mouse_haptic, (ALLEGRO_MOUSE *));
+       AL_METHOD(bool, is_joystick_haptic, (ALLEGRO_JOYSTICK *));
+       AL_METHOD(bool, is_keyboard_haptic, (ALLEGRO_KEYBOARD *));
+       AL_METHOD(bool, is_display_haptic, (ALLEGRO_DISPLAY *));
+       AL_METHOD(bool, is_touch_input_haptic, (ALLEGRO_TOUCH_INPUT *));
+
+       AL_METHOD(ALLEGRO_HAPTIC *, get_from_mouse, (ALLEGRO_MOUSE *));
+       AL_METHOD(ALLEGRO_HAPTIC *, get_from_joystick, (ALLEGRO_JOYSTICK *));
+       AL_METHOD(ALLEGRO_HAPTIC *, get_from_keyboard, (ALLEGRO_KEYBOARD *));
+       AL_METHOD(ALLEGRO_HAPTIC *, get_from_display, (ALLEGRO_DISPLAY *));
+       AL_METHOD(ALLEGRO_HAPTIC *, get_from_touch_input,
+                 (ALLEGRO_TOUCH_INPUT *));
+
+       AL_METHOD(bool, get_active, (ALLEGRO_HAPTIC *));
+       AL_METHOD(int, get_capabilities, (ALLEGRO_HAPTIC *));
+       AL_METHOD(double, get_gain, (ALLEGRO_HAPTIC *));
+       AL_METHOD(bool, set_gain, (ALLEGRO_HAPTIC *, double));
+       AL_METHOD(int, get_num_effects, (ALLEGRO_HAPTIC *));
+
+       AL_METHOD(bool, is_effect_ok,
+                 (ALLEGRO_HAPTIC *, ALLEGRO_HAPTIC_EFFECT *));
+       AL_METHOD(bool, upload_effect,
+                 (ALLEGRO_HAPTIC *, ALLEGRO_HAPTIC_EFFECT *,
+                  ALLEGRO_HAPTIC_EFFECT_ID *));
+       AL_METHOD(bool, play_effect, (ALLEGRO_HAPTIC_EFFECT_ID *, int));
+       AL_METHOD(bool, stop_effect, (ALLEGRO_HAPTIC_EFFECT_ID *));
+       AL_METHOD(bool, is_effect_playing, (ALLEGRO_HAPTIC_EFFECT_ID *));
+       AL_METHOD(bool, release_effect, (ALLEGRO_HAPTIC_EFFECT_ID *));
+       AL_METHOD(bool, release, (ALLEGRO_HAPTIC *));
+       AL_METHOD(double, get_autocenter, (ALLEGRO_HAPTIC *));
+       AL_METHOD(bool, set_autocenter, (ALLEGRO_HAPTIC *, double));
+   } ALLEGRO_HAPTIC_DRIVER;
+
+
+   enum ALLEGRO_HAPTIC_PARENT
+   {
+      _AL_HAPTIC_FROM_JOYSTICK = 1,
+      _AL_HAPTIC_FROM_MOUSE,
+      _AL_HAPTIC_FROM_KEYBOARD,
+      _AL_HAPTIC_FROM_DISPLAY,
+      _AL_HAPTIC_FROM_TOUCH_INPUT
+   };
+
+
+   struct ALLEGRO_HAPTIC
+   {
+      enum ALLEGRO_HAPTIC_PARENT from;
+      void *device;
+      double gain;
+      double autocenter;
+   };
+
+/* Haptic diver list. */
+   extern const _AL_DRIVER_INFO _al_haptic_driver_list[];
+
+/* Macros for constructing the driver list */
+#define _AL_BEGIN_HAPTIC_DRIVER_LIST                                 \
+   const _AL_DRIVER_INFO _al_haptic_driver_list[] =                  \
+   {
+
+#define _AL_END_HAPTIC_DRIVER_LIST                              \
+      {  0,                NULL,                false }         \
+   };
+
+
+/* Haptic diver list. */
+extern const _AL_DRIVER_INFO _al_haptic_driver_list[];
+
+/* Macros for constructing the driver list */
+#define _AL_BEGIN_HAPTIC_DRIVER_LIST                                 \
+   const _AL_DRIVER_INFO _al_haptic_driver_list[] =                  \
+   {
+
+#define _AL_END_HAPTIC_DRIVER_LIST                              \
+      {  0,                NULL,                false }         \
+   };
+
 
 
 #ifdef __cplusplus
-   }
+}
 #endif
 
 #endif
diff --git a/include/allegro5/internal/aintern_wjoydxnu.h b/include/allegro5/internal/aintern_wjoydxnu.h
new file mode 100644
index 0000000..5a0c7bb
--- /dev/null
+++ b/include/allegro5/internal/aintern_wjoydxnu.h
@@ -0,0 +1,84 @@
+#ifndef __al_included_allegro_aintern_wjoydxnu_h
+#define __al_included_allegro_aintern_wjoydxnu_h
+
+
+/** Part of the Windows DirectInput joystick 
+ * types are shared here for use by the haptic susbystem. */
+
+/* arbitrary limit to make life easier; this was the limit in Allegro 4.1.x */
+#define MAX_JOYSTICKS        8
+
+/* these limits are from DIJOYSTICK_STATE in dinput.h */
+#define MAX_SLIDERS          2
+#define MAX_POVS             4
+#define MAX_BUTTONS          32
+
+/* the number of joystick events that DirectInput is told to buffer */
+#define DEVICE_BUFFER_SIZE   10
+
+/* make sure all the constants add up */
+/* the first two sticks are (x,y,z) and (rx,ry,rz) */
+ALLEGRO_STATIC_ASSERT(wjoydxnu, _AL_MAX_JOYSTICK_STICKS >= (2 + MAX_SLIDERS + MAX_POVS));
+ALLEGRO_STATIC_ASSERT(wjoydxnu, _AL_MAX_JOYSTICK_BUTTONS >= MAX_BUTTONS);
+
+
+#define GUID_EQUAL(a, b)     (0 == memcmp(&(a), &(b), sizeof(GUID)))
+
+
+typedef enum {
+   STATE_UNUSED,
+   STATE_BORN,
+   STATE_ALIVE,
+   STATE_DYING
+} CONFIG_STATE;
+
+#define ACTIVE_STATE(st) \
+   ((st) == STATE_ALIVE || (st) == STATE_DYING)
+
+
+/* helper structure to record information through object_enum_callback */
+#define NAME_LEN     128
+
+typedef struct {
+   bool have_x;      char name_x[NAME_LEN];
+   bool have_y;      char name_y[NAME_LEN];
+   bool have_z;      char name_z[NAME_LEN];
+   bool have_rx;     char name_rx[NAME_LEN];
+   bool have_ry;     char name_ry[NAME_LEN];
+   bool have_rz;     char name_rz[NAME_LEN];
+   int num_sliders;  char name_slider[MAX_SLIDERS][NAME_LEN];
+   int num_povs;     char name_pov[MAX_POVS][NAME_LEN];
+   int num_buttons;  char name_button[MAX_BUTTONS][NAME_LEN];
+} CAPS_AND_NAMES;
+
+
+/* map a DirectInput axis to an Allegro (stick,axis) pair */
+typedef struct {
+   int stick, axis;
+} AXIS_MAPPING;
+
+
+typedef struct ALLEGRO_JOYSTICK_DIRECTX {
+   ALLEGRO_JOYSTICK parent;          /* must be first */
+   CONFIG_STATE config_state;
+   bool marked;
+   LPDIRECTINPUTDEVICE2 device;
+   GUID guid;
+   HANDLE waker_event;
+
+   ALLEGRO_JOYSTICK_STATE joystate;
+   AXIS_MAPPING x_mapping;
+   AXIS_MAPPING y_mapping;
+   AXIS_MAPPING z_mapping;
+   AXIS_MAPPING rx_mapping;
+   AXIS_MAPPING ry_mapping;
+   AXIS_MAPPING rz_mapping;
+   AXIS_MAPPING slider_mapping[MAX_SLIDERS];
+   int pov_mapping_stick[MAX_POVS];
+   char name[80];
+   char all_names[512]; /* button/stick/axis names with NUL terminators */
+} ALLEGRO_JOYSTICK_DIRECTX;
+
+#endif
+
+/* vim: set sts=3 sw=3 et: */
diff --git a/include/allegro5/platform/alwin.h b/include/allegro5/platform/alwin.h
index ed2a136..b699113 100644
--- a/include/allegro5/platform/alwin.h
+++ b/include/allegro5/platform/alwin.h
@@ -1,6 +1,6 @@
 /*         ______   ___    ___
  *        /\  _  \ /\_ \  /\_ \
- *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___ 
+ *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___
  *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
  *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
  *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
@@ -62,3 +62,25 @@ AL_VAR(struct ALLEGRO_JOYSTICK_DRIVER, _al_joydrv_directx);
 #define _AL_JOYSTICK_DRIVER_DIRECTX                                     \
    { AL_JOY_TYPE_DIRECTX,  &_al_joydrv_directx,    true  },
 
+
+/*******************************************/
+/************ haptic drivers   *************/
+/*******************************************/
+
+#define AL_HAPTIC_TYPE_DIRECTX   AL_ID('D','X','H','D')
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+AL_VAR(struct ALLEGRO_HAPTIC_DRIVER, _al_hapdrv_directx);
+
+#ifdef __cplusplus
+}
+#endif
+
+#define _AL_HAPTIC_DRIVER_DIRECTX                                     \
+   { AL_HAPTIC_TYPE_DIRECTX,  &_al_hapdrv_directx,    true  },
+
+
+
diff --git a/src/haptic.c b/src/haptic.c
index d9c7922..c89c261 100644
--- a/src/haptic.c
+++ b/src/haptic.c
@@ -199,6 +199,12 @@ int al_get_haptic_capabilities(ALLEGRO_HAPTIC *hap)
    return haptic_driver->get_capabilities(hap);
 }
 
+/* Function: al_is_haptic_capable
+ */ 
+bool al_is_haptic_capable(ALLEGRO_HAPTIC * hap, int query) {
+  int capabilities = al_get_haptic_capabilities(hap);
+  return (capabilities & query) == query;
+}
 
 /* Function: al_get_haptic_gain
  */
@@ -221,6 +227,28 @@ bool al_set_haptic_gain(ALLEGRO_HAPTIC *hap, double gain)
    return haptic_driver->set_gain(hap, gain);
 }
 
+/* Function: al_get_haptic_autocenter
+ */
+double al_get_haptic_autocenter(ALLEGRO_HAPTIC *hap)
+{
+   ASSERT(hap);
+   ASSERT(haptic_driver);
+
+   return haptic_driver->get_autocenter(hap);
+}
+
+
+/* Function: al_set_haptic_autocenter
+ */
+bool al_set_haptic_autocenter(ALLEGRO_HAPTIC *hap, double intensity)
+{
+   ASSERT(hap);
+   ASSERT(haptic_driver);
+   
+   return haptic_driver->set_autocenter(hap, intensity);
+}
+
+
 
 /* Function: al_get_num_haptic_effects
  */
@@ -278,7 +306,13 @@ bool al_upload_and_play_haptic_effect(ALLEGRO_HAPTIC *hap,
 
    if (!al_upload_haptic_effect(hap, effect, id))
       return false;
-   return al_play_haptic_effect(id, loop);
+   /* If playing the effect failed, unload the haptic effect automatically 
+    */
+   if (!al_play_haptic_effect(id, loop)) {
+     al_release_haptic_effect(id);
+     return false;
+   }
+   return true;
 }
 
 
@@ -301,6 +335,12 @@ bool al_is_haptic_effect_playing(ALLEGRO_HAPTIC_EFFECT_ID *id)
    return haptic_driver->is_effect_playing(id);
 }
 
+/* Function: al_get_haptic_effect_duration
+ */
+double al_get_haptic_effect_duration(ALLEGRO_HAPTIC_EFFECT * effect)
+{
+  return effect->replay.delay + effect->replay.length;
+}
 
 /* Function: al_rumble_haptic
  */
diff --git a/src/linux/lhaptic.c b/src/linux/lhaptic.c
index 88a252a..d409e8d 100644
--- a/src/linux/lhaptic.c
+++ b/src/linux/lhaptic.c
@@ -89,6 +89,8 @@ static bool lhap_stop_effect(ALLEGRO_HAPTIC_EFFECT_ID *id);
 static bool lhap_is_effect_playing(ALLEGRO_HAPTIC_EFFECT_ID *id);
 static bool lhap_release_effect(ALLEGRO_HAPTIC_EFFECT_ID *id);
 
+static double lhap_get_autocenter(ALLEGRO_HAPTIC *dev);
+static bool lhap_set_autocenter(ALLEGRO_HAPTIC *dev, double);
 
 ALLEGRO_HAPTIC_DRIVER _al_hapdrv_linux =
 {
@@ -124,7 +126,10 @@ ALLEGRO_HAPTIC_DRIVER _al_hapdrv_linux =
    lhap_is_effect_playing,
    lhap_release_effect,
 
-   lhap_release
+   lhap_release,
+   
+   lhap_get_autocenter,
+   lhap_set_autocenter
 };
 
 
@@ -153,6 +158,7 @@ static const struct CAP_MAP cap_map[] = {
    { FF_SAW_DOWN, ALLEGRO_HAPTIC_SAW_DOWN },
    { FF_CUSTOM,   ALLEGRO_HAPTIC_CUSTOM },
    { FF_GAIN,     ALLEGRO_HAPTIC_GAIN },
+   { FF_AUTOCENTER, ALLEGRO_HAPTIC_AUTOCENTER },
    { -1,          -1 }
 };
 
@@ -598,7 +604,11 @@ static double lhap_get_gain(ALLEGRO_HAPTIC *dev)
 {
    ALLEGRO_HAPTIC_LINUX *lhap = lhap_from_al(dev);
    (void)dev;
-
+   
+   if(!al_is_haptic_capable(dev, ALLEGRO_HAPTIC_GAIN)) { 
+     return 0.0;  
+   } 
+   
    /* Unfortunately there seems to be no API to GET gain, only to set?!
     * So, return the stored gain.
     */
@@ -623,6 +633,37 @@ static bool lhap_set_gain(ALLEGRO_HAPTIC *dev, double gain)
 }
 
 
+static bool lhap_set_autocenter(ALLEGRO_HAPTIC *dev, double autocenter)
+{
+   ALLEGRO_HAPTIC_LINUX *lhap = lhap_from_al(dev);
+   struct input_event ie;
+
+   lhap->parent.autocenter = autocenter;
+   timerclear(&ie.time);
+   ie.type = EV_FF;
+   ie.code = FF_AUTOCENTER;
+   ie.value = (__s32) ((double)0xFFFF * autocenter);
+   if (write(lhap->fd, &ie, sizeof(ie)) < 0) {
+      return false;
+   }
+   return true;
+}
+
+static double lhap_get_autocenter(ALLEGRO_HAPTIC *dev)
+{
+   ALLEGRO_HAPTIC_LINUX *lhap = lhap_from_al(dev);
+   (void)dev;
+   
+   if(!al_is_haptic_capable(dev, ALLEGRO_HAPTIC_AUTOCENTER)) { 
+     return 0.0;
+   }
+
+   /* Unfortunately there seems to be no API to GET gain, only to set?!
+    * So, return the stored autocenter.
+    */
+   return lhap->parent.autocenter;
+}
+
 int lhap_get_num_effects(ALLEGRO_HAPTIC *dev)
 {
    ALLEGRO_HAPTIC_LINUX *lhap = lhap_from_al(dev);
@@ -654,12 +695,6 @@ static bool lhap_is_effect_ok(ALLEGRO_HAPTIC *haptic,
 }
 
 
-static double lhap_effect_duration(ALLEGRO_HAPTIC_EFFECT *effect)
-{
-   return effect->replay.delay + effect->replay.length;
-}
-
-
 static bool lhap_upload_effect(ALLEGRO_HAPTIC *dev,
    ALLEGRO_HAPTIC_EFFECT *effect, ALLEGRO_HAPTIC_EFFECT_ID *id)
 {
@@ -707,7 +742,7 @@ static bool lhap_upload_effect(ALLEGRO_HAPTIC *dev,
    id->_haptic = dev;
    id->_id = found;
    id->_handle = leff.id;
-   id->_effect_duration = lhap_effect_duration(effect);
+   id->_effect_duration = al_get_haptic_effect_duration(effect);
    id->_playing = false;
 
    /* XXX should be bool or something? */
diff --git a/src/unix/uhapdrv.c b/src/unix/uhapdrv.c
new file mode 100644
index 0000000..eae23e6
--- /dev/null
+++ b/src/unix/uhapdrv.c
@@ -0,0 +1,30 @@
+/*         ______   ___    ___ 
+ *        /\  _  \ /\_ \  /\_ \ 
+ *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___ 
+ *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
+ *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
+ *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
+ *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
+ *                                           /\____/
+ *                                           \_/__/
+ *
+ *      List of Unix haptic drivers.
+ *
+ *      By Beoran.
+ *
+ *      See readme.txt for copyright information.
+ */
+
+
+#include "allegro5/allegro.h"
+#include "allegro5/platform/aintunix.h"
+#include "allegro5/internal/aintern.h"
+#include "allegro5/internal/aintern_haptic.h"
+
+
+
+_AL_BEGIN_HAPTIC_DRIVER_LIST
+#if defined ALLEGRO_HAVE_LINUX_INPUT_H && (defined ALLEGRO_WITH_XWINDOWS || defined ALLEGRO_RASPBERRYPI)
+   { _ALLEGRO_HAPDRV_LINUX,   &_al_hapdrv_linux,   true  },
+#endif
+_AL_END_HAPTIC_DRIVER_LIST
diff --git a/src/win/whaptic.c b/src/win/whaptic.c
new file mode 100644
index 0000000..7d803a0
--- /dev/null
+++ b/src/win/whaptic.c
@@ -0,0 +1,31 @@
+/*         ______   ___    ___
+ *        /\  _  \ /\_ \  /\_ \
+ *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___
+ *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
+ *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
+ *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
+ *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
+ *                                           /\____/
+ *                                           \_/__/
+ *
+ *      List of Windows joystick drivers, kept in a seperate file so that
+ *      they can be overriden by user programs.
+ *
+ *      By Shawn Hargreaves.
+ *
+ *      See readme.txt for copyright information.
+ */
+
+
+#include "allegro5/allegro.h"
+#include "allegro5/internal/aintern.h"
+#include "allegro5/internal/aintern_haptic.h"
+
+#ifndef ALLEGRO_WINDOWS
+#error something is wrong with the makefile
+#endif
+
+/* Construct the DirectInput haptics driver list */
+_AL_BEGIN_HAPTIC_DRIVER_LIST
+_AL_HAPTIC_DRIVER_DIRECTX
+_AL_END_HAPTIC_DRIVER_LIST
diff --git a/src/win/whaptic.cpp b/src/win/whaptic.cpp
new file mode 100644
index 0000000..6981008
--- /dev/null
+++ b/src/win/whaptic.cpp
@@ -0,0 +1,1438 @@
+/*         ______   ___    ___
+ *        /\  _  \ /\_ \  /\_ \
+ *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___
+ *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
+ *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
+ *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
+ *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
+ *                                           /\____/
+ *                                           \_/__/
+ *
+ *      Windows haptic (force-feedback) device driver.
+ *
+ *      By Beoran.
+ *
+ *      See LICENSE.txt for copyright information.
+ */
+
+
+
+#define ALLEGRO_NO_COMPATIBILITY
+
+#define DIRECTINPUT_VERSION 0x0800
+
+/* For waitable timers */
+#define _WIN32_WINNT 0x400
+
+#include "allegro5/allegro.h"
+#include "allegro5/internal/aintern.h"
+#include "allegro5/platform/aintwin.h"
+#include "allegro5/internal/aintern_haptic.h"
+#include "allegro5/internal/aintern_events.h"
+#include "allegro5/internal/aintern_joystick.h"
+#include "allegro5/internal/aintern_bitmap.h"
+
+#ifndef ALLEGRO_WINDOWS
+#error something is wrong with the makefile
+#endif
+
+#ifdef ALLEGRO_MINGW32
+#undef MAKEFOURCC
+#endif
+
+#include <initguid.h>
+#include <stdio.h>
+#include <mmsystem.h>
+#include <process.h>
+#include <math.h>
+#include <dinput.h>
+/* #include <sys/time.h> */
+
+#include "allegro5/internal/aintern_wjoydxnu.h"
+
+ALLEGRO_DEBUG_CHANNEL("whaptic")
+
+/* Support at most 32 haptic devices. */
+#define HAPTICS_MAX             32
+/* Support at most 16 effects per device. */
+#define HAPTICS_EFFECTS_MAX     16
+/* Support at most 3 axes per device. */
+#define HAPTICS_AXES_MAX        3
+
+/** This union is needed to avoid
+ dynamical memory allocation. */
+typedef union
+{
+   DICONSTANTFORCE constant;
+   DIRAMPFORCE ramp;
+   DIPERIODIC periodic;
+   DICONDITION condition;
+   DICUSTOMFORCE custom;
+} ALLEGRO_HAPTIC_PARAMETER_WINDOWS;
+
+
+/*
+ * Haptic effect system data.
+ */
+typedef struct
+{
+   int id;
+   bool active;
+   DIEFFECT effect;
+   DIENVELOPE envelope;
+   LPDIRECTINPUTEFFECT ref;
+   DWORD axes[HAPTICS_AXES_MAX];
+   LONG directions[HAPTICS_AXES_MAX];
+   ALLEGRO_HAPTIC_PARAMETER_WINDOWS parameter;
+   const GUID *guid;
+} ALLEGRO_HAPTIC_EFFECT_WINDOWS;
+
+
+
+typedef struct
+{
+   struct ALLEGRO_HAPTIC parent;        /* must be first */
+   bool active;
+   LPDIRECTINPUTDEVICE2 device;
+   GUID guid;
+   DIDEVICEINSTANCE instance;
+   DIDEVCAPS capabilities;
+   LPDIRECTINPUTDEVICE8 device8;
+   ALLEGRO_DISPLAY_WIN *display;
+   int flags;
+   ALLEGRO_HAPTIC_EFFECT_WINDOWS effects[HAPTICS_EFFECTS_MAX];
+   DWORD axes[HAPTICS_AXES_MAX];
+   int naxes;
+} ALLEGRO_HAPTIC_WINDOWS;
+
+
+#define LONG_BITS    (sizeof(long) * 8)
+#define NLONGS(x)    (((x) + LONG_BITS - 1) / LONG_BITS)
+/* Tests if a bit in an array of longs is set. */
+#define TEST_BIT(nr, addr) \
+   ((1UL << ((nr) % LONG_BITS)) & (addr)[(nr) / LONG_BITS])
+
+
+/* forward declarations */
+static bool whap_init_haptic(void);
+static void whap_exit_haptic(void);
+
+static bool whap_is_mouse_haptic(ALLEGRO_MOUSE * dev);
+static bool whap_is_joystick_haptic(ALLEGRO_JOYSTICK *);
+static bool whap_is_keyboard_haptic(ALLEGRO_KEYBOARD * dev);
+static bool whap_is_display_haptic(ALLEGRO_DISPLAY * dev);
+static bool whap_is_touch_input_haptic(ALLEGRO_TOUCH_INPUT * dev);
+
+static ALLEGRO_HAPTIC *whap_get_from_mouse(ALLEGRO_MOUSE * dev);
+static ALLEGRO_HAPTIC *whap_get_from_joystick(ALLEGRO_JOYSTICK * dev);
+static ALLEGRO_HAPTIC *whap_get_from_keyboard(ALLEGRO_KEYBOARD * dev);
+static ALLEGRO_HAPTIC *whap_get_from_display(ALLEGRO_DISPLAY * dev);
+static ALLEGRO_HAPTIC *whap_get_from_touch_input(ALLEGRO_TOUCH_INPUT * dev);
+
+static bool whap_release(ALLEGRO_HAPTIC * haptic);
+
+static bool whap_get_active(ALLEGRO_HAPTIC * hap);
+static int whap_get_capabilities(ALLEGRO_HAPTIC * dev);
+static double whap_get_gain(ALLEGRO_HAPTIC * dev);
+static bool whap_set_gain(ALLEGRO_HAPTIC * dev, double);
+static int whap_get_num_effects(ALLEGRO_HAPTIC * dev);
+
+static bool whap_is_effect_ok(ALLEGRO_HAPTIC * dev,
+                              ALLEGRO_HAPTIC_EFFECT * eff);
+static bool whap_upload_effect(ALLEGRO_HAPTIC * dev,
+                               ALLEGRO_HAPTIC_EFFECT * eff,
+                               ALLEGRO_HAPTIC_EFFECT_ID * id);
+static bool whap_play_effect(ALLEGRO_HAPTIC_EFFECT_ID * id, int loop);
+static bool whap_stop_effect(ALLEGRO_HAPTIC_EFFECT_ID * id);
+static bool whap_is_effect_playing(ALLEGRO_HAPTIC_EFFECT_ID * id);
+static bool whap_release_effect(ALLEGRO_HAPTIC_EFFECT_ID * id);
+
+static double whap_get_autocenter(ALLEGRO_HAPTIC * dev);
+static bool whap_set_autocenter(ALLEGRO_HAPTIC * dev, double);
+
+ALLEGRO_HAPTIC_DRIVER _al_hapdrv_directx = {
+   AL_HAPTIC_TYPE_DIRECTX,
+   "",
+   "",
+   "Windows haptic(s)",
+   whap_init_haptic,
+   whap_exit_haptic,
+
+   whap_is_mouse_haptic,
+   whap_is_joystick_haptic,
+   whap_is_keyboard_haptic,
+   whap_is_display_haptic,
+   whap_is_touch_input_haptic,
+
+   whap_get_from_mouse,
+   whap_get_from_joystick,
+   whap_get_from_keyboard,
+   whap_get_from_display,
+   whap_get_from_touch_input,
+
+   whap_get_active,
+   whap_get_capabilities,
+   whap_get_gain,
+   whap_set_gain,
+   whap_get_num_effects,
+
+   whap_is_effect_ok,
+   whap_upload_effect,
+   whap_play_effect,
+   whap_stop_effect,
+   whap_is_effect_playing,
+   whap_release_effect,
+
+   whap_release,
+
+   whap_get_autocenter,
+   whap_set_autocenter
+};
+
+
+static ALLEGRO_HAPTIC_WINDOWS haptics[HAPTICS_MAX];
+static ALLEGRO_MUTEX *haptic_mutex = NULL;
+
+/* Capability map between directinput effects and allegro effect types. */
+struct CAP_MAP
+{
+   GUID guid;
+   int allegro_bit;
+};
+
+/* GUID values are borrowed from Wine */
+#define DEFINE_PRIVATE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
+   static const GUID name = { l, w1, w2, { b1, b2,  b3,  b4,  b5,  b6,  b7,  b8 } }
+
+DEFINE_PRIVATE_GUID(_al_GUID_None, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+
+
+static const struct CAP_MAP cap_map[] = {
+   {GUID_ConstantForce, ALLEGRO_HAPTIC_CONSTANT},
+   {GUID_Spring, ALLEGRO_HAPTIC_SPRING},
+   {GUID_Spring, ALLEGRO_HAPTIC_FRICTION},
+   {GUID_Damper, ALLEGRO_HAPTIC_DAMPER},
+   {GUID_Inertia, ALLEGRO_HAPTIC_INERTIA},
+   {GUID_RampForce, ALLEGRO_HAPTIC_RAMP},
+   {GUID_Square, ALLEGRO_HAPTIC_SQUARE},
+   {GUID_Triangle, ALLEGRO_HAPTIC_TRIANGLE},
+   {GUID_Sine, ALLEGRO_HAPTIC_SINE},
+   {GUID_SawtoothUp, ALLEGRO_HAPTIC_SAW_UP},
+   {GUID_SawtoothDown, ALLEGRO_HAPTIC_SAW_DOWN},
+   {GUID_CustomForce, ALLEGRO_HAPTIC_CUSTOM},
+   /*{ { _al_GUID_None    },      -1 } */
+};
+
+
+static bool whap_init_haptic(void)
+{
+   int i;
+
+   ASSERT(haptic_mutex == NULL);
+   haptic_mutex = al_create_mutex();
+   if (!haptic_mutex)
+      return false;
+
+   for (i = 0; i < HAPTICS_MAX; i++) {
+      haptics[i].active = false;
+   }
+
+   return true;
+}
+
+
+static ALLEGRO_HAPTIC_WINDOWS *whap_get_available_haptic(void)
+{
+   int i;
+
+   for (i = 0; i < HAPTICS_MAX; i++) {
+      if (!haptics[i].active) {
+         haptics[i].active = true;
+         return &haptics[i];
+      }
+   }
+
+   return NULL;
+}
+
+ /* Look for a free haptic effect slot for a device and return it,
+  * or NULL if exhausted. Also initializes the effect
+  * reference to NULL. */
+static ALLEGRO_HAPTIC_EFFECT_WINDOWS
+    *whap_get_available_effect(ALLEGRO_HAPTIC_WINDOWS * whap)
+{
+   ALLEGRO_HAPTIC_EFFECT_WINDOWS *weff;
+   int i;
+   for (i = 0; i < al_get_num_haptic_effects(&whap->parent); i++) {
+      if (!whap->effects[i].active) {
+         weff = whap->effects + i;
+         weff->id = i;
+         weff->active = true;
+         weff->ref = NULL;
+         return weff;
+      }
+   }
+   return NULL;
+}
+
+
+/* Releases a windows haptics effect and unloads it from the device. */
+static bool whap_release_effect_windows(ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff)
+{
+   bool result = true;
+   if (!weff)
+      return false;             /* make it easy to handle all cases later on. */
+   if (!weff->active)
+      return false;             /* already not in use, bail out. */
+
+   /* Unload the effect from the device. */
+   if (weff->ref) {
+      HRESULT ret;
+      ret = IDirectInputEffect_Unload(weff->ref);
+      if (FAILED(ret)) {
+         ALLEGRO_WARN("Could not unload effect.");
+         result = false;
+      }
+   }
+   /* Custom force needs to clean up it's data. */
+   if (weff->guid == &GUID_CustomForce) {
+      al_free(weff->parameter.custom.rglForceData);
+      weff->parameter.custom.rglForceData = NULL;
+   }
+   weff->active = false;        /* not in use */
+   weff->ref = NULL;            /* No reference to effect anymore. */
+   return result;
+}
+
+
+/* Converts a generic haptic device to a Windows-specific one. */
+static ALLEGRO_HAPTIC_WINDOWS *whap_from_al(ALLEGRO_HAPTIC * hap)
+{
+   return (ALLEGRO_HAPTIC_WINDOWS *) hap;
+}
+
+static void whap_exit_haptic(void)
+{
+   ASSERT(haptic_mutex);
+   al_destroy_mutex(haptic_mutex);
+   haptic_mutex = NULL;
+}
+
+/* Convert the type of the periodic allegro effect to the windows effect*/
+static bool whap_periodictype2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff,
+                                  ALLEGRO_HAPTIC_EFFECT * effect)
+{
+   switch (effect->data.periodic.waveform) {
+      case ALLEGRO_HAPTIC_SINE:
+         weff->guid = &GUID_Sine;
+         return true;
+
+      case ALLEGRO_HAPTIC_SQUARE:
+         weff->guid = &GUID_Square;
+         return true;
+
+      case ALLEGRO_HAPTIC_TRIANGLE:
+         weff->guid = &GUID_Triangle;
+         return true;
+
+      case ALLEGRO_HAPTIC_SAW_UP:
+         weff->guid = &GUID_SawtoothUp;
+         return true;
+
+      case ALLEGRO_HAPTIC_SAW_DOWN:
+         weff->guid = &GUID_SawtoothDown;
+         return true;
+
+      case ALLEGRO_HAPTIC_CUSTOM:
+         weff->guid = &GUID_CustomForce;
+         return true;
+      default:
+         return false;
+   }
+}
+
+
+
+/* Convert the type of the allegro effect to the windows effect*/
+static bool whap_type2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff,
+                          ALLEGRO_HAPTIC_EFFECT * effect)
+{
+   switch (effect->type) {
+      case ALLEGRO_HAPTIC_RUMBLE:
+         weff->guid = &GUID_Sine;
+         return true;
+      case ALLEGRO_HAPTIC_PERIODIC:
+         return whap_periodictype2win(weff, effect);
+      case ALLEGRO_HAPTIC_CONSTANT:
+         weff->guid = &GUID_ConstantForce;
+         return true;
+      case ALLEGRO_HAPTIC_SPRING:
+         weff->guid = &GUID_Spring;
+         return true;
+      case ALLEGRO_HAPTIC_FRICTION:
+         weff->guid = &GUID_Friction;
+         return true;
+      case ALLEGRO_HAPTIC_DAMPER:
+         weff->guid = &GUID_Damper;
+         return true;
+      case ALLEGRO_HAPTIC_INERTIA:
+         weff->guid = &GUID_Inertia;
+         return true;
+      case ALLEGRO_HAPTIC_RAMP:
+         weff->guid = &GUID_RampForce;
+         return true;
+      default:
+         return NULL;
+   }
+   return true;
+}
+
+/* Convert the direction of the allegro effect to the windows effect*/
+static bool whap_direction2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff,
+                               ALLEGRO_HAPTIC_EFFECT * effect,
+                               ALLEGRO_HAPTIC_WINDOWS * whap)
+{
+   unsigned int index;
+   double calc_x, calc_y;
+
+   /* It seems that (at least for sine motions), 1 or 2 axes work, but 0 or 3 don't
+      for my test joystick. Also, while polar or spherical coordinates are accepted,
+      they don't cause any vibration for a periodic effect. All in
+      all it seems that cartesian is the only well-supported axis system,
+      at least for periodic effects. Need more tests with other joysticks to see
+      their behavior.
+      Annoyingly there seems to be no way to discover
+      what kind of axis system is supported without trying to upload the
+      effect... Hence, use a 1 or 2 axis cartesian system and hope for the
+      best for non-periodic effects.
+    */
+
+
+   /* Use CARTESIAN coordinates since those seem to be the only well suppported
+      ones. */
+   weff->effect.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
+   /* Prepare axes. If whap naxes is > 2, use 2 because more than 2
+      axes isn't always supported. */
+   weff->effect.cAxes = whap->naxes;
+   if (weff->effect.cAxes > 2) {
+      weff->effect.cAxes = 2;
+   }
+   memset((void *)weff->axes, 0, sizeof(weff->axes));
+   for (index = 0; index < weff->effect.cAxes; index++) {
+      weff->axes[index] = whap->axes[index];
+   }
+   weff->effect.rgdwAxes = weff->axes;
+   /* Set up directions as well.. */
+   memset((void *)weff->directions, 0, sizeof(weff->directions));
+   /* Calculate the X and Y coordinates of the effect based on the angle.
+      That is map angular coordinates to cartesian ones. */
+   calc_x =
+       sin(effect->direction.angle) * effect->direction.radius *
+       DI_FFNOMINALMAX;
+   calc_y =
+       cos(effect->direction.angle) * effect->direction.radius *
+       DI_FFNOMINALMAX;
+
+   /* Set X if there is 1 axis and also y if there are more .
+    */
+   if (weff->effect.cAxes > 1) {
+      weff->directions[0] = (long)calc_x;
+   }
+   if (whap->naxes > 2) {
+      weff->directions[1] = (long)calc_y;
+   }
+   weff->effect.rglDirection = weff->directions;
+   return true;
+}
+
+/* Converts the time in seconds to a Windows-compatible time.
+ * Return false if out of bounds.
+ */
+static bool whap_time2win(DWORD * res, double sec)
+{
+   ASSERT(res);
+
+   if (sec < 0.0 || sec >= 4294.967296)
+      return false;
+   (*res) = (DWORD) floor(sec * DI_SECONDS);
+   return true;
+}
+
+/* Converts the level in range 0.0 to 1.0 to a Windows-compatible level.
+ * Returns false if out of bounds.
+ */
+static bool whap_level2win(DWORD * res, double level)
+{
+   ASSERT(res);
+
+   if (level < 0.0 || level > 1.0)
+      return false;
+   *res = (DWORD) floor(level * DI_FFNOMINALMAX);
+   return true;
+}
+
+
+/* Converts the level in range -1.0 to 1.0 to a Windows-compatible level.
+ * Returns false if out of bounds.
+ */
+static bool whap_slevel2win(LONG * res, double level)
+{
+   ASSERT(res);
+
+   if (level < -1.0 || level > 1.0)
+      return false;
+   *res = (LONG) (level * DI_FFNOMINALMAX);
+   return true;
+}
+
+/* Converts a phase in range 0.0 to 1.0 to a Windows-compatible level.
+ * Returns false if out of bounds.
+ */
+static bool whap_phase2win(DWORD * res, double phase)
+{
+   ASSERT(res);
+
+   if (phase < 0.0 || phase > 1.0)
+      return false;
+   *res = (DWORD) (phase * 35999);
+   return true;
+}
+
+
+/* Converts replay data to Widows-compatible data. */
+static bool whap_replay2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff,
+                            ALLEGRO_HAPTIC_EFFECT * effect)
+{
+   return whap_time2win(&weff->effect.dwStartDelay, effect->replay.delay)
+       && whap_time2win(&weff->effect.dwDuration, effect->replay.length);
+}
+
+
+/* Converts an Allegro haptic effect envelope to DirectInput API. */
+static bool whap_envelope2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff,
+                              ALLEGRO_HAPTIC_ENVELOPE * aenv)
+{
+   /* Prepare envelope. */
+   DIENVELOPE *wenv = &weff->envelope;
+
+   /* Do not set any envelope if all values are 0.0  */
+   if ((aenv->attack_length == 0.0) &&
+       (aenv->fade_length == 0.0) &&
+       (aenv->attack_level == 0.0) && (aenv->fade_level == 0.0)) {
+      return true;
+   }
+
+   /* Prepare the envelope. */
+   memset((void *)wenv, 0, sizeof(DIENVELOPE));
+   weff->envelope.dwSize = sizeof(DIENVELOPE);
+   weff->effect.lpEnvelope = wenv;
+
+   /* Set the values. */
+   return whap_time2win(&wenv->dwAttackTime, aenv->attack_length)
+       && whap_time2win(&wenv->dwFadeTime, aenv->fade_length)
+       && whap_level2win(&wenv->dwAttackLevel, aenv->attack_level)
+       && whap_level2win(&wenv->dwFadeLevel, aenv->fade_level);
+}
+
+/* Converts a constant effect to directinput API. */
+static bool whap_constant2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff,
+                              ALLEGRO_HAPTIC_EFFECT * effect)
+{
+   weff->effect.cbTypeSpecificParams = sizeof(weff->parameter.constant);
+   weff->effect.lpvTypeSpecificParams = &weff->parameter.constant;
+   return whap_envelope2win(weff, &effect->data.constant.envelope)
+       && whap_slevel2win(&weff->parameter.constant.lMagnitude,
+                          effect->data.constant.level);
+}
+
+
+/* Converts a ramp effect to directinput input API. */
+static bool whap_ramp2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff,
+                          ALLEGRO_HAPTIC_EFFECT * effect)
+{
+   weff->effect.cbTypeSpecificParams = sizeof(weff->parameter.ramp);
+   weff->effect.lpvTypeSpecificParams = &weff->parameter.ramp;
+
+   return whap_envelope2win(weff, &effect->data.ramp.envelope)
+       && whap_slevel2win(&weff->parameter.ramp.lStart,
+                          effect->data.ramp.start_level)
+       && whap_slevel2win(&weff->parameter.ramp.lEnd,
+                          effect->data.ramp.end_level);
+}
+
+/* Converts a condition effect to directinput input API. */
+static bool whap_condition2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff,
+                               ALLEGRO_HAPTIC_EFFECT * effect)
+{
+   weff->effect.cbTypeSpecificParams = sizeof(weff->parameter.condition);
+   weff->effect.lpvTypeSpecificParams = &weff->parameter.condition;
+   /* XXX: no envelope here ???  */
+
+   return whap_level2win(&weff->parameter.condition.dwNegativeSaturation,
+                         effect->data.condition.left_saturation)
+       && whap_level2win(&weff->parameter.condition.dwPositiveSaturation,
+                         effect->data.condition.right_saturation)
+       && whap_slevel2win(&weff->parameter.condition.lNegativeCoefficient,
+                          effect->data.condition.left_coeff)
+       && whap_slevel2win(&weff->parameter.condition.lPositiveCoefficient,
+                          effect->data.condition.right_coeff)
+       && whap_slevel2win(&weff->parameter.condition.lDeadBand,
+                          effect->data.condition.deadband)
+       && whap_slevel2win(&weff->parameter.condition.lOffset,
+                          effect->data.condition.center);
+}
+
+/* Converts a custom effect to directinput input API. */
+static bool whap_custom2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff,
+                            ALLEGRO_HAPTIC_EFFECT * effect)
+{
+   int index;
+   weff->effect.cbTypeSpecificParams = sizeof(weff->parameter.custom);
+   weff->effect.lpvTypeSpecificParams = &weff->parameter.custom;
+   weff->parameter.custom.cChannels = 1;
+   weff->parameter.custom.cSamples = effect->data.periodic.custom_len;
+   /* Use al malloc only in this case since the custom_data can be arbitrarily long. */
+   weff->parameter.custom.rglForceData =
+       (LONG *) al_malloc(sizeof(LONG) * effect->data.periodic.custom_len);
+   if (!weff->parameter.custom.rglForceData)
+      return false;
+   /* Gotta copy this to long values, and scale them too... */
+   for (index = 0; index < effect->data.periodic.custom_len; index++) {
+      weff->parameter.custom.rglForceData[index] =
+          (LONG) (effect->data.periodic.custom_data[index] *
+                  ((double)(1 << 31)));
+   }
+   return true;
+}
+
+
+/* Converts a periodic effect to directinput input API. */
+static bool whap_periodic2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff,
+                              ALLEGRO_HAPTIC_EFFECT * effect)
+{
+   if (effect->data.periodic.waveform == ALLEGRO_HAPTIC_CUSTOM) {
+      return whap_custom2win(weff, effect);
+   }
+
+   weff->effect.cbTypeSpecificParams = sizeof(weff->parameter.periodic);
+   weff->effect.lpvTypeSpecificParams = &weff->parameter.periodic;
+
+   return whap_envelope2win(weff, &effect->data.periodic.envelope)
+       && whap_level2win(&weff->parameter.periodic.dwMagnitude,
+                         effect->data.periodic.magnitude)
+       && whap_phase2win(&weff->parameter.periodic.dwPhase,
+                         effect->data.periodic.phase)
+       && whap_time2win(&weff->parameter.periodic.dwPeriod,
+                        effect->data.periodic.period)
+       && whap_slevel2win(&weff->parameter.periodic.lOffset,
+                          effect->data.periodic.offset);
+}
+
+
+/* Converts a periodic effect to directinput input API. */
+static bool whap_rumble2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff,
+                            ALLEGRO_HAPTIC_EFFECT * effect)
+{
+   weff->effect.cbTypeSpecificParams = sizeof(weff->parameter.periodic);
+   weff->effect.lpvTypeSpecificParams = &weff->parameter.periodic;
+
+   return whap_level2win(&weff->parameter.periodic.dwMagnitude,
+                         effect->data.rumble.strong_magnitude)
+       && whap_phase2win(&weff->parameter.periodic.dwPhase, 0)
+       && whap_time2win(&weff->parameter.periodic.dwPeriod, 0.01)
+       && whap_slevel2win(&weff->parameter.periodic.lOffset, 0);
+}
+
+/* Converts Allegro haptic effect to dinput API. */
+static bool whap_effect2win(ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff,
+                            ALLEGRO_HAPTIC_EFFECT * effect,
+                            ALLEGRO_HAPTIC_WINDOWS * whap)
+{
+   /* Generic setup */
+   memset((void *)weff, 0, sizeof(*weff));
+   /* Set global stuff. */
+   weff->effect.dwSize = sizeof(DIEFFECT);
+   weff->effect.dwGain = DI_FFNOMINALMAX;
+   weff->effect.dwSamplePeriod = 0;
+   weff->effect.dwFlags = DIEFF_OBJECTOFFSETS;
+   weff->effect.lpEnvelope = NULL;
+   /* Gain of the effect must be set to max, otherwise it won't be felt
+      (enough) as the per effect gain multiplies with the per-device gain. */
+   weff->effect.dwGain = DI_FFNOMINALMAX;
+   /* This effect is not mapped to a trigger, and must be played explicitly. */
+   weff->effect.dwTriggerButton = DIEB_NOTRIGGER;
+
+   if (!whap_type2win(weff, effect)) {
+      return false;
+   }
+
+   if (!whap_direction2win(weff, effect, whap)) {
+      return false;
+   }
+
+   if (!whap_replay2win(weff, effect)) {
+      return false;
+   }
+
+
+   switch (effect->type) {
+      case ALLEGRO_HAPTIC_RUMBLE:
+         return whap_rumble2win(weff, effect);
+      case ALLEGRO_HAPTIC_PERIODIC:
+         return whap_periodic2win(weff, effect);
+      case ALLEGRO_HAPTIC_CONSTANT:
+         return whap_constant2win(weff, effect);
+      case ALLEGRO_HAPTIC_RAMP:
+         return whap_ramp2win(weff, effect);
+      case ALLEGRO_HAPTIC_SPRING:
+      case ALLEGRO_HAPTIC_FRICTION:
+      case ALLEGRO_HAPTIC_DAMPER:
+      case ALLEGRO_HAPTIC_INERTIA:
+         return whap_condition2win(weff, effect);
+      default:
+         return false;
+   }
+}
+
+static bool whap_get_active(ALLEGRO_HAPTIC * haptic)
+{
+   ALLEGRO_HAPTIC_WINDOWS *whap = whap_from_al(haptic);
+   return whap->active;
+}
+
+
+static bool whap_is_dinput_device_haptic(LPDIRECTINPUTDEVICE2 device)
+{
+   HRESULT ret;
+   DIDEVCAPS dicaps;
+   /* Get capabilities. */
+   ALLEGRO_DEBUG("IDirectInputDevice_GetCapabilities on %p\n", device);
+   dicaps.dwSize = sizeof(dicaps);
+   ret = IDirectInputDevice_GetCapabilities(device, &dicaps);
+   if (FAILED(ret)) {
+      ALLEGRO_ERROR("IDirectInputDevice_GetCapabilities failed on %p\n",
+                    device);
+      return false;
+   }
+   /** Is it a haptic device? */
+   bool ishaptic = (dicaps.dwFlags & DIDC_FORCEFEEDBACK);
+   ALLEGRO_DEBUG("dicaps.dwFlags: %lu, %d, %d\n", dicaps.dwFlags,
+                 DIDC_FORCEFEEDBACK, ishaptic);
+   return (ishaptic);
+}
+
+
+
+static bool whap_is_mouse_haptic(ALLEGRO_MOUSE * mouse)
+{
+   (void)mouse;
+   return false;
+}
+
+
+static bool whap_is_joystick_haptic(ALLEGRO_JOYSTICK * joy)
+{
+   ALLEGRO_JOYSTICK_DIRECTX *joydx = (ALLEGRO_JOYSTICK_DIRECTX *) joy;
+   (void)joydx;
+   if (!al_is_joystick_installed())
+      return false;
+   if (!al_get_joystick_active(joy))
+      return false;
+   ALLEGRO_DEBUG("Checking capabilities of joystick %s\n", joydx->name);
+   return whap_is_dinput_device_haptic(joydx->device);
+}
+
+
+static bool whap_is_display_haptic(ALLEGRO_DISPLAY * dev)
+{
+   (void)dev;
+   return false;
+}
+
+
+static bool whap_is_keyboard_haptic(ALLEGRO_KEYBOARD * dev)
+{
+   (void)dev;
+   return false;
+}
+
+
+static bool whap_is_touch_input_haptic(ALLEGRO_TOUCH_INPUT * dev)
+{
+   (void)dev;
+   return false;
+}
+
+
+static ALLEGRO_HAPTIC *whap_get_from_mouse(ALLEGRO_MOUSE * mouse)
+{
+   (void)mouse;
+   return NULL;
+}
+
+
+/* Sets the force feedback gain on a directinput device.
+ Returns true on success and false on failure.  */
+static bool whap_set_dinput_device_gain(LPDIRECTINPUTDEVICE2 device,
+                                        double gain)
+{
+   HRESULT ret;
+   DIPROPDWORD dipdw;
+   if (gain < 0.0)
+      return false;
+   if (gain > 1.0)
+      return false;
+
+   dipdw.diph.dwSize = sizeof(DIPROPDWORD);
+   dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
+   dipdw.diph.dwObj = 0;
+   dipdw.diph.dwHow = DIPH_DEVICE;
+   dipdw.dwData = gain * DI_FFNOMINALMAX;
+
+   ret = IDirectInputDevice_SetProperty(device, DIPROP_FFGAIN, &dipdw.diph);
+   return (!FAILED(ret));
+}
+
+/* Sets the force feedback autocentering intensity on a directinput device.
+ Returns true on success and false on failure. */
+static bool whap_set_dinput_device_autocenter(LPDIRECTINPUTDEVICE2 device,
+                                              double intensity)
+{
+   HRESULT ret;
+   DIPROPDWORD dipdw;
+   if (intensity < 0.0)
+      return false;
+   if (intensity > 1.0)
+      return false;
+
+   dipdw.diph.dwSize = sizeof(DIPROPDWORD);
+   dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
+   dipdw.diph.dwObj = 0;
+   dipdw.diph.dwHow = DIPH_DEVICE;
+   if (intensity < 0.5) {
+      dipdw.dwData = DIPROPAUTOCENTER_OFF;
+   }
+   else {
+      dipdw.dwData = DIPROPAUTOCENTER_ON;
+   }
+   /* Try to set the autocenter. */
+   ret = IDirectInputDevice_SetProperty(device, DIPROP_AUTOCENTER, &dipdw.diph);
+   return (!FAILED(ret));
+}
+
+
+/* Callback to check which effect types are supported. */
+static BOOL CALLBACK
+whap_check_effect_callback(LPCDIEFFECTINFO info, LPVOID data)
+{
+   ALLEGRO_HAPTIC *haptic = (ALLEGRO_HAPTIC *) data;
+   ALLEGRO_HAPTIC_WINDOWS *whap = whap_from_al(haptic);
+
+   const CAP_MAP *map;
+   for (map = cap_map; map->allegro_bit != -1; map++) {
+      if (GUID_EQUAL(info->guid, map->guid)) {
+         whap->flags |= map->allegro_bit;
+      }
+   }
+   /* Check for more supported effect types. */
+   return DIENUM_CONTINUE;
+}
+
+
+/* Callback to check which axes are supported. */
+static BOOL CALLBACK
+whap_check_axes_callback(LPCDIDEVICEOBJECTINSTANCE dev, LPVOID data)
+{
+   ALLEGRO_HAPTIC *haptic = (ALLEGRO_HAPTIC *) data;
+   ALLEGRO_HAPTIC_WINDOWS *whap = whap_from_al(haptic);
+
+   if ((dev->dwType & DIDFT_AXIS) && (dev->dwFlags & DIDOI_FFACTUATOR)) {
+      whap->axes[whap->naxes] = dev->dwOfs;
+      whap->naxes++;
+
+      /* Stop if the axes limit is reached */
+      if (whap->naxes >= HAPTICS_AXES_MAX) {
+         return DIENUM_STOP;
+      }
+   }
+   return DIENUM_CONTINUE;
+}
+
+/* Acquires an exclusive lock on the device. */
+static bool whap_acquire_lock(ALLEGRO_HAPTIC_WINDOWS * whap)
+{
+   HRESULT ret;
+   
+   /* Release previous acquire lock on device if any */
+   ret = IDirectInputDevice_Unacquire(whap->device);
+   if (FAILED(ret)) {
+      ALLEGRO_WARN
+          ("IDirectInputDevice_Unacquire failed for haptic device.\n");
+      return false;
+   }
+
+   /* Need a display to lock the cooperative level of the haptic device on */
+   whap->display = (ALLEGRO_DISPLAY_WIN *) al_get_current_display();
+   if (!whap->display) {
+      ALLEGRO_WARN("No active window available to lock the haptic device on.");
+      return false;
+   }
+
+   /* Must set the cooperative level to exclusive now to enable force feedback.
+    */
+   ret =
+       IDirectInputDevice_SetCooperativeLevel(whap->device,
+                                              whap->display->window,
+                                              DISCL_BACKGROUND |
+                                              DISCL_EXCLUSIVE);
+   if (FAILED(ret)) {
+      ALLEGRO_WARN
+          ("IDirectInputDevice_SetCooperativeLevel failed for haptic device.\n");
+      return false;
+   }
+
+   /* Get acquire lock on device */
+   ret = IDirectInputDevice_Acquire(whap->device);
+   if (FAILED(ret)) {
+      ALLEGRO_WARN("IDirectInputDevice_Acquire failed for haptic device.\n");
+      return false;
+   }
+   return true;
+}
+
+
+/* Initializes the haptic device for use with DirectInput */
+static bool whap_initialize_dinput(ALLEGRO_HAPTIC_WINDOWS * whap)
+{
+   HRESULT ret;
+   ALLEGRO_HAPTIC *haptic = &whap->parent;
+   /* Set number of axes to zero, and then ... */
+   whap->naxes = 0;
+   /* ... get number of axes. */
+   ret = IDirectInputDevice_EnumObjects(whap->device,
+                                        whap_check_axes_callback,
+                                        haptic, DIDFT_AXIS);
+   if (FAILED(ret)) {
+      ALLEGRO_WARN("Could not get haptic device axes \n");
+      return false;
+   }
+
+   /* Support angle and radius if we have at least 2 axes.
+    * Axis support on DirectInput is a big mess, so Azimuth is unlikely to be
+    * supported.
+    */
+   if (whap->naxes >= 1) {
+      whap->flags |= ALLEGRO_HAPTIC_ANGLE;
+      whap->flags |= ALLEGRO_HAPTIC_RADIUS;
+   }
+
+   if (!whap_acquire_lock(whap)) {
+      ALLEGRO_WARN("Could not lock haptic device \n");
+      return false;
+   }
+   /* Reset all actuators in case some where active */
+   ret = IDirectInputDevice8_SendForceFeedbackCommand(whap->device,
+                                                      DISFFC_RESET);
+   if (FAILED(ret)) {
+      ALLEGRO_WARN("Could not reset haptic device \n");
+   }
+
+   /* Enable all actuators. */
+   ret = IDirectInputDevice8_SendForceFeedbackCommand(whap->device,
+                                                      DISFFC_SETACTUATORSON);
+   if (FAILED(ret)) {
+      ALLEGRO_WARN("Could not enable haptic device actuators\n");
+      return false;
+   }
+
+   /* Get known supported effects. */
+   ret = IDirectInputDevice8_EnumEffects(whap->device,
+                                         whap_check_effect_callback, haptic,
+                                         DIEFT_ALL);
+   if (FAILED(ret)) {
+      ALLEGRO_WARN("Could not get haptic device supported effects\n");
+      return false;
+   }
+
+   /* Check if any periodic effects are supported. */
+   bool periodic_ok = al_is_haptic_capable(haptic, ALLEGRO_HAPTIC_SINE);
+   periodic_ok |= al_is_haptic_capable(haptic, ALLEGRO_HAPTIC_SQUARE);
+   periodic_ok |= al_is_haptic_capable(haptic, ALLEGRO_HAPTIC_TRIANGLE);
+   periodic_ok |= al_is_haptic_capable(haptic, ALLEGRO_HAPTIC_SAW_DOWN);
+   periodic_ok |= al_is_haptic_capable(haptic, ALLEGRO_HAPTIC_SAW_UP);
+
+   if (periodic_ok) {
+      /* If we have any of the effects above, we can use
+         periodic and rumble effects. */
+      whap->flags |= (ALLEGRO_HAPTIC_PERIODIC | ALLEGRO_HAPTIC_RUMBLE);
+   }
+
+   if (whap_set_dinput_device_gain(whap->device, 1.0)) {
+      whap->flags |= ALLEGRO_HAPTIC_GAIN;
+   }
+
+   /* Check autocenter and turn it off in one go. */
+   if (whap_set_dinput_device_autocenter(whap->device, 0.0)) {
+      whap->flags |= ALLEGRO_HAPTIC_AUTOCENTER;
+   }
+
+   return true;
+}
+
+
+
+
+static ALLEGRO_HAPTIC *whap_get_from_joystick(ALLEGRO_JOYSTICK * joy)
+{
+   ALLEGRO_JOYSTICK_DIRECTX *joydx = (ALLEGRO_JOYSTICK_DIRECTX *) joy;
+   ALLEGRO_HAPTIC_WINDOWS *whap;
+
+   int i;
+
+   if (!al_is_joystick_haptic(joy))
+      return NULL;
+
+   al_lock_mutex(haptic_mutex);
+
+   whap = whap_get_available_haptic();
+
+   if (!whap) {
+      al_unlock_mutex(haptic_mutex);
+      return NULL;
+   }
+
+   whap->parent.device = joy;
+   whap->parent.from = _AL_HAPTIC_FROM_JOYSTICK;
+
+   whap->guid = joydx->guid;
+   whap->device = joydx->device;
+   whap->active = true;
+   for (i = 0; i < HAPTICS_EFFECTS_MAX; i++) {
+      whap->effects[i].active = false;  /* not in use */
+   }
+   whap->parent.gain = 1.0;
+   whap->parent.autocenter = 0.0;
+
+   /* result is ok if init functions returns true. */
+   if (!whap_initialize_dinput(whap)) {
+      al_release_haptic(&whap->parent);
+      al_unlock_mutex(haptic_mutex);
+      return NULL;
+   }
+
+   al_unlock_mutex(haptic_mutex);
+
+   return &whap->parent;
+}
+
+
+static ALLEGRO_HAPTIC *whap_get_from_display(ALLEGRO_DISPLAY * dev)
+{
+   (void)dev;
+   return NULL;
+}
+
+
+static ALLEGRO_HAPTIC *whap_get_from_keyboard(ALLEGRO_KEYBOARD * dev)
+{
+   (void)dev;
+   return NULL;
+}
+
+
+static ALLEGRO_HAPTIC *whap_get_from_touch_input(ALLEGRO_TOUCH_INPUT * dev)
+{
+   (void)dev;
+   return NULL;
+}
+
+
+static int whap_get_capabilities(ALLEGRO_HAPTIC * dev)
+{
+   ALLEGRO_HAPTIC_WINDOWS *whap = whap_from_al(dev);
+   return whap->flags;
+}
+
+
+static double whap_get_gain(ALLEGRO_HAPTIC * dev)
+{
+   ALLEGRO_HAPTIC_WINDOWS *whap = whap_from_al(dev);
+   /* Just return the stored gain, it's easier than querying. */
+   return whap->parent.gain;
+}
+
+
+static bool whap_set_gain(ALLEGRO_HAPTIC * dev, double gain)
+{
+   ALLEGRO_HAPTIC_WINDOWS *whap = whap_from_al(dev);
+   bool ok = whap_set_dinput_device_gain(whap->device, gain);
+   if (ok) {
+      whap->parent.gain = gain;
+   }
+   else {
+      whap->parent.gain = 1.0;
+   }
+   return ok;
+}
+
+
+double whap_get_autocenter(ALLEGRO_HAPTIC * dev)
+{
+   ALLEGRO_HAPTIC_WINDOWS *whap = whap_from_al(dev);
+   /* Return the stored autocenter value. It's easiest like that. */
+   return whap->parent.autocenter;
+}
+
+
+static bool whap_set_autocenter(ALLEGRO_HAPTIC * dev, double intensity)
+{
+   ALLEGRO_HAPTIC_WINDOWS *whap = whap_from_al(dev);
+   bool ok = whap_set_dinput_device_autocenter(whap->device, intensity);
+   if (ok) {
+      whap->parent.autocenter = intensity;
+   }
+   else {
+      whap->parent.autocenter = 0.0;
+   }
+   return ok;
+}
+
+static int whap_get_num_effects(ALLEGRO_HAPTIC * dev)
+{
+   ALLEGRO_HAPTIC_WINDOWS *whap = whap_from_al(dev);
+   int n_effects;
+   (void)n_effects, (void)whap;
+
+   return HAPTICS_EFFECTS_MAX;
+}
+
+
+static bool whap_is_effect_ok(ALLEGRO_HAPTIC * haptic,
+                              ALLEGRO_HAPTIC_EFFECT * effect)
+{
+   int caps;
+
+   caps = al_get_haptic_capabilities(haptic);
+   if (caps & effect->type) {
+      return true;
+   }
+   /* XXX: should do more checking here? */
+   return false;
+}
+
+
+struct dinput_error_pair
+{
+   HRESULT error;
+   const char *text;
+};
+
+#define DIMKEP(ERROR) { ((HRESULT)ERROR), #ERROR }
+
+struct dinput_error_pair dinput_errors[] = {
+   DIMKEP(DI_BUFFEROVERFLOW),
+   DIMKEP(DI_DOWNLOADSKIPPED),
+   DIMKEP(DI_EFFECTRESTARTED),
+   DIMKEP(DI_NOEFFECT),
+   DIMKEP(DI_NOTATTACHED),
+   DIMKEP(DI_OK),
+   DIMKEP(DI_POLLEDDEVICE),
+   DIMKEP(DI_PROPNOEFFECT),
+   DIMKEP(DI_SETTINGSNOTSAVED),
+   DIMKEP(DI_TRUNCATED),
+   DIMKEP(DI_TRUNCATEDANDRESTARTED),
+   DIMKEP(DI_WRITEPROTECT),
+   DIMKEP(DIERR_ACQUIRED),
+   DIMKEP(DIERR_ALREADYINITIALIZED),
+   DIMKEP(DIERR_BADDRIVERVER),
+   DIMKEP(DIERR_BETADIRECTINPUTVERSION),
+   DIMKEP(DIERR_DEVICEFULL),
+   DIMKEP(DIERR_DEVICENOTREG),
+   DIMKEP(DIERR_EFFECTPLAYING),
+   DIMKEP(DIERR_GENERIC),
+   DIMKEP(DIERR_HANDLEEXISTS),
+   DIMKEP(DIERR_HASEFFECTS),
+   DIMKEP(DIERR_INCOMPLETEEFFECT),
+   DIMKEP(DIERR_INPUTLOST),
+   DIMKEP(DIERR_INVALIDPARAM),
+   DIMKEP(DIERR_MAPFILEFAIL),
+   DIMKEP(DIERR_MOREDATA),
+   DIMKEP(DIERR_NOAGGREGATION),
+   DIMKEP(DIERR_NOINTERFACE),
+   DIMKEP(DIERR_NOTACQUIRED),
+   DIMKEP(DIERR_NOTBUFFERED),
+   DIMKEP(DIERR_NOTDOWNLOADED),
+   DIMKEP(DIERR_NOTEXCLUSIVEACQUIRED),
+   DIMKEP(DIERR_NOTFOUND),
+   DIMKEP(DIERR_NOTINITIALIZED),
+   DIMKEP(DIERR_OBJECTNOTFOUND),
+   DIMKEP(DIERR_OLDDIRECTINPUTVERSION),
+   DIMKEP(DIERR_OTHERAPPHASPRIO),
+   DIMKEP(DIERR_OUTOFMEMORY),
+   DIMKEP(DIERR_READONLY),
+   DIMKEP(DIERR_REPORTFULL),
+   DIMKEP(DIERR_UNPLUGGED),
+   DIMKEP(DIERR_UNSUPPORTED),
+   DIMKEP(E_HANDLE),
+   DIMKEP(E_PENDING),
+   DIMKEP(E_POINTER),
+   {0, NULL}
+};
+
+static void warn_on_error(HRESULT hr)
+{
+   struct dinput_error_pair *pair = dinput_errors;
+   while (pair->text) {
+      if (hr == pair->error) {
+         ALLEGRO_WARN("HRESULT error: %s\n", pair->text);
+      }
+      pair++;
+   }
+   ALLEGRO_WARN("Unknown HRESULT error: %u\n", (unsigned int)hr);
+}
+
+
+
+static bool whap_upload_effect_helper
+    (ALLEGRO_HAPTIC_WINDOWS * whap,
+     ALLEGRO_HAPTIC_EFFECT_WINDOWS * weff, ALLEGRO_HAPTIC_EFFECT * effect)
+{
+   HRESULT ret;
+   
+   if(!whap_effect2win(weff, effect, whap)) {
+      ALLEGRO_WARN("Could not convert haptic effect.\n");
+      return false;
+   }
+
+   /* Create the effect. */
+   ret = IDirectInputDevice8_CreateEffect(whap->device, (*weff->guid),
+                                          &weff->effect,
+                                          &weff->ref   , NULL);
+
+   /* XXX Need to re-lock since the joystick driver steals my thunder
+    * by calling Unacquire on the device.
+    * The better way would be to fix this in that driver somehow.
+    */
+   if (!whap_acquire_lock(whap)) {
+      ALLEGRO_WARN("Could not lock haptic device.\n");
+      return false;
+   }
+
+
+   /* Create the effect. */
+   ret = IDirectInputDevice8_CreateEffect(whap->device, (*weff->guid),
+                                          &weff->effect, &weff->ref, NULL);
+
+   if (FAILED(ret)) {
+      ALLEGRO_WARN("Could not create haptic effect.\n");
+      warn_on_error(ret);
+      return false;
+   }
+
+   /* Upload the effect to the device. */
+   ret = IDirectInputEffect_Download(weff->ref);
+   if (FAILED(ret)) {
+      ALLEGRO_WARN("Could not upload haptic effect.\n");
+      warn_on_error(ret);
+      return false;
+   }
+
+   return true;
+}
+
+static bool whap_upload_effect(ALLEGRO_HAPTIC * dev,
+                               ALLEGRO_HAPTIC_EFFECT * effect,
+                               ALLEGRO_HAPTIC_EFFECT_ID * id)
+{
+   bool ok = FALSE;
+   ALLEGRO_HAPTIC_WINDOWS *whap = whap_from_al(dev);
+   ALLEGRO_HAPTIC_EFFECT_WINDOWS *weff = NULL;
+
+   ASSERT(dev);
+   ASSERT(id);
+   ASSERT(effect);
+
+   /* Set id's values to indicate failure beforehand. */
+   id->_haptic = NULL;
+   id->_id = -1;
+   id->_pointer = NULL;
+   id->_playing = false;
+   id->_effect_duration = 0.0;
+   id->_start_time = 0.0;
+   id->_end_time = 0.0;
+
+   al_lock_mutex(haptic_mutex);
+
+   /* Look for a free haptic effect slot. */
+   weff = whap_get_available_effect(whap);
+   /* Returns NULL if there is no more space for an effect. */
+   if (weff) {
+      if (whap_upload_effect_helper(whap, weff, effect)) {
+         /* set ID handle to signify success */
+         id->_haptic = dev;
+         id->_pointer = weff;
+         id->_id = weff->id;
+         id->_effect_duration = al_get_haptic_effect_duration(effect);
+         ok = true;
+      }
+      else {
+         ALLEGRO_WARN("Could not upload effect.");
+      }
+   }
+   else {
+      ALLEGRO_WARN("No free effect slot.");
+   }
+
+   al_unlock_mutex(haptic_mutex);
+   return ok;
+}
+
+
+static bool whap_play_effect(ALLEGRO_HAPTIC_EFFECT_ID * id, int loops)
+{
+   HRESULT res;
+   ALLEGRO_HAPTIC_WINDOWS *whap = (ALLEGRO_HAPTIC_WINDOWS *) id->_haptic;
+   ALLEGRO_HAPTIC_EFFECT_WINDOWS *weff;
+   if ((!whap) || (id->_id < 0))
+      return false;
+   weff = whap->effects + id->_id;
+
+   /* Need to re-lock since the joystick driver steals the haptics' thunder
+    * by calling Unacquire on the device.
+    * The better way would be to fix this in that driver somehow.
+    */
+   if (!whap_acquire_lock(whap)) {
+      ALLEGRO_WARN("Could not lock haptic device \n");
+      return false;
+   }
+
+   res = IDirectInputEffect_Start(weff->ref, loops, 0);
+   if (FAILED(res)) {
+      ALLEGRO_WARN("Failed to play an effect.");
+      warn_on_error(res);
+      return false;
+   }
+   id->_playing = true;
+   id->_start_time = al_get_time();
+   id->_end_time = id->_start_time;
+   id->_end_time += id->_effect_duration * (double)loops;
+   return true;
+}
+
+
+static bool whap_stop_effect(ALLEGRO_HAPTIC_EFFECT_ID * id)
+{
+   HRESULT res;
+   ALLEGRO_HAPTIC_WINDOWS *whap = (ALLEGRO_HAPTIC_WINDOWS *) id->_haptic;
+   ALLEGRO_HAPTIC_EFFECT_WINDOWS *weff;
+
+   if ((!whap) || (id->_id < 0))
+      return false;
+
+   weff = whap->effects + id->_id;
+
+   res = IDirectInputEffect_Stop(weff->ref);
+   if (FAILED(res)) {
+      ALLEGRO_WARN("Failed to play an effect.");
+      return false;
+   }
+   id->_playing = false;
+
+
+   return true;
+}
+
+
+static bool whap_is_effect_playing(ALLEGRO_HAPTIC_EFFECT_ID * id)
+{
+   ASSERT(id);
+   HRESULT res;
+   DWORD flags = 0;
+   ALLEGRO_HAPTIC_WINDOWS *whap = (ALLEGRO_HAPTIC_WINDOWS *) id->_haptic;
+   ALLEGRO_HAPTIC_EFFECT_WINDOWS *weff;
+
+   if ((!whap) || (id->_id < 0) || (!id->_playing))
+      return false;
+
+   weff = whap->effects + id->_id;
+
+   res = IDirectInputEffect_GetEffectStatus(weff->ref, &flags);
+   if (FAILED(res)) {
+      ALLEGRO_WARN("Failed to get the status of effect.");
+      /* If we get here, then use the play time in stead to
+       * see if the effect should still be playing.
+       * Do this because in case GeteffectStatus fails, we can't
+       * assume the sample isn't playing. In fact, if the play command
+       * was sucessful, it should still be playing as long as the play
+       * time has not passed.
+       */
+      return (al_get_time() < id->_end_time);
+   }
+   if (flags & DIEGES_PLAYING)
+      return true;
+   /* WINE is bugged here, it doesn't set flags, but it also
+    * just returns DI_OK. Thats why here, don't believe the API
+    * when it the playing flag isn't set if the effect's duration
+    * has not passed. On real Windows it should probably always be the
+    * case that the effect will have played completely when
+    * the play time has ended.
+    */
+   return (al_get_time() < id->_end_time);
+}
+
+
+
+static bool whap_release_effect(ALLEGRO_HAPTIC_EFFECT_ID * id)
+{
+   ALLEGRO_HAPTIC_WINDOWS *whap = (ALLEGRO_HAPTIC_WINDOWS *) id->_haptic;
+   ALLEGRO_HAPTIC_EFFECT_WINDOWS *weff;
+   if ((!whap) || (id->_id < 0))
+      return false;
+
+   whap_stop_effect(id);
+
+   weff = whap->effects + id->_id;
+   return whap_release_effect_windows(weff);
+}
+
+
+static bool whap_release(ALLEGRO_HAPTIC * haptic)
+{
+   ALLEGRO_HAPTIC_WINDOWS *whap = whap_from_al(haptic);
+   int index;
+   HRESULT res;
+
+   ASSERT(haptic);
+
+   if (!whap->active)
+      return false;
+
+   /* Release all effects for this device. */
+   for (index = 0; index < HAPTICS_EFFECTS_MAX; index++) {
+      whap_release_effect_windows(whap->effects + index);
+   }
+
+   /* Release the acquire lock on the device */
+   IDirectInputDevice_Unacquire(whap->device);
+
+   /* Reset the cooperative level to nonexclusive.
+    */
+   res =
+       IDirectInputDevice_SetCooperativeLevel(whap->device,
+                                              whap->display->window,
+                                              DISCL_FOREGROUND |
+                                              DISCL_NONEXCLUSIVE);
+   if (FAILED(res)) {
+      ALLEGRO_WARN
+          ("IDirectInputDevice8_SetCooperativeLevel NONEXCLUSIVE failed for haptic device.\n");
+   }
+
+   whap->display = NULL;
+   whap->active = false;
+   whap->device = NULL;
+   return true;
+}
+
+
+
+/* vim: set sts=3 sw=3 et: */
diff --git a/src/win/wjoydxnu.cpp b/src/win/wjoydxnu.cpp
index 8fd0eda..cb5de9c 100644
--- a/src/win/wjoydxnu.cpp
+++ b/src/win/wjoydxnu.cpp
@@ -82,79 +82,10 @@
 
 ALLEGRO_DEBUG_CHANNEL("dinput")
 
-/* arbitrary limit to make life easier; this was the limit in Allegro 4.1.x */
-#define MAX_JOYSTICKS        8
-
-/* these limits are from DIJOYSTICK_STATE in dinput.h */
-#define MAX_SLIDERS          2
-#define MAX_POVS             4
-#define MAX_BUTTONS          32
-
-/* the number of joystick events that DirectInput is told to buffer */
-#define DEVICE_BUFFER_SIZE   10
-
-/* make sure all the constants add up */
-/* the first two sticks are (x,y,z) and (rx,ry,rz) */
-ALLEGRO_STATIC_ASSERT(wjoydxnu, _AL_MAX_JOYSTICK_STICKS >= (2 + MAX_SLIDERS + MAX_POVS));
-ALLEGRO_STATIC_ASSERT(wjoydxnu, _AL_MAX_JOYSTICK_BUTTONS >= MAX_BUTTONS);
-
-
-#define GUID_EQUAL(a, b)     (0 == memcmp(&(a), &(b), sizeof(GUID)))
-
-
-typedef enum {
-   STATE_UNUSED,
-   STATE_BORN,
-   STATE_ALIVE,
-   STATE_DYING
-} CONFIG_STATE;
-
-#define ACTIVE_STATE(st) \
-   ((st) == STATE_ALIVE || (st) == STATE_DYING)
-
-
-/* helper structure to record information through object_enum_callback */
-#define NAME_LEN     128
-
-typedef struct {
-   bool have_x;      char name_x[NAME_LEN];
-   bool have_y;      char name_y[NAME_LEN];
-   bool have_z;      char name_z[NAME_LEN];
-   bool have_rx;     char name_rx[NAME_LEN];
-   bool have_ry;     char name_ry[NAME_LEN];
-   bool have_rz;     char name_rz[NAME_LEN];
-   int num_sliders;  char name_slider[MAX_SLIDERS][NAME_LEN];
-   int num_povs;     char name_pov[MAX_POVS][NAME_LEN];
-   int num_buttons;  char name_button[MAX_BUTTONS][NAME_LEN];
-} CAPS_AND_NAMES;
-
-
-/* map a DirectInput axis to an Allegro (stick,axis) pair */
-typedef struct {
-   int stick, axis;
-} AXIS_MAPPING;
-
-
-typedef struct ALLEGRO_JOYSTICK_DIRECTX {
-   ALLEGRO_JOYSTICK parent;          /* must be first */
-   CONFIG_STATE config_state;
-   bool marked;
-   LPDIRECTINPUTDEVICE2 device;
-   GUID guid;
-   HANDLE waker_event;
-
-   ALLEGRO_JOYSTICK_STATE joystate;
-   AXIS_MAPPING x_mapping;
-   AXIS_MAPPING y_mapping;
-   AXIS_MAPPING z_mapping;
-   AXIS_MAPPING rx_mapping;
-   AXIS_MAPPING ry_mapping;
-   AXIS_MAPPING rz_mapping;
-   AXIS_MAPPING slider_mapping[MAX_SLIDERS];
-   int pov_mapping_stick[MAX_POVS];
-   char name[80];
-   char all_names[512]; /* button/stick/axis names with NUL terminators */
-} ALLEGRO_JOYSTICK_DIRECTX;
+#include "allegro5/joystick.h"
+#include "allegro5/internal/aintern_joystick.h"
+#include "allegro5/internal/aintern_wjoydxnu.h"
+
 
 
 
diff --git a/src/win/wsystem.c b/src/win/wsystem.c
index 5348bcf..4b088a4 100644
--- a/src/win/wsystem.c
+++ b/src/win/wsystem.c
@@ -309,6 +309,12 @@ static ALLEGRO_JOYSTICK_DRIVER *win_get_joystick_driver(void)
    return _al_joystick_driver_list[0].driver;
 }
 
+static ALLEGRO_HAPTIC_DRIVER *win_get_haptic_driver(void)
+{
+   return &_al_hapdrv_directx;
+   /* return _al_haptic_driver_list[0].driver; XXX: this list is empty for some reason!?  */
+}
+
 static int win_get_num_display_modes(void)
 {
    int format = _al_deduce_color_format(_al_get_new_display_settings());
@@ -693,6 +699,7 @@ static ALLEGRO_SYSTEM_INTERFACE *_al_system_win_driver(void)
    vt->get_keyboard_driver = win_get_keyboard_driver;
    vt->get_mouse_driver = win_get_mouse_driver;
    vt->get_touch_input_driver = win_get_touch_input_driver;
+   vt->get_haptic_driver = win_get_haptic_driver;
    vt->get_joystick_driver = win_get_joystick_driver;
    vt->get_num_display_modes = win_get_num_display_modes;
    vt->get_display_mode = win_get_display_mode;
diff --git a/src/x/xsystem.c b/src/x/xsystem.c
index 70614a6..cc51fba 100644
--- a/src/x/xsystem.c
+++ b/src/x/xsystem.c
@@ -175,11 +175,7 @@ static ALLEGRO_JOYSTICK_DRIVER *xglx_get_joystick_driver(void)
 
 static ALLEGRO_HAPTIC_DRIVER *xglx_get_haptic_driver(void)
 {
-#ifdef ALLEGRO_HAVE_LINUX_INPUT_H
-   return &_al_hapdrv_linux;
-#else
-   return NULL;
-#endif
+   return _al_haptic_driver_list[0].driver;
 }
 
 static int xglx_get_num_video_adapters(void)


Mail converted by MHonArc 2.6.19+ http://listengine.tuxfamily.org/