Big import of updated joystick code from PLIB, with permission from
authorRichard Rauch <rkr@olib.org>
Thu, 5 Feb 2004 23:31:09 +0000 (23:31 +0000)
committerRichard Rauch <rkr@olib.org>
Thu, 5 Feb 2004 23:31:09 +0000 (23:31 +0000)
Steve Baker.  Most of the adaptation done by John, with a little bit
of testing and changes by Thierry Thomas and myself.  Seems to compile on:

  WIN32
  Red Hat LINUX
  FreeBSD
  NetBSD/amd64

git-svn-id: svn+ssh://svn.code.sf.net/p/freeglut/code/trunk/freeglut/freeglut@450 7f0cb862-5218-0410-a997-914c9d46530a

src/freeglut_init.c
src/freeglut_joystick.c

index c9e85c5..2d25dd2 100644 (file)
@@ -205,6 +205,7 @@ void fgInitialize( const char* displayName )
 #endif
 
     fgJoystickInit( 0 );
+    fgJoystickInit( 1 );
 
     fgState.Initialised = GL_TRUE;
 }
index ff8738a..a6622be 100644 (file)
  */
 
 /*
- * PWO: This is not exactly what Steve Baker has done for PLIB, as I had to
- *      convert it from C++ to C. And I've also reformatted it a bit (that's
- *      my little personal deviation :]) I don't really know if it is still
- *      portable...
- *      Steve: could you please add some comments to the code? :)
- *
  * FreeBSD port by Stephen Montgomery-Smith <stephen@math.missouri.edu>
+ *
+ * Redone by John Fay 2/4/04 with another look from the PLIB "js" library.
+ *  Many thanks for Steve Baker for permission to pull from that library.
  */
 
 #if defined( __FreeBSD__ ) || defined( __NetBSD__ )
-#include <sys/param.h>
+#    include <sys/param.h>
 #endif
 
 #ifdef HAVE_CONFIG_H
-#include "config.h"
+#    include "config.h"
 #endif
 
 #include "../include/GL/freeglut.h"
 #include "freeglut_internal.h"
 
 /*
- * PWO: I don't like it at all. It's a mess. Could it be cleared?
+ * Initial defines from "js.h" starting around line 33 with the existing "freeglut_joystick.c"
+ * interspersed
  */
+#define _JS_MAX_BUTTONS 32
+
+
+#if TARGET_HOST_MACINTOSH
+#    define _JS_MAX_AXES  9
+#    include <InputSprocket.h>
+#endif
+
+#if TARGET_HOST_MAC_OSX
+#    define _JS_MAX_AXES 16
+#    include <mach/mach.h>
+#    include <IOKit/IOkitLib.h>
+#    include <IOKit/hid/IOHIDLib.h>
+#endif
+
 #if TARGET_HOST_WIN32
-#   include <windows.h>
-#   if defined( __CYGWIN32__ ) || defined( __CYGWIN__ )
-#       define NEAR /* */
-#       define FAR  /* */
+#    define _JS_MAX_AXES  8
+#    include <windows.h>
+#    include <mmsystem.h>
+#    include <string.h>
+#    include <regstr.h>
+
+#endif
+
+#if TARGET_HOST_UNIX_X11
+#    define _JS_MAX_AXES 16
+#    if defined(__FreeBSD__) || defined(__NetBSD__)
+/* XXX The next line is an unjustified hack which needs to be changed by someone who
+ * XXX is familiar with *nix, BSD, and USB joysticks.
+ */
+#        define HAVE_USB_JS    1
+
+#        include <sys/ioctl.h>
+#        include <machine/joystick.h>         /* For analog joysticks */
+
+#        if __FreeBSD_version >= 500000
+#            include <sys/joystick.h>
+#        else
+#            include <machine/joystick.h>
+#        endif
+#        define JS_DATA_TYPE joystick
+#        define JS_RETURN (sizeof(struct JS_DATA_TYPE))
+#    endif
+
+#    include <unistd.h>
+#    include <fcntl.h>
+#    include <errno.h>
+
+#    if defined(__linux__)
+#        include <sys/ioctl.h>
+#        include <linux/joystick.h>
+
+/* check the joystick driver version */
+#        if defined(JS_VERSION) && JS_VERSION >= 0x010000
+#            define JS_NEW
+#        endif
+#    else  /* Not BSD or Linux */
+#        ifndef JS_RETURN
+
+  /*
+   * We'll put these values in and that should
+   * allow the code to at least compile when there is
+   * no support. The JS open routine should error out
+   * and shut off all the code downstream anyway and if
+   * the application doesn't use a joystick we'll be fine.
+   */
+
+  struct JS_DATA_TYPE
+  {
+    int buttons;
+    int x;
+    int y;
+  };
+
+#            define JS_RETURN (sizeof(struct JS_DATA_TYPE))
+#        endif
+#    endif
+#endif
+
+#define JS_TRUE  1
+#define JS_FALSE 0
+
+/*
+ * BSD defines from "jsBSD.cxx" around lines 42-270
+ */
+
+#if defined(__NetBSD__) || defined(__FreeBSD__)
+
+#    ifdef HAVE_USB_JS
+#        if defined(__NetBSD__)
+/*
+ * XXX Apparently another ugly hack which someone who knows BSD and USBHID needs to solve
+ */
+#            define HAVE_USBHID_H 1
+#            ifdef HAVE_USBHID_H
+#                include <usbhid.h>
+#            else
+#                include <usb.h>
+#            endif
+#        elif defined(__FreeBSD__)
+#            include <libusbhid.h>
+#        endif
+#        include <dev/usb/usb.h>
+#        include <dev/usb/usbhid.h>
+
+/* Compatibility with older usb.h revisions */
+#        if !defined(USB_MAX_DEVNAMES) && defined(MAXDEVNAMES)
+#            define USB_MAX_DEVNAMES MAXDEVNAMES
+#        endif
+#    endif
+
+static int hatmap_x[9] = { 0, 0, 1, 1, 1, 0, -1, -1, -1 };
+static int hatmap_y[9] = { 0, 1, 1, 0, -1, -1, -1, 0, 1 };
+struct os_specific_s {
+  char             fname [128 ];
+  int              fd;
+  int              is_analog;
+  /* The following structure members are specific to analog joysticks */
+  struct joystick  ajs;
+#    ifdef HAVE_USB_JS
+  /* The following structure members are specific to USB joysticks */
+  struct hid_item *hids;
+  int              hid_dlen;
+  int              hid_offset;
+  char            *hid_data_buf;
+  int              axes_usage [ _JS_MAX_AXES ];
+#    endif
+  /* We keep button and axes state ourselves, as they might not be updated
+   * on every read of a USB device
+   */
+  int              cache_buttons;
+  float            cache_axes [ _JS_MAX_AXES ];
+};
+
+/* Idents lower than USB_IDENT_OFFSET are for analog joysticks. */
+#    define USB_IDENT_OFFSET    2
+
+#    define USBDEV "/dev/usb"
+#    define UHIDDEV "/dev/uhid"
+#    define AJSDEV "/dev/joy"
+
+#    ifdef HAVE_USB_JS
+/*
+ * fghJoystickFindUSBdev (and its helper, fghJoystickWalkUSBdev) try to locate
+ * the full name of a USB device. If /dev/usbN isn't readable, we punt and
+ * return the uhidN device name. We warn the user of this situation once.
+ */
+static char *fghJoystickWalkUSBdev(int f, char *dev, char *out, int outlen)
+{
+  struct usb_device_info di;
+  int i, a;
+  char *cp;
+
+  for (a = 1; a < USB_MAX_DEVICES; a++) {
+    di.udi_addr = a;
+    if (ioctl(f, USB_DEVICEINFO, &di) != 0)
+      return NULL;
+    for (i = 0; i < USB_MAX_DEVNAMES; i++)
+      if (di.udi_devnames[i][0] &&
+          strcmp(di.udi_devnames[i], dev) == 0) {
+        cp =  calloc( 1, strlen(di.udi_vendor) + strlen(di.udi_product) + 2);
+        strcpy(cp, di.udi_vendor);
+        strcat(cp, " ");
+        strcat(cp, di.udi_product);
+        strncpy(out, cp, outlen - 1);
+        out[outlen - 1] = 0;
+        free( cp );
+        return out;
+      }
+  }
+  return NULL;
+}
+
+static int fghJoystickFindUSBdev(char *name, char *out, int outlen)
+{
+  int i, f;
+  char buf[50];
+  char *cp;
+  static int protection_warned = 0;
+
+  for (i = 0; i < 16; i++) {
+    sprintf(buf, "%s%d", USBDEV, i);
+    f = open(buf, O_RDONLY);
+    if (f >= 0) {
+      cp = fghJoystickWalkUSBdev(f, name, out, outlen);
+      close(f);
+      if (cp)
+        return 1;
+    } else if (errno == EACCES) {
+      if (!protection_warned) {
+        fprintf(stderr, "Can't open %s for read!\n",
+          buf);
+        protection_warned = 1;
+      }
+    }
+  }
+  return 0;
+}
+
+static int fghJoystickInitializeHID(struct os_specific_s *os,
+       int *num_axes, int *num_buttons)
+{
+    int size, is_joystick;
+#   ifdef HAVE_USBHID_H
+        int report_id = 0;
 #   endif
-#   include <mmsystem.h>
-#   include <string.h>
-#else
-#   include <unistd.h>
-#   include <fcntl.h>
-#   if defined(__FreeBSD__) || defined(__NetBSD__)
-#   if __FreeBSD_version >= 500000
-#       include <sys/joystick.h>
+    struct hid_data *d;
+    struct hid_item h;
+    report_desc_t rd;
+
+    if ( ( rd = hid_get_report_desc( os->fd ) ) == 0 )
+    {
+        fprintf( stderr, "error: %s: %s", os->fname, strerror( errno ) );
+        return FALSE;
+    }
+
+    os->hids = NULL;
+
+#   ifdef HAVE_USBHID_H
+        if( ioctl( os->fd, USB_GET_REPORT_ID, &report_id ) < 0)
+        {
+            /*** XXX {report_id} may not be the right variable? ***/
+            fprintf( stderr, "error: %s%d: %s",
+                     UHIDDEV, report_id, strerror( errno ) );
+            return FALSE;
+        }
+
+        size = hid_report_size( rd, hid_input, report_id );
 #   else
-#       include <machine/joystick.h>
+        size = hid_report_size( rd, 0, hid_input );
 #   endif
-#       define JS_DATA_TYPE joystick
-#       define JS_RETURN (sizeof(struct JS_DATA_TYPE))
-#   elif defined(__linux__)
-#       include <sys/ioctl.h>
-#       include <linux/joystick.h>
-#       include <errno.h>
+    os->hid_data_buf = calloc( 1, size );
+    os->hid_dlen = size;
 
-        /*
-         * Check the joystick driver version
-         */
-#       ifdef JS_VERSION
-#           if JS_VERSION >= 0x010000
-#               define JS_NEW
-#           endif
-#       endif
+    is_joystick = 0;
+#   ifdef HAVE_USBHID_H
+        d = hid_start_parse( rd, 1 << hid_input, report_id );
 #   else
-#       ifndef JS_DATA_TYPE
-
-            /*
-             * Not Windoze and no (known) joystick driver...
-             *
-             * Well - we'll put these values in and that should
-             * allow the code to at least compile. The JS open
-             * routine should error out and shut off all the code
-             * downstream anyway
-             */
-            struct JS_DATA_TYPE
+        d = hid_start_parse( rd, 1 << hid_input );
+#   endif
+        while( hid_get_item( d, &h ) )
+        {
+            int usage, page, interesting_hid;
+
+            page = HID_PAGE( h.usage );
+            usage = HID_USAGE( h.usage );
+
+            /* This test is somewhat too simplistic, but this is how MicroSoft
+             * does, so I guess it works for all joysticks/game pads. */
+            is_joystick = is_joystick ||
+                ( h.kind == hid_collection &&
+                  page == HUP_GENERIC_DESKTOP &&
+                  ( usage == HUG_JOYSTICK || usage == HUG_GAME_PAD ) );
+
+            if( h.kind != hid_input )
+                continue;
+
+            if( !is_joystick )
+                continue;
+
+            interesting_hid = TRUE;
+            if( page == HUP_GENERIC_DESKTOP )
+            {
+                switch( usage )
+                {
+                case HUG_X:
+                case HUG_RX:
+                case HUG_Y:
+                case HUG_RY:
+                case HUG_Z:
+                case HUG_RZ:
+                case HUG_SLIDER:
+                    if( *num_axes < _JS_MAX_AXES )
+                    {
+                        os->axes_usage[ *num_axes ] = usage;
+                        ( *num_axes )++;
+                    }
+                    break;
+                case HUG_HAT_SWITCH:
+                    /* Allocate two axes for a hat */
+                    if( *num_axes + 1 < _JS_MAX_AXES )
+                    {
+                        os->axes_usage[ *num_axes ] = usage;
+                        (*num_axes)++;
+                        os->axes_usage[ *num_axes ] = usage;
+                        (*num_axes)++;
+                    }
+                    break;
+                default:
+                    interesting_hid = FALSE;
+                    break;
+                }
+            }
+            else if( page == HUP_BUTTON )
             {
-                int buttons;
-                int x;
-                int y;
-            };
+                interesting_hid = ( usage > 0 ) &&
+                    ( usage <= _JS_MAX_BUTTONS );
 
-#           define JS_RETURN (sizeof(struct JS_DATA_TYPE))
-#       endif
-#   endif
-#endif
+                if( interesting_hid && usage - 1 > *num_buttons )
+                    *num_buttons = usage - 1;
+            }
 
-#if TARGET_HOST_WIN32
-#   define _JS_MAX_AXES 6
-#else
-#   ifdef __FreeBSD__
-#       define _JS_MAX_AXES 2
-#   else
-#       define _JS_MAX_AXES 6
-#   endif
+            if( interesting_hid )
+            {
+                h.next = os->hids;
+                os->hids = calloc( 1, sizeof ( struct hid_item ) );
+                *os->hids = h;
+            }
+        }
+        hid_end_parse( d );
+
+        return os->hids != NULL;
+}
+#    endif
 #endif
 
+/*
+ * Definition of "SFG_Joystick" structure -- based on JS's "jsJoystick" object class.
+ * See "js.h" lines 80-178.
+ */
 typedef struct tagSFG_Joystick SFG_Joystick;
 struct tagSFG_Joystick
 {
-/*
- * XXX All BSDs might share this?
- */
-#ifdef __FreeBSD__
-    int         id;
+#if TARGET_HOST_MACINTOSH
+#define  ISP_NUM_AXIS    9
+#define  ISP_NUM_NEEDS  41
+    ISpElementReference isp_elem  [ ISP_NUM_NEEDS ];
+    ISpNeed             isp_needs [ ISP_NUM_NEEDS ];
+#endif
+
+#if TARGET_HOST_MAC_OSX
+    IOHIDDeviceInterface ** hidDev;
+    IOHIDElementCookie buttonCookies[41];
+    IOHIDElementCookie axisCookies[_JS_MAX_AXES];
+    long minReport[_JS_MAX_AXES],
+         maxReport[_JS_MAX_AXES];
 #endif
 
 #if TARGET_HOST_WIN32
+    JOYCAPS     jsCaps;
     JOYINFOEX   js;
     UINT        js_id;
-#else
+#endif
+
+
+#if TARGET_HOST_UNIX_X11
+#   if defined(__FreeBSD__) || defined(__NetBSD__)
+       struct os_specific_s *os;
+#   endif
+
 #   ifdef JS_NEW
-        struct js_event js;
-        int         tmp_buttons;
-        float       tmp_axes[ _JS_MAX_AXES ];
+       struct js_event     js         ;
+       int          tmp_buttons;
+       float        tmp_axes [ _JS_MAX_AXES ];
 #   else
-        struct JS_DATA_TYPE js;
+       struct JS_DATA_TYPE js;
 #   endif
 
-    char fname[ 128 ];
-    int  fd;
+    char         fname [ 128 ];
+    int          fd;
 #endif
 
-    GLboolean error;
-    int       num_axes;
-    int       num_buttons;
+    int          id;
+    GLboolean    error;
+    char         name [ 128 ];
+    int          num_axes;
+    int          num_buttons;
 
     float dead_band[ _JS_MAX_AXES ];
     float saturate [ _JS_MAX_AXES ];
@@ -151,9 +422,34 @@ struct tagSFG_Joystick
 };
 
 /*
+ * Functions associated with the "jsJoystick" class in PLIB
+ */
+#if TARGET_HOST_MAC_OSX
+#define K_NUM_DEVICES   32
+int numDevices;
+io_object_t ioDevices[K_NUM_DEVICES];
+
+static void fghJoystickFindDevices ( SFG_Joystick* joy, mach_port_t );
+static CFDictionaryRef fghJoystickGetCFProperties ( SFG_Joystick* joy, io_object_t );
+
+void fghJoystickEnumerateElements ( SFG_Joystick* joy, CFTypeRef element );
+/* callback for CFArrayApply */
+static void fghJoystickElementEnumerator ( SFG_Joystick* joy, void *element, void* vjs );
+void fghJoystickParseElement ( SFG_Joystick* joy, CFDictionaryRef element );
+
+void fghJoystickAddAxisElement ( SFG_Joystick* joy, CFDictionaryRef axis );
+void fghJoystickAddButtonElement ( SFG_Joystick* joy, CFDictionaryRef button );
+void fghJoystickAddHatElement ( SFG_Joystick* joy, CFDictionaryRef hat );
+#endif
+
+
+/*
  * The static joystick structure pointer
  */
-static SFG_Joystick* fgJoystick = NULL;
+#define MAX_NUM_JOYSTICKS  2
+static int fgNumberOfJoysticks = 0;
+static SFG_Joystick *fgJoystick [ MAX_NUM_JOYSTICKS ];
+
 
 /*
  * Read the raw joystick data
@@ -166,39 +462,132 @@ static void fghJoystickRawRead( SFG_Joystick* joy, int* buttons, float* axes )
     int status;
 #endif
 
+#if defined(__FreeBSD__) || defined(__NetBSD__)
+    int len;
+#endif
+
     int i;
 
+    /* Defaults */
+    if( buttons )
+        *buttons = 0;
+
+    if( axes )
+        for( i = 0; i < joy->num_axes; i++ )
+            axes[ i ] = 1500.0f;
+
     if( joy->error )
+        return;
+
+#if TARGET_HOST_MACINTOSH
+    if ( buttons )
     {
-        if( buttons )
-            *buttons = 0;
+        *buttons = 0;
 
-        if( axes )
-            for( i=0; i<joy->num_axes; i++ )
-                axes[ i ] = 1500.0f;
+        for ( i = 0; i < joy->num_buttons; i++ )
+        {
+            UInt32 state;
+            int err = ISpElement_GetSimpleState ( isp_elem [ i + isp_num_axis ], &state);
+            ISP_CHECK_ERR(err)
 
-        return;
+            *buttons |= state << i;
+        }
+    }
+
+    if ( axes )
+    {
+        for ( i = 0; i < joy->num_axes; i++ )
+        {
+            UInt32 state;
+            int err = ISpElement_GetSimpleState ( isp_elem [ i ], &state );
+            ISP_CHECK_ERR(err)
+
+            axes [i] = (float) state;
+        }
+    }
+#endif
+
+#if TARGET_HOST_MAC_OSX
+    if ( buttons != NULL )
+    {
+        *buttons = 0;
+
+        for ( i = 0; i < joy->num_buttons; i++ )
+        {
+            IOHIDEventStruct hidEvent;
+            (*(joy->hidDev))->getElementValue ( joy->hidDev, buttonCookies[i], &hidEvent );
+            if ( hidEvent.value )
+                *buttons |= 1 << i; 
+        }
+    }
+
+    if ( axes != NULL )
+    {
+        for ( i = 0; i < joy->num_axes; i++ )
+        {
+            IOHIDEventStruct hidEvent;
+            (*(joy->hidDev))->getElementValue ( joy->hidDev, axisCookies[i], &hidEvent );
+            axes[i] = hidEvent.value;
+        }
     }
+#endif
 
 #if TARGET_HOST_WIN32
     status = joyGetPosEx( joy->js_id, &joy->js );
 
-    if( status != JOYERR_NOERROR )
+    if ( status != JOYERR_NOERROR )
     {
         joy->error = GL_TRUE;
         return;
     }
 
-    if( buttons )
+    if ( buttons )
         *buttons = joy->js.dwButtons;
 
-    if( axes )
+    if ( axes )
     {
         /*
          * WARNING - Fall through case clauses!!
          */
-        switch( joy->num_axes )
+        switch ( joy->num_axes )
         {
+        case 8:
+            /* Generate two POV axes from the POV hat angle.
+             * Low 16 bits of js.dwPOV gives heading (clockwise from ahead) in
+             *   hundredths of a degree, or 0xFFFF when idle.
+             */
+            if ( ( joy->js.dwPOV & 0xFFFF ) == 0xFFFF )
+            {
+              axes [ 6 ] = 0.0;
+              axes [ 7 ] = 0.0;
+            }
+            else
+            {
+              /* This is the contentious bit: how to convert angle to X/Y.
+               *    wk: I know of no define for PI that we could use here:
+               *    SG_PI would pull in sg, M_PI is undefined for MSVC
+               * But the accuracy of the value of PI is very unimportant at
+               * this point.
+               */
+              float s = (float) sin ( ( joy->js.dwPOV & 0xFFFF ) * ( 0.01 * 3.1415926535f / 180.0f ) );
+              float c = (float) cos ( ( joy->js.dwPOV & 0xFFFF ) * ( 0.01 * 3.1415926535f / 180.0f ) );
+
+              /* Convert to coordinates on a square so that North-East
+               * is (1,1) not (.7,.7), etc.
+               * s and c cannot both be zero so we won't divide by zero.
+               */
+              if ( fabs ( s ) < fabs ( c ) )
+              {
+                axes [ 6 ] = ( c < 0.0 ) ? -s/c  : s/c ;
+                axes [ 7 ] = ( c < 0.0 ) ? -1.0f : 1.0f;
+              }
+              else
+              {
+                axes [ 6 ] = ( s < 0.0 ) ? -1.0f : 1.0f;
+                axes [ 7 ] = ( s < 0.0 ) ? -c/s  : c/s ;
+              }
+            }
+
         case 6: axes[5] = (float) joy->js.dwVpos;
         case 5: axes[4] = (float) joy->js.dwUpos;
         case 4: axes[3] = (float) joy->js.dwRpos;
@@ -207,34 +596,110 @@ static void fghJoystickRawRead( SFG_Joystick* joy, int* buttons, float* axes )
         case 1: axes[0] = (float) joy->js.dwXpos;
         }
     }
-#else
-#   ifdef JS_NEW
+#endif
+
+#if TARGET_HOST_UNIX_X11
+#    if defined(__FreeBSD__) || defined(__NetBSD__)
+    if ( joy->os->is_analog )
+    {
+        int status = read ( joy->os->fd, &joy->os->ajs, sizeof(joy->os->ajs) );
+        if ( status != sizeof(joy->os->ajs) ) {
+            perror ( joy->os->fname );
+            joy->error = GL_TRUE;
+            return;
+        }
+        if ( buttons != NULL )
+            *buttons = ( joy->os->ajs.b1 ? 1 : 0 ) | ( joy->os->ajs.b2 ? 2 : 0 );
+
+        if ( axes != NULL )
+        {
+            axes[0] = (float) joy->os->ajs.x;
+            axes[1] = (float) joy->os->ajs.y;
+        }
+
+        return;
+    }
 
-    while( 1 )
+#        ifdef HAVE_USB_JS
+    while ( ( len = read ( joy->os->fd, joy->os->hid_data_buf, joy->os->hid_dlen ) ) == joy->os->hid_dlen )
     {
-        status = read( joy->fd, &joy->js, sizeof(struct js_event) );
+        struct hid_item *h;
 
-        if( status != sizeof( struct js_event ) )
+        for  ( h = joy->os->hids; h; h = h->next )
         {
-            if( errno == EAGAIN )
+            int d = hid_get_data ( joy->os->hid_data_buf, h );
+
+            int page = HID_PAGE ( h->usage );
+            int usage = HID_USAGE ( h->usage );
+
+            if ( page == HUP_GENERIC_DESKTOP )
+            {
+                int i;
+                for ( i = 0; i < joy->num_axes; i++ )
+                    if (joy->os->axes_usage[i] == usage)
+                    {
+                        if (usage == HUG_HAT_SWITCH)
+                        {
+                            if (d < 0 || d > 8)
+                                d = 0;  /* safety */
+                            joy->os->cache_axes[i] = (float)hatmap_x[d];
+                            joy->os->cache_axes[i + 1] = (float)hatmap_y[d];
+                        }
+                        else
+                        {
+                            joy->os->cache_axes[i] = (float)d;
+                        }
+                        break;
+                    }
+            }
+            else if (page == HUP_BUTTON)
             {
-                /*
-                 * Use the old values
-                 */
-                if( buttons )
+               if (usage > 0 && usage < _JS_MAX_BUTTONS + 1)
+               {
+                   if (d)
+                       joy->os->cache_buttons |= (1 << usage - 1);
+                   else
+                       joy->os->cache_buttons &= ~(1 << usage - 1);
+               }
+            }
+        }
+    }
+    if ( len < 0 && errno != EAGAIN )
+    {
+        perror( joy->os->fname );
+        joy->error = 1;
+    }
+    if ( buttons != NULL ) *buttons = joy->os->cache_buttons;
+    if ( axes    != NULL )
+        memcpy ( axes, joy->os->cache_axes, sizeof(float) * joy->num_axes );
+#        endif
+#    endif
+
+#    ifdef JS_NEW
+
+    while ( 1 )
+    {
+        status = read ( joy->fd, &joy->js, sizeof(struct js_event) );
+
+        if ( status != sizeof( struct js_event ) )
+        {
+            if ( errno == EAGAIN )
+            {
+                /* Use the old values */
+                if ( buttons )
                     *buttons = joy->tmp_buttons;
-                if( axes )
+                if ( axes )
                     memcpy( axes, joy->tmp_axes,
                             sizeof( float ) * joy->num_axes );
                 return;
             }
 
-            fgWarning( "%s", joy->fname );
+            fgWarning ( "%s", joy->fname );
             joy->error = GL_TRUE;
             return;
         }
 
-        switch( joy->js.type & ~JS_EVENT_INIT )
+        switch ( joy->js.type & ~JS_EVENT_INIT )
         {
         case JS_EVENT_BUTTON:
             if( joy->js.value == 0 ) /* clear the flag */
@@ -244,40 +709,54 @@ static void fghJoystickRawRead( SFG_Joystick* joy, int* buttons, float* axes )
             break;
 
         case JS_EVENT_AXIS:
-            joy->tmp_axes[ joy->js.number ] = ( float )joy->js.value;
-            
-            if( axes )
-                memcpy( axes, joy->tmp_axes, sizeof(float) * joy->num_axes );
+            if ( joy->js.number < joy->num_axes )
+            {
+                joy->tmp_axes[ joy->js.number ] = ( float )joy->js.value;
+
+                if( axes )
+                    memcpy( axes, joy->tmp_axes, sizeof(float) * joy->num_axes );
+            }
             break;
+
+        default:
+            fgWarning ( "%s", "PLIB_JS: Unrecognised /dev/js return!?!" );
+
+            /* use the old values */
+
+            if ( buttons != NULL ) *buttons = joy->tmp_buttons;
+            if ( axes    != NULL )
+                memcpy ( axes, joy->tmp_axes, sizeof(float) * num_axes );
+
+            return;
         }
 
         if( buttons )
             *buttons = joy->tmp_buttons;
     }
-#   else
+#    else
 
     status = read( joy->fd, &joy->js, JS_RETURN );
 
-    if( status != JS_RETURN )
+    if ( status != JS_RETURN )
     {
         fgWarning( "%s", joy->fname );
         joy->error = GL_TRUE;
         return;
     }
 
-    if( buttons )
-#       if defined( __FreeBSD__ ) || defined( __NetBSD__ )
-        *buttons = ( joy->js.b1 ? 1 : 0 ) | ( joy->js.b2 ? 2 : 0 );
-#       else
+    if ( buttons )
+#        if defined( __FreeBSD__ ) || defined( __NetBSD__ )
+        *buttons = ( joy->js.b1 ? 1 : 0 ) | ( joy->js.b2 ? 2 : 0 );  /* XXX Should not be here -- BSD is handled earlier */
+#        else
         *buttons = joy->js.buttons;
-#       endif
+#        endif
 
-    if( axes )
+    if ( axes )
     {
         axes[ 0 ] = (float) joy->js.x;
         axes[ 1 ] = (float) joy->js.y;
     }
-#   endif
+#    endif
 #endif
 }
 
@@ -334,158 +813,662 @@ static void fghJoystickRead( SFG_Joystick* joy, int* buttons, float* axes )
             *buttons = 0;
 
         if( axes )
-            for ( i=0; i<joy->num_axes ; i++ )
-                axes[ i ] = 0.0f ;
+            for ( i=0; i<joy->num_axes; i++ )
+                axes[ i ] = 0.0f;
     }
 
     fghJoystickRawRead( joy, buttons, raw_axes );
 
     if( axes )
-        for( i=0 ; i<joy->num_axes ; i++ )
+        for( i=0; i<joy->num_axes; i++ )
             axes[ i ] = fghJoystickFudgeAxis( joy, raw_axes[ i ], i );
 }
 
 /*
  * Happy happy happy joy joy joy (happy new year toudi :D)
  */
-static void fghJoystickOpen( SFG_Joystick* joy )
+
+
+#if TARGET_HOST_MAC_OSX
+/** open the IOKit connection, enumerate all the HID devices, add their
+interface references to the static array. We then use the array index
+as the device number when we come to open() the joystick. */
+static int fghJoystickFindDevices ( SFG_Joystick *joy, mach_port_t masterPort )
+{
+    CFMutableDictionaryRef hidMatch = NULL;
+    IOReturn rv = kIOReturnSuccess;
+
+    io_iterator_t hidIterator;
+    io_object_t ioDev;
+
+    /* build a dictionary matching HID devices */
+    hidMatch = IOServiceMatching(kIOHIDDeviceKey);
+
+    rv = IOServiceGetMatchingServices(masterPort, hidMatch, &hidIterator);
+    if (rv != kIOReturnSuccess || !hidIterator) {
+      fgWarning ( "%s", "no joystick (HID) devices found");
+      return;
+    }
+
+    /* iterate */
+    while ((ioDev = IOIteratorNext(hidIterator))) {
+        /* filter out keyboard and mouse devices */
+        CFDictionaryRef properties = getCFProperties(ioDev);
+        long usage, page;
+
+        CFTypeRef refPage = CFDictionaryGetValue (properties, CFSTR(kIOHIDPrimaryUsagePageKey));
+        CFTypeRef refUsage = CFDictionaryGetValue (properties, CFSTR(kIOHIDPrimaryUsageKey));
+        CFNumberGetValue((CFNumberRef) refUsage, kCFNumberLongType, &usage);
+        CFNumberGetValue((CFNumberRef) refPage, kCFNumberLongType, &page);
+
+        /* keep only joystick devices */
+        if ( ( page == kHIDPage_GenericDesktop ) && (
+                            (usage == kHIDUsage_GD_Joystick)
+                         || (usage == kHIDUsage_GD_GamePad)
+                         || (usage == kHIDUsage_GD_MultiAxisController)
+                         || (usage == kHIDUsage_GD_Hatswitch) /* last two necessary ? */
+            /* add it to the array */
+            ioDevices[numDevices++] = ioDev;
+    }
+
+    IOObjectRelease(hidIterator);
+}
+
+static CFDictionaryRef fghJoystickGetCFProperties ( SFG_Joystick *joy, io_object_t ioDev )
+{
+    IOReturn rv;
+    CFMutableDictionaryRef cfProperties;
+
+#if 0  
+    /* comment copied from darwin/SDL_sysjoystick.c */
+    /* Mac OS X currently is not mirroring all USB properties to HID page so need to look at USB device page also
+     * get dictionary for usb properties: step up two levels and get CF dictionary for USB properties
+     */
+
+    io_registry_entry_t parent1, parent2;
+
+    rv = IORegistryEntryGetParentEntry (ioDev, kIOServicePlane, &parent1);
+    if (rv != kIOReturnSuccess) {
+        fgWarning ( "%s", "error getting device entry parent");
+        return NULL;
+    }
+
+    rv = IORegistryEntryGetParentEntry (parent1, kIOServicePlane, &parent2);
+    if (rv != kIOReturnSuccess) {
+        fgWarning ( "%s", "error getting device entry parent 2");
+        return NULL;
+    }
+#endif
+
+    rv = IORegistryEntryCreateCFProperties( ioDev /*parent2*/, 
+        &cfProperties, kCFAllocatorDefault, kNilOptions);
+    if (rv != kIOReturnSuccess || !cfProperties) {
+        fgWarning ( "%s", "error getting device properties");
+        return NULL;
+    }
+
+    return cfProperties;
+}
+
+static void fghJoystickElementEnumerator ( SFG_Joystick *joy, void *element, void* vjs )
 {
+      if (CFGetTypeID((CFTypeRef) element) != CFDictionaryGetTypeID()) {
+            fgError ( "%s", "element enumerator passed non-dictionary value");
+            return;
+    }
+
+      static_cast<jsJoystick*>(vjs)->parseElement ( (CFDictionaryRef) element );        
+}
+
+/** element enumerator function : pass NULL for top-level*/
+static void fghJoystickEnumerateElements ( SFG_Joystick *joy, CFTypeRef element )
+{
+      assert(CFGetTypeID(element) == CFArrayGetTypeID());
+
+      CFRange range = {0, CFArrayGetCount ((CFArrayRef)element)};
+      CFArrayApplyFunction((CFArrayRef) element, range, 
+            &fghJoystickElementEnumerator, joy );
+}
+
+static void fghJoystickParseElement ( SFG_Joystick *joy, CFDictionaryRef element )
+{
+    CFTypeRef refPage = CFDictionaryGetValue ((CFDictionaryRef) element, CFSTR(kIOHIDElementUsagePageKey));
+    CFTypeRef refUsage = CFDictionaryGetValue ((CFDictionaryRef) element, CFSTR(kIOHIDElementUsageKey));
+
+    long type, page, usage;
+
+    CFNumberGetValue((CFNumberRef)
+        CFDictionaryGetValue ((CFDictionaryRef) element, CFSTR(kIOHIDElementTypeKey)), 
+        kCFNumberLongType, &type);
+
+    switch ( typ e) {
+    case kIOHIDElementTypeInput_Misc:
+    case kIOHIDElementTypeInput_Axis:
+    case kIOHIDElementTypeInput_Button:
+        printf("got input element...");
+        CFNumberGetValue( (CFNumberRef) refUsage, kCFNumberLongType, &usage );
+        CFNumberGetValue( (CFNumberRef) refPage, kCFNumberLongType, &page );
+
+        if (page == kHIDPage_GenericDesktop) {    
+            switch ( usage ) /* look at usage to determine function */
+            {
+            case kHIDUsage_GD_X:
+            case kHIDUsage_GD_Y:
+            case kHIDUsage_GD_Z:
+            case kHIDUsage_GD_Rx:
+            case kHIDUsage_GD_Ry:
+            case kHIDUsage_GD_Rz:
+            case kHIDUsage_GD_Slider: /* for throttle / trim controls */
+                printf(" axis\n");
+                fghJoystickAddAxisElement((CFDictionaryRef) element);
+                break;
+
+            case kHIDUsage_GD_Hatswitch:
+                printf(" hat\n");
+                fghJoystickAddHatElement((CFDictionaryRef) element);
+                break;
+
+            default:
+                printf("input type element has weird usage (%x)\n", usage);
+                break;
+            }
+        } else if (page == kHIDPage_Button) {
+            printf(" button\n");
+            fghJoystickAddButtonElement((CFDictionaryRef) element);
+        } else
+            printf("input type element has weird page (%x)\n", page);
+        break;
+
+    case kIOHIDElementTypeCollection:
+        fghJoystickEnumerateElements (
+            CFDictionaryGetValue ( element, CFSTR(kIOHIDElementKey) )
+        );
+        break;
+
+    default:
+        break;
+    }  
+}
+
+static void fghJoystickAddAxisElement ( SFG_Joystick *joy, CFDictionaryRef axis )
+{
+    long cookie, lmin, lmax;
+    int index = joy->num_axes++;
+
+    CFNumberGetValue ((CFNumberRef)
+        CFDictionaryGetValue ( axis, CFSTR(kIOHIDElementCookieKey) ), 
+        kCFNumberLongType, &cookie);
+
+    axisCookies[index] = (IOHIDElementCookie) cookie;
+
+    CFNumberGetValue ((CFNumberRef)
+        CFDictionaryGetValue ( axis, CFSTR(kIOHIDElementMinKey) ), 
+        kCFNumberLongType, &lmin);
+
+    CFNumberGetValue ((CFNumberRef)
+        CFDictionaryGetValue ( axis, CFSTR(kIOHIDElementMaxKey) ), 
+        kCFNumberLongType, &lmax);
+
+    joy->min[index] = lmin;
+    joy->max[index] = lmax;
+    joy->dead_band[index] = 0.0;
+    joy->saturate[index] = 1.0;
+    joy->center[index] = (lmax + lmin) * 0.5;
+}
+
+static void fghJoystickAddButtonElement ( SFG_Joystick *joy, CFDictionaryRef button )
+{
+    long cookie;
+    CFNumberGetValue ((CFNumberRef)
+            CFDictionaryGetValue ( button, CFSTR(kIOHIDElementCookieKey) ), 
+            kCFNumberLongType, &cookie);
+
+    joy->buttonCookies[num_buttons++] = (IOHIDElementCookie) cookie;
+    /* anything else for buttons? */
+}
+
+static void fghJoystickAddHatElement ( SFG_Joystick *joy, CFDictionaryRef button )
+{
+    /* hatCookies[num_hats++] = (IOHIDElementCookie) cookie; */
+    /* do we map hats to axes or buttons? */
+}
+#endif
+
 #if TARGET_HOST_WIN32
-    JOYCAPS jsCaps;
-    int     i;
+/* Inspired by
+   http://msdn.microsoft.com/archive/en-us/dnargame/html/msdn_sidewind3d.asp
+ */
+
+static int fghJoystickGetOEMProductName ( SFG_Joystick* joy, char *buf, int buf_sz )
+{
+    char buffer [ 256 ];
+
+    char OEMKey [ 256 ];
+
+    HKEY  hKey;
+    DWORD dwcb;
+    LONG  lr;
+
+    if ( joy->error )
+        return 0;
+
+    /* Open .. MediaResources\CurrentJoystickSettings */
+    sprintf ( buffer, "%s\\%s\\%s",
+              REGSTR_PATH_JOYCONFIG, joy->jsCaps.szRegKey,
+              REGSTR_KEY_JOYCURR );
+
+    lr = RegOpenKeyEx ( HKEY_LOCAL_MACHINE, buffer, 0, KEY_QUERY_VALUE, &hKey);
+
+    if ( lr != ERROR_SUCCESS ) return 0;
+
+    /* Get OEM Key name */
+    dwcb = sizeof(OEMKey);
+
+    /* JOYSTICKID1-16 is zero-based; registry entries for VJOYD are 1-based. */
+    sprintf ( buffer, "Joystick%d%s", joy->js_id + 1, REGSTR_VAL_JOYOEMNAME );
+
+    lr = RegQueryValueEx ( hKey, buffer, 0, 0, (LPBYTE) OEMKey, &dwcb);
+    RegCloseKey ( hKey );
+
+    if ( lr != ERROR_SUCCESS ) return 0;
+
+    /* Open OEM Key from ...MediaProperties */
+    sprintf ( buffer, "%s\\%s", REGSTR_PATH_JOYOEM, OEMKey );
+
+    lr = RegOpenKeyEx ( HKEY_LOCAL_MACHINE, buffer, 0, KEY_QUERY_VALUE, &hKey );
+
+    if ( lr != ERROR_SUCCESS ) return 0;
+
+    /* Get OEM Name */
+    dwcb = buf_sz;
+
+    lr = RegQueryValueEx ( hKey, REGSTR_VAL_JOYOEMNAME, 0, 0, (LPBYTE) buf,
+                             &dwcb );
+    RegCloseKey ( hKey );
+
+    if ( lr != ERROR_SUCCESS ) return 0;
+
+    return 1;
+}
+#endif
+
+
+static void fghJoystickOpen( SFG_Joystick* joy )
+{
+    int i;
+#if TARGET_HOST_MACINTOSH
+    OSStatus err;
+#endif
+#if TARGET_HOST_MAC_OSX
+        IOReturn rv;
+        SInt32 score;
+        IOCFPlugInInterface **plugin;
+
+        HRESULT pluginResult;
+
+        CFDictionaryRef props;
+    CFTypeRef topLevelElement;
+#endif
+#if TARGET_HOST_UNIX_X11
+#    if defined(__FreeBSD__) || defined(__NetBSD__)
+       char *cp;
+#    endif
+#    ifdef JS_NEW
+       unsigned char u;
+#    else
+       int counter;
+#    endif
+#endif
+
+    /* Default values (for no joystick -- each conditional will reset the error flag) */
+    joy->error = TRUE;
+    joy->num_axes = joy->num_buttons = 0;
+    joy->name [0] = '\0';
+
+#if TARGET_HOST_MACINTOSH
+    /*
+     * XXX FIXME: get joystick name in Mac
+     */
+
+    err = ISpStartup ();
+
+    if ( err == noErr )
+    {
+#define ISP_CHECK_ERR(x) if ( x != noErr ) { joy->error = GL_TRUE; return; }
+
+        joy->error = GL_TRUE;
+
+        /* initialize the needs structure */
+        ISpNeed temp_isp_needs[isp_num_needs] =
+        {
+          { "\pX-Axis",    128, 0, 0, kISpElementKind_Axis,   kISpElementLabel_None, 0, 0, 0, 0 },
+          { "\pY-Axis",    128, 0, 0, kISpElementKind_Axis,   kISpElementLabel_None, 0, 0, 0, 0 },
+          { "\pZ-Axis",    128, 0, 0, kISpElementKind_Axis,   kISpElementLabel_None, 0, 0, 0, 0 },
+          { "\pR-Axis",    128, 0, 0, kISpElementKind_Axis,   kISpElementLabel_None, 0, 0, 0, 0 },
+          { "\pAxis   4",  128, 0, 0, kISpElementKind_Axis,   kISpElementLabel_None, 0, 0, 0, 0 },
+          { "\pAxis   5",  128, 0, 0, kISpElementKind_Axis,   kISpElementLabel_None, 0, 0, 0, 0 },
+          { "\pAxis   6",  128, 0, 0, kISpElementKind_Axis,   kISpElementLabel_None, 0, 0, 0, 0 },
+          { "\pAxis   7",  128, 0, 0, kISpElementKind_Axis,   kISpElementLabel_None, 0, 0, 0, 0 },
+          { "\pAxis   8",  128, 0, 0, kISpElementKind_Axis,   kISpElementLabel_None, 0, 0, 0, 0 },
+
+          { "\pButton 0",  128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 1",  128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 2",  128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 3",  128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 4",  128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 5",  128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 6",  128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 7",  128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 8",  128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 9",  128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 10", 128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 11", 128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 12", 128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 13", 128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 14", 128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 15", 128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 16", 128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 17", 128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 18", 128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 19", 128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 20", 128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 21", 128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 22", 128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 23", 128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 24", 128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 25", 128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 26", 128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 27", 128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 28", 128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 29", 128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 30", 128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+          { "\pButton 31", 128, 0, 0, kISpElementKind_Button, kISpElementLabel_Btn_Select, 0, 0, 0, 0 },
+        };
+
+        memcpy ( joy->isp_needs, temp_isp_needs, sizeof(temp_isp_needs) );
+
+
+        /* next two calls allow keyboard and mouse to emulate other input
+         * devices (gamepads, joysticks, etc)
+         */
+        /*
+          err = ISpDevices_ActivateClass ( kISpDeviceClass_Keyboard );
+          ISP_CHECK_ERR(err)
+
+
+          err = ISpDevices_ActivateClass ( kISpDeviceClass_Mouse );
+          ISP_CHECK_ERR(err)
+        */
 
+        err = ISpElement_NewVirtualFromNeeds ( joy->isp_num_needs, joy->isp_needs, joy->isp_elem, 0 );
+        ISP_CHECK_ERR(err)
+
+        err = ISpInit ( joy->isp_num_needs, joy->isp_needs, joy->isp_elem, 'freeglut', nil, 0, 128, 0 );
+        ISP_CHECK_ERR(err)
+
+        joy->num_buttons = joy->isp_num_needs - joy->isp_num_axis;
+        joy->num_axes    = joy->isp_num_axis;
+
+        for ( i = 0; i < joy->num_axes; i++ )
+        {
+          joy->dead_band [ i ] = 0;
+          joy->saturate  [ i ] = 1;
+          joy->center    [ i ] = kISpAxisMiddle;
+          joy->max       [ i ] = kISpAxisMaximum;
+          joy->min       [ i ] = kISpAxisMinimum;
+        }
+
+        joy->error = GL_FALSE;
+    }
+    else
+        joy->num_buttons = joy->num_axes = 0;
+#endif
+
+#if TARGET_HOST_MAC_OSX
+    if (joy->id >= numDevices) {
+        fgWarning ( "%s", "device index out of range in fgJoystickOpen()");
+        return;
+    }
+
+    /* create device interface */
+    rv = IOCreatePlugInInterfaceForService ( ioDevices[joy->id], 
+                                             kIOHIDDeviceUserClientTypeID,
+                                             kIOCFPlugInInterfaceID,
+                                             &plugin, &score);
+
+    if (rv != kIOReturnSuccess) {
+        fgWarning ( "%s", "error creating plugin for io device");
+        return;
+    }
+
+    pluginResult = (*plugin)->QueryInterface ( plugin, 
+                    CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), &(LPVOID) joy->hidDev );
+
+    if ( pluginResult != S_OK )
+        fgWarning ( "%s", "QI-ing IO plugin to HID Device interface failed");
+
+    (*plugin)->Release(plugin); /* don't leak a ref */
+    if (joy->hidDev == NULL) return;
+
+    /* store the interface in this instance */
+    rv = (*(joy->hidDev))->open(joy->hidDev, 0);
+    if (rv != kIOReturnSuccess) {
+        fgWarning ( "%s", "error opening device interface");
+        return;
+    }
+
+    props = getCFProperties(ioDevices[joy->id]);
+
+    /* recursively enumerate all the bits */
+    CFTypeRef topLevelElement = 
+                      CFDictionaryGetValue ( props, CFSTR ( kIOHIDElementKey ) );
+    enumerateElements ( topLevelElement );
+
+    CFRelease ( props );
+#endif
+
+#if TARGET_HOST_WIN32
     joy->js.dwFlags = JOY_RETURNALL;
     joy->js.dwSize  = sizeof( joy->js );
 
-    memset( &jsCaps, 0, sizeof( jsCaps ) );
+    memset( &joy->jsCaps, 0, sizeof( joy->jsCaps ) );
 
     joy->error =
-        ( joyGetDevCaps( joy->js_id, &jsCaps, sizeof( jsCaps ) ) !=
+        ( joyGetDevCaps( joy->js_id, &joy->jsCaps, sizeof( joy->jsCaps ) ) !=
           JOYERR_NOERROR );
-    joy->num_axes =
-        ( jsCaps.wNumAxes < _JS_MAX_AXES ) ? jsCaps.wNumAxes : _JS_MAX_AXES;
 
-    /*
-     * WARNING - Fall through case clauses!!
-     */
-    switch( joy->num_axes )
+    if ( joy->jsCaps.wNumAxes == 0 )
     {
-    case 6:
-        joy->min[ 5 ] = (float) jsCaps.wVmin;
-        joy->max[ 5 ] = (float) jsCaps.wVmax;
-    case 5:
-        joy->min[ 4 ] = (float) jsCaps.wUmin;
-        joy->max[ 4 ] = (float) jsCaps.wUmax;
-    case 4:
-        joy->min[ 3 ] = (float) jsCaps.wRmin;
-        joy->max[ 3 ] = (float) jsCaps.wRmax;
-    case 3:
-        joy->min[ 2 ] = (float) jsCaps.wZmin;
-        joy->max[ 2 ] = (float) jsCaps.wZmax;
-    case 2:
-        joy->min[ 1 ] = (float) jsCaps.wYmin;
-        joy->max[ 1 ] = (float) jsCaps.wYmax;
-    case 1:
-        joy->min[ 0 ] = (float) jsCaps.wXmin;
-        joy->max[ 0 ] = (float) jsCaps.wXmax;
-        break;
-
-    /*
-     * I guess we have no axes at all
-     */
-    default:
+        joy->num_axes = 0;
         joy->error = GL_TRUE;
-        break;
+    }
+    else
+    {
+        /* Device name from jsCaps is often "Microsoft PC-joystick driver",
+         * at least for USB.  Try to get the real name from the registry.
+         */
+        if ( ! fghJoystickGetOEMProductName ( joy, joy->name, sizeof(joy->name) ) )
+        {
+            fgWarning ( "%s", "JS: Failed to read joystick name from registry" );
+            strncpy ( joy->name, joy->jsCaps.szPname, sizeof(joy->name) );
+        }
+
+        /* Windows joystick drivers may provide any combination of
+         * X,Y,Z,R,U,V,POV - not necessarily the first n of these.
+         */
+        if ( joy->jsCaps.wCaps & JOYCAPS_HASPOV )
+        {
+            joy->num_axes = _JS_MAX_AXES;
+            joy->min [ 7 ] = -1.0; joy->max [ 7 ] = 1.0;  /* POV Y */
+            joy->min [ 6 ] = -1.0; joy->max [ 6 ] = 1.0;  /* POV X */
+        }
+        else
+            joy->num_axes = 6;
+
+        joy->min[ 5 ] = (float) joy->jsCaps.wVmin;
+        joy->max[ 5 ] = (float) joy->jsCaps.wVmax;
+        joy->min[ 4 ] = (float) joy->jsCaps.wUmin;
+        joy->max[ 4 ] = (float) joy->jsCaps.wUmax;
+        joy->min[ 3 ] = (float) joy->jsCaps.wRmin;
+        joy->max[ 3 ] = (float) joy->jsCaps.wRmax;
+        joy->min[ 2 ] = (float) joy->jsCaps.wZmin;
+        joy->max[ 2 ] = (float) joy->jsCaps.wZmax;
+        joy->min[ 1 ] = (float) joy->jsCaps.wYmin;
+        joy->max[ 1 ] = (float) joy->jsCaps.wYmax;
+        joy->min[ 0 ] = (float) joy->jsCaps.wXmin;
+        joy->max[ 0 ] = (float) joy->jsCaps.wXmax;
     }
 
     /*
      * Guess all the rest judging on the axes extremals
      */
-    for( i=0 ; i<joy->num_axes ; i++ )
+    for( i = 0; i < joy->num_axes; i++ )
     {
         joy->center   [ i ] = (joy->max[i] + joy->min[i]) * 0.5f;
         joy->dead_band[ i ] = 0.0f;
         joy->saturate [ i ] = 1.0f;
     }
+#endif
 
-#else
-#   ifdef __FreeBSD__
-    int   buttons[ _JS_MAX_AXES ];
-    float axes[ _JS_MAX_AXES ];
-    int   noargs, in_no_axes;
-    char  joyfname[ 1024 ];
-    FILE* joyfile;
-#   else
-#       ifndef JS_NEW
-    int counter;
-#       endif
-#   endif
-    int i;
+#if TARGET_HOST_UNIX_X11
+#if defined(__FreeBSD__) || defined(__NetBSD__)
+    for ( i = 0; i < _JS_MAX_AXES; i++ )
+        joy->os->cache_axes [ i ] = 0.0f;
 
-    /*
-     * Default for older Linux systems.
-     */
-    joy->num_axes    =  2;
-    joy->num_buttons = 32;
+    joy->os->cache_buttons = 0;
 
-#   ifdef JS_NEW
-    for( i=0 ; i<_JS_MAX_AXES ; i++ )
-        joy->tmp_axes[ i ] = 0.0f ;
+    joy->os->fd = open ( joy->os->fname, O_RDONLY | O_NONBLOCK);
 
-    joy->tmp_buttons = 0 ;
-#   endif
+    if ( joy->os->fd < 0 && errno == EACCES)
+      fgWarning ( "%s exists but is not readable by you\n", joy->os->fname );
 
-    joy->fd = open( joy->fname, O_RDONLY );
+    joy->error = ( joy->os->fd < 0 );
 
-    joy->error = (joy->fd < 0);
+    if ( joy->error )
+      return;
 
-    if( joy->error )
-        return;
-/*
- * XXX All BSDs should share this?
- */
-#   ifdef __FreeBSD__
-    fghJoystickRawRead(joy, buttons, axes );
-    joy->error = axes[ 0 ] < -1000000000.0f;
-    if( joy->error )
-        return;
+    joy->num_axes = 0;
+    joy->num_buttons = 0;
+    if ( joy->os->is_analog )
+    {
+        FILE *joyfile;
+        char joyfname [ 1024 ];
+        int noargs, in_no_axes;
 
-    sprintf( joyfname, "%s/.joy%drc", getenv( "HOME" ), joy->id );
+        float axes  [ _JS_MAX_AXES ];
+        int buttons [ _JS_MAX_AXES ];
 
-    joyfile = fopen( joyfname, "r" );
-    joy->error =( joyfile == NULL );
-    if( joy->error )
-      return;
+        joy->num_axes    =  2;
+        joy->num_buttons = 32;
 
-    noargs = fscanf(
-        joyfile, "%d%f%f%f%f%f%f",
-        &in_no_axes,
-        &joy->min[ 0 ], &joy->center[ 0 ], &joy->max[ 0 ],
-        &joy->min[ 1 ], &joy->center[ 1 ], &joy->max[ 1 ]
-    );
+        fghJoystickRawRead ( joy, buttons, axes );
+        joy->error = axes[0] < -1000000000.0f;
+        if ( joy->error )
+          return;
 
-    joy->error =( noargs != 7 ) || ( in_no_axes != _JS_MAX_AXES );
-    fclose( joyfile );
-    if( joy->error )
+        sprintf( joyfname, "%s/.joy%drc", getenv ( "HOME" ), joy->id );
+
+        joyfile = fopen ( joyfname, "r" );
+        joy->error = ( joyfile == NULL );
+        if ( joy->error )
+          return;
+
+        noargs = fscanf ( joyfile, "%d%f%f%f%f%f%f", &in_no_axes,
+                          &joy->min [ 0 ], &joy->center [ 0 ], &joy->max [ 0 ],
+                          &joy->min [ 1 ], &joy->center [ 1 ], &joy->max [ 1 ] );
+        joy->error = noargs != 7 || in_no_axes != _JS_MAX_AXES;
+        fclose ( joyfile );
+        if ( joy->error )
+          return;
+
+        for ( i = 0; i < _JS_MAX_AXES; i++ )
+        {
+            joy->dead_band [ i ] = 0.0f;
+            joy->saturate  [ i ] = 1.0f;
+        }
+
+        return;    /* End of analog code */
+    }
+
+#    ifdef HAVE_USB_JS
+    if( ! fghJoystickInitializeHID( joy->os, &joy->num_axes,
+                                    &joy->num_buttons ) )
+    {
+        close ( joy->os->fd );
+        joy->error = GL_TRUE;
         return;
+    }
 
-    for( i = 0; i < _JS_MAX_AXES; i++ )
+    cp = strrchr(joy->os->fname, '/');
+    if ( cp ) {
+        if ( fghJoystickFindUSBdev ( &cp[1], joy->name, sizeof(joy->name) ) == 0 )
+            strcpy ( joy->name, &cp[1] );
+    }
+
+    if ( joy->num_axes > _JS_MAX_AXES )
+        joy->num_axes = _JS_MAX_AXES;
+
+    for ( i = 0; i < _JS_MAX_AXES; i++ )
     {
-        joy->dead_band[ i ] = 0.0f;
-        joy->saturate [ i ] = 1.0f;
+        /* We really should get this from the HID, but that data seems
+         * to be quite unreliable for analog-to-USB converters. Punt for
+         * now.
+         */
+        if ( joy->os->axes_usage [ i ] == HUG_HAT_SWITCH )
+        {
+            joy->max       [ i ] = 1.0f;
+            joy->center    [ i ] = 0.0f;
+            joy->min       [ i ] = -1.0f;
+        }
+        else
+        {
+            joy->max       [ i ] = 255.0f;
+            joy->center    [ i ] = 127.0f;
+            joy->min       [ i ] = 0.0f;
+        }
+
+        joy->dead_band [ i ] = 0.0f;
+        joy->saturate  [ i ] = 1.0f;
     }
-#   else
+#    endif
+#endif
 
+#if defined(__linux__)
     /*
-     * Set the correct number of axes for the linux driver
+     * Default for older Linux systems.
      */
-#       ifdef JS_NEW
+    joy->num_axes    =  2;
+    joy->num_buttons = 32;
 
-    ioctl( joy->fd, JSIOCGAXES   , &joy->num_axes    );
-    ioctl( joy->fd, JSIOCGBUTTONS, &joy->num_buttons );
-    fcntl( joy->fd, F_SETFL, O_NONBLOCK );
+#    ifdef JS_NEW
+    for( i = 0; i < _JS_MAX_AXES; i++ )
+        joy->tmp_axes[ i ] = 0.0f;
+
+    joy->tmp_buttons = 0;
+#    endif
+
+    joy->fd = open( joy->fname, O_RDONLY );
+
+    joy->error = (joy->fd < 0);
+
+    if ( joy->error )
+        return;
 
-#       endif
+    /*
+     * Set the correct number of axes for the linux driver
+     */
+#    ifdef JS_NEW
+    /* Melchior Franz's fixes for big-endian Linuxes since writing 
+     *  to the upper byte of an uninitialized word doesn't work. 
+     *  9 April 2003 
+     */
+    ioctl ( joy->fd, JSIOCGAXES   , &u ); 
+    joy->num_axes = u;
+    ioctl ( joy->fd, JSIOCGBUTTONS, &u );
+    joy->num_buttons = u;
+    ioctl ( joy->fd, JSIOCGNAME ( sizeof(joy->name) ), joy->name );
+    fcntl ( joy->fd, F_SETFL      , O_NONBLOCK   );
+#    endif
 
     /*
      * The Linux driver seems to return 512 for all axes
@@ -495,7 +1478,7 @@ static void fghJoystickOpen( SFG_Joystick* joy )
      *
      * PWO: shouldn't be that done somehow wiser on the kernel level?
      */
-#       ifndef JS_NEW
+#    ifndef JS_NEW
     counter = 0;
 
     do
@@ -506,68 +1489,130 @@ static void fghJoystickOpen( SFG_Joystick* joy )
              counter < 100 &&
              joy->center[ 0 ] == 512.0f &&
              joy->center[ 1 ] == 512.0f );
-   
-    if( counter >= 100 )
+
+    if ( counter >= 100 )
         joy->error = GL_TRUE;
-#       endif
+#    endif
 
-    for( i = 0; i < _JS_MAX_AXES; i++ )
+    for ( i = 0; i < _JS_MAX_AXES; i++ )
     {
-#       ifdef JS_NEW
+#    ifdef JS_NEW
         joy->max   [ i ] =  32767.0f;
         joy->center[ i ] =      0.0f;
         joy->min   [ i ] = -32767.0f;
-#       else
+#    else
         joy->max[ i ] = joy->center[ i ] * 2.0f;
         joy->min[ i ] = 0.0f;
-#       endif
-        joy->dead_band[ i ] = 0.0f ;
-        joy->saturate [ i ] = 1.0f ;
+#    endif
+        joy->dead_band[ i ] = 0.0f;
+        joy->saturate [ i ] = 1.0f;
     }
-#   endif
+#endif
 #endif
 }
 
 /*
- *
+ * This function replaces the constructor method in the JS library.
  */
 void fgJoystickInit( int ident )
 {
-    if( fgJoystick )
-        fgError( "illegal attemp to initialize joystick device" );
+    if ( ident >= MAX_NUM_JOYSTICKS )
+      fgError( "Too large a joystick number" );
+
+    if( fgJoystick[ident] )
+        fgError( "illegal attempt to initialize joystick device" );
+
+    fgJoystick[ident] = ( SFG_Joystick * )calloc( sizeof( SFG_Joystick ), 1 );
+
+    /* Set defaults */
+    fgJoystick[ident]->num_axes = fgJoystick[ident]->num_buttons = 0;
+    fgJoystick[ident]->error = GL_TRUE;
 
-    fgJoystick = ( SFG_Joystick * )calloc( sizeof( SFG_Joystick ), 1 );
+#if TARGET_HOST_MACINTOSH
+    fgJoystick[ident]->id = ident;
+    sprintf ( fgJoystick[ident]->fname, "/dev/js%d", ident ); /* FIXME */
+    fgJoystick[ident]->error = GL_FALSE;
+#endif
+
+#if TARGET_HOST_MAC_OSX
+    fgJoystick[ident]->id = ident;
+    fgJoystick[ident]->error = GL_FALSE;
+    fgJoystick[ident]->num_axes = 0;
+    fgJoystick[ident]->num_buttons = 0;
+
+    if (numDevices < 0) {
+        /* do first-time init (since we can't over-ride jsInit, hmm */
+        numDevices = 0;
+
+        mach_port_t masterPort;
+        IOReturn rv = IOMasterPort ( bootstrap_port, &masterPort );
+        if ( rv != kIOReturnSuccess ) {
+            fgWarning ( "%s", "error getting master Mach port");
+            return;
+        }
+        fghJoystickFindDevices ( masterPort );
+    }
+
+    if ( ident >= numDevices ) {
+        fgJoystick[ident]->error = GL_TRUE;
+        return;
+    }
+
+    /* get the name now too */
+    CFDictionaryRef properties = getCFProperties(ioDevices[ident]);
+    CFTypeRef ref = CFDictionaryGetValue (properties, CFSTR(kIOHIDProductKey));
+    if (!ref)
+      ref = CFDictionaryGetValue (properties, CFSTR("USB Product Name"));
+
+    if (!ref || !CFStringGetCString ((CFStringRef) ref, name, 128, CFStringGetSystemEncoding ())) {
+      fgWarning ( "%s", "error getting device name");
+      name[0] = '\0';
+    }
+#endif
 
 #if TARGET_HOST_WIN32
     switch( ident )
     {
     case 0:
-        fgJoystick->js_id = JOYSTICKID1;
-        fghJoystickOpen( fgJoystick );
+        fgJoystick[ident]->js_id = JOYSTICKID1;
+        fgJoystick[ident]->error = GL_FALSE;
         break;
     case 1:
-        fgJoystick->js_id = JOYSTICKID2;
-        fghJoystickOpen( fgJoystick );
+        fgJoystick[ident]->js_id = JOYSTICKID2;
+        fgJoystick[ident]->error = GL_FALSE;
         break;
     default:
-        fgJoystick->num_axes = 0;
-        fgJoystick->error = GL_TRUE;
-        break;
+        fgJoystick[ident]->num_axes = 0;
+        fgJoystick[ident]->error = GL_TRUE;
+        return;
     }
-#else
+#endif
 
-/*
- * XXX All BSDs should share this code?
- */
-#   ifdef __FreeBSD__
-    fgJoystick->id = ident;
-    sprintf( fgJoystick->fname, "/dev/joy%d", ident );
-#   else
-    sprintf( fgJoystick->fname, "/dev/js%d", ident );
-#   endif
+#if TARGET_HOST_UNIX_X11
+#    if defined(__FreeBSD__) || defined(__NetBSD__)
+    fgJoystick[ident]->id = ident;
+    fgJoystick[ident]->error = GL_FALSE;
+
+    fgJoystick[ident]->os = calloc (1, sizeof (struct os_specific_s));
+    memset ( fgJoystick[ident]->os, 0, sizeof(struct os_specific_s) );
+    if (ident < USB_IDENT_OFFSET)
+        fgJoystick[ident]->os->is_analog = 1;
+    if (fgJoystick[ident]->os->is_analog)
+        sprintf ( fgJoystick[ident]->os->fname, "%s%d", AJSDEV, ident );
+    else
+        sprintf ( fgJoystick[ident]->os->fname, "%s%d", UHIDDEV, ident - USB_IDENT_OFFSET );
+#    elif defined(__linux__)
+    fgJoystick[ident]->id = ident;
+    fgJoystick[ident]->error = GL_FALSE;
 
-    fghJoystickOpen( fgJoystick );
+    sprintf ( fgJoystick[ident]->fname, "/dev/input/js%d", ident );
+
+    if ( access ( fgJoystick[ident]->fname, F_OK ) != 0 )
+        sprintf ( fgJoystick[ident]->fname, "/dev/js%d", ident );
+#    endif
 #endif
+
+    fghJoystickOpen ( fgJoystick[ident] );
 }
 
 /*
@@ -575,16 +1620,50 @@ void fgJoystickInit( int ident )
  */
 void fgJoystickClose( void )
 {
-    if( !fgJoystick )
-        fgError( "illegal attempt to deinitialize joystick device" );
+    int ident ;
+    for ( ident = 0; ident < MAX_NUM_JOYSTICKS; ident++ )
+    {
+        if ( fgJoystick[ident] )
+        {
+
+#if TARGET_HOST_MACINTOSH
+            ISpSuspend  ();
+            ISpStop     ();
+            ISpShutdown ();
+#endif
+
+#if TARGET_HOST_MAC_OSX
+            (*(fgJoystick[ident]->hidDev))->close(fgJoystick[ident]->hidDev);
+#endif
+
+#if TARGET_HOST_WIN32
+            /* Do nothing special */
+#endif
+
+#if TARGET_HOST_UNIX_X11
+#if defined(__FreeBSD__) || defined(__NetBSD__)
+            if ( fgJoystick[ident]->os )
+            {
+                if ( ! fgJoystick[ident]->error )
+                    close ( fgJoystick[ident]->os->fd );
+#ifdef HAVE_USB_JS
+                if ( fgJoystick[ident]->os->hids )
+                    free (fgJoystick[ident]->os->hids);
+                if ( fgJoystick[ident]->os->hid_data_buf )
+                    free (fgJoystick[ident]->os->hid_data_buf);
+#endif
+                free (fgJoystick[ident]->os);
+            }
+#endif
 
-#if !TARGET_HOST_WIN32
-    if( ! fgJoystick->error )
-        close( fgJoystick->fd );
+            if( ! fgJoystick[ident]->error )
+                close( fgJoystick[ident]->fd );
 #endif
 
-    free( fgJoystick );
-    fgJoystick = NULL;  /* show joystick has been deinitialized */
+            free( fgJoystick[ident] );
+            fgJoystick[ident] = NULL;  /* show joystick has been deinitialized */
+        }
+    }
 }
 
 /*
@@ -595,64 +1674,59 @@ void fgJoystickPollWindow( SFG_Window* window )
 {
     float axes[ _JS_MAX_AXES ];
     int buttons;
+    int ident;
 
-    freeglut_return_if_fail( fgJoystick );
     freeglut_return_if_fail( window );
     freeglut_return_if_fail( FETCH_WCB( *window, Joystick ) );
 
-    fghJoystickRead( fgJoystick, &buttons, axes );
-
-    INVOKE_WCB( *window, Joystick,
-                ( buttons,
-                  (int) (axes[ 0 ] * 1000.0f ),
-                  (int) (axes[ 1 ] * 1000.0f ),
-                  (int) (axes[ 2 ] * 1000.0f ) )
-    );
-    
-    /*
-     * fgSetWindow (window);
-     * window->Callbacks.Joystick(
-     *   buttons,
-     *   (int) (axes[ 0 ] * 1000.0f),
-     *   (int) (axes[ 1 ] * 1000.0f),
-     *   (int) (axes[ 2 ] * 1000.0f)
-     * );
-     */
+    for ( ident = 0; ident < MAX_NUM_JOYSTICKS; ident++ )
+    {
+        if ( fgJoystick[ident] )
+        {
+            fghJoystickRead( fgJoystick[ident], &buttons, axes );
+
+            if ( !fgJoystick[ident]->error )
+                INVOKE_WCB( *window, Joystick,
+                            ( buttons,
+                              (int) (axes[ 0 ] * 1000.0f ),
+                              (int) (axes[ 1 ] * 1000.0f ),
+                              (int) (axes[ 2 ] * 1000.0f ) )
+                          );
+        }
+    }
 }
 
 /*
  * PWO: These jsJoystick class methods have not been implemented.
  *      We might consider adding such functions to freeglut-2.0.
  */
-#if 0
-int  getNumAxes ()
-    { return num_axes; }
-int  notWorking ()
-    { return error; }
-
-float getDeadBand ( int axis )
-    { return dead_band [ axis ]; }
-void  setDeadBand ( int axis, float db )
-    { dead_band [ axis ] = db; }
-
-float getSaturation ( int axis )
-    { return saturate [ axis ]; }
-void  setSaturation ( int axis, float st )
-    { saturate [ axis ] = st; }
-
-void setMinRange ( float *axes )
-    { memcpy ( min   , axes, num_axes * sizeof(float) ); }
-void setMaxRange ( float *axes )
-    { memcpy ( max   , axes, num_axes * sizeof(float) ); }
-void setCenter   ( float *axes )
-    { memcpy ( center, axes, num_axes * sizeof(float) ); }
-
-void getMinRange ( float *axes )
-    { memcpy ( axes, min   , num_axes * sizeof(float) ); }
-void getMaxRange ( float *axes )
-    { memcpy ( axes, max   , num_axes * sizeof(float) ); }
-void getCenter   ( float *axes )
-    { memcpy ( axes, center, num_axes * sizeof(float) ); }
-#endif
+int  getNumAxes ( int ident )
+    { return fgJoystick[ident]->num_axes; }
+int  notWorking ( int ident )
+    { return fgJoystick[ident]->error; }
+
+float getDeadBand ( int ident, int axis )
+    { return fgJoystick[ident]->dead_band [ axis ]; }
+void  setDeadBand ( int ident, int axis, float db )
+    { fgJoystick[ident]->dead_band [ axis ] = db; }
+
+float getSaturation ( int ident, int axis )
+    { return fgJoystick[ident]->saturate [ axis ]; }
+void  setSaturation ( int ident, int axis, float st )
+    { fgJoystick[ident]->saturate [ axis ] = st; }
+
+void setMinRange ( int ident, float *axes )
+    { memcpy ( fgJoystick[ident]->min   , axes, fgJoystick[ident]->num_axes * sizeof(float) ); }
+void setMaxRange ( int ident, float *axes )
+    { memcpy ( fgJoystick[ident]->max   , axes, fgJoystick[ident]->num_axes * sizeof(float) ); }
+void setCenter   ( int ident, float *axes )
+    { memcpy ( fgJoystick[ident]->center, axes, fgJoystick[ident]->num_axes * sizeof(float) ); }
+
+void getMinRange ( int ident, float *axes )
+    { memcpy ( axes, fgJoystick[ident]->min   , fgJoystick[ident]->num_axes * sizeof(float) ); }
+void getMaxRange ( int ident, float *axes )
+    { memcpy ( axes, fgJoystick[ident]->max   , fgJoystick[ident]->num_axes * sizeof(float) ); }
+void getCenter   ( int ident, float *axes )
+    { memcpy ( axes, fgJoystick[ident]->center, fgJoystick[ident]->num_axes * sizeof(float) ); }
 
 /*** END OF FILE ***/