1/* 2 The zlib/libpng License 3 4 Copyright (c) 2005-2007 Phillip Castaneda (pjcast -- www.wreckedgames.com) 5 6 This software is provided 'as-is', without any express or implied warranty. In no event will 7 the authors be held liable for any damages arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, including commercial 10 applications, and to alter it and redistribute it freely, subject to the following 11 restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not claim that 14 you wrote the original software. If you use this software in a product, 15 an acknowledgment in the product documentation would be appreciated but is 16 not required. 17 18 2. Altered source versions must be plainly marked as such, and must not be 19 misrepresented as being the original software. 20 21 3. This notice may not be removed or altered from any source distribution. 22 */ 23 24#include "mac/CocoaJoyStick.h" 25#include "mac/MacHIDManager.h" 26#include "mac/CocoaInputManager.h" 27#include "OISEvents.h" 28#include "OISException.h" 29 30#include <cassert> 31 32using namespace OIS; 33 34//--------------------------------------------------------------------------------------------------// 35CocoaJoyStick::CocoaJoyStick(const std::string &vendor, bool buffered, HidInfo* info, InputManager* creator, int devID) : 36JoyStick(vendor, buffered, devID, creator), mInfo(info) 37{ 38 39} 40 41//--------------------------------------------------------------------------------------------------// 42CocoaJoyStick::~CocoaJoyStick() 43{ 44 //TODO: check if the queue has been started first? 45 //(*mQueue)->stop(mQueue); 46 (*mQueue)->dispose(mQueue); 47 (*mQueue)->Release(mQueue); 48 49 50 //TODO: check if the interface has been opened first? 51 (*mInfo->interface)->close(mInfo->interface); 52 (*mInfo->interface)->Release(mInfo->interface); 53} 54 55//--------------------------------------------------------------------------------------------------// 56void CocoaJoyStick::_initialize() 57{ 58 assert(mInfo && "Given HidInfo invalid"); 59 assert(mInfo->interface && "Joystick interface invalid"); 60 61 //TODO: Is this necessary? 62 //Clear old state 63 mState.mAxes.clear(); 64 65 if ((*mInfo->interface)->open(mInfo->interface, 0) != KERN_SUCCESS) 66 OIS_EXCEPT(E_General, "CocoaJoyStick::_initialize() >> Could not initialize joy device!"); 67 68 mState.clear(); 69 70 _enumerateCookies(); 71 72 mState.mButtons.resize(mInfo->numButtons); 73 mState.mAxes.resize(mInfo->numAxes); 74 75 mQueue = _createQueue(); 76} 77 78class FindAxisCookie : public std::unary_function<std::pair<IOHIDElementCookie, AxisInfo>&, bool> 79{ 80public: 81 FindAxisCookie(IOHIDElementCookie cookie) : m_Cookie(cookie) {} 82 bool operator()(const std::pair<IOHIDElementCookie, AxisInfo>& pair) const 83 { 84 return pair.first == m_Cookie; 85 } 86private: 87 IOHIDElementCookie m_Cookie; 88}; 89 90//--------------------------------------------------------------------------------------------------// 91void CocoaJoyStick::capture() 92{ 93 assert(mQueue && "Queue must be initialized before calling CocoaJoyStick::capture()"); 94 95 AbsoluteTime zeroTime = {0,0}; 96 97 IOHIDEventStruct event; 98 IOReturn result = (*mQueue)->getNextEvent(mQueue, &event, zeroTime, 0); 99 while(result == kIOReturnSuccess) 100 { 101 switch(event.type) 102 { 103 case kIOHIDElementTypeInput_Button: 104 { 105 std::vector<IOHIDElementCookie>::iterator buttonIt = std::find(mCookies.buttonCookies.begin(), mCookies.buttonCookies.end(), event.elementCookie); 106 int button = std::distance(mCookies.buttonCookies.begin(), buttonIt); 107 mState.mButtons[button] = (event.value == 1); 108 109 if(mBuffered && mListener) 110 { 111 if(event.value == 1) 112 mListener->buttonPressed(JoyStickEvent(this, mState), button); 113 else if(event.value == 0) 114 mListener->buttonReleased(JoyStickEvent(this, mState), button); 115 } 116 break; 117 } 118 case kIOHIDElementTypeInput_Misc: 119 //TODO: It's an axis! - kind of - for gamepads - or should this be a pov? 120 case kIOHIDElementTypeInput_Axis: 121 std::map<IOHIDElementCookie, AxisInfo>::iterator axisIt = std::find_if(mCookies.axisCookies.begin(), mCookies.axisCookies.end(), FindAxisCookie(event.elementCookie)); 122 int axis = std::distance(mCookies.axisCookies.begin(), axisIt); 123 124 //Copied from LinuxJoyStickEvents.cpp, line 149 125 const AxisInfo& axisInfo = axisIt->second; 126 float proportion = (float) (event.value - axisInfo.max) / (float) (axisInfo.min - axisInfo.max); 127 mState.mAxes[axis].abs = -JoyStick::MIN_AXIS - (JoyStick::MAX_AXIS * 2 * proportion); 128 129 if(mBuffered && mListener) mListener->axisMoved(JoyStickEvent(this, mState), axis); 130 break; 131 } 132 133 result = (*mQueue)->getNextEvent(mQueue, &event, zeroTime, 0); 134 } 135} 136 137//--------------------------------------------------------------------------------------------------// 138void CocoaJoyStick::setBuffered(bool buffered) 139{ 140 mBuffered = buffered; 141} 142 143//--------------------------------------------------------------------------------------------------// 144Interface* CocoaJoyStick::queryInterface(Interface::IType type) 145{ 146 //Thought about using covariant return type here.. however, 147 //some devices may allow LED light changing, or other interface stuff 148 149 //f( ff_device && type == Interface::ForceFeedback ) 150 //return ff_device; 151 //else 152 return 0; 153} 154 155//--------------------------------------------------------------------------------------------------// 156void CocoaJoyStick::_enumerateCookies() 157{ 158 assert(mInfo && "Given HidInfo invalid"); 159 assert(mInfo->interface && "Joystick interface invalid"); 160 161 CFTypeRef object; 162 long number; 163 IOHIDElementCookie cookie; 164 long usage; 165 long usagePage; 166 int min; 167 int max; 168 169 CFDictionaryRef element; 170 171 // Copy all elements, since we're grabbing most of the elements 172 // for this device anyway, and thus, it's faster to iterate them 173 // ourselves. When grabbing only one or two elements, a matching 174 // dictionary should be passed in here instead of NULL. 175 CFArrayRef elements; 176 IOReturn success = reinterpret_cast<IOHIDDeviceInterface122*>(*mInfo->interface)->copyMatchingElements(mInfo->interface, NULL, &elements); 177 178 if (success == kIOReturnSuccess) 179 { 180 const CFIndex numOfElements = CFArrayGetCount(elements); 181 for (CFIndex i = 0; i < numOfElements; ++i) 182 { 183 element = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(elements, i)); 184 185 //Get cookie 186 object = (CFDictionaryGetValue(element, 187 CFSTR(kIOHIDElementCookieKey))); 188 if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) 189 continue; 190 if(!CFNumberGetValue((CFNumberRef) object, kCFNumberLongType, 191 &number)) 192 continue; 193 cookie = (IOHIDElementCookie) number; 194 195 //Get usage 196 object = CFDictionaryGetValue(element, 197 CFSTR(kIOHIDElementUsageKey)); 198 if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) 199 continue; 200 if (!CFNumberGetValue((CFNumberRef) object, kCFNumberLongType, 201 &number)) 202 continue; 203 usage = number; 204 205 //Get min 206 object = CFDictionaryGetValue(element, 207 CFSTR(kIOHIDElementMinKey)); // kIOHIDElementMinKey or kIOHIDElementScaledMinKey?, no idea ... 208 if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) 209 continue; 210 if (!CFNumberGetValue((CFNumberRef) object, kCFNumberIntType, 211 &number)) 212 continue; 213 min = number; 214 215 //Get max 216 object = CFDictionaryGetValue(element, 217 CFSTR(kIOHIDElementMaxKey)); // kIOHIDElementMaxKey or kIOHIDElementScaledMaxKey?, no idea ... 218 if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) 219 continue; 220 if (!CFNumberGetValue((CFNumberRef) object, kCFNumberIntType, 221 &number)) 222 continue; 223 max = number; 224 225 //Get usage page 226 object = CFDictionaryGetValue(element, 227 CFSTR(kIOHIDElementUsagePageKey)); 228 229 if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) 230 continue; 231 232 if (!CFNumberGetValue((CFNumberRef) object, kCFNumberLongType, 233 &number)) 234 continue; 235 236 usagePage = number; 237 switch(usagePage) 238 { 239 case kHIDPage_GenericDesktop: 240 switch(usage) 241 { 242 case kHIDUsage_GD_Pointer: 243 break; 244 case kHIDUsage_GD_X: 245 case kHIDUsage_GD_Y: 246 case kHIDUsage_GD_Z: 247 case kHIDUsage_GD_Rx: 248 case kHIDUsage_GD_Ry: 249 case kHIDUsage_GD_Rz: 250 mCookies.axisCookies.insert(std::make_pair(cookie, AxisInfo(min, max))); 251 break; 252 case kHIDUsage_GD_Slider: 253 case kHIDUsage_GD_Dial: 254 case kHIDUsage_GD_Wheel: 255 break; 256 case kHIDUsage_GD_Hatswitch: 257 break; 258 } 259 break; 260 case kHIDPage_Button: 261 mCookies.buttonCookies.push_back(cookie); 262 break; 263 } 264 } 265 266 mInfo->numButtons = mCookies.buttonCookies.size(); 267 mInfo->numAxes = mCookies.axisCookies.size(); 268 269 } 270 else 271 { 272 OIS_EXCEPT(E_General, "JoyStick elements could not be copied: copyMatchingElements failed with error: " + success); 273 } 274 275} 276 277//--------------------------------------------------------------------------------------------------// 278IOHIDQueueInterface** CocoaJoyStick::_createQueue(unsigned int depth) 279{ 280 assert(mInfo && "Given HidInfo invalid"); 281 assert(mInfo->interface && "Joystick interface invalid"); 282 283 IOHIDQueueInterface** queue = (*mInfo->interface)->allocQueue(mInfo->interface); 284 285 if (queue) 286 { 287 //create the queue 288 IOReturn result = (*queue)->create(queue, 0, depth); 289 290 if(result == kIOReturnSuccess) 291 { 292 //add elements to the queue 293 std::map<IOHIDElementCookie, AxisInfo>::iterator axisIt = mCookies.axisCookies.begin(); 294 for(; axisIt != mCookies.axisCookies.end(); ++axisIt) 295 { 296 result = (*queue)->addElement(queue, axisIt->first, 0); 297 } 298 299 std::vector<IOHIDElementCookie>::iterator buttonIt = mCookies.buttonCookies.begin(); 300 for(; buttonIt != mCookies.buttonCookies.end(); ++buttonIt) 301 { 302 result = (*queue)->addElement(queue, (*buttonIt), 0); 303 } 304 305 //start data delivery to queue 306 result = (*queue)->start(queue); 307 if(result == kIOReturnSuccess) 308 { 309 return queue; 310 } 311 else 312 { 313 OIS_EXCEPT(E_General, "Queue could not be started."); 314 } 315 } 316 else 317 { 318 OIS_EXCEPT(E_General, "Queue could not be created."); 319 } 320 } 321 else 322 { 323 OIS_EXCEPT(E_General, "Queue allocation failed."); 324 } 325} 326