hidclass.sys: Return STATUS_INVALID_USER_BUFFER if buffer_len is 0.
[wine.git] / dlls / winebus.sys / bus_iohid.c
blob0123e73f7ba76f7282b033b885e0d31a65e08973
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
20 #include "config.h"
21 #include "wine/port.h"
23 #include <stdarg.h>
25 #if defined(HAVE_IOKIT_HID_IOHIDLIB_H)
26 #define DWORD UInt32
27 #define LPDWORD UInt32*
28 #define LONG SInt32
29 #define LPLONG SInt32*
30 #define E_PENDING __carbon_E_PENDING
31 #define ULONG __carbon_ULONG
32 #define E_INVALIDARG __carbon_E_INVALIDARG
33 #define E_OUTOFMEMORY __carbon_E_OUTOFMEMORY
34 #define E_HANDLE __carbon_E_HANDLE
35 #define E_ACCESSDENIED __carbon_E_ACCESSDENIED
36 #define E_UNEXPECTED __carbon_E_UNEXPECTED
37 #define E_FAIL __carbon_E_FAIL
38 #define E_ABORT __carbon_E_ABORT
39 #define E_POINTER __carbon_E_POINTER
40 #define E_NOINTERFACE __carbon_E_NOINTERFACE
41 #define E_NOTIMPL __carbon_E_NOTIMPL
42 #define S_FALSE __carbon_S_FALSE
43 #define S_OK __carbon_S_OK
44 #define HRESULT_FACILITY __carbon_HRESULT_FACILITY
45 #define IS_ERROR __carbon_IS_ERROR
46 #define FAILED __carbon_FAILED
47 #define SUCCEEDED __carbon_SUCCEEDED
48 #define MAKE_HRESULT __carbon_MAKE_HRESULT
49 #define HRESULT __carbon_HRESULT
50 #define STDMETHODCALLTYPE __carbon_STDMETHODCALLTYPE
51 #define PAGE_SHIFT __carbon_PAGE_SHIFT
52 #include <IOKit/IOKitLib.h>
53 #include <IOKit/hid/IOHIDLib.h>
54 #undef ULONG
55 #undef E_INVALIDARG
56 #undef E_OUTOFMEMORY
57 #undef E_HANDLE
58 #undef E_ACCESSDENIED
59 #undef E_UNEXPECTED
60 #undef E_FAIL
61 #undef E_ABORT
62 #undef E_POINTER
63 #undef E_NOINTERFACE
64 #undef E_NOTIMPL
65 #undef S_FALSE
66 #undef S_OK
67 #undef HRESULT_FACILITY
68 #undef IS_ERROR
69 #undef FAILED
70 #undef SUCCEEDED
71 #undef MAKE_HRESULT
72 #undef HRESULT
73 #undef STDMETHODCALLTYPE
74 #undef DWORD
75 #undef LPDWORD
76 #undef LONG
77 #undef LPLONG
78 #undef E_PENDING
79 #undef PAGE_SHIFT
80 #endif /* HAVE_IOKIT_HID_IOHIDLIB_H */
82 #define NONAMELESSUNION
84 #include "ntstatus.h"
85 #define WIN32_NO_STATUS
86 #include "windef.h"
87 #include "winbase.h"
88 #include "winternl.h"
89 #include "winioctl.h"
90 #include "ddk/wdm.h"
91 #include "ddk/hidtypes.h"
92 #include "wine/debug.h"
94 #include "bus.h"
96 WINE_DEFAULT_DEBUG_CHANNEL(plugplay);
97 #ifdef HAVE_IOHIDMANAGERCREATE
99 static IOHIDManagerRef hid_manager;
100 static CFRunLoopRef run_loop;
101 static HANDLE run_loop_handle;
103 static const WCHAR busidW[] = {'I','O','H','I','D',0};
105 struct platform_private
107 IOHIDDeviceRef device;
108 uint8_t *buffer;
111 static inline struct platform_private *impl_from_DEVICE_OBJECT(DEVICE_OBJECT *device)
113 return (struct platform_private *)get_platform_private(device);
116 static void CFStringToWSTR(CFStringRef cstr, LPWSTR wstr, int length)
118 int len = min(CFStringGetLength(cstr), length-1);
119 CFStringGetCharacters(cstr, CFRangeMake(0, len), (UniChar*)wstr);
120 wstr[len] = 0;
123 static DWORD CFNumberToDWORD(CFNumberRef num)
125 int dwNum = 0;
126 if (num)
127 CFNumberGetValue(num, kCFNumberIntType, &dwNum);
128 return dwNum;
131 static void handle_IOHIDDeviceIOHIDReportCallback(void *context,
132 IOReturn result, void *sender, IOHIDReportType type,
133 uint32_t reportID, uint8_t *report, CFIndex report_length)
135 DEVICE_OBJECT *device = (DEVICE_OBJECT*)context;
136 process_hid_report(device, report, report_length);
139 static void free_device(DEVICE_OBJECT *device)
143 static int compare_platform_device(DEVICE_OBJECT *device, void *platform_dev)
145 struct platform_private *private = impl_from_DEVICE_OBJECT(device);
146 IOHIDDeviceRef dev2 = (IOHIDDeviceRef)platform_dev;
147 if (private->device != dev2)
148 return 1;
149 else
150 return 0;
153 static NTSTATUS get_reportdescriptor(DEVICE_OBJECT *device, BYTE *buffer, DWORD length, DWORD *out_length)
155 struct platform_private *private = impl_from_DEVICE_OBJECT(device);
156 CFDataRef data = IOHIDDeviceGetProperty(private->device, CFSTR(kIOHIDReportDescriptorKey));
157 int data_length = CFDataGetLength(data);
158 const UInt8 *ptr;
160 *out_length = data_length;
161 if (length < data_length)
162 return STATUS_BUFFER_TOO_SMALL;
164 ptr = CFDataGetBytePtr(data);
165 memcpy(buffer, ptr, data_length);
166 return STATUS_SUCCESS;
169 static NTSTATUS get_string(DEVICE_OBJECT *device, DWORD index, WCHAR *buffer, DWORD length)
171 struct platform_private *private = impl_from_DEVICE_OBJECT(device);
172 CFStringRef str;
173 switch (index)
175 case HID_STRING_ID_IPRODUCT:
176 str = IOHIDDeviceGetProperty(private->device, CFSTR(kIOHIDProductKey));
177 break;
178 case HID_STRING_ID_IMANUFACTURER:
179 str = IOHIDDeviceGetProperty(private->device, CFSTR(kIOHIDManufacturerKey));
180 break;
181 case HID_STRING_ID_ISERIALNUMBER:
182 str = IOHIDDeviceGetProperty(private->device, CFSTR(kIOHIDSerialNumberKey));
183 break;
184 default:
185 ERR("Unknown string index\n");
186 return STATUS_NOT_IMPLEMENTED;
189 if (str)
191 if (length < CFStringGetLength(str) + 1)
192 return STATUS_BUFFER_TOO_SMALL;
193 CFStringToWSTR(str, buffer, length);
195 else
197 if (!length) return STATUS_BUFFER_TOO_SMALL;
198 buffer[0] = 0;
201 return STATUS_SUCCESS;
204 static NTSTATUS begin_report_processing(DEVICE_OBJECT *device)
206 DWORD length;
207 struct platform_private *private = impl_from_DEVICE_OBJECT(device);
208 CFNumberRef num;
210 if (private->buffer)
211 return STATUS_SUCCESS;
213 num = IOHIDDeviceGetProperty(private->device, CFSTR(kIOHIDMaxInputReportSizeKey));
214 length = CFNumberToDWORD(num);
215 private->buffer = HeapAlloc(GetProcessHeap(), 0, length);
217 IOHIDDeviceRegisterInputReportCallback(private->device, private->buffer, length, handle_IOHIDDeviceIOHIDReportCallback, device);
218 return STATUS_SUCCESS;
221 static NTSTATUS set_output_report(DEVICE_OBJECT *device, UCHAR id, BYTE *report, DWORD length, ULONG_PTR *written)
223 IOReturn result;
224 struct platform_private *private = impl_from_DEVICE_OBJECT(device);
225 result = IOHIDDeviceSetReport(private->device, kIOHIDReportTypeOutput, id, report, length);
226 if (result == kIOReturnSuccess)
228 *written = length;
229 return STATUS_SUCCESS;
231 else
233 *written = 0;
234 return STATUS_UNSUCCESSFUL;
238 static NTSTATUS get_feature_report(DEVICE_OBJECT *device, UCHAR id, BYTE *report, DWORD length, ULONG_PTR *read)
240 IOReturn ret;
241 CFIndex report_length = length;
242 struct platform_private *private = impl_from_DEVICE_OBJECT(device);
244 ret = IOHIDDeviceGetReport(private->device, kIOHIDReportTypeFeature, id, report, &report_length);
245 if (ret == kIOReturnSuccess)
247 *read = report_length;
248 return STATUS_SUCCESS;
250 else
252 *read = 0;
253 return STATUS_UNSUCCESSFUL;
257 static NTSTATUS set_feature_report(DEVICE_OBJECT *device, UCHAR id, BYTE *report, DWORD length, ULONG_PTR *written)
259 IOReturn result;
260 struct platform_private *private = impl_from_DEVICE_OBJECT(device);
262 result = IOHIDDeviceSetReport(private->device, kIOHIDReportTypeFeature, id, report, length);
263 if (result == kIOReturnSuccess)
265 *written = length;
266 return STATUS_SUCCESS;
268 else
270 *written = 0;
271 return STATUS_UNSUCCESSFUL;
275 static const platform_vtbl iohid_vtbl =
277 free_device,
278 compare_platform_device,
279 get_reportdescriptor,
280 get_string,
281 begin_report_processing,
282 set_output_report,
283 get_feature_report,
284 set_feature_report,
287 static void handle_DeviceMatchingCallback(void *context, IOReturn result, void *sender, IOHIDDeviceRef IOHIDDevice)
289 DEVICE_OBJECT *device;
290 DWORD vid, pid, version, uid;
291 CFStringRef str = NULL;
292 WCHAR serial_string[256];
293 BOOL is_gamepad = FALSE;
294 WORD input = -1;
296 TRACE("OS/X IOHID Device Added %p\n", IOHIDDevice);
298 vid = CFNumberToDWORD(IOHIDDeviceGetProperty(IOHIDDevice, CFSTR(kIOHIDVendorIDKey)));
299 pid = CFNumberToDWORD(IOHIDDeviceGetProperty(IOHIDDevice, CFSTR(kIOHIDProductIDKey)));
300 version = CFNumberToDWORD(IOHIDDeviceGetProperty(IOHIDDevice, CFSTR(kIOHIDVersionNumberKey)));
301 str = IOHIDDeviceGetProperty(IOHIDDevice, CFSTR(kIOHIDSerialNumberKey));
302 if (str) CFStringToWSTR(str, serial_string, ARRAY_SIZE(serial_string));
303 uid = CFNumberToDWORD(IOHIDDeviceGetProperty(IOHIDDevice, CFSTR(kIOHIDLocationIDKey)));
305 if (IOHIDDeviceOpen(IOHIDDevice, 0) != kIOReturnSuccess)
307 ERR("Failed to open HID device %p (vid %04x, pid %04x)\n", IOHIDDevice, vid, pid);
308 return;
310 IOHIDDeviceScheduleWithRunLoop(IOHIDDevice, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
312 if (IOHIDDeviceConformsTo(IOHIDDevice, kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad) ||
313 IOHIDDeviceConformsTo(IOHIDDevice, kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick))
315 if (is_xbox_gamepad(vid, pid))
316 is_gamepad = TRUE;
317 else
319 int axes=0, buttons=0;
320 CFArrayRef element_array = IOHIDDeviceCopyMatchingElements(
321 IOHIDDevice, NULL, kIOHIDOptionsTypeNone);
323 if (element_array) {
324 CFIndex index;
325 CFIndex count = CFArrayGetCount(element_array);
326 for (index = 0; index < count; index++)
328 IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(element_array, index);
329 if (element)
331 int type = IOHIDElementGetType(element);
332 if (type == kIOHIDElementTypeInput_Button) buttons++;
333 if (type == kIOHIDElementTypeInput_Axis) axes++;
334 if (type == kIOHIDElementTypeInput_Misc)
336 uint32_t usage = IOHIDElementGetUsage(element);
337 switch (usage)
339 case kHIDUsage_GD_X:
340 case kHIDUsage_GD_Y:
341 case kHIDUsage_GD_Z:
342 case kHIDUsage_GD_Rx:
343 case kHIDUsage_GD_Ry:
344 case kHIDUsage_GD_Rz:
345 case kHIDUsage_GD_Slider:
346 axes ++;
351 CFRelease(element_array);
353 is_gamepad = (axes == 6 && buttons >= 14);
356 if (is_gamepad)
357 input = 0;
359 device = bus_create_hid_device(busidW, vid, pid, input,
360 version, uid, str ? serial_string : NULL, is_gamepad,
361 &iohid_vtbl, sizeof(struct platform_private));
362 if (!device)
363 ERR("Failed to create device\n");
364 else
366 struct platform_private *private = impl_from_DEVICE_OBJECT(device);
367 private->device = IOHIDDevice;
368 private->buffer = NULL;
369 IoInvalidateDeviceRelations(bus_pdo, BusRelations);
373 static void handle_RemovalCallback(void *context, IOReturn result, void *sender, IOHIDDeviceRef IOHIDDevice)
375 DEVICE_OBJECT *device;
376 TRACE("OS/X IOHID Device Removed %p\n", IOHIDDevice);
377 IOHIDDeviceRegisterInputReportCallback(IOHIDDevice, NULL, 0, NULL, NULL);
378 /* Note: Yes, we leak the buffer. But according to research there is no
379 safe way to deallocate that buffer. */
380 IOHIDDeviceUnscheduleFromRunLoop(IOHIDDevice, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
381 IOHIDDeviceClose(IOHIDDevice, 0);
382 device = bus_find_hid_device(&iohid_vtbl, IOHIDDevice);
383 if (device)
385 bus_unlink_hid_device(device);
386 IoInvalidateDeviceRelations(bus_pdo, BusRelations);
390 /* This puts the relevant run loop for event handling into a WINE thread */
391 static DWORD CALLBACK runloop_thread(void *args)
393 run_loop = CFRunLoopGetCurrent();
395 IOHIDManagerSetDeviceMatching(hid_manager, NULL);
396 IOHIDManagerRegisterDeviceMatchingCallback(hid_manager, handle_DeviceMatchingCallback, NULL);
397 IOHIDManagerRegisterDeviceRemovalCallback(hid_manager, handle_RemovalCallback, NULL);
398 IOHIDManagerScheduleWithRunLoop(hid_manager, run_loop, kCFRunLoopDefaultMode);
400 CFRunLoopRun();
401 TRACE("Run Loop exiting\n");
402 return 1;
406 NTSTATUS iohid_driver_init(void)
408 hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, 0L);
409 if (!(run_loop_handle = CreateThread(NULL, 0, runloop_thread, NULL, 0, NULL)))
411 ERR("Failed to initialize IOHID Manager thread\n");
412 CFRelease(hid_manager);
413 return STATUS_UNSUCCESSFUL;
416 TRACE("Initialization successful\n");
417 return STATUS_SUCCESS;
420 void iohid_driver_unload( void )
422 TRACE("Unloading Driver\n");
424 if (!run_loop_handle)
425 return;
427 IOHIDManagerUnscheduleFromRunLoop(hid_manager, run_loop, kCFRunLoopDefaultMode);
428 CFRunLoopStop(run_loop);
429 WaitForSingleObject(run_loop_handle, INFINITE);
430 CloseHandle(run_loop_handle);
431 IOHIDManagerRegisterDeviceMatchingCallback(hid_manager, NULL, NULL);
432 IOHIDManagerRegisterDeviceRemovalCallback(hid_manager, NULL, NULL);
433 CFRelease(hid_manager);
434 TRACE("Driver Unloaded\n");
437 #else
439 NTSTATUS iohid_driver_init(void)
441 return STATUS_NOT_IMPLEMENTED;
444 void iohid_driver_unload( void )
446 TRACE("Stub: Unload Driver\n");
449 #endif /* HAVE_IOHIDMANAGERCREATE */