Index: joy.c
===================================================================
--- joy.c	(revision 748)
+++ joy.c	(revision 749)
@@ -14,6 +14,22 @@
 // Need to add winmm lib to windows dll build
 #include <mmsystem.h>
 #endif
+#ifdef __APPLE__
+#include <unistd.h>
+#include <ctype.h>
+#include <sysexits.h>
+#include <mach/mach.h>
+#include <mach/mach_error.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/IOCFPlugIn.h>
+#include <Kernel/IOKit/hidsystem/IOHIDUsageTables.h>
+#include <IOKit/hid/IOHIDLib.h>
+#include <IOKit/hid/IOHIDKeys.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <Carbon/Carbon.h>
+#include <ForceFeedback/ForceFeedback.h>
+#include <ForceFeedback/ForceFeedbackConstants.h>
+#endif
 
 // proto
 int raydium_init_cli_option_default(char *option, char *value, char *default_value);
@@ -48,6 +64,121 @@
 #else
 int raydium_joy_win_id;
 #endif
+#else
+struct recElement
+{
+    // Unique value which identifies element.
+    IOHIDElementCookie cookie;
+
+    // Reported min value possible.
+    long min;
+
+    // Reported max value possible.
+    long max;
+
+    // Runtime variables used for auto-calibration.
+    long minReport;
+    long maxReport;
+
+    // Next element in list.
+    struct recElement *pNext;
+};
+typedef struct recElement recElement;
+
+struct joystick_hwdata
+{
+    // Interface for force feedback, 0 = no ff.
+    io_service_t ffservice;
+
+    // Interface to device, NULL = no interface.
+    IOHIDDeviceInterface **interface;
+
+    // Name of product.
+    char product[256];
+
+    // Usage page from IOUSBHID Parser.h which defines general usage.
+    long usage;
+
+    // Usage within above page from IOUSBHID Parser.h which defines specific usage.
+    long usagePage;
+
+    // Number of axis (calculated, not reported by device).
+    long axes;
+
+    // Number of buttons (calculated, not reported by device).
+    long buttons;
+
+    // Number of hat switches (calculated, not reported by device).
+    long hats;
+
+    // Number of total elements (shouldbe total of above) (calculated, not reported by device).
+    long elements;
+
+    recElement *firstAxis;
+    recElement *firstButton;
+    recElement *firstHat;
+
+    int removed;
+    int uncentered;
+
+    // Next device.
+    struct joystick_hwdata *pNext;
+};
+typedef struct joystick_hwdata recDevice;
+
+struct IOKit_Joystick
+{
+    // Device index.
+    uint8_t index;
+
+    // Joystick name.
+    const char *name;
+
+    // Number of axis controls on the joystick.
+    int naxes;
+
+    // Current axis states.
+    int16_t *axes;
+
+    // Number of hats on the joystick.
+    int nhats;
+
+    // Current hat states.
+    uint8_t *hats;
+
+    // Number of trackballs on the joystick
+    int nballs;
+
+    // Current ball motion deltas.
+    struct balldelta
+    {
+        int dx;
+        int dy;
+    } *balls;
+
+    // Number of buttons on the joystick.
+    int nbuttons;
+
+    // Current button states.
+    uint8_t *buttons;
+
+    // Driver dependent information.
+    struct joystick_hwdata *hwdata;
+
+    // Reference count for multiple opens.
+    int ref_count;
+};
+typedef struct IOKit_Joystick IOKitJoystick;
+
+// Linked list of all available devices.
+static recDevice *gpDeviceList = NULL;
+
+// The joystick/joypad device.
+recDevice *device = NULL;
+IOKitJoystick *joystick = NULL;
+
+// Number of detected devices.
+int detectedDevices = 0;
 #endif
 char effect_tremble_state=0;
 unsigned long last_event;
@@ -167,6 +298,461 @@
     return(e.type);
 }
 #endif
+#else
+static void HIDReportErrorNum(char* strError, long numError)
+{
+    raydium_log("joy: %s (%i)", strError, numError);
+}
+
+static void HIDGetCollectionElements(CFMutableDictionaryRef deviceProperties, recDevice* pDevice);
+
+// Returns current value for element, polling element.
+// Will return 0 on error conditions which should be accounted for by application.
+static int HIDGetElementValue(recDevice *pDevice, recElement *pElement)
+{
+    IOReturn result = kIOReturnSuccess;
+    IOHIDEventStruct hidEvent;
+    hidEvent.value = 0;
+
+    if (NULL != pDevice && NULL != pElement && NULL != pDevice->interface)
+    {
+        result = (*(pDevice->interface))->getElementValue(pDevice->interface,
+                                                          pElement->cookie,
+                                                          &hidEvent);
+        if (kIOReturnSuccess == result)
+        {
+            // Record min and max for auto calibration.
+            if (hidEvent.value < pElement->minReport)
+                pElement->minReport = hidEvent.value;
+            if (hidEvent.value > pElement->maxReport)
+                pElement->maxReport = hidEvent.value;
+        }
+    }
+
+    // Auto user scale.
+    return hidEvent.value;
+}
+
+static int HIDScaledCalibratedValue(recDevice *pDevice, recElement *pElement, long min, long max)
+{
+    float deviceScale = max - min;
+    float readScale = pElement->maxReport - pElement->minReport;
+    SInt32 value = HIDGetElementValue(pDevice, pElement);
+    if (readScale == 0)
+        return value;
+    else
+        return ((value - pElement->minReport) * deviceScale / readScale) + min;
+}
+
+static void HIDRemovalCallback(void* target, IOReturn result, void* refcon, void* sender)
+{
+    recDevice* device = (recDevice*) refcon;
+    device->removed = 1;
+    device->uncentered = 1;
+}
+
+// Create and open an interface to device, required prior to extracting values or building queues.
+// Note: appliction now owns the device and must close and release it prior to exiting.
+static IOReturn HIDCreateOpenDeviceInterface(io_object_t hidDevice, recDevice* pDevice)
+{
+    IOReturn result = kIOReturnSuccess;
+    HRESULT plugInResult = S_OK;
+    SInt32 score = 0;
+    IOCFPlugInInterface** ppPlugInInterface = NULL;
+    
+    if (NULL == pDevice->interface)
+    {
+        result = IOCreatePlugInInterfaceForService(hidDevice,
+                                                   kIOHIDDeviceUserClientTypeID,
+                                                   kIOCFPlugInInterfaceID,
+                                                   &ppPlugInInterface, &score);
+        if (kIOReturnSuccess == result)
+        {
+            // Call a method of the intermediate plug-in to create the device interface.
+            plugInResult = (*ppPlugInInterface)->QueryInterface(ppPlugInInterface,
+                                                                CFUUIDGetUUIDBytes
+                                                                (kIOHIDDeviceInterfaceID),
+                                                                (void*)
+                                                                &(pDevice->interface));
+            if (S_OK != plugInResult)
+                HIDReportErrorNum("could not query HID class device interface from plugInInterface",
+                                  plugInResult);
+            
+            (*ppPlugInInterface)->Release(ppPlugInInterface);
+        }
+        else
+        {
+            HIDReportErrorNum("failed to create **plugInInterface via IOCreatePlugInInterfaceForService",
+                              result);
+        }
+    }
+    
+    if (NULL != pDevice->interface)
+    {
+        result = (*(pDevice->interface))->open(pDevice->interface, 0);
+        if (kIOReturnSuccess != result)
+            HIDReportErrorNum("failed to open pDevice->interface via open",
+                              result);
+        else
+        {
+            (*(pDevice->interface))->setRemovalCallback(pDevice->interface,
+                                                        HIDRemovalCallback,
+                                                        pDevice, pDevice);
+        }
+    }
+    
+    return result;
+}
+
+// Closes and releases interface to device, should be done prior to exting application.
+// Note: Will have no affect if device or interface do not exist
+// application will "own" the device if interface is not closed
+// (device may have to be plug and re-plugged in different location to get it working again without a restart)
+static IOReturn HIDCloseReleaseInterface(recDevice* pDevice)
+{
+    IOReturn result = kIOReturnSuccess;
+
+    if ((NULL != pDevice) && (NULL != pDevice->interface))
+    {
+        // Close the interface.
+        result = (*(pDevice->interface))->close(pDevice->interface);
+        if (kIOReturnNotOpen == result)
+        {
+            // Do nothing as device was not opened, thus can't be closed.
+        }
+        else if (kIOReturnSuccess != result)
+            HIDReportErrorNum("failed to close IOHIDDeviceInterface",
+                              result);
+        // Release the interface.
+        result = (*(pDevice->interface))->Release(pDevice->interface);
+        if (kIOReturnSuccess != result)
+            HIDReportErrorNum("failed to release IOHIDDeviceInterface",
+                              result);
+        pDevice->interface = NULL;
+    }
+
+    return result;
+}
+
+// Extracts actual specific element information from each element CF dictionary entry.
+static void HIDGetElementInfo(CFTypeRef refElement, recElement* pElement)
+{
+    long number;
+    CFTypeRef refType;
+
+    refType = CFDictionaryGetValue(refElement, CFSTR(kIOHIDElementCookieKey));
+    if (refType && CFNumberGetValue(refType, kCFNumberLongType, &number))
+        pElement->cookie = (IOHIDElementCookie) number;
+
+    refType = CFDictionaryGetValue(refElement, CFSTR(kIOHIDElementMinKey));
+    if (refType && CFNumberGetValue(refType, kCFNumberLongType, &number))
+        pElement->minReport = pElement->min = number;
+
+    pElement->maxReport = pElement->min;
+
+    refType = CFDictionaryGetValue(refElement, CFSTR(kIOHIDElementMaxKey));
+    if (refType && CFNumberGetValue(refType, kCFNumberLongType, &number))
+        pElement->maxReport = pElement->max = number;
+}
+
+// Examines CF dictionary vlaue in device element hierarchy to determine if it is element of interest or a collection of more elements
+// if element of interest allocate storage, add to list and retrieve element specific info
+// if collection then pass on to deconstruction collection into additional individual elements.
+static void HIDAddElement(CFTypeRef refElement, recDevice* pDevice)
+{
+    recElement* element = NULL;
+    recElement** headElement = NULL;
+    long elementType, usagePage, usage;
+    CFTypeRef refElementType =
+    CFDictionaryGetValue(refElement, CFSTR(kIOHIDElementTypeKey));
+    CFTypeRef refUsagePage = CFDictionaryGetValue(refElement, CFSTR(kIOHIDElementUsagePageKey));
+    CFTypeRef refUsage = CFDictionaryGetValue(refElement, CFSTR(kIOHIDElementUsageKey));
+
+    if ((refElementType) && (CFNumberGetValue(refElementType, kCFNumberLongType, &elementType)))
+    {
+        // Look at types of interest.
+        if ((elementType == kIOHIDElementTypeInput_Misc)
+            || (elementType == kIOHIDElementTypeInput_Button)
+            || (elementType == kIOHIDElementTypeInput_Axis))
+        {
+            if (refUsagePage
+                && CFNumberGetValue(refUsagePage, kCFNumberLongType, &usagePage) 
+                && refUsage
+                && CFNumberGetValue(refUsage, kCFNumberLongType, &usage))
+            {
+                switch (usagePage)
+                {
+                    case kHIDPage_GenericDesktop:
+                    {
+                        switch (usage)
+                        {
+                            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:
+                            case kHIDUsage_GD_Dial:
+                            case kHIDUsage_GD_Wheel:
+                                element = (recElement*)
+                                NewPtrClear(sizeof(recElement));
+                                if (element)
+                                {
+                                    pDevice->axes++;
+                                    headElement = &(pDevice->firstAxis);
+                                }
+                                break;
+                                case kHIDUsage_GD_Hatswitch:
+                                element = (recElement*)
+                                NewPtrClear(sizeof(recElement));
+                                if (element)
+                                {
+                                    pDevice->hats++;
+                                    headElement = &(pDevice->firstHat);
+                                }
+                                break;
+                        }
+                    }
+                        break;
+                    case kHIDPage_Button:
+                        element = (recElement*)
+                        NewPtrClear(sizeof(recElement));
+                        if (element)
+                        {
+                            pDevice->buttons++;
+                            headElement = &(pDevice->firstButton);
+                        }
+                        break;
+                        default:
+                        break;
+                }
+            }
+        }
+        else if (kIOHIDElementTypeCollection == elementType)
+            HIDGetCollectionElements((CFMutableDictionaryRef) refElement,
+                                     pDevice);
+    }
+
+    if (element && headElement)
+    {
+        pDevice->elements++;
+        if (NULL == *headElement)
+            *headElement = element;
+        else
+        {
+            recElement* elementPrevious, *elementCurrent;
+            elementCurrent = *headElement;
+            while (elementCurrent)
+            {
+                elementPrevious = elementCurrent;
+                elementCurrent = elementPrevious->pNext;
+            }
+
+            elementPrevious->pNext = element;
+        }
+
+        element->pNext = NULL;
+        HIDGetElementInfo(refElement, element);
+    }
+}
+
+// Collects information from each array member in device element list (each array memeber = element).
+static void HIDGetElementsCFArrayHandler(const void* value, void* parameter)
+{
+    if (CFGetTypeID(value) == CFDictionaryGetTypeID())
+        HIDAddElement((CFTypeRef) value, (recDevice*) parameter);
+}
+
+// Handles retrieval of element information from arrays of elements in device IO registry information.
+static void HIDGetElements(CFTypeRef refElementCurrent, recDevice* pDevice)
+{
+    CFTypeID type = CFGetTypeID(refElementCurrent);
+
+    if (type == CFArrayGetTypeID())
+    {
+        CFRange range = { 0, CFArrayGetCount(refElementCurrent) };
+        // CountElementsCFArrayHandler called for each array member.
+        CFArrayApplyFunction(refElementCurrent, range, HIDGetElementsCFArrayHandler, pDevice);
+    }
+}
+
+// Handles extracting element information from element collection CF types
+// used from top level element decoding and hierarchy deconstruction to flatten device element list.
+static void HIDGetCollectionElements(CFMutableDictionaryRef deviceProperties, recDevice* pDevice)
+{
+    CFTypeRef refElementTop = CFDictionaryGetValue(deviceProperties, CFSTR(kIOHIDElementKey));
+    if (refElementTop) HIDGetElements(refElementTop, pDevice);
+}
+
+// Use top level element usage page and usage to discern device usage page and usage setting appropriate vlaues in device record.
+static void HIDTopLevelElementHandler(const void* value, void* parameter)
+{
+    CFTypeRef refCF = 0;
+
+    if (CFGetTypeID(value) != CFDictionaryGetTypeID())
+        return;
+
+    refCF = CFDictionaryGetValue(value, CFSTR(kIOHIDElementUsagePageKey));
+    if (!CFNumberGetValue(refCF, kCFNumberLongType, &((recDevice*) parameter)->usagePage))
+        raydium_log("joy: CFNumberGetValue error retrieving pDevice->usagePage");
+
+    refCF = CFDictionaryGetValue(value, CFSTR(kIOHIDElementUsageKey));
+    if (!CFNumberGetValue(refCF, kCFNumberLongType, &((recDevice*) parameter)->usage))
+        raydium_log("joy: CFNumberGetValue error retrieving pDevice->usage");
+}
+
+// Extracts device info from CF dictionary records in IO registry.
+static void HIDGetDeviceInfo(io_object_t hidDevice, CFMutableDictionaryRef hidProperties, recDevice* pDevice)
+{
+    CFMutableDictionaryRef usbProperties = 0;
+    io_registry_entry_t parent1, parent2;
+
+    // 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.
+    if ((KERN_SUCCESS == IORegistryEntryGetParentEntry(hidDevice, kIOServicePlane, &parent1))
+        && (KERN_SUCCESS == IORegistryEntryGetParentEntry(parent1, kIOServicePlane, &parent2))
+        && (KERN_SUCCESS == IORegistryEntryCreateCFProperties(parent2, &usbProperties, kCFAllocatorDefault, kNilOptions)))
+    {
+        if (usbProperties)
+        {
+            CFTypeRef refCF = 0;
+            // Get device info
+            // Try hid dictionary first, if fail then go to usb dictionary.
+
+            // Get product name.
+            refCF = CFDictionaryGetValue(hidProperties, CFSTR(kIOHIDProductKey));
+            if (!refCF) refCF = CFDictionaryGetValue(usbProperties, CFSTR("USB Product Name"));
+
+            if (refCF)
+            {
+                if (!CFStringGetCString(refCF, pDevice->product, 256, CFStringGetSystemEncoding()))
+                    raydium_log("joy: CFStringGetCString error retrieving pDevice->product");
+            }
+
+            // Get usage page and usage.
+            refCF = CFDictionaryGetValue(hidProperties, CFSTR(kIOHIDPrimaryUsagePageKey));
+            if (refCF)
+            {
+                if (!CFNumberGetValue(refCF, kCFNumberLongType, &pDevice->usagePage))
+                    raydium_log("joy: CFNumberGetValue error retrieving pDevice->usagePage");
+
+                refCF = CFDictionaryGetValue(hidProperties, CFSTR(kIOHIDPrimaryUsageKey));
+                if (refCF)
+                    if (!CFNumberGetValue(refCF, kCFNumberLongType, &pDevice->usage))
+                        raydium_log("joy: CFNumberGetValue error retrieving pDevice->usage");
+            }
+
+            // Get top level element HID usage page or usage.
+            if (NULL == refCF)
+            {
+                // Use top level element instead.
+                CFTypeRef refCFTopElement = 0;
+                refCFTopElement = CFDictionaryGetValue(hidProperties, CFSTR(kIOHIDElementKey));
+                {
+                    // RefCFTopElement points to an array of element dictionaries.
+                    CFRange range = { 0, CFArrayGetCount(refCFTopElement) };
+                    CFArrayApplyFunction(refCFTopElement, range, HIDTopLevelElementHandler, pDevice);
+                }
+            }
+
+            CFRelease(usbProperties);
+        } else raydium_log("joy: IORegistryEntryCreateCFProperties failed to create usbProperties");
+
+        if (kIOReturnSuccess != IOObjectRelease(parent2))
+            raydium_log("joy: IOObjectRelease error with parent2");
+        if (kIOReturnSuccess != IOObjectRelease(parent1))
+            raydium_log("joy: IOObjectRelease error with parent1");
+    }
+}
+
+static recDevice* HIDBuildDevice(io_object_t hidDevice)
+{
+    recDevice* pDevice = (recDevice*) NewPtrClear(sizeof(recDevice));
+    if (pDevice)
+    {
+        // Get dictionary for HID properties.
+        CFMutableDictionaryRef hidProperties = 0;
+        kern_return_t result = IORegistryEntryCreateCFProperties(hidDevice,
+                                                                 &hidProperties,
+                                                                 kCFAllocatorDefault,
+                                                                 kNilOptions);
+        if ((result == KERN_SUCCESS) && hidProperties)
+        {
+            // Create device interface.
+            result = HIDCreateOpenDeviceInterface(hidDevice, pDevice);
+            if (kIOReturnSuccess == result)
+            {
+                // HIDDevice used to find parents in registry tree.
+                HIDGetDeviceInfo(hidDevice, hidProperties, pDevice);
+                HIDGetCollectionElements(hidProperties, pDevice);
+            }
+            else
+            {
+                DisposePtr((Ptr) pDevice);
+                pDevice = NULL;
+            }
+
+            CFRelease(hidProperties);
+        }
+        else
+        {
+            DisposePtr((Ptr) pDevice);
+            pDevice = NULL;
+        }
+    }
+
+    return pDevice;
+}
+
+// Disposes of the element list associated with a device and the memory associated with the list.
+static void HIDDisposeElementList(recElement** elementList)
+{
+    recElement* pElement = *elementList;
+    while (pElement)
+    {
+        recElement* pElementNext = pElement->pNext;
+        DisposePtr((Ptr) pElement);
+        pElement = pElementNext;
+    }
+
+    *elementList = NULL;
+}
+
+// Disposes of a single device, closing and releaseing interface, freeing memory fro device and elements, setting device pointer to NULL.
+// All your device no longer belong to us... (i.e., you do not 'own' the device anymore).
+static recDevice* HIDDisposeDevice(recDevice** ppDevice)
+{
+    kern_return_t result = KERN_SUCCESS;
+    recDevice *pDeviceNext = NULL;
+    if (*ppDevice)
+    {
+        // Save next device prior to disposing of this device.
+        pDeviceNext = (*ppDevice)->pNext;
+
+        // Free posible io_service_t.
+        if ((*ppDevice)->ffservice)
+        {
+            IOObjectRelease((*ppDevice)->ffservice);
+            (*ppDevice)->ffservice = 0;
+        }
+
+        // Free element lists.
+        HIDDisposeElementList(&(*ppDevice)->firstAxis);
+        HIDDisposeElementList(&(*ppDevice)->firstButton);
+        HIDDisposeElementList(&(*ppDevice)->firstHat);
+
+        // Function sanity checks interface value (now application does not own device).
+        result = HIDCloseReleaseInterface(*ppDevice);
+        if (kIOReturnSuccess != result)
+            HIDReportErrorNum("HIDCloseReleaseInterface failed when trying to dipose device",
+                              result);
+        DisposePtr((Ptr)* ppDevice);
+        *ppDevice = NULL;
+    }
+
+    return pDeviceNext;
+}
 #endif
 
 void raydium_joy_callback(void)
@@ -225,6 +811,169 @@
         }
         dwButtons = ActualPos.dwButtons;
 #endif
+#else
+    if (!raydium_joy)
+    {
+        raydium_joy_init_vars();
+        return;
+    }
+
+    raydium_joy_click = 0;
+
+    recElement *element;
+    int value, range;
+    int i;
+
+    if (device && joystick)
+    {
+        // Device was unplugged, ignore it.
+        if (device->removed)
+        {
+            if (device->uncentered)
+            {
+                device->uncentered = 0;
+            }
+
+            // Tell the app that everything is centered.
+            raydium_joy_x = 0.0;
+            raydium_joy_y = 0.0;
+            raydium_joy_z = 0.0;
+
+            return;
+        }
+
+        element = device->firstAxis;
+        i = 0;
+        while (element)
+        {
+            value = HIDScaledCalibratedValue(device, element, -32768, 32767);
+            switch (i)
+            {
+                case 0:
+                    raydium_joy_x = value / (float)32767;
+                    raydium_joy_axis[0] = value / (float)32767;
+                    break;
+                case 1:
+                    raydium_joy_y = value / (float)32767 * -1;
+                    raydium_joy_axis[1] = value / (float)32767;
+                    break;
+                case 2:
+                    raydium_joy_z = value / (float)32767 * -1;
+                    raydium_joy_axis[2] = value / (float)32767;
+                    break;
+                default:
+                    raydium_joy_x = 0.0;
+                    raydium_joy_axis[0] = 0.0;
+                    
+                    raydium_joy_y = 0.0;
+                    raydium_joy_axis[1] = 0.0;
+                    
+                    raydium_joy_z = 0.0;
+                    raydium_joy_axis[2] = 0.0;
+                    break;
+            }
+            element = element->pNext;
+            ++i;
+        }
+
+        element = device->firstButton;
+        i = 0;
+        while (element)
+        {
+            value = HIDGetElementValue(device, element);
+            if (value > 1) value = 1;
+            raydium_joy_button[i] = value;
+            #ifdef joy_debug
+            if (value == 1) raydium_log("joy: button %i %s", i + 1, "pressed");
+            #endif
+            element = element->pNext;
+            ++i;
+        }
+
+        element = device->firstHat;
+        i = 0;
+        while (element)
+        {
+            uint8_t pos = 0;
+
+            range = (element->max - element->min + 1);
+            value = HIDGetElementValue(device, element) - element->min;
+            // 4 position hatswitch - scale up value.
+            if (range == 4)
+                value *= 2;
+            // Neither a 4 nor 8 positions - fall back to default position (centered).
+            else if (range != 8)
+                value = -1;
+            switch (value)
+            {
+                case 0:
+                    // Up
+                    pos = 0x01;
+                    #ifdef joy_debug
+                    raydium_log("joy: hatswitch up");
+                    #endif
+                    break;
+                case 1:
+                    // Right up
+                    pos = (0x02 | 0x01);
+                    #ifdef joy_debug
+                    raydium_log("joy: hatswitch right up");
+                    #endif
+                    break;
+                case 2:
+                    // Right
+                    pos = 0x02;
+                    #ifdef joy_debug
+                    raydium_log("joy: hatswitch right");
+                    #endif
+                    break;
+                case 3:
+                    // Right down
+                    pos = (0x02 | 0x04);
+                    #ifdef joy_debug
+                    raydium_log("joy: hatswitch right down");
+                    #endif
+                    break;
+                case 4:
+                    // Down
+                    pos = 0x04;
+                    #ifdef joy_debug
+                    raydium_log("joy: hatswitch down");
+                    #endif
+                    break;
+                case 5:
+                    // Left down
+                    pos = (0x08 | 0x04);
+                    #ifdef joy_debug
+                    raydium_log("joy: hatswitch left down");
+                    #endif
+                    break;
+                case 6:
+                    // Left
+                    pos = 0x08;
+                    #ifdef joy_debug
+                    raydium_log("joy: hatswitch left");
+                    #endif
+                    break;
+                case 7:
+                    // Left up
+                    pos = (0x08 | 0x01);
+                    #ifdef joy_debug
+                    raydium_log("joy: hatswitch left up");
+                    #endif
+                    break;
+                default:
+                    // Every other value is mapped to center. We do that because some
+                    // joysticks use 8 and some 15 for this value, and apparently
+                    // there are even more variants out there - so we try to be generous.
+                    pos = 0x00;
+                    break;
+            }
+
+            element = element->pNext;
+            ++i;
+        }
+    }
 #endif
 //raydium_log("Joy x=%f,y=%f,z=%f",raydium_joy_x,raydium_joy_y,raydium_joy_z);
 }
@@ -362,6 +1111,192 @@
         }
     }
 #endif
+#else
+    raydium_init_cli_option_default("joydev", name, "0");
+
+    IOReturn result = kIOReturnSuccess;
+    mach_port_t masterPort = 0;
+    io_iterator_t hidObjectIterator = 0;
+    CFMutableDictionaryRef hidMatchDictionary = NULL;
+    recDevice* lastDevice;
+    io_object_t ioHIDDeviceObject = 0;
+
+    if (raydium_joy || gpDeviceList)
+    {
+        raydium_log("joy: device list already inited");
+        return;
+    }
+
+    result = IOMasterPort(bootstrap_port, &masterPort);
+
+    if (kIOReturnSuccess != result)
+    {
+        raydium_log("joy: IOMasterPort error with bootstrap_port");
+        return;
+    }
+
+    // Set up a matching dictionary to search I/O Registry by class name for all HID class devices.
+    hidMatchDictionary = IOServiceMatching(kIOHIDDeviceKey);
+    if (!hidMatchDictionary)
+    {
+        raydium_log("joy: failed to get HID CFMutableDictionaryRef via IOServiceMatching");
+        return;
+    }
+
+    // Now search I/O Registry for matching devices.
+    result = IOServiceGetMatchingServices(masterPort, hidMatchDictionary, &hidObjectIterator);
+    if (kIOReturnSuccess != result)
+    {
+        raydium_log("joy: could not create a HID object iterator");
+        return;
+    }
+
+    if (!hidObjectIterator)
+    {
+        // There are no joysticks.
+        raydium_log("joy: FAILED (no device found)");
+        raydium_joy = 0;
+        return;
+    }
+
+    // IOServiceGetMatchingServices consumes a reference to the dictionary,
+    // so we don't need to release the dictionary ref.
+
+    // Build flat linked list of devices from device iterator.
+    gpDeviceList = lastDevice = NULL;
+
+    while ((ioHIDDeviceObject = IOIteratorNext(hidObjectIterator)))
+    {
+        // Build a device record.
+        device = HIDBuildDevice(ioHIDDeviceObject);
+        if (!device) continue;
+
+        // Filter device list to non-keyboard/mouse stuff.
+        if ((device->usagePage != kHIDPage_GenericDesktop) ||
+            ((device->usage != kHIDUsage_GD_Joystick &&
+              device->usage != kHIDUsage_GD_GamePad &&
+              device->usage != kHIDUsage_GD_MultiAxisController)))
+        {
+            // Release memory for the device.
+            HIDDisposeDevice(&device);
+            DisposePtr((Ptr) device);
+            continue;
+        }
+
+        // We have to do some storage of the io_service_t.
+        if (FFIsForceFeedback(ioHIDDeviceObject) == FF_OK)
+        {
+            device->ffservice = ioHIDDeviceObject;
+        }
+        else
+        {
+            device->ffservice = 0;
+        }
+
+        // Add device to the end of the list.
+        if (lastDevice) 
+            lastDevice->pNext = device;
+        else
+            gpDeviceList = device;
+
+        lastDevice = device;
+    }
+
+    // Release the iterator.
+    result = IOObjectRelease(hidObjectIterator);
+
+    // Count the total number of devices we found.
+    device = gpDeviceList;
+
+    while(device)
+    {
+        detectedDevices++;
+        device = device->pNext;
+    }
+
+    raydium_log("joy: OK (found %i)", detectedDevices);
+
+    // Print information about the devices.
+    device = gpDeviceList;
+    int detectedDevice = 1;
+
+    while(device)
+    {
+        raydium_log("joy: #%i %s (%i axes, %i buttons, %i hats)",
+                    detectedDevice,
+                    device->product,
+                    device->axes,
+                    device->buttons,
+                    device->hats);
+
+        detectedDevice++;
+        device = device->pNext;
+    }
+
+    // Use the first device in list.
+    device = gpDeviceList;
+
+    // Create and initialize the joystick.
+    joystick = (IOKitJoystick*) malloc((sizeof *joystick));
+
+    if (joystick != NULL)
+    {
+        memset(joystick, 0, (sizeof *joystick));
+        joystick->index = 0;
+        if (joystick->naxes > 0)
+        {
+            joystick->axes = (int16_t*) malloc
+            (joystick->naxes * sizeof(int16_t));
+        }
+        if (joystick->nhats > 0)
+        {
+            joystick->hats = (uint8_t*) malloc
+            (joystick->nhats * sizeof(uint8_t));
+        }
+        if (joystick->nballs > 0)
+        {
+            joystick->balls = (struct balldelta*) malloc
+            (joystick->nballs * sizeof(*joystick->balls));
+        }
+        if (joystick->nbuttons > 0)
+        {
+            joystick->buttons = (uint8_t*) malloc
+            (joystick->nbuttons * sizeof(uint8_t));
+        }
+        if (joystick->axes)
+        {
+            memset(joystick->axes, 0, joystick->naxes * sizeof(int16_t));
+        }
+        if (joystick->hats)
+        {
+            memset(joystick->hats, 0, joystick->nhats * sizeof(uint8_t));
+        }
+        if (joystick->balls)
+        {
+            memset(joystick->balls, 0, joystick->nballs * sizeof(*joystick->balls));
+        }
+        if (joystick->buttons)
+        {
+            memset(joystick->buttons, 0, joystick->nbuttons * sizeof(uint8_t));
+        }
+    }
+
+    joystick->hwdata = device;
+    joystick->name = device->product;
+
+    joystick->naxes = device->axes;
+    joystick->nhats = device->hats;
+    joystick->nballs = 0;
+    joystick->nbuttons = device->buttons;
+
+    strcpy(raydium_joy_name, device->product);
+    raydium_joy_n_axes = device->axes;
+    raydium_joy_n_buttons = device->buttons;
+
+    raydium_log("joy: using device #1 (%i elements)", device->elements);
+
+    // Enable joystick/joypad support.
+    raydium_joy = 1;
 #endif
 }