From 535733f5c7b379ec0b823aa5433fe61632a5772b Mon Sep 17 00:00:00 2001
From: Marco Antognini <antognini.marco@gmail.com>
Date: Wed, 13 Jul 2011 22:14:49 +0200
Subject: [PATCH] Implemented OSX/Joysticks and fixed some tabulations

---
 include/SFML/Window/WindowHandle.hpp    |   2 +-
 src/SFML/Window/GlContext.cpp           |   4 +-
 src/SFML/Window/InputImpl.hpp           |   2 +-
 src/SFML/Window/JoystickImpl.hpp        |   2 +-
 src/SFML/Window/OSX/HIDInputManager.hpp |  26 +-
 src/SFML/Window/OSX/HIDInputManager.mm  |  42 +++-
 src/SFML/Window/OSX/InputImpl.mm        |   6 +-
 src/SFML/Window/OSX/JoystickImpl.cpp    | 304 +++++++++++++++++++++++-
 src/SFML/Window/OSX/JoystickImpl.hpp    |  15 +-
 9 files changed, 372 insertions(+), 31 deletions(-)

diff --git a/include/SFML/Window/WindowHandle.hpp b/include/SFML/Window/WindowHandle.hpp
index 2947a7c5..81e44144 100644
--- a/include/SFML/Window/WindowHandle.hpp
+++ b/include/SFML/Window/WindowHandle.hpp
@@ -54,7 +54,7 @@ namespace sf
 #elif defined(SFML_SYSTEM_MACOS)
 
     // Window handle is NSWindow (void*) on Mac OS X - Cocoa
-	typedef void* WindowHandle;
+    typedef void* WindowHandle;
 
 #endif
 
diff --git a/src/SFML/Window/GlContext.cpp b/src/SFML/Window/GlContext.cpp
index fe5a59cf..196ed9c8 100644
--- a/src/SFML/Window/GlContext.cpp
+++ b/src/SFML/Window/GlContext.cpp
@@ -48,8 +48,8 @@
 
 #elif defined(SFML_SYSTEM_MACOS)
 
-	#include <SFML/Window/OSX/SFContext.hpp>
-	typedef sf::priv::SFContext ContextType;
+    #include <SFML/Window/OSX/SFContext.hpp>
+    typedef sf::priv::SFContext ContextType;
 
 #endif
 
diff --git a/src/SFML/Window/InputImpl.hpp b/src/SFML/Window/InputImpl.hpp
index 7c501471..f4aa0247 100644
--- a/src/SFML/Window/InputImpl.hpp
+++ b/src/SFML/Window/InputImpl.hpp
@@ -40,7 +40,7 @@
 
 #elif defined(SFML_SYSTEM_MACOS)
 
-	#include <SFML/Window/OSX/InputImpl.hpp>
+    #include <SFML/Window/OSX/InputImpl.hpp>
 
 #endif
 
diff --git a/src/SFML/Window/JoystickImpl.hpp b/src/SFML/Window/JoystickImpl.hpp
index 5ec45ecb..40fe6b32 100644
--- a/src/SFML/Window/JoystickImpl.hpp
+++ b/src/SFML/Window/JoystickImpl.hpp
@@ -87,7 +87,7 @@ struct JoystickState
 
 #elif defined(SFML_SYSTEM_MACOS)
 
-	#include <SFML/Window/OSX/JoystickImpl.hpp>
+    #include <SFML/Window/OSX/JoystickImpl.hpp>
 
 #endif
 
diff --git a/src/SFML/Window/OSX/HIDInputManager.hpp b/src/SFML/Window/OSX/HIDInputManager.hpp
index e6e13ad9..81befac8 100644
--- a/src/SFML/Window/OSX/HIDInputManager.hpp
+++ b/src/SFML/Window/OSX/HIDInputManager.hpp
@@ -47,7 +47,8 @@ namespace priv
 /// keyboard and mouse states. It's only purpose is
 /// to help sf::priv::InputImpl class.
 ///
-/// sf::priv::JoystickImpl is not concerned by this class.
+/// HIDInputManager provides a function to get all connected joysticks
+/// to help sf::priv::JoystickImpl.
 ///
 ////////////////////////////////////////////////////////////
 class HIDInputManager : NonCopyable
@@ -84,6 +85,27 @@ public :
     ////////////////////////////////////////////////////////////
     bool IsMouseButtonPressed(Mouse::Button button);
     
+    ////////////////////////////////////////////////////////////
+    /// \brief List all connected joysticks
+    ///
+    /// \return a retained CFSetRef of IOHIDDeviceRef or NULL
+    ///
+    ////////////////////////////////////////////////////////////
+    CFSetRef CopyJoystickDevices();
+
+public :
+    
+    ////////////////////////////////////////////////////////////
+    /// \brief Get the usb location ID of a given device
+    ///
+    /// This location ID is unique and can be used a usb port identifier
+    ///
+    /// \param device HID device to query
+    /// \return the device's location ID or 0 if something went wrong
+    ///
+    ////////////////////////////////////////////////////////////
+    static long GetLocationID(IOHIDDeviceRef device);
+    
     ////////////////////////////////////////////////////////////
     /// Try to convert a character into a SFML key code.
     ///
@@ -199,7 +221,7 @@ private :
     ////////////////////////////////////////////////////////////
     /// \brief Filter the devices and return them.
     ///
-    /// If something went wrong FreeUp is called
+    /// FreeUp is _not_ called by this function.
     ///
     /// \param page  HID page like kHIDPage_GenericDesktop
     /// \param usage HID usage page like kHIDUsage_GD_Keyboard or kHIDUsage_GD_Mouse
diff --git a/src/SFML/Window/OSX/HIDInputManager.mm b/src/SFML/Window/OSX/HIDInputManager.mm
index a8118db8..84b51db9 100644
--- a/src/SFML/Window/OSX/HIDInputManager.mm
+++ b/src/SFML/Window/OSX/HIDInputManager.mm
@@ -84,7 +84,6 @@ bool HIDInputManager::IsKeyPressed(Keyboard::Key key)
     return state;
 }
 
-
 ////////////////////////////////////////////////////////////
 bool HIDInputManager::IsMouseButtonPressed(Mouse::Button button)
 {
@@ -128,6 +127,35 @@ bool HIDInputManager::IsMouseButtonPressed(Mouse::Button button)
 }
 
 
+////////////////////////////////////////////////////////////
+CFSetRef HIDInputManager::CopyJoystickDevices()
+{
+    return CopyDevices(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick);
+}
+
+
+////////////////////////////////////////////////////////////
+long HIDInputManager::GetLocationID(IOHIDDeviceRef device)
+{
+    long loc = 0;
+    
+    // Get a unique ID : its usb location ID
+    CFTypeRef typeRef = IOHIDDeviceGetProperty(device, 
+                                               CFSTR(kIOHIDLocationIDKey));
+    if (!typeRef || CFGetTypeID(typeRef) != CFNumberGetTypeID()) {
+        return 0;
+    }
+    
+    CFNumberRef locRef = (CFNumberRef)typeRef;
+    
+    if (!CFNumberGetValue(locRef, kCFNumberLongType, &loc)) {
+        return 0;
+    }
+    
+    return loc;
+}
+
+
 ////////////////////////////////////////////////////////////
 HIDInputManager::HIDInputManager()
 : amIValid(true)
@@ -198,8 +226,7 @@ void HIDInputManager::InitializeKeyboard()
     // Get only keyboards
     CFSetRef keyboards = CopyDevices(kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard);
     if (keyboards == NULL) {
-        
-        // FreeUp was already called
+        FreeUp();
         return;
     }
     
@@ -234,8 +261,7 @@ void HIDInputManager::InitializeMouse()
     // Get only mouses
     CFSetRef mouses = CopyDevices(kHIDPage_GenericDesktop, kHIDUsage_GD_Mouse);
     if (mouses == NULL) {
-        
-        // FreeUp was already called
+        FreeUp();
         return;
     }
     
@@ -525,17 +551,13 @@ CFSetRef HIDInputManager::CopyDevices(UInt32 page, UInt32 usage)
     
     CFSetRef devices = IOHIDManagerCopyDevices(myManager);
     if (devices == NULL) {
-        sf::Err() << "Cannot find any devices." << std::endl;
-        FreeUp();
         return NULL;
     }
     
-    // Is there at least one keyboard ?
+    // Is there at least one device ?
     CFIndex deviceCount = CFSetGetCount(devices);
     if (deviceCount < 1) {
-        sf::Err() << "Found no device." << std::endl;
         CFRelease(devices);
-        FreeUp();
         return NULL;
     }
     
diff --git a/src/SFML/Window/OSX/InputImpl.mm b/src/SFML/Window/OSX/InputImpl.mm
index 0690442f..62fe7ee3 100644
--- a/src/SFML/Window/OSX/InputImpl.mm
+++ b/src/SFML/Window/OSX/InputImpl.mm
@@ -102,14 +102,14 @@ SFOpenGLView* GetSFOpenGLViewFromSFMLWindow(const Window& window)
 ////////////////////////////////////////////////////////////
 bool InputImpl::IsKeyPressed(Keyboard::Key key)
 {
-	return HIDInputManager::GetInstance().IsKeyPressed(key);
+    return HIDInputManager::GetInstance().IsKeyPressed(key);
 }
 
 
 ////////////////////////////////////////////////////////////
 bool InputImpl::IsMouseButtonPressed(Mouse::Button button)
 {
-	return HIDInputManager::GetInstance().IsMouseButtonPressed(button);
+    return HIDInputManager::GetInstance().IsMouseButtonPressed(button);
 }
 
 
@@ -117,7 +117,7 @@ bool InputImpl::IsMouseButtonPressed(Mouse::Button button)
 Vector2i InputImpl::GetMousePosition()
 {
     // Reverse Y axis to match SFML coord.
-	NSPoint pos = [NSEvent mouseLocation];
+    NSPoint pos = [NSEvent mouseLocation];
     pos.y = sf::VideoMode::GetDesktopMode().Height - pos.y;
 
     return Vector2i(pos.x, pos.y);
diff --git a/src/SFML/Window/OSX/JoystickImpl.cpp b/src/SFML/Window/OSX/JoystickImpl.cpp
index cd082fa3..becb3857 100644
--- a/src/SFML/Window/OSX/JoystickImpl.cpp
+++ b/src/SFML/Window/OSX/JoystickImpl.cpp
@@ -27,48 +27,332 @@
 // Headers
 ////////////////////////////////////////////////////////////
 #include <SFML/Window/JoystickImpl.hpp>
-
+#include <SFML/Window/OSX/HIDInputManager.hpp>
 
 namespace sf
 {
 namespace priv
 {
+////////////////////////////////////////////////////////////
+JoystickImpl::Location JoystickImpl::myLocationIDs[sf::Joystick::Count] = { 0 };
+
+
 ////////////////////////////////////////////////////////////
 bool JoystickImpl::IsConnected(unsigned int index)
 {
-	// @to be implemented
-    return false;
+    bool state = false; // Is the index-th joystick connected ?
+    
+    // First, let's check if the device was previously detected :
+    
+    if (myLocationIDs[index] != 0) {
+        state = true;
+    }
+    
+    // Otherwise, let's check if it is now connected :
+    else { // i.e., myLocationIDs[index] == 0
+    
+        // Get all devices
+        CFSetRef devices = HIDInputManager::GetInstance().CopyJoystickDevices();
+        
+        if (devices != NULL) {
+            
+            CFIndex size = CFSetGetCount(devices);
+            
+            if (size > 0) {
+            
+                CFTypeRef array[size]; // array of IOHIDDeviceRef
+                CFSetGetValues(devices, array);
+                
+                // If there exists a device d s.t. there is no j s.t.
+                // myLocationIDs[j] == d's location then we have a new device.
+                
+                for (CFIndex didx(0); didx < size; ++didx) {
+                    IOHIDDeviceRef d = (IOHIDDeviceRef)array[didx];
+                    Location dloc = HIDInputManager::GetLocationID(d);
+                    
+                    bool foundJ = false;
+                    for (unsigned int j(0); j < Joystick::Count; ++j) {
+                        if (myLocationIDs[j] == dloc) {
+                            foundJ = true;
+                            break; // no need to loop again
+                        }
+                    }
+                    
+                    if (foundJ) {
+                        // This is a known device
+                        // Nothing else to do
+                    } else {
+                        // This is a new device
+                        // We set it up for Open(..)
+                        myLocationIDs[index] = dloc;
+                        state = true;
+                        break; // We stop looking for a new device
+                    }
+                }
+                
+            }
+            
+            CFRelease(devices);
+        }
+    }
+    
+    return state;
 }
 
 
 ////////////////////////////////////////////////////////////
 bool JoystickImpl::Open(unsigned int index)
 {
-	// @to be implemented
-    return false;
+    myIndex = index;
+    Location deviceLoc = myLocationIDs[index]; // The device we need to load
+    
+    // Get all devices
+    CFSetRef devices = HIDInputManager::GetInstance().CopyJoystickDevices();
+    if (devices == NULL) {
+        return false;
+    }
+    
+    // Get a usable copy of the joysticks devices.
+    CFIndex joysticksCount = CFSetGetCount(devices);
+    CFTypeRef devicesArray[joysticksCount];
+    CFSetGetValues(devices, devicesArray);
+    
+    // Get the desired joystick.
+    IOHIDDeviceRef self = 0;
+    for (CFIndex i(0); i < joysticksCount; ++i) {
+        IOHIDDeviceRef d = (IOHIDDeviceRef)devicesArray[i];
+        if (deviceLoc == HIDInputManager::GetLocationID(d)) {
+            self = d;
+            break; // We found it so we stop looping.
+        }
+    }
+    
+    if (self == 0) {
+        // This shouldn't happen!
+        CFRelease(devices);
+        return false;
+    }
+    
+    // Get a list of all elements attached to the device.
+    CFArrayRef elements = IOHIDDeviceCopyMatchingElements(self, 
+                                                          NULL, 
+                                                          kIOHIDOptionsTypeNone);
+    
+    if (elements == NULL) {
+        CFRelease(devices);
+        return false;
+    }
+    
+    // How many elements are there ?
+    CFIndex elementsCount = CFArrayGetCount(elements);
+    
+    if (elementsCount == 0) {
+        // What is a joystick with no element ?
+        CFRelease(elements);
+        CFRelease(devices);
+        return false;
+    }
+        
+    // Go through all connected elements.
+    for (int i = 0; i < elementsCount; ++i) {
+        IOHIDElementRef element = (IOHIDElementRef) CFArrayGetValueAtIndex(elements, i);
+        
+        switch (IOHIDElementGetType(element)) {
+                
+            case kIOHIDElementTypeInput_Misc:
+                switch (IOHIDElementGetUsage(element)) {
+                        
+                    case kHIDUsage_GD_X:
+                        myAxis[Joystick::X] = element;
+                        break;
+                        
+                    case kHIDUsage_GD_Y:
+                        myAxis[Joystick::Y] = element;
+                        break;
+                        
+                    case kHIDUsage_GD_Z:
+                        myAxis[Joystick::Z] = element;
+                        break;
+                        
+                    case kHIDUsage_GD_Rx:
+                        myAxis[Joystick::U] = element; 
+                        break;
+                        
+                    case kHIDUsage_GD_Ry:
+                        myAxis[Joystick::V] = element; 
+                        break;
+                        
+                    case kHIDUsage_GD_Rz:
+                        myAxis[Joystick::R] = element; 
+                        break;
+                        
+                        // kHIDUsage_GD_Vx, kHIDUsage_GD_Vy, kHIDUsage_GD_Vz are ignored.
+                }
+                break;
+                
+            case kIOHIDElementTypeInput_Button:
+                if (myButtons.size() < Joystick::ButtonCount) { // If we have free slot...
+                    myButtons.push_back(element); // ...we add this element to the list
+                } else {
+                    // Too many buttons. We ignore this one.
+                }
+                break;
+                
+            default: // Make compiler happy
+                break;
+        }
+    }
+    
+    // Note : Joy::AxisPovX/Y are not supported (yet).
+    // Maybe kIOHIDElementTypeInput_Axis is the corresponding type but I can't test.
+    
+    // Retain all these objets for personal use
+    for (ButtonsVector::iterator it(myButtons.begin()); it != myButtons.end(); ++it) {
+        CFRetain(*it);
+    }
+    for (AxisMap::iterator it(myAxis.begin()); it != myAxis.end(); ++it) {
+        CFRetain(it->second);
+    }
+    
+    // Note : we didn't retain element in the switch because we might have multiple
+    // Axis X (for example) and we want to keep only the last one. So to prevent
+    // leaking we retain objects 'only' now.
+    
+    CFRelease(devices);
+    CFRelease(elements);
+    
+    return true;
 }
 
 
 ////////////////////////////////////////////////////////////
 void JoystickImpl::Close()
 {
-	// @to be implemented
+    for (ButtonsVector::iterator it(myButtons.begin()); it != myButtons.end(); ++it) {
+        CFRelease(*it);
+    }
+    myButtons.clear();
+    
+    for (AxisMap::iterator it(myAxis.begin()); it != myAxis.end(); ++it) {
+        CFRelease(it->second);
+    }
+    myAxis.clear();
+    
+    // And we unregister this joystick
+    myLocationIDs[myIndex] = 0;
 }
 
 
 ////////////////////////////////////////////////////////////
 JoystickCaps JoystickImpl::GetCapabilities() const
 {
-	// @to be implemented
-    return JoystickCaps();
+    JoystickCaps caps;
+    
+    // Buttons :
+    caps.ButtonCount = myButtons.size();
+    
+    // Axis :
+    for (AxisMap::const_iterator it(myAxis.begin()); it != myAxis.end(); ++it) {
+        caps.Axes[it->first] = true;
+    }
+    
+    return caps;
 }
 
 
 ////////////////////////////////////////////////////////////
 JoystickState JoystickImpl::Update()
 {
-	// @to be implemented
-    return JoystickState();
+    static const JoystickState disconnectedState; // return this if joystick was disconnected
+    JoystickState       state; // otherwise return that
+    state.Connected = true;
+    
+    // Note : free up is done in Close() which is called, if required,
+    //        by the joystick manager. So we don't release buttons nor axes here.
+    
+    // First, let's determine if the joystick is still connected
+    Location selfLoc = myLocationIDs[myIndex];
+    
+    // Get all devices
+    CFSetRef devices = HIDInputManager::GetInstance().CopyJoystickDevices();
+    if (devices == NULL) {
+        return disconnectedState;
+    }
+    
+    // Get a usable copy of the joysticks devices.
+    CFIndex joysticksCount = CFSetGetCount(devices);
+    CFTypeRef devicesArray[joysticksCount];
+    CFSetGetValues(devices, devicesArray);
+    
+    // Search for it
+    bool found = false;
+    for (CFIndex i(0); i < joysticksCount; ++i) {
+        IOHIDDeviceRef d = (IOHIDDeviceRef)devicesArray[i];
+        if (selfLoc == HIDInputManager::GetLocationID(d)) {
+            found = true;
+            break; // Stop looping
+        }
+    }
+    
+    // Release unused stuff
+    CFRelease(devices);
+    
+    // Was it found ?
+    if (found) {
+        // Yes, so we can continue.
+    } else {
+        // No, so we stop here
+        return disconnectedState;
+    }
+    
+    // Update buttons' state
+    unsigned int i = 0;
+    for (ButtonsVector::iterator it(myButtons.begin()); it != myButtons.end(); ++it, ++i) {
+        IOHIDValueRef value = 0;
+        IOHIDDeviceGetValue(IOHIDElementGetDevice(*it), *it, &value);
+        
+        // Check for plug out.
+        if (!value) {
+            // No value ? Hum... Seems like the joystick is gone
+            return disconnectedState;
+        }
+        
+        // 1 means pressed, others mean released
+        state.Buttons[i] = IOHIDValueGetIntegerValue(value) == 1;
+    }
+    
+    // Update axes' state
+    for (AxisMap::iterator it = myAxis.begin(); it != myAxis.end(); ++it) {
+        IOHIDValueRef value = 0;
+        IOHIDDeviceGetValue(IOHIDElementGetDevice(it->second), it->second, &value);
+        
+        // Check for plug out.
+        if (!value) {
+            // No value ? Hum... Seems like the joystick is gone
+            return disconnectedState;
+        }
+        
+        // We want to bind [physicalMin,physicalMax] to [-100=min,100=max].
+        //
+        // General formula to bind [a,b] to [c,d] with a linear progression :
+        // 
+        // f : [a, b] -> [c, d]
+        //        x  |->  (x-a)(d-c)/(b-a)+c
+        //
+        // This method might not be very accurate (the "0 position" can be
+        // slightly shift with some device) but we don't care because most
+        // of devices are so sensitive that this is not relevant.
+        double  physicalMax   = IOHIDElementGetPhysicalMax(it->second);
+        double  physicalMin   = IOHIDElementGetPhysicalMin(it->second);
+        double  scaledMin     = -100;
+        double  scaledMax     =  100;
+        double  physicalValue = IOHIDValueGetScaledValue(value, kIOHIDValueScaleTypePhysical);
+        float   scaledValue   = ((physicalValue - physicalMin) * (scaledMax - scaledMin) / (physicalMax - physicalMin)) + scaledMin;
+        state.Axes[it->first] = scaledValue;
+    }
+    
+    
+    return state;
 }
 
 } // namespace priv
diff --git a/src/SFML/Window/OSX/JoystickImpl.hpp b/src/SFML/Window/OSX/JoystickImpl.hpp
index 7dbef005..e0c0add8 100644
--- a/src/SFML/Window/OSX/JoystickImpl.hpp
+++ b/src/SFML/Window/OSX/JoystickImpl.hpp
@@ -30,7 +30,9 @@
 // Headers
 ////////////////////////////////////////////////////////////
 #include <SFML/Window/JoystickImpl.hpp>
-
+#include <IOKit/hid/IOHIDDevice.h>
+#include <map>
+#include <vector>
 
 namespace sf
 {
@@ -91,6 +93,17 @@ private :
     ////////////////////////////////////////////////////////////
     // Member data
     ////////////////////////////////////////////////////////////
+    typedef long Location;
+    typedef std::map<sf::Joystick::Axis, IOHIDElementRef> AxisMap;
+    typedef std::vector<IOHIDElementRef>                  ButtonsVector;
+    
+    AxisMap       myAxis;    ///< Axis (IOHIDElementRef) connected to the joystick
+    ButtonsVector myButtons; ///< Buttons (IOHIDElementRef) connected to the joystick
+    unsigned int  myIndex;   ///< SFML index
+    
+    static Location myLocationIDs[sf::Joystick::Count]; ///< Global Joystick register
+    /// For a corresponding SFML index, myLocationIDs is either some usb 
+    /// location or 0 if there isn't currently a connected joystick device
 };
 
 } // namespace priv