1 /* Bus like function for mac HID devices
3 * Copyright 2016 CodeWeavers, Aric Stewart
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
27 #include <sys/types.h>
31 #define LPDWORD UInt32*
33 #define LPLONG SInt32*
34 #define E_PENDING __carbon_E_PENDING
35 #define ULONG __carbon_ULONG
36 #define E_INVALIDARG __carbon_E_INVALIDARG
37 #define E_OUTOFMEMORY __carbon_E_OUTOFMEMORY
38 #define E_HANDLE __carbon_E_HANDLE
39 #define E_ACCESSDENIED __carbon_E_ACCESSDENIED
40 #define E_UNEXPECTED __carbon_E_UNEXPECTED
41 #define E_FAIL __carbon_E_FAIL
42 #define E_ABORT __carbon_E_ABORT
43 #define E_POINTER __carbon_E_POINTER
44 #define E_NOINTERFACE __carbon_E_NOINTERFACE
45 #define E_NOTIMPL __carbon_E_NOTIMPL
46 #define S_FALSE __carbon_S_FALSE
47 #define S_OK __carbon_S_OK
48 #define HRESULT_FACILITY __carbon_HRESULT_FACILITY
49 #define IS_ERROR __carbon_IS_ERROR
50 #define FAILED __carbon_FAILED
51 #define SUCCEEDED __carbon_SUCCEEDED
52 #define MAKE_HRESULT __carbon_MAKE_HRESULT
53 #define HRESULT __carbon_HRESULT
54 #define STDMETHODCALLTYPE __carbon_STDMETHODCALLTYPE
55 #define PAGE_SHIFT __carbon_PAGE_SHIFT
56 #include <IOKit/IOKitLib.h>
57 #include <IOKit/hid/IOHIDLib.h>
71 #undef HRESULT_FACILITY
77 #undef STDMETHODCALLTYPE
84 #endif /* __APPLE__ */
89 #define WIN32_NO_STATUS
95 #include "ddk/hidtypes.h"
96 #include "wine/debug.h"
98 #include "unix_private.h"
100 WINE_DEFAULT_DEBUG_CHANNEL(hid
);
103 static pthread_mutex_t iohid_cs
= PTHREAD_MUTEX_INITIALIZER
;
105 static IOHIDManagerRef hid_manager
;
106 static CFRunLoopRef run_loop
;
107 static struct list event_queue
= LIST_INIT(event_queue
);
108 static struct list device_list
= LIST_INIT(device_list
);
109 static struct iohid_bus_options options
;
113 struct unix_device unix_device
;
114 IOHIDDeviceRef device
;
118 static inline struct iohid_device
*impl_from_unix_device(struct unix_device
*iface
)
120 return CONTAINING_RECORD(iface
, struct iohid_device
, unix_device
);
123 static struct iohid_device
*find_device_from_iohid(IOHIDDeviceRef IOHIDDevice
)
125 struct iohid_device
*impl
;
127 LIST_FOR_EACH_ENTRY(impl
, &device_list
, struct iohid_device
, unix_device
.entry
)
128 if (impl
->device
== IOHIDDevice
) return impl
;
133 static void CFStringToWSTR(CFStringRef cstr
, LPWSTR wstr
, int length
)
135 int len
= min(CFStringGetLength(cstr
), length
- 1);
136 CFStringGetCharacters(cstr
, CFRangeMake(0, len
), (UniChar
*)wstr
);
140 static DWORD
CFNumberToDWORD(CFNumberRef num
)
144 CFNumberGetValue(num
, kCFNumberIntType
, &dwNum
);
148 static void handle_IOHIDDeviceIOHIDReportCallback(void *context
,
149 IOReturn result
, void *sender
, IOHIDReportType type
,
150 uint32_t reportID
, uint8_t *report
, CFIndex report_length
)
152 struct unix_device
*iface
= (struct unix_device
*)context
;
153 bus_event_queue_input_report(&event_queue
, iface
, report
, report_length
);
156 static void iohid_device_destroy(struct unix_device
*iface
)
160 static NTSTATUS
iohid_device_start(struct unix_device
*iface
)
163 struct iohid_device
*impl
= impl_from_unix_device(iface
);
166 num
= IOHIDDeviceGetProperty(impl
->device
, CFSTR(kIOHIDMaxInputReportSizeKey
));
167 length
= CFNumberToDWORD(num
);
168 impl
->buffer
= malloc(length
);
170 IOHIDDeviceRegisterInputReportCallback(impl
->device
, impl
->buffer
, length
, handle_IOHIDDeviceIOHIDReportCallback
, iface
);
171 return STATUS_SUCCESS
;
174 static void iohid_device_stop(struct unix_device
*iface
)
176 struct iohid_device
*impl
= impl_from_unix_device(iface
);
178 IOHIDDeviceRegisterInputReportCallback(impl
->device
, NULL
, 0, NULL
, NULL
);
180 pthread_mutex_lock(&iohid_cs
);
181 list_remove(&impl
->unix_device
.entry
);
182 pthread_mutex_unlock(&iohid_cs
);
185 static NTSTATUS
iohid_device_get_report_descriptor(struct unix_device
*iface
, BYTE
*buffer
,
186 UINT length
, UINT
*out_length
)
188 struct iohid_device
*impl
= impl_from_unix_device(iface
);
189 CFDataRef data
= IOHIDDeviceGetProperty(impl
->device
, CFSTR(kIOHIDReportDescriptorKey
));
190 int data_length
= CFDataGetLength(data
);
193 *out_length
= data_length
;
194 if (length
< data_length
)
195 return STATUS_BUFFER_TOO_SMALL
;
197 ptr
= CFDataGetBytePtr(data
);
198 memcpy(buffer
, ptr
, data_length
);
199 return STATUS_SUCCESS
;
202 static void iohid_device_set_output_report(struct unix_device
*iface
, HID_XFER_PACKET
*packet
, IO_STATUS_BLOCK
*io
)
205 struct iohid_device
*impl
= impl_from_unix_device(iface
);
206 result
= IOHIDDeviceSetReport(impl
->device
, kIOHIDReportTypeOutput
, packet
->reportId
,
207 packet
->reportBuffer
, packet
->reportBufferLen
);
208 if (result
== kIOReturnSuccess
)
210 io
->Information
= packet
->reportBufferLen
;
211 io
->Status
= STATUS_SUCCESS
;
216 io
->Status
= STATUS_UNSUCCESSFUL
;
220 static void iohid_device_get_feature_report(struct unix_device
*iface
, HID_XFER_PACKET
*packet
, IO_STATUS_BLOCK
*io
)
223 CFIndex report_length
= packet
->reportBufferLen
;
224 struct iohid_device
*impl
= impl_from_unix_device(iface
);
226 ret
= IOHIDDeviceGetReport(impl
->device
, kIOHIDReportTypeFeature
, packet
->reportId
,
227 packet
->reportBuffer
, &report_length
);
228 if (ret
== kIOReturnSuccess
)
230 io
->Information
= report_length
;
231 io
->Status
= STATUS_SUCCESS
;
236 io
->Status
= STATUS_UNSUCCESSFUL
;
240 static void iohid_device_set_feature_report(struct unix_device
*iface
, HID_XFER_PACKET
*packet
, IO_STATUS_BLOCK
*io
)
243 struct iohid_device
*impl
= impl_from_unix_device(iface
);
245 result
= IOHIDDeviceSetReport(impl
->device
, kIOHIDReportTypeFeature
, packet
->reportId
,
246 packet
->reportBuffer
, packet
->reportBufferLen
);
247 if (result
== kIOReturnSuccess
)
249 io
->Information
= packet
->reportBufferLen
;
250 io
->Status
= STATUS_SUCCESS
;
255 io
->Status
= STATUS_UNSUCCESSFUL
;
259 static const struct raw_device_vtbl iohid_device_vtbl
=
261 iohid_device_destroy
,
264 iohid_device_get_report_descriptor
,
265 iohid_device_set_output_report
,
266 iohid_device_get_feature_report
,
267 iohid_device_set_feature_report
,
270 static void handle_DeviceMatchingCallback(void *context
, IOReturn result
, void *sender
, IOHIDDeviceRef IOHIDDevice
)
272 struct device_desc desc
=
274 .input
= -1, .is_hidraw
= TRUE
,
275 .serialnumber
= {'0','0','0','0',0},
277 struct iohid_device
*impl
;
280 desc
.usages
.UsagePage
= CFNumberToDWORD(IOHIDDeviceGetProperty(IOHIDDevice
, CFSTR(kIOHIDPrimaryUsagePageKey
)));
281 desc
.usages
.Usage
= CFNumberToDWORD(IOHIDDeviceGetProperty(IOHIDDevice
, CFSTR(kIOHIDPrimaryUsageKey
)));
283 desc
.vid
= CFNumberToDWORD(IOHIDDeviceGetProperty(IOHIDDevice
, CFSTR(kIOHIDVendorIDKey
)));
284 desc
.pid
= CFNumberToDWORD(IOHIDDeviceGetProperty(IOHIDDevice
, CFSTR(kIOHIDProductIDKey
)));
285 desc
.version
= CFNumberToDWORD(IOHIDDeviceGetProperty(IOHIDDevice
, CFSTR(kIOHIDVersionNumberKey
)));
286 desc
.uid
= CFNumberToDWORD(IOHIDDeviceGetProperty(IOHIDDevice
, CFSTR(kIOHIDLocationIDKey
)));
288 if (desc
.usages
.UsagePage
!= HID_USAGE_PAGE_GENERIC
||
289 !(desc
.usages
.Usage
== HID_USAGE_GENERIC_JOYSTICK
|| desc
.usages
.Usage
== HID_USAGE_GENERIC_GAMEPAD
))
291 /* winebus isn't currently meant to handle anything but these, and
292 * opening keyboards, mice, or the Touch Bar on older MacBooks triggers
293 * a permissions dialog for input monitoring.
295 ERR("Ignoring HID device %p (vid %04x, pid %04x): not a joystick or gamepad\n", IOHIDDevice
, desc
.vid
, desc
.pid
);
299 if (IOHIDDeviceOpen(IOHIDDevice
, 0) != kIOReturnSuccess
)
301 ERR("Failed to open HID device %p (vid %04x, pid %04x)\n", IOHIDDevice
, desc
.vid
, desc
.pid
);
304 IOHIDDeviceScheduleWithRunLoop(IOHIDDevice
, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode
);
306 str
= IOHIDDeviceGetProperty(IOHIDDevice
, CFSTR(kIOHIDManufacturerKey
));
307 if (str
) CFStringToWSTR(str
, desc
.manufacturer
, ARRAY_SIZE(desc
.manufacturer
));
308 str
= IOHIDDeviceGetProperty(IOHIDDevice
, CFSTR(kIOHIDProductKey
));
309 if (str
) CFStringToWSTR(str
, desc
.product
, ARRAY_SIZE(desc
.product
));
310 str
= IOHIDDeviceGetProperty(IOHIDDevice
, CFSTR(kIOHIDSerialNumberKey
));
311 if (str
) CFStringToWSTR(str
, desc
.serialnumber
, ARRAY_SIZE(desc
.serialnumber
));
313 if (IOHIDDeviceConformsTo(IOHIDDevice
, kHIDPage_GenericDesktop
, kHIDUsage_GD_GamePad
) ||
314 IOHIDDeviceConformsTo(IOHIDDevice
, kHIDPage_GenericDesktop
, kHIDUsage_GD_Joystick
))
316 if (is_xbox_gamepad(desc
.vid
, desc
.pid
))
317 desc
.is_gamepad
= TRUE
;
320 int axes
=0, buttons
=0;
321 CFArrayRef element_array
= IOHIDDeviceCopyMatchingElements(
322 IOHIDDevice
, NULL
, kIOHIDOptionsTypeNone
);
326 CFIndex count
= CFArrayGetCount(element_array
);
327 for (index
= 0; index
< count
; index
++)
329 IOHIDElementRef element
= (IOHIDElementRef
)CFArrayGetValueAtIndex(element_array
, index
);
332 int type
= IOHIDElementGetType(element
);
333 if (type
== kIOHIDElementTypeInput_Button
) buttons
++;
334 if (type
== kIOHIDElementTypeInput_Axis
) axes
++;
335 if (type
== kIOHIDElementTypeInput_Misc
)
337 uint32_t usage
= IOHIDElementGetUsage(element
);
343 case kHIDUsage_GD_Rx
:
344 case kHIDUsage_GD_Ry
:
345 case kHIDUsage_GD_Rz
:
346 case kHIDUsage_GD_Slider
:
352 CFRelease(element_array
);
354 desc
.is_gamepad
= (axes
== 6 && buttons
>= 14);
358 TRACE("dev %p, desc %s.\n", IOHIDDevice
, debugstr_device_desc(&desc
));
360 if (!(impl
= raw_device_create(&iohid_device_vtbl
, sizeof(struct iohid_device
)))) return;
361 list_add_tail(&device_list
, &impl
->unix_device
.entry
);
362 impl
->device
= IOHIDDevice
;
365 bus_event_queue_device_created(&event_queue
, &impl
->unix_device
, &desc
);
368 static void handle_RemovalCallback(void *context
, IOReturn result
, void *sender
, IOHIDDeviceRef IOHIDDevice
)
370 struct iohid_device
*impl
;
372 TRACE("OS/X IOHID Device Removed %p\n", IOHIDDevice
);
373 IOHIDDeviceRegisterInputReportCallback(IOHIDDevice
, NULL
, 0, NULL
, NULL
);
374 /* Note: Yes, we leak the buffer. But according to research there is no
375 safe way to deallocate that buffer. */
376 IOHIDDeviceUnscheduleFromRunLoop(IOHIDDevice
, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode
);
377 IOHIDDeviceClose(IOHIDDevice
, 0);
379 impl
= find_device_from_iohid(IOHIDDevice
);
380 if (impl
) bus_event_queue_device_removed(&event_queue
, &impl
->unix_device
);
381 else WARN("failed to find device for iohid device %p\n", IOHIDDevice
);
384 NTSTATUS
iohid_bus_init(void *args
)
386 TRACE("args %p\n", args
);
388 options
= *(struct iohid_bus_options
*)args
;
390 if (!(hid_manager
= IOHIDManagerCreate(kCFAllocatorDefault
, 0L)))
392 ERR("IOHID manager creation failed\n");
393 return STATUS_UNSUCCESSFUL
;
396 run_loop
= CFRunLoopGetCurrent();
398 IOHIDManagerSetDeviceMatching(hid_manager
, NULL
);
399 IOHIDManagerRegisterDeviceMatchingCallback(hid_manager
, handle_DeviceMatchingCallback
, NULL
);
400 IOHIDManagerRegisterDeviceRemovalCallback(hid_manager
, handle_RemovalCallback
, NULL
);
401 IOHIDManagerScheduleWithRunLoop(hid_manager
, run_loop
, kCFRunLoopDefaultMode
);
402 return STATUS_SUCCESS
;
405 NTSTATUS
iohid_bus_wait(void *args
)
407 struct bus_event
*result
= args
;
408 CFRunLoopRunResult ret
;
410 /* cleanup previously returned event */
411 bus_event_cleanup(result
);
415 if (bus_event_queue_pop(&event_queue
, result
)) return STATUS_PENDING
;
416 pthread_mutex_lock(&iohid_cs
);
417 ret
= CFRunLoopRunInMode(kCFRunLoopDefaultMode
, 10, TRUE
);
418 pthread_mutex_unlock(&iohid_cs
);
419 } while (ret
!= kCFRunLoopRunStopped
);
421 TRACE("IOHID main loop exiting\n");
422 bus_event_queue_destroy(&event_queue
);
423 IOHIDManagerRegisterDeviceMatchingCallback(hid_manager
, NULL
, NULL
);
424 IOHIDManagerRegisterDeviceRemovalCallback(hid_manager
, NULL
, NULL
);
425 CFRelease(hid_manager
);
426 return STATUS_SUCCESS
;
429 NTSTATUS
iohid_bus_stop(void *args
)
431 if (!run_loop
) return STATUS_SUCCESS
;
433 IOHIDManagerUnscheduleFromRunLoop(hid_manager
, run_loop
, kCFRunLoopDefaultMode
);
434 CFRunLoopStop(run_loop
);
435 return STATUS_SUCCESS
;
440 NTSTATUS
iohid_bus_init(void *args
)
442 WARN("IOHID support not compiled in!\n");
443 return STATUS_NOT_IMPLEMENTED
;
446 NTSTATUS
iohid_bus_wait(void *args
)
448 WARN("IOHID support not compiled in!\n");
449 return STATUS_NOT_IMPLEMENTED
;
452 NTSTATUS
iohid_bus_stop(void *args
)
454 WARN("IOHID support not compiled in!\n");
455 return STATUS_NOT_IMPLEMENTED
;
458 #endif /* __APPLE__ */