From 816a36ea0d9700df8e50e6a318406a5c3ba50559 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Tue, 3 May 2011 17:42:51 +0200 Subject: [PATCH] Add OS X implementation of joysticks --- src/SFML/Window/CMakeLists.txt | 2 +- src/SFML/Window/OSX/Joystick.cpp | 311 ++++++++++++++++++++++++++++++- src/SFML/Window/OSX/Joystick.hpp | 93 ++++++++- 3 files changed, 392 insertions(+), 14 deletions(-) diff --git a/src/SFML/Window/CMakeLists.txt b/src/SFML/Window/CMakeLists.txt index 2b02eb9d..d0dd89c6 100644 --- a/src/SFML/Window/CMakeLists.txt +++ b/src/SFML/Window/CMakeLists.txt @@ -90,7 +90,7 @@ if(WINDOWS) elseif(LINUX) set(WINDOW_EXT_LIBS ${WINDOW_EXT_LIBS} ${X11_X11_LIB} ${X11_Xrandr_LIB}) elseif(MACOSX) - set(WINDOW_EXT_LIBS ${WINDOW_EXT_LIBS} "-framework Foundation -framework AppKit") + set(WINDOW_EXT_LIBS ${WINDOW_EXT_LIBS} "-framework Foundation -framework AppKit -framework IOKit") endif() # define the sfml-window target diff --git a/src/SFML/Window/OSX/Joystick.cpp b/src/SFML/Window/OSX/Joystick.cpp index 88c0c92c..8b0e203b 100644 --- a/src/SFML/Window/OSX/Joystick.cpp +++ b/src/SFML/Window/OSX/Joystick.cpp @@ -27,6 +27,8 @@ // Headers //////////////////////////////////////////////////////////// #include +#include +#include namespace sf @@ -34,20 +36,120 @@ namespace sf namespace priv { +//////////////////////////////////////////////////////////// +Joystick::Joystick() +: myManager(0) +, myElements(0) +{ + /* Nothing else */ +} + + +//////////////////////////////////////////////////////////// +Joystick::~Joystick() +{ + FreeUp(); +} + + //////////////////////////////////////////////////////////// void Joystick::Initialize(unsigned int Index) { - // Reset the joystick state + // Try to create a joystick manager. + if (!CreateManager()) return; - // Initialize the Index-th available joystick + // Get the joysticks. + CFSetRef devices = CopyJoysticksOnly(); + + // If none exit the function. + if (devices == NULL) { + FreeUp(); + return; + } + + // Is there enough joystick ? + CFIndex joysticksCount = CFSetGetCount(devices); + if (joysticksCount <= Index) { + FreeUp(); + return; + } + + // Get a usable copy of the joysticks devices. + CFTypeRef devicesArray[joysticksCount]; + CFSetGetValues(devices, devicesArray); + + // Release unused stuff. + CFRelease(devices); // Maybe we should have a field for that and not release it here... + + // Get the Index-th joystick. + IOHIDDeviceRef device = (IOHIDDeviceRef) devicesArray[Index]; + + // Retrive all connected elements to this joystick. + if (!RetriveElements(device)) { + FreeUp(); + return; + } + + // Happy end! } //////////////////////////////////////////////////////////// JoystickState Joystick::UpdateState() { - // Fill a JoystickState instance with the current joystick state + // If we don't have any joystick we exit. + if (myElements == 0) return JoystickState(); + + // Fill a JoystickState instance with the current joystick state. JoystickState s; + + // Update the buttons. + for (ButtonsVector::size_type i = 0; i < myButtons.size(); ++i) { + IOHIDValueRef value = 0; + IOHIDDeviceGetValue(IOHIDElementGetDevice(myButtons[i]), myButtons[i], &value); + + // Check for plug out. + if (!value) { + // No value ? Hum... Seems like the joystick is gone. + + FreeUp(); + return JoystickState(); + } + + s.Buttons[i] = IOHIDValueGetIntegerValue(value) == 1; // 1 means pressed, others mean released. + } + + 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. + + FreeUp(); + return JoystickState(); + } + + // 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; + s.Axis[it->first] = scaledValue; + } + return s; } @@ -55,17 +157,212 @@ JoystickState Joystick::UpdateState() //////////////////////////////////////////////////////////// bool Joystick::HasAxis(Joy::Axis Axis) const { - return false; + return myAxis.find(Axis) != myAxis.end(); } //////////////////////////////////////////////////////////// unsigned int Joystick::GetButtonsCount() const { - // Return number of supported buttons - return 0; + // Return number of supported buttons. + return myButtons.size(); } + + +//////////////////////////////////////////////////////////// +CFDictionaryRef Joystick::DevicesMaskForManager(UInt32 page, UInt32 usage) +{ + // Create the dictionary. + CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 2, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + // Add the page value. + CFNumberRef value = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page); + CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsagePageKey), value); + CFRelease(value); + + // Add the usage value (which is only valid if page value exists). + value = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage); + CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsageKey), value); + CFRelease(value); + + return dict; +} + + +//////////////////////////////////////////////////////////// +bool Joystick::CreateManager() +{ + // Create HID Manager reference. + myManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + + // Open a HID Manager reference. + IOReturn openStatus = IOHIDManagerOpen(myManager, kIOHIDOptionsTypeNone); + + if (openStatus != kIOReturnSuccess) { + sf::Err() << "Error when opening the joystick manager : " + << std::hex << openStatus << std::endl; + + CFRelease(myManager); + myManager = 0; + + return false; + } + + // Everything went fine. + return true; +} + + +//////////////////////////////////////////////////////////// +CFSetRef Joystick::CopyJoysticksOnly() +{ + // Create a mask to get only joystick devices. + CFDictionaryRef joysticksMask = DevicesMaskForManager(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick); + + // Sets single matching criteria (dictionary) for device enumeration. + IOHIDManagerSetDeviceMatching(myManager, joysticksMask); + + // No more needed -> free up. + CFRelease(joysticksMask); + + // Retrieve devices. + CFSetRef devices = IOHIDManagerCopyDevices(myManager); + return devices; // The caller is responsible for releasing it. +} + + +//////////////////////////////////////////////////////////// +bool Joystick::RetriveElements(IOHIDDeviceRef device) +{ + // Get a list of all elements attached to the device. + myElements = IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone); + + if (myElements == NULL) { + // What is a joystick with no element ? Let the user know that. + sf::Err() << "No array of element for this device" << std::endl; + + return false; + } + + // How many elements are there ? + CFIndex elements_count = CFArrayGetCount(myElements); + + if (elements_count == 0) { + // What is a joystick with no element ? Let the user know that. + sf::Err() << "No element attached to this device" << std::endl; + + CFRelease(myElements); + myElements = 0; + + return false; + } + + // Go through all connected elements. + for (int i = 0; i < elements_count; ++i) { + IOHIDElementRef element = (IOHIDElementRef) CFArrayGetValueAtIndex(myElements, i); + + switch (IOHIDElementGetType(element)) { + + case kIOHIDElementTypeInput_Misc: + switch (IOHIDElementGetUsage(element)) { + + case kHIDUsage_GD_X: + myAxis[Joy::AxisX] = element; + break; + + case kHIDUsage_GD_Y: + myAxis[Joy::AxisY] = element; + break; + + case kHIDUsage_GD_Z: + myAxis[Joy::AxisZ] = element; + break; + + case kHIDUsage_GD_Rx: + myAxis[Joy::AxisU] = element; // use same binding as on Linux. + break; + + case kHIDUsage_GD_Ry: + myAxis[Joy::AxisV] = element; // use same binding as on Linux. + break; + + case kHIDUsage_GD_Rz: + myAxis[Joy::AxisR] = element; // use same binding as on Linux. + break; + + // kHIDUsage_GD_Vx, kHIDUsage_GD_Vy, kHIDUsage_GD_Vz are ignored. + } + break; + + case kIOHIDElementTypeInput_Button: + if (myButtons.size() < Joy::ButtonCount) { // If we can managed this button through events... + myButtons.push_back(element); // ...we add this element to the list. + } else { + // Too many buttons. We ignore this one. + } + break; + } + } + + // Note : Joy::AxisPOV not yet supported. + // Maybe kIOHIDElementTypeInput_Axis is the type but I can't test. + + return true; +} + + +//////////////////////////////////////////////////////////// +void Joystick::FreeUp() +{ + ReleaseElements(); + + ReleaseManager(); +} + + +//////////////////////////////////////////////////////////// +void Joystick::ReleaseManager() +{ + if (myManager != 0) { + // Closes the IOHIDManager + IOReturn closeStatus = IOHIDManagerClose(myManager, kIOHIDOptionsTypeNone); + + if (closeStatus != kIOReturnSuccess) { + // Closing the manager failed. We don't care that much about this. + // It often happens when the connection with the device is closed after + // the device is deconected from the computer. + + /* + sf::Err() << "Error when closing the manager : " + << std::hex << closeStatus << std::endl; + //*/ + } + + // Release the manager. + CFRelease(myManager); + myManager = 0; + } +} + + +//////////////////////////////////////////////////////////// +void Joystick::ReleaseElements() +{ + if (myElements != 0) { + // Release all elements. + CFRelease(myElements); + myElements = 0; + + // Both myAxis and myButton contains only reference from myElements. + // Thus no special cleanup is required on these two. + myButtons.clear(); + myAxis.clear(); + } +} + + } // namespace priv - + } // namespace sf diff --git a/src/SFML/Window/OSX/Joystick.hpp b/src/SFML/Window/OSX/Joystick.hpp index 51920fd2..389319bd 100644 --- a/src/SFML/Window/OSX/Joystick.hpp +++ b/src/SFML/Window/OSX/Joystick.hpp @@ -29,10 +29,10 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// - -#if USE_OS_X_VERSION_10_6 - #include -#endif +#include +#include +#include +#include namespace sf { @@ -44,7 +44,24 @@ namespace priv //////////////////////////////////////////////////////////// class Joystick { - public : +public : + + //////////////////////////////////////////////////////////// + /// \brief Default constructor + /// + /// This constructors initializes all members to 0. + /// That is, it does nothing. + /// + //////////////////////////////////////////////////////////// + Joystick(); + + //////////////////////////////////////////////////////////// + /// \brief Destructor + /// + /// Close all connections to any devices if required. + /// + //////////////////////////////////////////////////////////// + ~Joystick(); //////////////////////////////////////////////////////////// /// \brief Initialize the instance and bind it to a physical joystick @@ -80,11 +97,75 @@ class Joystick //////////////////////////////////////////////////////////// unsigned int GetButtonsCount() const; - private : +private : + + //////////////////////////////////////////////////////////// + /// \brief Create a mask (dictionary) for an IOHIDManager + /// + /// \param page + /// \param usage + /// + //////////////////////////////////////////////////////////// + static CFDictionaryRef DevicesMaskForManager(UInt32 page, UInt32 usage); + + //////////////////////////////////////////////////////////// + /// \brief Create and open the manager + /// + /// \return Return false if someting went wrong + /// + //////////////////////////////////////////////////////////// + bool CreateManager(); + + //////////////////////////////////////////////////////////// + /// \brief Copy all connected joysticks to the manager + /// + /// \return NULL or a valid (possibly empty) set of devices + /// + //////////////////////////////////////////////////////////// + CFSetRef CopyJoysticksOnly(); + + //////////////////////////////////////////////////////////// + /// \brief Load all connected elements to the given device + /// + /// \param device The desired joystick + /// \return False if something went wrong + /// + //////////////////////////////////////////////////////////// + bool RetriveElements(IOHIDDeviceRef device); + + //////////////////////////////////////////////////////////// + /// \brief Release all resources + /// + /// Close all connections to any devices, if required + /// + //////////////////////////////////////////////////////////// + void FreeUp(); + + //////////////////////////////////////////////////////////// + /// \brief Close and release the manager + /// + //////////////////////////////////////////////////////////// + void ReleaseManager(); + + //////////////////////////////////////////////////////////// + /// \brief Release all elements + /// + //////////////////////////////////////////////////////////// + void ReleaseElements(); //////////////////////////////////////////////////////////// // Member data //////////////////////////////////////////////////////////// + typedef std::map AxisMap; + typedef std::vector ButtonsVector; + + AxisMap myAxis; ///< Axis (IOHIDElementRef) connected to the joystick. + ButtonsVector myButtons; ///< Buttons (IOHIDElementRef) connected to the joystick. + // Note : Both myAxis and myButton contains only reference from myElements. + // Thus no special cleanup is required on these two. + + IOHIDManagerRef myManager; ///< HID Manager. + CFArrayRef myElements; ///< IOHIDElementRef connected to the joytick. }; } // namespace priv