2 * WinMM joystick driver OS X implementation
4 * Copyright 1997 Andreas Mohr
5 * Copyright 1998 Marcus Meissner
6 * Copyright 1998,1999 Lionel Ulmer
7 * Copyright 2000 Wolfgang Schwotzer
8 * Copyright 2000-2001 TransGaming Technologies Inc.
9 * Copyright 2002 David Hagood
10 * Copyright 2009 CodeWeavers, Aric Stewart
11 * Copyright 2015 Ken Thomases for CodeWeavers Inc.
13 * This library is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU Lesser General Public
15 * License as published by the Free Software Foundation; either
16 * version 2.1 of the License, or (at your option) any later version.
18 * This library is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 * Lesser General Public License for more details.
23 * You should have received a copy of the GNU Lesser General Public
24 * License along with this library; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
30 #if defined(HAVE_IOKIT_HID_IOHIDLIB_H)
33 #define LPDWORD UInt32*
35 #define LPLONG SInt32*
36 #define E_PENDING __carbon_E_PENDING
37 #define ULONG __carbon_ULONG
38 #define E_INVALIDARG __carbon_E_INVALIDARG
39 #define E_OUTOFMEMORY __carbon_E_OUTOFMEMORY
40 #define E_HANDLE __carbon_E_HANDLE
41 #define E_ACCESSDENIED __carbon_E_ACCESSDENIED
42 #define E_UNEXPECTED __carbon_E_UNEXPECTED
43 #define E_FAIL __carbon_E_FAIL
44 #define E_ABORT __carbon_E_ABORT
45 #define E_POINTER __carbon_E_POINTER
46 #define E_NOINTERFACE __carbon_E_NOINTERFACE
47 #define E_NOTIMPL __carbon_E_NOTIMPL
48 #define S_FALSE __carbon_S_FALSE
49 #define S_OK __carbon_S_OK
50 #define HRESULT_FACILITY __carbon_HRESULT_FACILITY
51 #define IS_ERROR __carbon_IS_ERROR
52 #define FAILED __carbon_FAILED
53 #define SUCCEEDED __carbon_SUCCEEDED
54 #define MAKE_HRESULT __carbon_MAKE_HRESULT
55 #define HRESULT __carbon_HRESULT
56 #define STDMETHODCALLTYPE __carbon_STDMETHODCALLTYPE
57 #include <IOKit/IOKitLib.h>
58 #include <IOKit/hid/IOHIDLib.h>
72 #undef HRESULT_FACILITY
78 #undef STDMETHODCALLTYPE
87 #include "wine/debug.h"
90 WINE_DEFAULT_DEBUG_CHANNEL(joystick
);
93 #define MAXJOYSTICK (JOYSTICKID2 + 30)
107 IOHIDElementRef element
;
108 CFIndex min_value
, max_value
;
113 IOHIDElementRef element
;
114 struct axis axes
[NUM_AXES
];
115 CFMutableArrayRef buttons
;
116 IOHIDElementRef hatswitch
;
120 static joystick_t joysticks
[MAXJOYSTICK
];
121 static CFMutableArrayRef device_main_elements
= NULL
;
124 static const char* debugstr_cf(CFTypeRef t
)
129 if (!t
) return "(null)";
131 if (CFGetTypeID(t
) == CFStringGetTypeID())
134 s
= CFCopyDescription(t
);
135 ret
= CFStringGetCStringPtr(s
, kCFStringEncodingUTF8
);
136 if (ret
) ret
= debugstr_a(ret
);
139 const UniChar
* u
= CFStringGetCharactersPtr(s
);
141 ret
= debugstr_wn((const WCHAR
*)u
, CFStringGetLength(s
));
146 int len
= min(CFStringGetLength(s
), sizeof(buf
)/sizeof(buf
[0]));
147 CFStringGetCharacters(s
, CFRangeMake(0, len
), buf
);
148 ret
= debugstr_wn(buf
, len
);
150 if (s
!= t
) CFRelease(s
);
154 static const char* debugstr_device(IOHIDDeviceRef device
)
156 return wine_dbg_sprintf("<IOHIDDevice %p product %s>", device
,
157 debugstr_cf(IOHIDDeviceGetProperty(device
, CFSTR(kIOHIDProductKey
))));
160 static const char* debugstr_element(IOHIDElementRef element
)
162 return wine_dbg_sprintf("<IOHIDElement %p type %d usage %u/%u device %p>", element
,
163 IOHIDElementGetType(element
), IOHIDElementGetUsagePage(element
),
164 IOHIDElementGetUsage(element
), IOHIDElementGetDevice(element
));
168 static int axis_for_usage(int usage
)
172 case kHIDUsage_GD_X
: return AXIS_X
;
173 case kHIDUsage_GD_Y
: return AXIS_Y
;
174 case kHIDUsage_GD_Z
: return AXIS_Z
;
175 case kHIDUsage_GD_Rx
: return AXIS_RX
;
176 case kHIDUsage_GD_Ry
: return AXIS_RY
;
177 case kHIDUsage_GD_Rz
: return AXIS_RZ
;
184 /**************************************************************************
187 static joystick_t
* joystick_from_id(DWORD_PTR device_id
)
191 if ((device_id
- (DWORD_PTR
)joysticks
) % sizeof(joysticks
[0]) != 0)
193 index
= (device_id
- (DWORD_PTR
)joysticks
) / sizeof(joysticks
[0]);
194 if (index
< 0 || index
>= MAXJOYSTICK
|| !((joystick_t
*)device_id
)->in_use
)
197 return (joystick_t
*)device_id
;
200 /**************************************************************************
201 * create_osx_device_match
203 static CFDictionaryRef
create_osx_device_match(int usage
)
205 CFDictionaryRef result
= NULL
;
207 CFStringRef keys
[] = { CFSTR(kIOHIDDeviceUsagePageKey
), CFSTR(kIOHIDDeviceUsageKey
) };
208 CFNumberRef values
[2];
211 TRACE("usage %d\n", usage
);
213 number
= kHIDPage_GenericDesktop
;
214 values
[0] = CFNumberCreate(kCFAllocatorDefault
, kCFNumberIntType
, &number
);
215 values
[1] = CFNumberCreate(kCFAllocatorDefault
, kCFNumberIntType
, &usage
);
217 if (values
[0] && values
[1])
219 result
= CFDictionaryCreate(NULL
, (const void**)keys
, (const void**)values
, sizeof(values
) / sizeof(values
[0]),
220 &kCFCopyStringDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
223 ERR("CFDictionaryCreate failed.\n");
226 ERR("CFNumberCreate failed.\n");
228 for (i
= 0; i
< sizeof(values
) / sizeof(values
[0]); i
++)
229 if (values
[i
]) CFRelease(values
[i
]);
234 /**************************************************************************
237 static CFIndex
find_top_level(IOHIDDeviceRef hid_device
, CFMutableArrayRef main_elements
)
242 TRACE("hid_device %s\n", debugstr_device(hid_device
));
247 elements
= IOHIDDeviceCopyMatchingElements(hid_device
, NULL
, 0);
251 CFIndex i
, count
= CFArrayGetCount(elements
);
252 for (i
= 0; i
< count
; i
++)
254 IOHIDElementRef element
= (IOHIDElementRef
)CFArrayGetValueAtIndex(elements
, i
);
255 int type
= IOHIDElementGetType(element
);
257 TRACE("element %s\n", debugstr_element(element
));
259 /* Check for top-level gaming device collections */
260 if (type
== kIOHIDElementTypeCollection
&& IOHIDElementGetParent(element
) == 0)
262 int usage_page
= IOHIDElementGetUsagePage(element
);
263 int usage
= IOHIDElementGetUsage(element
);
265 if (usage_page
== kHIDPage_GenericDesktop
&&
266 (usage
== kHIDUsage_GD_Joystick
|| usage
== kHIDUsage_GD_GamePad
))
268 CFArrayAppendValue(main_elements
, element
);
276 TRACE("-> total %d\n", (int)total
);
280 /**************************************************************************
283 static int find_osx_devices(void)
285 IOHIDManagerRef hid_manager
;
286 int usages
[] = { kHIDUsage_GD_Joystick
, kHIDUsage_GD_GamePad
};
288 CFDictionaryRef matching_dicts
[sizeof(usages
) / sizeof(usages
[0])];
294 hid_manager
= IOHIDManagerCreate(kCFAllocatorDefault
, 0L);
295 if (IOHIDManagerOpen(hid_manager
, 0) != kIOReturnSuccess
)
297 ERR("Couldn't open IOHIDManager.\n");
298 CFRelease(hid_manager
);
302 for (i
= 0; i
< sizeof(matching_dicts
) / sizeof(matching_dicts
[0]); i
++)
304 matching_dicts
[i
] = create_osx_device_match(usages
[i
]);
305 if (!matching_dicts
[i
])
308 CFRelease(matching_dicts
[--i
]);
313 matching
= CFArrayCreate(NULL
, (const void**)matching_dicts
, sizeof(matching_dicts
) / sizeof(matching_dicts
[0]),
314 &kCFTypeArrayCallBacks
);
316 for (i
= 0; i
< sizeof(matching_dicts
) / sizeof(matching_dicts
[0]); i
++)
317 CFRelease(matching_dicts
[i
]);
319 IOHIDManagerSetDeviceMatchingMultiple(hid_manager
, matching
);
321 devset
= IOHIDManagerCopyDevices(hid_manager
);
324 CFIndex num_devices
, num_main_elements
;
328 num_devices
= CFSetGetCount(devset
);
329 refs
= HeapAlloc(GetProcessHeap(), 0, num_devices
* sizeof(*refs
));
336 CFSetGetValues(devset
, refs
);
337 devices
= CFArrayCreate(NULL
, refs
, num_devices
, &kCFTypeArrayCallBacks
);
338 HeapFree(GetProcessHeap(), 0, refs
);
343 device_main_elements
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
344 if (!device_main_elements
)
350 num_main_elements
= 0;
351 for (i
= 0; i
< num_devices
; i
++)
353 IOHIDDeviceRef hid_device
= (IOHIDDeviceRef
)CFArrayGetValueAtIndex(devices
, i
);
354 TRACE("hid_device %s\n", debugstr_device(hid_device
));
355 num_main_elements
+= find_top_level(hid_device
, device_main_elements
);
360 TRACE("found %i device(s), %i collection(s)\n",(int)num_devices
,(int)num_main_elements
);
361 return (int)num_main_elements
;
365 IOHIDManagerClose(hid_manager
, 0);
366 CFRelease(hid_manager
);
370 /**************************************************************************
371 * collect_joystick_elements
373 static void collect_joystick_elements(joystick_t
* joystick
, IOHIDElementRef collection
)
376 CFArrayRef children
= IOHIDElementGetChildren(collection
);
378 TRACE("collection %s\n", debugstr_element(collection
));
380 count
= CFArrayGetCount(children
);
381 for (i
= 0; i
< count
; i
++)
383 IOHIDElementRef child
;
386 child
= (IOHIDElementRef
)CFArrayGetValueAtIndex(children
, i
);
387 TRACE("child %s\n", debugstr_element(child
));
388 type
= IOHIDElementGetType(child
);
391 case kIOHIDElementTypeCollection
:
392 collect_joystick_elements(joystick
, child
);
394 case kIOHIDElementTypeInput_Button
:
396 int usage_page
= IOHIDElementGetUsagePage(child
);
398 TRACE("kIOHIDElementTypeInput_Button usage_page %d\n", usage_page
);
400 /* avoid strange elements found on the 360 controller */
401 if (usage_page
== kHIDPage_Button
)
402 CFArrayAppendValue(joystick
->buttons
, child
);
405 case kIOHIDElementTypeInput_Axis
:
407 TRACE("kIOHIDElementTypeInput_Axis; ignoring\n");
410 case kIOHIDElementTypeInput_Misc
:
412 uint32_t usage
= IOHIDElementGetUsage( child
);
415 case kHIDUsage_GD_Hatswitch
:
417 TRACE("kIOHIDElementTypeInput_Misc / kHIDUsage_GD_Hatswitch\n");
418 if (joystick
->hatswitch
)
419 TRACE(" ignoring additional hatswitch\n");
421 joystick
->hatswitch
= (IOHIDElementRef
)CFRetain(child
);
427 case kHIDUsage_GD_Rx
:
428 case kHIDUsage_GD_Ry
:
429 case kHIDUsage_GD_Rz
:
431 int axis
= axis_for_usage(usage
);
432 TRACE("kIOHIDElementTypeInput_Misc / kHIDUsage_GD_<axis> (%d) axis %d\n", usage
, axis
);
433 if (axis
< 0 || joystick
->axes
[axis
].element
)
434 TRACE(" ignoring\n");
437 joystick
->axes
[axis
].element
= (IOHIDElementRef
)CFRetain(child
);
438 joystick
->axes
[axis
].min_value
= IOHIDElementGetLogicalMin(child
);
439 joystick
->axes
[axis
].max_value
= IOHIDElementGetLogicalMax(child
);
443 case kHIDUsage_GD_Slider
:
444 TRACE("kIOHIDElementTypeInput_Misc / kHIDUsage_GD_Slider; ignoring\n");
447 FIXME("kIOHIDElementTypeInput_Misc / Unhandled usage %d\n", usage
);
453 FIXME("Unhandled type %i\n",type
);
459 /**************************************************************************
460 * button_usage_comparator
462 static CFComparisonResult
button_usage_comparator(const void *val1
, const void *val2
, void *context
)
464 IOHIDElementRef element1
= (IOHIDElementRef
)val1
, element2
= (IOHIDElementRef
)val2
;
465 int usage1
= IOHIDElementGetUsage(element1
), usage2
= IOHIDElementGetUsage(element2
);
468 return kCFCompareLessThan
;
470 return kCFCompareGreaterThan
;
471 return kCFCompareEqualTo
;
474 /**************************************************************************
477 LRESULT
driver_open(LPSTR str
, DWORD index
)
479 if (index
>= MAXJOYSTICK
|| joysticks
[index
].in_use
)
482 joysticks
[index
].in_use
= TRUE
;
483 return (LRESULT
)&joysticks
[index
];
486 /**************************************************************************
489 LRESULT
driver_close(DWORD_PTR device_id
)
491 joystick_t
* joystick
= joystick_from_id(device_id
);
494 if (joystick
== NULL
)
497 CFRelease(joystick
->element
);
498 for (i
= 0; i
< NUM_AXES
; i
++)
500 if (joystick
->axes
[i
].element
)
501 CFRelease(joystick
->axes
[i
].element
);
503 if (joystick
->buttons
)
504 CFRelease(joystick
->buttons
);
505 if (joystick
->hatswitch
)
506 CFRelease(joystick
->hatswitch
);
508 memset(joystick
, 0, sizeof(*joystick
));
512 /**************************************************************************
515 static BOOL
open_joystick(joystick_t
* joystick
)
520 if (joystick
->element
)
523 if (!device_main_elements
)
526 if (!device_main_elements
)
530 index
= joystick
- joysticks
;
531 if (index
>= CFArrayGetCount(device_main_elements
))
534 joystick
->element
= (IOHIDElementRef
)CFArrayGetValueAtIndex(device_main_elements
, index
);
535 joystick
->buttons
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
536 collect_joystick_elements(joystick
, joystick
->element
);
538 /* Sort buttons into correct order */
540 range
.length
= CFArrayGetCount(joystick
->buttons
);
541 CFArraySortValues(joystick
->buttons
, range
, button_usage_comparator
, NULL
);
542 if (range
.length
> 32)
544 /* Delete any buttons beyond the first 32 */
547 CFArrayReplaceValues(joystick
->buttons
, range
, NULL
, 0);
554 /**************************************************************************
555 * driver_joyGetDevCaps
557 LRESULT
driver_joyGetDevCaps(DWORD_PTR device_id
, JOYCAPSW
* caps
, DWORD size
)
559 joystick_t
* joystick
;
560 IOHIDDeviceRef device
;
562 if ((joystick
= joystick_from_id(device_id
)) == NULL
)
563 return MMSYSERR_NODRIVER
;
565 if (!open_joystick(joystick
))
568 caps
->szPname
[0] = 0;
570 device
= IOHIDElementGetDevice(joystick
->element
);
573 CFStringRef product_name
= IOHIDDeviceGetProperty(device
, CFSTR(kIOHIDProductKey
));
579 range
.length
= min(MAXPNAMELEN
- 1, CFStringGetLength(product_name
));
580 CFStringGetCharacters(product_name
, range
, (UniChar
*)caps
->szPname
);
581 caps
->szPname
[range
.length
] = 0;
585 caps
->wMid
= MM_MICROSOFT
;
586 caps
->wPid
= MM_PC_JOYSTICK
;
588 caps
->wXmax
= 0xFFFF;
590 caps
->wYmax
= 0xFFFF;
592 caps
->wZmax
= joystick
->axes
[AXIS_Z
].element
? 0xFFFF : 0;
593 caps
->wNumButtons
= CFArrayGetCount(joystick
->buttons
);
594 if (size
== sizeof(JOYCAPSW
))
598 /* complete 95 structure */
600 caps
->wRmax
= 0xFFFF;
602 caps
->wUmax
= 0xFFFF;
604 caps
->wVmax
= 0xFFFF;
605 caps
->wMaxAxes
= 6; /* same as MS Joystick Driver */
607 caps
->wMaxButtons
= 32; /* same as MS Joystick Driver */
608 caps
->szRegKey
[0] = 0;
609 caps
->szOEMVxD
[0] = 0;
612 for (i
= 0; i
< NUM_AXES
; i
++)
614 if (joystick
->axes
[i
].element
)
619 case AXIS_Z
: caps
->wCaps
|= JOYCAPS_HASZ
; break;
620 case AXIS_RX
: caps
->wCaps
|= JOYCAPS_HASU
; break;
621 case AXIS_RY
: caps
->wCaps
|= JOYCAPS_HASV
; break;
622 case AXIS_RZ
: caps
->wCaps
|= JOYCAPS_HASR
; break;
627 if (joystick
->hatswitch
)
628 caps
->wCaps
|= JOYCAPS_HASPOV
| JOYCAPS_POV4DIR
;
631 TRACE("name %s buttons %u axes %d caps 0x%08x\n", debugstr_w(caps
->szPname
), caps
->wNumButtons
, caps
->wNumAxes
, caps
->wCaps
);
633 return JOYERR_NOERROR
;
636 /**************************************************************************
639 LRESULT
driver_joyGetPosEx(DWORD_PTR device_id
, JOYINFOEX
* info
)
641 static const struct {
644 } axis_map
[NUM_AXES
] = {
645 { JOY_RETURNX
, FIELD_OFFSET(JOYINFOEX
, dwXpos
) },
646 { JOY_RETURNY
, FIELD_OFFSET(JOYINFOEX
, dwYpos
) },
647 { JOY_RETURNZ
, FIELD_OFFSET(JOYINFOEX
, dwZpos
) },
648 { JOY_RETURNU
, FIELD_OFFSET(JOYINFOEX
, dwUpos
) },
649 { JOY_RETURNV
, FIELD_OFFSET(JOYINFOEX
, dwVpos
) },
650 { JOY_RETURNR
, FIELD_OFFSET(JOYINFOEX
, dwRpos
) },
653 joystick_t
* joystick
;
654 IOHIDDeviceRef device
;
656 IOHIDValueRef valueRef
;
659 if ((joystick
= joystick_from_id(device_id
)) == NULL
)
660 return MMSYSERR_NODRIVER
;
662 if (!open_joystick(joystick
))
665 device
= IOHIDElementGetDevice(joystick
->element
);
667 if (info
->dwFlags
& JOY_RETURNBUTTONS
)
670 info
->dwButtonNumber
= 0;
672 count
= CFArrayGetCount(joystick
->buttons
);
673 for (i
= 0; i
< count
; i
++)
675 IOHIDElementRef button
= (IOHIDElementRef
)CFArrayGetValueAtIndex(joystick
->buttons
, i
);
676 IOHIDDeviceGetValue(device
, button
, &valueRef
);
677 value
= IOHIDValueGetIntegerValue(valueRef
);
680 info
->dwButtons
|= 1 << i
;
681 info
->dwButtonNumber
++;
686 for (i
= 0; i
< NUM_AXES
; i
++)
688 if (info
->dwFlags
& axis_map
[i
].flag
)
690 DWORD
* field
= (DWORD
*)((char*)info
+ axis_map
[i
].offset
);
691 if (joystick
->axes
[i
].element
)
693 IOHIDDeviceGetValue(device
, joystick
->axes
[i
].element
, &valueRef
);
694 value
= IOHIDValueGetIntegerValue(valueRef
) - joystick
->axes
[i
].min_value
;
695 *field
= MulDiv(value
, 0xFFFF, joystick
->axes
[i
].max_value
- joystick
->axes
[i
].min_value
);
700 info
->dwFlags
&= ~axis_map
[i
].flag
;
705 if (info
->dwFlags
& JOY_RETURNPOV
)
707 if (joystick
->hatswitch
)
709 IOHIDDeviceGetValue(device
, joystick
->hatswitch
, &valueRef
);
710 value
= IOHIDValueGetIntegerValue(valueRef
);
712 info
->dwPOV
= JOY_POVCENTERED
;
714 info
->dwPOV
= value
* 4500;
719 info
->dwFlags
&= ~JOY_RETURNPOV
;
723 TRACE("x: %d, y: %d, z: %d, r: %d, u: %d, v: %d, buttons: 0x%04x, pov %d, flags: 0x%04x\n",
724 info
->dwXpos
, info
->dwYpos
, info
->dwZpos
, info
->dwRpos
, info
->dwUpos
, info
->dwVpos
, info
->dwButtons
, info
->dwPOV
, info
->dwFlags
);
726 return JOYERR_NOERROR
;
729 /**************************************************************************
732 LRESULT
driver_joyGetPos(DWORD_PTR device_id
, JOYINFO
* info
)
737 memset(&ji
, 0, sizeof(ji
));
739 ji
.dwSize
= sizeof(ji
);
740 ji
.dwFlags
= JOY_RETURNX
| JOY_RETURNY
| JOY_RETURNZ
| JOY_RETURNBUTTONS
;
741 ret
= driver_joyGetPosEx(device_id
, &ji
);
742 if (ret
== JOYERR_NOERROR
)
744 info
->wXpos
= ji
.dwXpos
;
745 info
->wYpos
= ji
.dwYpos
;
746 info
->wZpos
= ji
.dwZpos
;
747 info
->wButtons
= ji
.dwButtons
;
753 #endif /* HAVE_IOKIT_HID_IOHIDLIB_H */