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