src/joystick/darwin/SDL_sysjoystick.c
author Sam Lantinga <slouken@libsdl.org>
Thu, 13 Apr 2006 13:08:26 +0000
changeset 1621 f12379c41042
parent 1496 405e20dc004c
child 1635 92947e3a18db
permissions -rw-r--r--
Fixes bug #195:
The proper name of Apple's operating system is "Mac OS X" not "MacOS X", as can
bee seen in many places, for example http://www.apple.com/macosx/). This
contrasts the naming of the old operating system, which was called "MacOS" and
today is often refered to as "MacOS Classic".

The attached patches fixes the misuse of the name "MacOS X" in both the SDL12
and sdlweb CVS modules.
slouken@172
     1
/*
slouken@172
     2
	SDL - Simple DirectMedia Layer
slouken@769
     3
    Copyright (C) 1997-2004 Sam Lantinga
slouken@172
     4
slouken@172
     5
	This library is free software; you can redistribute it and/or
slouken@172
     6
	modify it under the terms of the GNU Library General Public
slouken@172
     7
	License as published by the Free Software Foundation; either
slouken@172
     8
	version 2 of the License, or (at your option) any later version.
slouken@172
     9
slouken@172
    10
	This library is distributed in the hope that it will be useful,
slouken@172
    11
	but WITHOUT ANY WARRANTY; without even the implied warranty of
slouken@172
    12
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
slouken@172
    13
	Library General Public License for more details.
slouken@172
    14
slouken@172
    15
	You should have received a copy of the GNU Library General Public
slouken@172
    16
	License along with this library; if not, write to the Free
slouken@172
    17
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
slouken@172
    18
slouken@172
    19
	Sam Lantinga
slouken@252
    20
	slouken@libsdl.org
slouken@172
    21
*/
slouken@1402
    22
#include "SDL_config.h"
slouken@172
    23
slouken@1621
    24
/* SDL joystick driver for Darwin / Mac OS X, based on the IOKit HID API */
slouken@172
    25
/* Written 2001 by Max Horn */
slouken@172
    26
slouken@172
    27
#include <unistd.h>
slouken@172
    28
#include <ctype.h>
slouken@172
    29
#include <sysexits.h>
slouken@172
    30
#include <mach/mach.h>
slouken@172
    31
#include <mach/mach_error.h>
slouken@172
    32
#include <IOKit/IOKitLib.h>
slouken@172
    33
#include <IOKit/IOCFPlugIn.h>
slouken@209
    34
#ifdef MACOS_10_0_4
slouken@209
    35
#include <IOKit/hidsystem/IOHIDUsageTables.h>
slouken@209
    36
#else
slouken@1621
    37
/* The header was moved here in Mac OS X 10.1 */
slouken@208
    38
#include <Kernel/IOKit/hidsystem/IOHIDUsageTables.h>
slouken@209
    39
#endif
slouken@172
    40
#include <IOKit/hid/IOHIDLib.h>
slouken@172
    41
#include <IOKit/hid/IOHIDKeys.h>
slouken@172
    42
#include <CoreFoundation/CoreFoundation.h>
slouken@475
    43
#include <Carbon/Carbon.h> /* for NewPtrClear, DisposePtr */
slouken@172
    44
slouken@172
    45
#include "SDL_joystick.h"
slouken@1361
    46
#include "../SDL_sysjoystick.h"
slouken@1361
    47
#include "../SDL_joystick_c.h"
slouken@172
    48
slouken@172
    49
struct recElement
slouken@172
    50
{
slouken@1487
    51
	IOHIDElementCookie cookie;				/* unique value which identifies element, will NOT change */
slouken@1487
    52
	long min;								/* reported min value possible */
slouken@1487
    53
	long max;								/* reported max value possible */
slouken@1487
    54
#if 0
slouken@1487
    55
	/* TODO: maybe should handle the following stuff somehow? */
slouken@172
    56
slouken@1487
    57
	long scaledMin;							/* reported scaled min value possible */
slouken@1487
    58
	long scaledMax;							/* reported scaled max value possible */
slouken@1487
    59
	long size;								/* size in bits of data return from element */
slouken@1487
    60
	Boolean relative;						/* are reports relative to last report (deltas) */
slouken@1487
    61
	Boolean wrapping;						/* does element wrap around (one value higher than max is min) */
slouken@1487
    62
	Boolean nonLinear;						/* are the values reported non-linear relative to element movement */
slouken@1487
    63
	Boolean preferredState;					/* does element have a preferred state (such as a button) */
slouken@1487
    64
	Boolean nullState;						/* does element have null state */
slouken@1487
    65
#endif /* 0 */
slouken@172
    66
slouken@172
    67
	/* runtime variables used for auto-calibration */
slouken@1487
    68
	long minReport;							/* min returned value */
slouken@1487
    69
	long maxReport;							/* max returned value */
slouken@172
    70
	
slouken@1487
    71
	struct recElement * pNext;				/* next element in list */
slouken@172
    72
};
slouken@172
    73
typedef struct recElement recElement;
slouken@172
    74
slouken@172
    75
struct joystick_hwdata
slouken@172
    76
{
slouken@1487
    77
	IOHIDDeviceInterface ** interface;		/* interface to device, NULL = no interface */
slouken@172
    78
slouken@1487
    79
	char product[256];							/* name of product */
slouken@1487
    80
	long usage;								/* usage page from IOUSBHID Parser.h which defines general usage */
slouken@1487
    81
	long usagePage;							/* usage within above page from IOUSBHID Parser.h which defines specific usage */
slouken@172
    82
slouken@1487
    83
	long axes;								/* number of axis (calculated, not reported by device) */
slouken@1487
    84
	long buttons;							/* number of buttons (calculated, not reported by device) */
slouken@1487
    85
	long hats;								/* number of hat switches (calculated, not reported by device) */
slouken@1487
    86
	long elements;							/* number of total elements (shouldbe total of above) (calculated, not reported by device) */
slouken@172
    87
slouken@172
    88
	recElement* firstAxis;
slouken@172
    89
	recElement* firstButton;
slouken@172
    90
	recElement* firstHat;
slouken@172
    91
slouken@858
    92
	int removed;
slouken@858
    93
	int uncentered;
slouken@858
    94
slouken@1487
    95
	struct joystick_hwdata* pNext;			/* next device */
slouken@172
    96
};
slouken@172
    97
typedef struct joystick_hwdata recDevice;
slouken@172
    98
slouken@172
    99
slouken@172
   100
/* Linked list of all available devices */
slouken@172
   101
static recDevice *gpDeviceList = NULL;
slouken@172
   102
slouken@172
   103
icculus@1077
   104
static void HIDReportErrorNum (char * strError, long numError)
slouken@172
   105
{
slouken@172
   106
	SDL_SetError(strError);
slouken@172
   107
}
slouken@172
   108
slouken@172
   109
static void HIDGetCollectionElements (CFMutableDictionaryRef deviceProperties, recDevice *pDevice);
slouken@172
   110
slouken@172
   111
/* returns current value for element, polling element
slouken@172
   112
 * will return 0 on error conditions which should be accounted for by application
slouken@172
   113
 */
slouken@172
   114
icculus@1077
   115
static SInt32 HIDGetElementValue (recDevice *pDevice, recElement *pElement)
slouken@172
   116
{
slouken@172
   117
	IOReturn result = kIOReturnSuccess;
slouken@172
   118
	IOHIDEventStruct hidEvent;
slouken@172
   119
	hidEvent.value = 0;
slouken@172
   120
	
slouken@172
   121
	if (NULL != pDevice && NULL != pElement && NULL != pDevice->interface)
slouken@172
   122
	{
slouken@172
   123
		result = (*(pDevice->interface))->getElementValue(pDevice->interface, pElement->cookie, &hidEvent);
slouken@172
   124
		if (kIOReturnSuccess == result)
slouken@172
   125
		{
slouken@172
   126
			/* record min and max for auto calibration */
slouken@172
   127
			if (hidEvent.value < pElement->minReport)
slouken@172
   128
				pElement->minReport = hidEvent.value;
slouken@172
   129
			if (hidEvent.value > pElement->maxReport)
slouken@172
   130
				pElement->maxReport = hidEvent.value;
slouken@172
   131
		}
slouken@172
   132
	}
slouken@172
   133
slouken@1487
   134
	/* auto user scale */
slouken@172
   135
	return hidEvent.value;
slouken@172
   136
}
slouken@172
   137
icculus@1077
   138
static SInt32 HIDScaledCalibratedValue (recDevice *pDevice, recElement *pElement, long min, long max)
slouken@172
   139
{
slouken@172
   140
	float deviceScale = max - min;
slouken@172
   141
	float readScale = pElement->maxReport - pElement->minReport;
slouken@172
   142
	SInt32 value = HIDGetElementValue(pDevice, pElement);
slouken@172
   143
	if (readScale == 0)
slouken@1487
   144
		return value; /* no scaling at all */
slouken@172
   145
	else
slouken@172
   146
		return ((value - pElement->minReport) * deviceScale / readScale) + min;
slouken@172
   147
}
slouken@172
   148
slouken@858
   149
slouken@858
   150
static void HIDRemovalCallback(void * target,
slouken@858
   151
                               IOReturn result,
slouken@858
   152
                               void * refcon,
slouken@858
   153
                               void * sender)
slouken@858
   154
{
slouken@858
   155
	recDevice *device = (recDevice *) refcon;
slouken@858
   156
	device->removed = 1;
slouken@858
   157
	device->uncentered = 1;
slouken@858
   158
}
slouken@858
   159
slouken@858
   160
slouken@858
   161
slouken@172
   162
/* Create and open an interface to device, required prior to extracting values or building queues.
slouken@172
   163
 * Note: appliction now owns the device and must close and release it prior to exiting
slouken@172
   164
 */
slouken@172
   165
icculus@1077
   166
static IOReturn HIDCreateOpenDeviceInterface (io_object_t hidDevice, recDevice *pDevice)
slouken@172
   167
{
slouken@172
   168
	IOReturn result = kIOReturnSuccess;
slouken@172
   169
	HRESULT plugInResult = S_OK;
slouken@172
   170
	SInt32 score = 0;
slouken@172
   171
	IOCFPlugInInterface ** ppPlugInInterface = NULL;
slouken@172
   172
	
slouken@172
   173
	if (NULL == pDevice->interface)
slouken@172
   174
	{
slouken@172
   175
		result = IOCreatePlugInInterfaceForService (hidDevice, kIOHIDDeviceUserClientTypeID,
slouken@172
   176
													kIOCFPlugInInterfaceID, &ppPlugInInterface, &score);
slouken@172
   177
		if (kIOReturnSuccess == result)
slouken@172
   178
		{
slouken@1487
   179
			/* Call a method of the intermediate plug-in to create the device interface */
slouken@172
   180
			plugInResult = (*ppPlugInInterface)->QueryInterface (ppPlugInInterface,
slouken@172
   181
								CFUUIDGetUUIDBytes (kIOHIDDeviceInterfaceID), (void *) &(pDevice->interface));
slouken@172
   182
			if (S_OK != plugInResult)
slouken@172
   183
				HIDReportErrorNum ("CouldnŐt query HID class device interface from plugInInterface", plugInResult);
slouken@172
   184
			(*ppPlugInInterface)->Release (ppPlugInInterface);
slouken@172
   185
		}
slouken@172
   186
		else
slouken@172
   187
			HIDReportErrorNum ("Failed to create **plugInInterface via IOCreatePlugInInterfaceForService.", result);
slouken@172
   188
	}
slouken@172
   189
	if (NULL != pDevice->interface)
slouken@172
   190
	{
slouken@172
   191
		result = (*(pDevice->interface))->open (pDevice->interface, 0);
slouken@172
   192
		if (kIOReturnSuccess != result)
slouken@172
   193
			HIDReportErrorNum ("Failed to open pDevice->interface via open.", result);
slouken@858
   194
		else
slouken@858
   195
			(*(pDevice->interface))->setRemovalCallback (pDevice->interface, HIDRemovalCallback, pDevice, pDevice);
slouken@858
   196
slouken@172
   197
	}
slouken@172
   198
	return result;
slouken@172
   199
}
slouken@172
   200
slouken@172
   201
/* Closes and releases interface to device, should be done prior to exting application
slouken@172
   202
 * Note: will have no affect if device or interface do not exist
slouken@172
   203
 * application will "own" the device if interface is not closed
slouken@172
   204
 * (device may have to be plug and re-plugged in different location to get it working again without a restart)
slouken@172
   205
 */
slouken@172
   206
icculus@1077
   207
static IOReturn HIDCloseReleaseInterface (recDevice *pDevice)
slouken@172
   208
{
slouken@172
   209
	IOReturn result = kIOReturnSuccess;
slouken@172
   210
	
slouken@172
   211
	if ((NULL != pDevice) && (NULL != pDevice->interface))
slouken@172
   212
	{
slouken@1487
   213
		/* close the interface */
slouken@172
   214
		result = (*(pDevice->interface))->close (pDevice->interface);
slouken@172
   215
		if (kIOReturnNotOpen == result)
slouken@172
   216
		{
slouken@1487
   217
			/* do nothing as device was not opened, thus can't be closed */
slouken@172
   218
		}
slouken@172
   219
		else if (kIOReturnSuccess != result)
slouken@172
   220
			HIDReportErrorNum ("Failed to close IOHIDDeviceInterface.", result);
slouken@1487
   221
		/* release the interface */
slouken@172
   222
		result = (*(pDevice->interface))->Release (pDevice->interface);
slouken@172
   223
		if (kIOReturnSuccess != result)
slouken@172
   224
			HIDReportErrorNum ("Failed to release IOHIDDeviceInterface.", result);
slouken@172
   225
		pDevice->interface = NULL;
slouken@172
   226
	}	
slouken@172
   227
	return result;
slouken@172
   228
}
slouken@172
   229
slouken@172
   230
/* extracts actual specific element information from each element CF dictionary entry */
slouken@172
   231
slouken@172
   232
static void HIDGetElementInfo (CFTypeRef refElement, recElement *pElement)
slouken@172
   233
{
slouken@172
   234
	long number;
slouken@172
   235
	CFTypeRef refType;
slouken@172
   236
slouken@172
   237
	refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementCookieKey));
slouken@172
   238
	if (refType && CFNumberGetValue (refType, kCFNumberLongType, &number))
slouken@172
   239
		pElement->cookie = (IOHIDElementCookie) number;
slouken@172
   240
	refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementMinKey));
slouken@172
   241
	if (refType && CFNumberGetValue (refType, kCFNumberLongType, &number))
slouken@172
   242
		pElement->min = number;
slouken@172
   243
		pElement->maxReport = pElement->min;
slouken@172
   244
	refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementMaxKey));
slouken@172
   245
	if (refType && CFNumberGetValue (refType, kCFNumberLongType, &number))
slouken@172
   246
		pElement->max = number;
slouken@172
   247
		pElement->minReport = pElement->max;
slouken@172
   248
/*
slouken@172
   249
	TODO: maybe should handle the following stuff somehow?
slouken@172
   250
slouken@172
   251
	refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementScaledMinKey));
slouken@172
   252
	if (refType && CFNumberGetValue (refType, kCFNumberLongType, &number))
slouken@172
   253
		pElement->scaledMin = number;
slouken@172
   254
	refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementScaledMaxKey));
slouken@172
   255
	if (refType && CFNumberGetValue (refType, kCFNumberLongType, &number))
slouken@172
   256
		pElement->scaledMax = number;
slouken@172
   257
	refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementSizeKey));
slouken@172
   258
	if (refType && CFNumberGetValue (refType, kCFNumberLongType, &number))
slouken@172
   259
		pElement->size = number;
slouken@172
   260
	refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementIsRelativeKey));
slouken@172
   261
	if (refType)
slouken@172
   262
		pElement->relative = CFBooleanGetValue (refType);
slouken@172
   263
	refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementIsWrappingKey));
slouken@172
   264
	if (refType)
slouken@172
   265
		pElement->wrapping = CFBooleanGetValue (refType);
slouken@172
   266
	refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementIsNonLinearKey));
slouken@172
   267
	if (refType)
slouken@172
   268
		pElement->nonLinear = CFBooleanGetValue (refType);
slouken@172
   269
	refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementHasPreferedStateKey));
slouken@172
   270
	if (refType)
slouken@172
   271
		pElement->preferredState = CFBooleanGetValue (refType);
slouken@172
   272
	refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementHasNullStateKey));
slouken@172
   273
	if (refType)
slouken@172
   274
		pElement->nullState = CFBooleanGetValue (refType);
slouken@172
   275
*/
slouken@172
   276
}			
slouken@172
   277
slouken@172
   278
/* examines CF dictionary vlaue in device element hierarchy to determine if it is element of interest or a collection of more elements
slouken@172
   279
 * if element of interest allocate storage, add to list and retrieve element specific info
slouken@172
   280
 * if collection then pass on to deconstruction collection into additional individual elements
slouken@172
   281
 */
slouken@172
   282
slouken@172
   283
static void HIDAddElement (CFTypeRef refElement, recDevice* pDevice)
slouken@172
   284
{
slouken@172
   285
	recElement* element = NULL;
slouken@172
   286
	recElement** headElement = NULL;
slouken@172
   287
	long elementType, usagePage, usage;
slouken@172
   288
	CFTypeRef refElementType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementTypeKey));
slouken@172
   289
	CFTypeRef refUsagePage = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementUsagePageKey));
slouken@172
   290
	CFTypeRef refUsage = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementUsageKey));
slouken@172
   291
slouken@172
   292
slouken@172
   293
	if ((refElementType) && (CFNumberGetValue (refElementType, kCFNumberLongType, &elementType)))
slouken@172
   294
	{
slouken@172
   295
		/* look at types of interest */
slouken@172
   296
		if ((elementType == kIOHIDElementTypeInput_Misc) || (elementType == kIOHIDElementTypeInput_Button) ||
slouken@172
   297
			(elementType == kIOHIDElementTypeInput_Axis))
slouken@172
   298
		{
slouken@172
   299
			if (refUsagePage && CFNumberGetValue (refUsagePage, kCFNumberLongType, &usagePage) &&
slouken@172
   300
				refUsage && CFNumberGetValue (refUsage, kCFNumberLongType, &usage))
slouken@172
   301
			{
slouken@172
   302
				switch (usagePage) /* only interested in kHIDPage_GenericDesktop and kHIDPage_Button */
slouken@172
   303
				{
slouken@172
   304
					case kHIDPage_GenericDesktop:
slouken@172
   305
						{
slouken@172
   306
							switch (usage) /* look at usage to determine function */
slouken@172
   307
							{
slouken@172
   308
								case kHIDUsage_GD_X:
slouken@172
   309
								case kHIDUsage_GD_Y:
slouken@172
   310
								case kHIDUsage_GD_Z:
slouken@172
   311
								case kHIDUsage_GD_Rx:
slouken@172
   312
								case kHIDUsage_GD_Ry:
slouken@172
   313
								case kHIDUsage_GD_Rz:
slouken@594
   314
								case kHIDUsage_GD_Slider:
slouken@594
   315
								case kHIDUsage_GD_Dial:
slouken@594
   316
								case kHIDUsage_GD_Wheel:
slouken@172
   317
									element = (recElement *) NewPtrClear (sizeof (recElement));
slouken@172
   318
									if (element)
slouken@172
   319
									{
slouken@172
   320
										pDevice->axes++;
slouken@172
   321
										headElement = &(pDevice->firstAxis);
slouken@172
   322
									}
slouken@172
   323
								break;
slouken@172
   324
								case kHIDUsage_GD_Hatswitch:
slouken@172
   325
									element = (recElement *) NewPtrClear (sizeof (recElement));
slouken@172
   326
									if (element)
slouken@172
   327
									{
slouken@172
   328
										pDevice->hats++;
slouken@172
   329
										headElement = &(pDevice->firstHat);
slouken@172
   330
									}
slouken@172
   331
								break;
slouken@172
   332
							}							
slouken@172
   333
						}
slouken@172
   334
						break;
slouken@172
   335
					case kHIDPage_Button:
slouken@172
   336
						element = (recElement *) NewPtrClear (sizeof (recElement));
slouken@172
   337
						if (element)
slouken@172
   338
						{
slouken@172
   339
							pDevice->buttons++;
slouken@172
   340
							headElement = &(pDevice->firstButton);
slouken@172
   341
						}
slouken@172
   342
						break;
slouken@172
   343
					default:
slouken@172
   344
						break;
slouken@172
   345
				}
slouken@172
   346
			}
slouken@172
   347
		}
slouken@172
   348
		else if (kIOHIDElementTypeCollection == elementType)
slouken@172
   349
			HIDGetCollectionElements ((CFMutableDictionaryRef) refElement, pDevice);
slouken@172
   350
	}
slouken@172
   351
slouken@172
   352
	if (element && headElement) /* add to list */
slouken@172
   353
	{
slouken@172
   354
		pDevice->elements++;
slouken@172
   355
		if (NULL == *headElement)
slouken@172
   356
			*headElement = element;
slouken@172
   357
		else
slouken@172
   358
		{
slouken@172
   359
			recElement *elementPrevious, *elementCurrent;
slouken@172
   360
			elementCurrent = *headElement;
slouken@172
   361
			while (elementCurrent)
slouken@172
   362
			{
slouken@172
   363
				elementPrevious = elementCurrent;
slouken@172
   364
				elementCurrent = elementPrevious->pNext;
slouken@172
   365
			}
slouken@172
   366
			elementPrevious->pNext = element;
slouken@172
   367
		}
slouken@172
   368
		element->pNext = NULL;
slouken@172
   369
		HIDGetElementInfo (refElement, element);
slouken@172
   370
	}
slouken@172
   371
}
slouken@172
   372
slouken@172
   373
/* collects information from each array member in device element list (each array memeber = element) */
slouken@172
   374
slouken@172
   375
static void HIDGetElementsCFArrayHandler (const void * value, void * parameter)
slouken@172
   376
{
slouken@172
   377
	if (CFGetTypeID (value) == CFDictionaryGetTypeID ())
slouken@172
   378
		HIDAddElement ((CFTypeRef) value, (recDevice *) parameter);
slouken@172
   379
}
slouken@172
   380
slouken@172
   381
/* handles retrieval of element information from arrays of elements in device IO registry information */
slouken@172
   382
slouken@172
   383
static void HIDGetElements (CFTypeRef refElementCurrent, recDevice *pDevice)
slouken@172
   384
{
slouken@172
   385
	CFTypeID type = CFGetTypeID (refElementCurrent);
slouken@172
   386
	if (type == CFArrayGetTypeID()) /* if element is an array */
slouken@172
   387
	{
slouken@172
   388
		CFRange range = {0, CFArrayGetCount (refElementCurrent)};
slouken@172
   389
		/* CountElementsCFArrayHandler called for each array member */
slouken@172
   390
		CFArrayApplyFunction (refElementCurrent, range, HIDGetElementsCFArrayHandler, pDevice);
slouken@172
   391
	}
slouken@172
   392
}			
slouken@172
   393
slouken@172
   394
/* handles extracting element information from element collection CF types
slouken@172
   395
 * used from top level element decoding and hierarchy deconstruction to flatten device element list
slouken@172
   396
 */
slouken@172
   397
slouken@172
   398
static void HIDGetCollectionElements (CFMutableDictionaryRef deviceProperties, recDevice *pDevice)
slouken@172
   399
{
slouken@172
   400
	CFTypeRef refElementTop = CFDictionaryGetValue (deviceProperties, CFSTR(kIOHIDElementKey));
slouken@172
   401
	if (refElementTop)
slouken@172
   402
		HIDGetElements (refElementTop, pDevice);
slouken@172
   403
}
slouken@172
   404
slouken@172
   405
/* use top level element usage page and usage to discern device usage page and usage setting appropriate vlaues in device record */
slouken@172
   406
slouken@172
   407
static void HIDTopLevelElementHandler (const void * value, void * parameter)
slouken@172
   408
{
slouken@172
   409
	CFTypeRef refCF = 0;
slouken@172
   410
	if (CFGetTypeID (value) != CFDictionaryGetTypeID ())
slouken@172
   411
		return;
slouken@172
   412
	refCF = CFDictionaryGetValue (value, CFSTR(kIOHIDElementUsagePageKey));
slouken@172
   413
	if (!CFNumberGetValue (refCF, kCFNumberLongType, &((recDevice *) parameter)->usagePage))
slouken@172
   414
		SDL_SetError ("CFNumberGetValue error retrieving pDevice->usagePage.");
slouken@172
   415
	refCF = CFDictionaryGetValue (value, CFSTR(kIOHIDElementUsageKey));
slouken@172
   416
	if (!CFNumberGetValue (refCF, kCFNumberLongType, &((recDevice *) parameter)->usage))
slouken@172
   417
		SDL_SetError ("CFNumberGetValue error retrieving pDevice->usage.");
slouken@172
   418
}
slouken@172
   419
slouken@172
   420
/* extracts device info from CF dictionary records in IO registry */
slouken@172
   421
slouken@172
   422
static void HIDGetDeviceInfo (io_object_t hidDevice, CFMutableDictionaryRef hidProperties, recDevice *pDevice)
slouken@172
   423
{
slouken@172
   424
	CFMutableDictionaryRef usbProperties = 0;
slouken@172
   425
	io_registry_entry_t parent1, parent2;
slouken@172
   426
	
slouken@172
   427
	/* Mac OS X currently is not mirroring all USB properties to HID page so need to look at USB device page also
slouken@172
   428
	 * get dictionary for usb properties: step up two levels and get CF dictionary for USB properties
slouken@172
   429
	 */
slouken@172
   430
	if ((KERN_SUCCESS == IORegistryEntryGetParentEntry (hidDevice, kIOServicePlane, &parent1)) &&
slouken@172
   431
		(KERN_SUCCESS == IORegistryEntryGetParentEntry (parent1, kIOServicePlane, &parent2)) &&
slouken@172
   432
		(KERN_SUCCESS == IORegistryEntryCreateCFProperties (parent2, &usbProperties, kCFAllocatorDefault, kNilOptions)))
slouken@172
   433
	{
slouken@172
   434
		if (usbProperties)
slouken@172
   435
		{
slouken@172
   436
			CFTypeRef refCF = 0;
slouken@172
   437
			/* get device info
slouken@172
   438
			 * try hid dictionary first, if fail then go to usb dictionary
slouken@172
   439
			 */
slouken@172
   440
			
slouken@172
   441
			
slouken@172
   442
			/* get product name */
slouken@172
   443
			refCF = CFDictionaryGetValue (hidProperties, CFSTR(kIOHIDProductKey));
slouken@172
   444
			if (!refCF)
slouken@172
   445
				refCF = CFDictionaryGetValue (usbProperties, CFSTR("USB Product Name"));
slouken@172
   446
			if (refCF)
slouken@172
   447
			{
slouken@172
   448
				if (!CFStringGetCString (refCF, pDevice->product, 256, CFStringGetSystemEncoding ()))
slouken@172
   449
					SDL_SetError ("CFStringGetCString error retrieving pDevice->product.");
slouken@172
   450
			}
slouken@172
   451
			
slouken@172
   452
			/* get usage page and usage */
slouken@172
   453
			refCF = CFDictionaryGetValue (hidProperties, CFSTR(kIOHIDPrimaryUsagePageKey));
slouken@172
   454
			if (refCF)
slouken@172
   455
			{
slouken@172
   456
				if (!CFNumberGetValue (refCF, kCFNumberLongType, &pDevice->usagePage))
slouken@172
   457
					SDL_SetError ("CFNumberGetValue error retrieving pDevice->usagePage.");
slouken@172
   458
				refCF = CFDictionaryGetValue (hidProperties, CFSTR(kIOHIDPrimaryUsageKey));
slouken@172
   459
				if (refCF)
slouken@172
   460
					if (!CFNumberGetValue (refCF, kCFNumberLongType, &pDevice->usage))
slouken@172
   461
						SDL_SetError ("CFNumberGetValue error retrieving pDevice->usage.");
slouken@172
   462
			}
slouken@172
   463
slouken@172
   464
			if (NULL == refCF) /* get top level element HID usage page or usage */
slouken@172
   465
			{
slouken@172
   466
				/* use top level element instead */
slouken@172
   467
				CFTypeRef refCFTopElement = 0;
slouken@172
   468
				refCFTopElement = CFDictionaryGetValue (hidProperties, CFSTR(kIOHIDElementKey));
slouken@172
   469
				{
slouken@172
   470
					/* refCFTopElement points to an array of element dictionaries */
slouken@172
   471
					CFRange range = {0, CFArrayGetCount (refCFTopElement)};
slouken@172
   472
					CFArrayApplyFunction (refCFTopElement, range, HIDTopLevelElementHandler, pDevice);
slouken@172
   473
				}
slouken@172
   474
			}
slouken@172
   475
slouken@172
   476
			CFRelease (usbProperties);
slouken@172
   477
		}
slouken@172
   478
		else
slouken@172
   479
			SDL_SetError ("IORegistryEntryCreateCFProperties failed to create usbProperties.");
slouken@172
   480
slouken@172
   481
		if (kIOReturnSuccess != IOObjectRelease (parent2))
slouken@172
   482
			SDL_SetError ("IOObjectRelease error with parent2.");
slouken@172
   483
		if (kIOReturnSuccess != IOObjectRelease (parent1))
slouken@172
   484
			SDL_SetError ("IOObjectRelease error with parent1.");
slouken@172
   485
	}
slouken@172
   486
}
slouken@172
   487
slouken@172
   488
slouken@172
   489
static recDevice *HIDBuildDevice (io_object_t hidDevice)
slouken@172
   490
{
slouken@172
   491
	recDevice *pDevice = (recDevice *) NewPtrClear (sizeof (recDevice));
slouken@172
   492
	if (pDevice)
slouken@172
   493
	{
slouken@172
   494
		/* get dictionary for HID properties */
slouken@172
   495
		CFMutableDictionaryRef hidProperties = 0;
slouken@172
   496
		kern_return_t result = IORegistryEntryCreateCFProperties (hidDevice, &hidProperties, kCFAllocatorDefault, kNilOptions);
slouken@172
   497
		if ((result == KERN_SUCCESS) && hidProperties)
slouken@172
   498
		{
slouken@172
   499
			/* create device interface */
slouken@172
   500
			result = HIDCreateOpenDeviceInterface (hidDevice, pDevice);
slouken@172
   501
			if (kIOReturnSuccess == result)
slouken@172
   502
			{
slouken@172
   503
				HIDGetDeviceInfo (hidDevice, hidProperties, pDevice); /* hidDevice used to find parents in registry tree */
slouken@172
   504
				HIDGetCollectionElements (hidProperties, pDevice);
slouken@172
   505
			}
slouken@172
   506
			else
slouken@172
   507
			{
slouken@210
   508
				DisposePtr((Ptr)pDevice);
slouken@172
   509
				pDevice = NULL;
slouken@172
   510
			}
slouken@172
   511
			CFRelease (hidProperties);
slouken@172
   512
		}
slouken@172
   513
		else
slouken@172
   514
		{
slouken@210
   515
			DisposePtr((Ptr)pDevice);
slouken@172
   516
			pDevice = NULL;
slouken@172
   517
		}
slouken@172
   518
	}
slouken@172
   519
	return pDevice;
slouken@172
   520
}
slouken@172
   521
slouken@172
   522
/* disposes of the element list associated with a device and the memory associated with the list
slouken@172
   523
 */
slouken@172
   524
slouken@172
   525
static void HIDDisposeElementList (recElement **elementList)
slouken@172
   526
{
slouken@172
   527
	recElement *pElement = *elementList;
slouken@172
   528
	while (pElement)
slouken@172
   529
	{
slouken@172
   530
		recElement *pElementNext = pElement->pNext;
slouken@172
   531
		DisposePtr ((Ptr) pElement);
slouken@172
   532
		pElement = pElementNext;
slouken@172
   533
	}
slouken@172
   534
	*elementList = NULL;
slouken@172
   535
}
slouken@172
   536
slouken@172
   537
/* disposes of a single device, closing and releaseing interface, freeing memory fro device and elements, setting device pointer to NULL
slouken@172
   538
 * all your device no longer belong to us... (i.e., you do not 'own' the device anymore)
slouken@172
   539
 */
slouken@172
   540
slouken@172
   541
static recDevice *HIDDisposeDevice (recDevice **ppDevice)
slouken@172
   542
{
slouken@172
   543
	kern_return_t result = KERN_SUCCESS;
slouken@172
   544
	recDevice *pDeviceNext = NULL;
slouken@172
   545
	if (*ppDevice)
slouken@172
   546
	{
slouken@1487
   547
		/* save next device prior to disposing of this device */
slouken@172
   548
		pDeviceNext = (*ppDevice)->pNext;
slouken@172
   549
		
slouken@172
   550
		/* free element lists */
slouken@172
   551
		HIDDisposeElementList (&(*ppDevice)->firstAxis);
slouken@172
   552
		HIDDisposeElementList (&(*ppDevice)->firstButton);
slouken@172
   553
		HIDDisposeElementList (&(*ppDevice)->firstHat);
slouken@172
   554
		
slouken@172
   555
		result = HIDCloseReleaseInterface (*ppDevice); /* function sanity checks interface value (now application does not own device) */
slouken@172
   556
		if (kIOReturnSuccess != result)
slouken@172
   557
			HIDReportErrorNum ("HIDCloseReleaseInterface failed when trying to dipose device.", result);
slouken@172
   558
		DisposePtr ((Ptr)*ppDevice);
slouken@172
   559
		*ppDevice = NULL;
slouken@172
   560
	}
slouken@172
   561
	return pDeviceNext;
slouken@172
   562
}
slouken@172
   563
slouken@172
   564
slouken@172
   565
/* Function to scan the system for joysticks.
slouken@172
   566
 * Joystick 0 should be the system default joystick.
slouken@172
   567
 * This function should return the number of available joysticks, or -1
slouken@172
   568
 * on an unrecoverable fatal error.
slouken@172
   569
 */
slouken@172
   570
int SDL_SYS_JoystickInit(void)
slouken@172
   571
{
slouken@172
   572
	IOReturn result = kIOReturnSuccess;
slouken@1409
   573
	mach_port_t masterPort = 0;
slouken@1409
   574
	io_iterator_t hidObjectIterator = 0;
slouken@172
   575
	CFMutableDictionaryRef hidMatchDictionary = NULL;
slouken@172
   576
	recDevice *device, *lastDevice;
slouken@1409
   577
	io_object_t ioHIDDeviceObject = 0;
slouken@172
   578
	
slouken@172
   579
	SDL_numjoysticks = 0;
slouken@172
   580
	
slouken@1496
   581
	if (gpDeviceList)
slouken@172
   582
	{
slouken@172
   583
		SDL_SetError("Joystick: Device list already inited.");
slouken@172
   584
		return -1;
slouken@172
   585
	}
slouken@172
   586
	
slouken@172
   587
	result = IOMasterPort (bootstrap_port, &masterPort);
slouken@172
   588
	if (kIOReturnSuccess != result)
slouken@172
   589
	{
slouken@172
   590
		SDL_SetError("Joystick: IOMasterPort error with bootstrap_port.");
slouken@172
   591
		return -1;
slouken@172
   592
	}
slouken@172
   593
slouken@172
   594
	/* Set up a matching dictionary to search I/O Registry by class name for all HID class devices. */
slouken@172
   595
	hidMatchDictionary = IOServiceMatching (kIOHIDDeviceKey);
slouken@1409
   596
	if (hidMatchDictionary)
slouken@172
   597
	{
slouken@172
   598
		/* Add key for device type (joystick, in this case) to refine the matching dictionary. */
slouken@495
   599
		
slouken@495
   600
		/* NOTE: we now perform this filtering later
slouken@495
   601
		UInt32 usagePage = kHIDPage_GenericDesktop;
slouken@495
   602
		UInt32 usage = kHIDUsage_GD_Joystick;
slouken@172
   603
		CFNumberRef refUsage = NULL, refUsagePage = NULL;
slouken@172
   604
slouken@475
   605
		refUsage = CFNumberCreate (kCFAllocatorDefault, kCFNumberIntType, &usage);
slouken@172
   606
		CFDictionarySetValue (hidMatchDictionary, CFSTR (kIOHIDPrimaryUsageKey), refUsage);
slouken@475
   607
		refUsagePage = CFNumberCreate (kCFAllocatorDefault, kCFNumberIntType, &usagePage);
slouken@172
   608
		CFDictionarySetValue (hidMatchDictionary, CFSTR (kIOHIDPrimaryUsagePageKey), refUsagePage);
slouken@495
   609
		*/
slouken@172
   610
	}
slouken@172
   611
	else
slouken@172
   612
	{
slouken@172
   613
		SDL_SetError("Joystick: Failed to get HID CFMutableDictionaryRef via IOServiceMatching.");
slouken@172
   614
		return -1;
slouken@172
   615
	}
slouken@172
   616
	
slouken@172
   617
	/*/ Now search I/O Registry for matching devices. */
slouken@172
   618
	result = IOServiceGetMatchingServices (masterPort, hidMatchDictionary, &hidObjectIterator);
slouken@172
   619
	/* Check for errors */
slouken@427
   620
	if (kIOReturnSuccess != result)
slouken@172
   621
	{
slouken@172
   622
		SDL_SetError("Joystick: Couldn't create a HID object iterator.");
slouken@172
   623
		return -1;
slouken@172
   624
	}
slouken@1409
   625
	if (!hidObjectIterator) /* there are no joysticks */
slouken@427
   626
	{
slouken@427
   627
		gpDeviceList = NULL;
slouken@427
   628
		SDL_numjoysticks = 0;
slouken@427
   629
		return 0;
slouken@427
   630
	}
slouken@172
   631
	/* IOServiceGetMatchingServices consumes a reference to the dictionary, so we don't need to release the dictionary ref. */
slouken@172
   632
slouken@172
   633
	/* build flat linked list of devices from device iterator */
slouken@172
   634
slouken@172
   635
	gpDeviceList = lastDevice = NULL;
slouken@172
   636
	
slouken@172
   637
	while ((ioHIDDeviceObject = IOIteratorNext (hidObjectIterator)))
slouken@172
   638
	{
slouken@172
   639
		/* build a device record */
slouken@172
   640
		device = HIDBuildDevice (ioHIDDeviceObject);
slouken@172
   641
		if (!device)
slouken@172
   642
			continue;
slouken@172
   643
slouken@172
   644
		/* dump device object, it is no longer needed */
slouken@172
   645
		result = IOObjectRelease (ioHIDDeviceObject);
slouken@1487
   646
/*		if (KERN_SUCCESS != result)
slouken@1487
   647
			HIDReportErrorNum ("IOObjectRelease error with ioHIDDeviceObject.", result);
slouken@1487
   648
*/
slouken@495
   649
slouken@495
   650
		/* Filter device list to non-keyboard/mouse stuff */ 
icculus@623
   651
		if ( (device->usagePage != kHIDPage_GenericDesktop) ||
icculus@623
   652
		     ((device->usage != kHIDUsage_GD_Joystick &&
icculus@623
   653
		      device->usage != kHIDUsage_GD_GamePad)) ) {
icculus@620
   654
slouken@495
   655
			/* release memory for the device */
slouken@495
   656
			HIDDisposeDevice (&device);
slouken@495
   657
			DisposePtr((Ptr)device);
slouken@495
   658
			continue;
slouken@495
   659
		}
slouken@495
   660
		
slouken@172
   661
		/* Add device to the end of the list */
slouken@172
   662
		if (lastDevice)
slouken@172
   663
			lastDevice->pNext = device;
slouken@172
   664
		else
slouken@172
   665
			gpDeviceList = device;
slouken@172
   666
		lastDevice = device;
slouken@172
   667
	}
slouken@172
   668
	result = IOObjectRelease (hidObjectIterator); /* release the iterator */
slouken@172
   669
slouken@172
   670
	/* Count the total number of devices we found */
slouken@172
   671
	device = gpDeviceList;
slouken@172
   672
	while (device)
slouken@172
   673
	{
slouken@172
   674
		SDL_numjoysticks++;
slouken@172
   675
		device = device->pNext;
slouken@172
   676
	}
slouken@172
   677
	
slouken@172
   678
	return SDL_numjoysticks;
slouken@172
   679
}
slouken@172
   680
slouken@172
   681
/* Function to get the device-dependent name of a joystick */
slouken@172
   682
const char *SDL_SYS_JoystickName(int index)
slouken@172
   683
{
slouken@172
   684
	recDevice *device = gpDeviceList;
slouken@172
   685
	
slouken@172
   686
	for (; index > 0; index--)
slouken@172
   687
		device = device->pNext;
slouken@172
   688
slouken@172
   689
	return device->product;
slouken@172
   690
}
slouken@172
   691
slouken@172
   692
/* Function to open a joystick for use.
slouken@172
   693
 * The joystick to open is specified by the index field of the joystick.
slouken@172
   694
 * This should fill the nbuttons and naxes fields of the joystick structure.
slouken@172
   695
 * It returns 0, or -1 if there is an error.
slouken@172
   696
 */
slouken@172
   697
int SDL_SYS_JoystickOpen(SDL_Joystick *joystick)
slouken@172
   698
{
slouken@172
   699
	recDevice *device = gpDeviceList;
slouken@172
   700
	int index;
slouken@172
   701
	
slouken@172
   702
	for (index = joystick->index; index > 0; index--)
slouken@172
   703
		device = device->pNext;
slouken@172
   704
slouken@172
   705
	joystick->hwdata = device;
slouken@172
   706
	joystick->name = device->product;
slouken@172
   707
slouken@172
   708
	joystick->naxes = device->axes;
slouken@172
   709
	joystick->nhats = device->hats;
slouken@172
   710
	joystick->nballs = 0;
slouken@172
   711
	joystick->nbuttons = device->buttons;
slouken@172
   712
slouken@172
   713
	return 0;
slouken@172
   714
}
slouken@172
   715
slouken@172
   716
/* Function to update the state of a joystick - called as a device poll.
slouken@172
   717
 * This function shouldn't update the joystick structure directly,
slouken@172
   718
 * but instead should call SDL_PrivateJoystick*() to deliver events
slouken@172
   719
 * and update joystick device state.
slouken@172
   720
 */
slouken@172
   721
void SDL_SYS_JoystickUpdate(SDL_Joystick *joystick)
slouken@172
   722
{
slouken@172
   723
	recDevice *device = joystick->hwdata;
slouken@172
   724
	recElement *element;
slouken@172
   725
	SInt32 value;
slouken@172
   726
	int i;
slouken@858
   727
slouken@858
   728
	if (device->removed)  /* device was unplugged; ignore it. */
slouken@858
   729
	{
slouken@858
   730
		if (device->uncentered)
slouken@858
   731
		{
slouken@858
   732
			device->uncentered = 0;
slouken@858
   733
slouken@858
   734
			/* Tell the app that everything is centered/unpressed... */
slouken@858
   735
			for (i = 0; i < device->axes; i++)
slouken@858
   736
				SDL_PrivateJoystickAxis(joystick, i, 0);
slouken@858
   737
slouken@858
   738
			for (i = 0; i < device->buttons; i++)
slouken@858
   739
				SDL_PrivateJoystickButton(joystick, i, 0);
slouken@858
   740
slouken@858
   741
			for (i = 0; i < device->hats; i++)
slouken@858
   742
				SDL_PrivateJoystickHat(joystick, i, SDL_HAT_CENTERED);
slouken@858
   743
		}
slouken@858
   744
slouken@858
   745
		return;
slouken@858
   746
	}
slouken@858
   747
slouken@172
   748
	element = device->firstAxis;
slouken@172
   749
	i = 0;
slouken@172
   750
	while (element)
slouken@172
   751
	{
slouken@172
   752
		value = HIDScaledCalibratedValue(device, element, -32768, 32767);
slouken@172
   753
		if ( value != joystick->axes[i] )
slouken@172
   754
			SDL_PrivateJoystickAxis(joystick, i, value);
slouken@172
   755
		element = element->pNext;
slouken@172
   756
		++i;
slouken@172
   757
	}
slouken@172
   758
	
slouken@172
   759
	element = device->firstButton;
slouken@172
   760
	i = 0;
slouken@172
   761
	while (element)
slouken@172
   762
	{
slouken@172
   763
		value = HIDGetElementValue(device, element);
slouken@629
   764
        if (value > 1)  /* handle pressure-sensitive buttons */
slouken@629
   765
            value = 1;
slouken@172
   766
		if ( value != joystick->buttons[i] )
slouken@172
   767
			SDL_PrivateJoystickButton(joystick, i, value);
slouken@172
   768
		element = element->pNext;
slouken@172
   769
		++i;
slouken@172
   770
	}
slouken@629
   771
	    
slouken@172
   772
	element = device->firstHat;
slouken@172
   773
	i = 0;
slouken@172
   774
	while (element)
slouken@172
   775
	{
slouken@210
   776
		Uint8 pos = 0;
slouken@172
   777
slouken@172
   778
		value = HIDGetElementValue(device, element);
slouken@617
   779
		if (element->max == 3) /* 4 position hatswitch - scale up value */
slouken@617
   780
			value *= 2;
slouken@617
   781
		else if (element->max != 7) /* Neither a 4 nor 8 positions - fall back to default position (centered) */
slouken@617
   782
			value = -1;
slouken@172
   783
		switch(value)
slouken@172
   784
		{
slouken@172
   785
			case 0:
slouken@617
   786
				pos = SDL_HAT_UP;
slouken@172
   787
				break;
slouken@172
   788
			case 1:
slouken@617
   789
				pos = SDL_HAT_RIGHTUP;
slouken@172
   790
				break;
slouken@172
   791
			case 2:
slouken@617
   792
				pos = SDL_HAT_RIGHT;
slouken@172
   793
				break;
slouken@172
   794
			case 3:
slouken@617
   795
				pos = SDL_HAT_RIGHTDOWN;
slouken@172
   796
				break;
slouken@172
   797
			case 4:
slouken@617
   798
				pos = SDL_HAT_DOWN;
slouken@172
   799
				break;
slouken@172
   800
			case 5:
slouken@617
   801
				pos = SDL_HAT_LEFTDOWN;
slouken@172
   802
				break;
slouken@172
   803
			case 6:
slouken@617
   804
				pos = SDL_HAT_LEFT;
slouken@172
   805
				break;
slouken@172
   806
			case 7:
slouken@617
   807
				pos = SDL_HAT_LEFTUP;
slouken@172
   808
				break;
slouken@617
   809
			default:
slouken@617
   810
				/* Every other value is mapped to center. We do that because some
slouken@617
   811
				 * joysticks use 8 and some 15 for this value, and apparently
slouken@617
   812
				 * there are even more variants out there - so we try to be generous.
slouken@617
   813
				 */
slouken@617
   814
				pos = SDL_HAT_CENTERED;
slouken@172
   815
				break;
slouken@172
   816
		}
slouken@172
   817
		if ( pos != joystick->hats[i] )
slouken@172
   818
			SDL_PrivateJoystickHat(joystick, i, pos);
slouken@172
   819
		element = element->pNext;
slouken@172
   820
		++i;
slouken@172
   821
	}
slouken@172
   822
	
slouken@172
   823
	return;
slouken@172
   824
}
slouken@172
   825
slouken@172
   826
/* Function to close a joystick after use */
slouken@172
   827
void SDL_SYS_JoystickClose(SDL_Joystick *joystick)
slouken@172
   828
{
slouken@172
   829
	/* Should we do anything here? */
slouken@172
   830
	return;
slouken@172
   831
}
slouken@172
   832
slouken@172
   833
/* Function to perform any system-specific joystick related cleanup */
slouken@172
   834
void SDL_SYS_JoystickQuit(void)
slouken@172
   835
{
slouken@172
   836
	while (NULL != gpDeviceList)
slouken@172
   837
		gpDeviceList = HIDDisposeDevice (&gpDeviceList);
slouken@172
   838
}