447 lines
14 KiB
C
447 lines
14 KiB
C
/* Bus like function for mac HID devices
|
|
*
|
|
* Copyright 2016 CodeWeavers, Aric Stewart
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "wine/port.h"
|
|
|
|
#include <stdarg.h>
|
|
|
|
#if defined(HAVE_IOKIT_HID_IOHIDLIB_H)
|
|
#define DWORD UInt32
|
|
#define LPDWORD UInt32*
|
|
#define LONG SInt32
|
|
#define LPLONG SInt32*
|
|
#define E_PENDING __carbon_E_PENDING
|
|
#define ULONG __carbon_ULONG
|
|
#define E_INVALIDARG __carbon_E_INVALIDARG
|
|
#define E_OUTOFMEMORY __carbon_E_OUTOFMEMORY
|
|
#define E_HANDLE __carbon_E_HANDLE
|
|
#define E_ACCESSDENIED __carbon_E_ACCESSDENIED
|
|
#define E_UNEXPECTED __carbon_E_UNEXPECTED
|
|
#define E_FAIL __carbon_E_FAIL
|
|
#define E_ABORT __carbon_E_ABORT
|
|
#define E_POINTER __carbon_E_POINTER
|
|
#define E_NOINTERFACE __carbon_E_NOINTERFACE
|
|
#define E_NOTIMPL __carbon_E_NOTIMPL
|
|
#define S_FALSE __carbon_S_FALSE
|
|
#define S_OK __carbon_S_OK
|
|
#define HRESULT_FACILITY __carbon_HRESULT_FACILITY
|
|
#define IS_ERROR __carbon_IS_ERROR
|
|
#define FAILED __carbon_FAILED
|
|
#define SUCCEEDED __carbon_SUCCEEDED
|
|
#define MAKE_HRESULT __carbon_MAKE_HRESULT
|
|
#define HRESULT __carbon_HRESULT
|
|
#define STDMETHODCALLTYPE __carbon_STDMETHODCALLTYPE
|
|
#define PAGE_SHIFT __carbon_PAGE_SHIFT
|
|
#include <IOKit/IOKitLib.h>
|
|
#include <IOKit/hid/IOHIDLib.h>
|
|
#undef ULONG
|
|
#undef E_INVALIDARG
|
|
#undef E_OUTOFMEMORY
|
|
#undef E_HANDLE
|
|
#undef E_ACCESSDENIED
|
|
#undef E_UNEXPECTED
|
|
#undef E_FAIL
|
|
#undef E_ABORT
|
|
#undef E_POINTER
|
|
#undef E_NOINTERFACE
|
|
#undef E_NOTIMPL
|
|
#undef S_FALSE
|
|
#undef S_OK
|
|
#undef HRESULT_FACILITY
|
|
#undef IS_ERROR
|
|
#undef FAILED
|
|
#undef SUCCEEDED
|
|
#undef MAKE_HRESULT
|
|
#undef HRESULT
|
|
#undef STDMETHODCALLTYPE
|
|
#undef DWORD
|
|
#undef LPDWORD
|
|
#undef LONG
|
|
#undef LPLONG
|
|
#undef E_PENDING
|
|
#undef PAGE_SHIFT
|
|
#endif /* HAVE_IOKIT_HID_IOHIDLIB_H */
|
|
|
|
#define NONAMELESSUNION
|
|
|
|
#include "ntstatus.h"
|
|
#define WIN32_NO_STATUS
|
|
#include "windef.h"
|
|
#include "winbase.h"
|
|
#include "winternl.h"
|
|
#include "winioctl.h"
|
|
#include "ddk/wdm.h"
|
|
#include "ddk/hidtypes.h"
|
|
#include "wine/debug.h"
|
|
|
|
#include "bus.h"
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(plugplay);
|
|
#ifdef HAVE_IOHIDMANAGERCREATE
|
|
|
|
static DRIVER_OBJECT *iohid_driver_obj = NULL;
|
|
static IOHIDManagerRef hid_manager;
|
|
static CFRunLoopRef run_loop;
|
|
static HANDLE run_loop_handle;
|
|
|
|
static const WCHAR busidW[] = {'I','O','H','I','D',0};
|
|
|
|
#include "initguid.h"
|
|
DEFINE_GUID(GUID_DEVCLASS_IOHID, 0x989D309D,0x0470,0x4E1A,0x89,0x38,0x50,0x1F,0x42,0xBD,0x9A,0xCD);
|
|
|
|
struct platform_private
|
|
{
|
|
IOHIDDeviceRef device;
|
|
uint8_t *buffer;
|
|
};
|
|
|
|
static inline struct platform_private *impl_from_DEVICE_OBJECT(DEVICE_OBJECT *device)
|
|
{
|
|
return (struct platform_private *)get_platform_private(device);
|
|
}
|
|
|
|
static void CFStringToWSTR(CFStringRef cstr, LPWSTR wstr, int length)
|
|
{
|
|
int len = min(CFStringGetLength(cstr), length-1);
|
|
CFStringGetCharacters(cstr, CFRangeMake(0, len), (UniChar*)wstr);
|
|
wstr[len] = 0;
|
|
}
|
|
|
|
static DWORD CFNumberToDWORD(CFNumberRef num)
|
|
{
|
|
int dwNum = 0;
|
|
if (num)
|
|
CFNumberGetValue(num, kCFNumberIntType, &dwNum);
|
|
return dwNum;
|
|
}
|
|
|
|
static void handle_IOHIDDeviceIOHIDReportCallback(void *context,
|
|
IOReturn result, void *sender, IOHIDReportType type,
|
|
uint32_t reportID, uint8_t *report, CFIndex report_length)
|
|
{
|
|
DEVICE_OBJECT *device = (DEVICE_OBJECT*)context;
|
|
process_hid_report(device, report, report_length);
|
|
}
|
|
|
|
static int compare_platform_device(DEVICE_OBJECT *device, void *platform_dev)
|
|
{
|
|
struct platform_private *private = impl_from_DEVICE_OBJECT(device);
|
|
IOHIDDeviceRef dev2 = (IOHIDDeviceRef)platform_dev;
|
|
if (private->device != dev2)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static NTSTATUS get_reportdescriptor(DEVICE_OBJECT *device, BYTE *buffer, DWORD length, DWORD *out_length)
|
|
{
|
|
struct platform_private *private = impl_from_DEVICE_OBJECT(device);
|
|
CFDataRef data = IOHIDDeviceGetProperty(private->device, CFSTR(kIOHIDReportDescriptorKey));
|
|
int data_length = CFDataGetLength(data);
|
|
const UInt8 *ptr;
|
|
|
|
*out_length = data_length;
|
|
if (length < data_length)
|
|
return STATUS_BUFFER_TOO_SMALL;
|
|
|
|
ptr = CFDataGetBytePtr(data);
|
|
memcpy(buffer, ptr, data_length);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS get_string(DEVICE_OBJECT *device, DWORD index, WCHAR *buffer, DWORD length)
|
|
{
|
|
struct platform_private *private = impl_from_DEVICE_OBJECT(device);
|
|
CFStringRef str;
|
|
switch (index)
|
|
{
|
|
case HID_STRING_ID_IPRODUCT:
|
|
str = IOHIDDeviceGetProperty(private->device, CFSTR(kIOHIDProductKey));
|
|
break;
|
|
case HID_STRING_ID_IMANUFACTURER:
|
|
str = IOHIDDeviceGetProperty(private->device, CFSTR(kIOHIDManufacturerKey));
|
|
break;
|
|
case HID_STRING_ID_ISERIALNUMBER:
|
|
str = IOHIDDeviceGetProperty(private->device, CFSTR(kIOHIDSerialNumberKey));
|
|
break;
|
|
default:
|
|
ERR("Unknown string index\n");
|
|
return STATUS_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
if (str)
|
|
{
|
|
if (length < CFStringGetLength(str) + 1)
|
|
return STATUS_BUFFER_TOO_SMALL;
|
|
CFStringToWSTR(str, buffer, length);
|
|
}
|
|
else
|
|
{
|
|
if (!length) return STATUS_BUFFER_TOO_SMALL;
|
|
buffer[0] = 0;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS begin_report_processing(DEVICE_OBJECT *device)
|
|
{
|
|
DWORD length;
|
|
struct platform_private *private = impl_from_DEVICE_OBJECT(device);
|
|
CFNumberRef num;
|
|
|
|
if (private->buffer)
|
|
return STATUS_SUCCESS;
|
|
|
|
num = IOHIDDeviceGetProperty(private->device, CFSTR(kIOHIDMaxInputReportSizeKey));
|
|
length = CFNumberToDWORD(num);
|
|
private->buffer = HeapAlloc(GetProcessHeap(), 0, length);
|
|
|
|
IOHIDDeviceRegisterInputReportCallback(private->device, private->buffer, length, handle_IOHIDDeviceIOHIDReportCallback, device);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS set_output_report(DEVICE_OBJECT *device, UCHAR id, BYTE *report, DWORD length, ULONG_PTR *written)
|
|
{
|
|
IOReturn result;
|
|
struct platform_private *private = impl_from_DEVICE_OBJECT(device);
|
|
result = IOHIDDeviceSetReport(private->device, kIOHIDReportTypeOutput, id, report, length);
|
|
if (result == kIOReturnSuccess)
|
|
{
|
|
*written = length;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
*written = 0;
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
}
|
|
|
|
static NTSTATUS get_feature_report(DEVICE_OBJECT *device, UCHAR id, BYTE *report, DWORD length, ULONG_PTR *read)
|
|
{
|
|
IOReturn ret;
|
|
CFIndex report_length = length;
|
|
struct platform_private *private = impl_from_DEVICE_OBJECT(device);
|
|
|
|
ret = IOHIDDeviceGetReport(private->device, kIOHIDReportTypeFeature, id, report, &report_length);
|
|
if (ret == kIOReturnSuccess)
|
|
{
|
|
*read = report_length;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
*read = 0;
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
}
|
|
|
|
static NTSTATUS set_feature_report(DEVICE_OBJECT *device, UCHAR id, BYTE *report, DWORD length, ULONG_PTR *written)
|
|
{
|
|
IOReturn result;
|
|
struct platform_private *private = impl_from_DEVICE_OBJECT(device);
|
|
|
|
result = IOHIDDeviceSetReport(private->device, kIOHIDReportTypeFeature, id, report, length);
|
|
if (result == kIOReturnSuccess)
|
|
{
|
|
*written = length;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
*written = 0;
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
}
|
|
|
|
static const platform_vtbl iohid_vtbl =
|
|
{
|
|
compare_platform_device,
|
|
get_reportdescriptor,
|
|
get_string,
|
|
begin_report_processing,
|
|
set_output_report,
|
|
get_feature_report,
|
|
set_feature_report,
|
|
};
|
|
|
|
static void handle_DeviceMatchingCallback(void *context, IOReturn result, void *sender, IOHIDDeviceRef IOHIDDevice)
|
|
{
|
|
DEVICE_OBJECT *device;
|
|
DWORD vid, pid, version, uid;
|
|
CFStringRef str = NULL;
|
|
WCHAR serial_string[256];
|
|
BOOL is_gamepad = FALSE;
|
|
|
|
TRACE("OS/X IOHID Device Added %p\n", IOHIDDevice);
|
|
|
|
vid = CFNumberToDWORD(IOHIDDeviceGetProperty(IOHIDDevice, CFSTR(kIOHIDVendorIDKey)));
|
|
pid = CFNumberToDWORD(IOHIDDeviceGetProperty(IOHIDDevice, CFSTR(kIOHIDProductIDKey)));
|
|
version = CFNumberToDWORD(IOHIDDeviceGetProperty(IOHIDDevice, CFSTR(kIOHIDVersionNumberKey)));
|
|
str = IOHIDDeviceGetProperty(IOHIDDevice, CFSTR(kIOHIDSerialNumberKey));
|
|
if (str) CFStringToWSTR(str, serial_string, ARRAY_SIZE(serial_string));
|
|
uid = CFNumberToDWORD(IOHIDDeviceGetProperty(IOHIDDevice, CFSTR(kIOHIDLocationIDKey)));
|
|
|
|
if (IOHIDDeviceConformsTo(IOHIDDevice, kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad) ||
|
|
IOHIDDeviceConformsTo(IOHIDDevice, kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick))
|
|
{
|
|
if (is_xbox_gamepad(vid, pid))
|
|
is_gamepad = TRUE;
|
|
else
|
|
{
|
|
int axes=0, buttons=0;
|
|
CFArrayRef element_array = IOHIDDeviceCopyMatchingElements(
|
|
IOHIDDevice, NULL, kIOHIDOptionsTypeNone);
|
|
|
|
if (element_array) {
|
|
CFIndex index;
|
|
CFIndex count = CFArrayGetCount(element_array);
|
|
for (index = 0; index < count; index++)
|
|
{
|
|
IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(element_array, index);
|
|
if (element)
|
|
{
|
|
int type = IOHIDElementGetType(element);
|
|
if (type == kIOHIDElementTypeInput_Button) buttons++;
|
|
if (type == kIOHIDElementTypeInput_Axis) axes++;
|
|
if (type == kIOHIDElementTypeInput_Misc)
|
|
{
|
|
uint32_t usage = IOHIDElementGetUsage(element);
|
|
switch (usage)
|
|
{
|
|
case kHIDUsage_GD_X:
|
|
case kHIDUsage_GD_Y:
|
|
case kHIDUsage_GD_Z:
|
|
case kHIDUsage_GD_Rx:
|
|
case kHIDUsage_GD_Ry:
|
|
case kHIDUsage_GD_Rz:
|
|
case kHIDUsage_GD_Slider:
|
|
axes ++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
CFRelease(element_array);
|
|
}
|
|
is_gamepad = (axes == 6 && buttons >= 14);
|
|
}
|
|
}
|
|
|
|
device = bus_create_hid_device(iohid_driver_obj, busidW, vid, pid, version, uid, str?serial_string:NULL, is_gamepad, &GUID_DEVCLASS_IOHID, &iohid_vtbl, sizeof(struct platform_private));
|
|
if (!device)
|
|
ERR("Failed to create device\n");
|
|
else
|
|
{
|
|
struct platform_private *private = impl_from_DEVICE_OBJECT(device);
|
|
private->device = IOHIDDevice;
|
|
private->buffer = NULL;
|
|
IoInvalidateDeviceRelations(device, BusRelations);
|
|
}
|
|
}
|
|
|
|
static void handle_RemovalCallback(void *context, IOReturn result, void *sender, IOHIDDeviceRef IOHIDDevice)
|
|
{
|
|
DEVICE_OBJECT *device;
|
|
TRACE("OS/X IOHID Device Removed %p\n", IOHIDDevice);
|
|
IOHIDDeviceRegisterInputReportCallback(IOHIDDevice, NULL, 0, NULL, NULL);
|
|
/* Note: Yes, we leak the buffer. But according to research there is no
|
|
safe way to deallocate that buffer. */
|
|
device = bus_find_hid_device(&iohid_vtbl, IOHIDDevice);
|
|
if (device)
|
|
{
|
|
IoInvalidateDeviceRelations(device, RemovalRelations);
|
|
bus_remove_hid_device(device);
|
|
}
|
|
}
|
|
|
|
/* This puts the relevant run loop for event handling into a WINE thread */
|
|
static DWORD CALLBACK runloop_thread(void *args)
|
|
{
|
|
run_loop = CFRunLoopGetCurrent();
|
|
|
|
IOHIDManagerSetDeviceMatching(hid_manager, NULL);
|
|
IOHIDManagerRegisterDeviceMatchingCallback(hid_manager, handle_DeviceMatchingCallback, NULL);
|
|
IOHIDManagerRegisterDeviceRemovalCallback(hid_manager, handle_RemovalCallback, NULL);
|
|
IOHIDManagerScheduleWithRunLoop(hid_manager, run_loop, kCFRunLoopDefaultMode);
|
|
if (IOHIDManagerOpen( hid_manager, 0 ) != kIOReturnSuccess)
|
|
{
|
|
ERR("Couldn't open IOHIDManager.\n");
|
|
IOHIDManagerUnscheduleFromRunLoop(hid_manager, run_loop, kCFRunLoopDefaultMode);
|
|
CFRelease(hid_manager);
|
|
return 0;
|
|
}
|
|
|
|
CFRunLoopRun();
|
|
TRACE("Run Loop exiting\n");
|
|
return 1;
|
|
|
|
}
|
|
|
|
NTSTATUS WINAPI iohid_driver_init(DRIVER_OBJECT *driver, UNICODE_STRING *registry_path)
|
|
{
|
|
TRACE("(%p, %s)\n", driver, debugstr_w(registry_path->Buffer));
|
|
|
|
iohid_driver_obj = driver;
|
|
driver->MajorFunction[IRP_MJ_PNP] = common_pnp_dispatch;
|
|
driver->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = hid_internal_dispatch;
|
|
hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, 0L);
|
|
if (!(run_loop_handle = CreateThread(NULL, 0, runloop_thread, NULL, 0, NULL)))
|
|
{
|
|
ERR("Failed to initialize IOHID Manager thread\n");
|
|
iohid_driver_obj = NULL;
|
|
CFRelease(hid_manager);
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
void iohid_driver_unload( void )
|
|
{
|
|
TRACE("Unloading Driver\n");
|
|
if (iohid_driver_obj != NULL)
|
|
{
|
|
IOHIDManagerUnscheduleFromRunLoop(hid_manager, run_loop, kCFRunLoopDefaultMode);
|
|
CFRunLoopStop(run_loop);
|
|
WaitForSingleObject(run_loop_handle, INFINITE);
|
|
CloseHandle(run_loop_handle);
|
|
IOHIDManagerRegisterDeviceMatchingCallback(hid_manager, NULL, NULL);
|
|
IOHIDManagerRegisterDeviceRemovalCallback(hid_manager, NULL, NULL);
|
|
CFRelease(hid_manager);
|
|
}
|
|
TRACE("Driver Unloaded\n");
|
|
}
|
|
|
|
#else
|
|
|
|
NTSTATUS WINAPI iohid_driver_init(DRIVER_OBJECT *driver, UNICODE_STRING *registry_path)
|
|
{
|
|
WARN("IOHID Support not compiled into Wine.\n");
|
|
return STATUS_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
void iohid_driver_unload( void )
|
|
{
|
|
TRACE("Stub: Unload Driver\n");
|
|
}
|
|
|
|
#endif /* HAVE_IOHIDMANAGERCREATE */
|