445 lines
14 KiB
C
445 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
|
|
*/
|
|
|
|
#if 0
|
|
#pragma makedep unix
|
|
#endif
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdarg.h>
|
|
#include <sys/types.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 */
|
|
|
|
#include <pthread.h>
|
|
|
|
#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 "unix_private.h"
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(hid);
|
|
#ifdef HAVE_IOHIDMANAGERCREATE
|
|
|
|
static pthread_mutex_t iohid_cs = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
static IOHIDManagerRef hid_manager;
|
|
static CFRunLoopRef run_loop;
|
|
static struct list event_queue = LIST_INIT(event_queue);
|
|
static struct list device_list = LIST_INIT(device_list);
|
|
static struct iohid_bus_options options;
|
|
|
|
struct iohid_device
|
|
{
|
|
struct unix_device unix_device;
|
|
IOHIDDeviceRef device;
|
|
uint8_t *buffer;
|
|
};
|
|
|
|
static inline struct iohid_device *impl_from_unix_device(struct unix_device *iface)
|
|
{
|
|
return CONTAINING_RECORD(iface, struct iohid_device, unix_device);
|
|
}
|
|
|
|
static struct iohid_device *find_device_from_iohid(IOHIDDeviceRef IOHIDDevice)
|
|
{
|
|
struct iohid_device *impl;
|
|
|
|
LIST_FOR_EACH_ENTRY(impl, &device_list, struct iohid_device, unix_device.entry)
|
|
if (impl->device == IOHIDDevice) return impl;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
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)
|
|
{
|
|
struct unix_device *iface = (struct unix_device *)context;
|
|
bus_event_queue_input_report(&event_queue, iface, report, report_length);
|
|
}
|
|
|
|
static void iohid_device_destroy(struct unix_device *iface)
|
|
{
|
|
}
|
|
|
|
static NTSTATUS iohid_device_start(struct unix_device *iface)
|
|
{
|
|
DWORD length;
|
|
struct iohid_device *impl = impl_from_unix_device(iface);
|
|
CFNumberRef num;
|
|
|
|
num = IOHIDDeviceGetProperty(impl->device, CFSTR(kIOHIDMaxInputReportSizeKey));
|
|
length = CFNumberToDWORD(num);
|
|
impl->buffer = malloc(length);
|
|
|
|
IOHIDDeviceRegisterInputReportCallback(impl->device, impl->buffer, length, handle_IOHIDDeviceIOHIDReportCallback, iface);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static void iohid_device_stop(struct unix_device *iface)
|
|
{
|
|
struct iohid_device *impl = impl_from_unix_device(iface);
|
|
|
|
IOHIDDeviceRegisterInputReportCallback(impl->device, NULL, 0, NULL, NULL);
|
|
|
|
pthread_mutex_lock(&iohid_cs);
|
|
list_remove(&impl->unix_device.entry);
|
|
pthread_mutex_unlock(&iohid_cs);
|
|
}
|
|
|
|
static NTSTATUS iohid_device_get_report_descriptor(struct unix_device *iface, BYTE *buffer,
|
|
DWORD length, DWORD *out_length)
|
|
{
|
|
struct iohid_device *impl = impl_from_unix_device(iface);
|
|
CFDataRef data = IOHIDDeviceGetProperty(impl->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 void iohid_device_set_output_report(struct unix_device *iface, HID_XFER_PACKET *packet, IO_STATUS_BLOCK *io)
|
|
{
|
|
IOReturn result;
|
|
struct iohid_device *impl = impl_from_unix_device(iface);
|
|
result = IOHIDDeviceSetReport(impl->device, kIOHIDReportTypeOutput, packet->reportId,
|
|
packet->reportBuffer, packet->reportBufferLen);
|
|
if (result == kIOReturnSuccess)
|
|
{
|
|
io->Information = packet->reportBufferLen;
|
|
io->Status = STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
io->Information = 0;
|
|
io->Status = STATUS_UNSUCCESSFUL;
|
|
}
|
|
}
|
|
|
|
static void iohid_device_get_feature_report(struct unix_device *iface, HID_XFER_PACKET *packet, IO_STATUS_BLOCK *io)
|
|
{
|
|
IOReturn ret;
|
|
CFIndex report_length = packet->reportBufferLen;
|
|
struct iohid_device *impl = impl_from_unix_device(iface);
|
|
|
|
ret = IOHIDDeviceGetReport(impl->device, kIOHIDReportTypeFeature, packet->reportId,
|
|
packet->reportBuffer, &report_length);
|
|
if (ret == kIOReturnSuccess)
|
|
{
|
|
io->Information = report_length;
|
|
io->Status = STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
io->Information = 0;
|
|
io->Status = STATUS_UNSUCCESSFUL;
|
|
}
|
|
}
|
|
|
|
static void iohid_device_set_feature_report(struct unix_device *iface, HID_XFER_PACKET *packet, IO_STATUS_BLOCK *io)
|
|
{
|
|
IOReturn result;
|
|
struct iohid_device *impl = impl_from_unix_device(iface);
|
|
|
|
result = IOHIDDeviceSetReport(impl->device, kIOHIDReportTypeFeature, packet->reportId,
|
|
packet->reportBuffer, packet->reportBufferLen);
|
|
if (result == kIOReturnSuccess)
|
|
{
|
|
io->Information = packet->reportBufferLen;
|
|
io->Status = STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
io->Information = 0;
|
|
io->Status = STATUS_UNSUCCESSFUL;
|
|
}
|
|
}
|
|
|
|
static const struct raw_device_vtbl iohid_device_vtbl =
|
|
{
|
|
iohid_device_destroy,
|
|
iohid_device_start,
|
|
iohid_device_stop,
|
|
iohid_device_get_report_descriptor,
|
|
iohid_device_set_output_report,
|
|
iohid_device_get_feature_report,
|
|
iohid_device_set_feature_report,
|
|
};
|
|
|
|
static void handle_DeviceMatchingCallback(void *context, IOReturn result, void *sender, IOHIDDeviceRef IOHIDDevice)
|
|
{
|
|
struct device_desc desc =
|
|
{
|
|
.input = -1,
|
|
.serialnumber = {'0','0','0','0',0},
|
|
};
|
|
struct iohid_device *impl;
|
|
CFStringRef str;
|
|
|
|
desc.vid = CFNumberToDWORD(IOHIDDeviceGetProperty(IOHIDDevice, CFSTR(kIOHIDVendorIDKey)));
|
|
desc.pid = CFNumberToDWORD(IOHIDDeviceGetProperty(IOHIDDevice, CFSTR(kIOHIDProductIDKey)));
|
|
desc.version = CFNumberToDWORD(IOHIDDeviceGetProperty(IOHIDDevice, CFSTR(kIOHIDVersionNumberKey)));
|
|
desc.uid = CFNumberToDWORD(IOHIDDeviceGetProperty(IOHIDDevice, CFSTR(kIOHIDLocationIDKey)));
|
|
|
|
if (IOHIDDeviceOpen(IOHIDDevice, 0) != kIOReturnSuccess)
|
|
{
|
|
ERR("Failed to open HID device %p (vid %04x, pid %04x)\n", IOHIDDevice, desc.vid, desc.pid);
|
|
return;
|
|
}
|
|
IOHIDDeviceScheduleWithRunLoop(IOHIDDevice, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
|
|
|
str = IOHIDDeviceGetProperty(IOHIDDevice, CFSTR(kIOHIDManufacturerKey));
|
|
if (str) CFStringToWSTR(str, desc.manufacturer, ARRAY_SIZE(desc.manufacturer));
|
|
str = IOHIDDeviceGetProperty(IOHIDDevice, CFSTR(kIOHIDProductKey));
|
|
if (str) CFStringToWSTR(str, desc.product, ARRAY_SIZE(desc.product));
|
|
str = IOHIDDeviceGetProperty(IOHIDDevice, CFSTR(kIOHIDSerialNumberKey));
|
|
if (str) CFStringToWSTR(str, desc.serialnumber, ARRAY_SIZE(desc.serialnumber));
|
|
|
|
if (IOHIDDeviceConformsTo(IOHIDDevice, kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad) ||
|
|
IOHIDDeviceConformsTo(IOHIDDevice, kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick))
|
|
{
|
|
if (is_xbox_gamepad(desc.vid, desc.pid))
|
|
desc.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);
|
|
}
|
|
desc.is_gamepad = (axes == 6 && buttons >= 14);
|
|
}
|
|
}
|
|
|
|
TRACE("dev %p, desc %s.\n", IOHIDDevice, debugstr_device_desc(&desc));
|
|
|
|
if (!(impl = raw_device_create(&iohid_device_vtbl, sizeof(struct iohid_device)))) return;
|
|
list_add_tail(&device_list, &impl->unix_device.entry);
|
|
impl->device = IOHIDDevice;
|
|
impl->buffer = NULL;
|
|
|
|
bus_event_queue_device_created(&event_queue, &impl->unix_device, &desc);
|
|
}
|
|
|
|
static void handle_RemovalCallback(void *context, IOReturn result, void *sender, IOHIDDeviceRef IOHIDDevice)
|
|
{
|
|
struct iohid_device *impl;
|
|
|
|
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. */
|
|
IOHIDDeviceUnscheduleFromRunLoop(IOHIDDevice, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
|
IOHIDDeviceClose(IOHIDDevice, 0);
|
|
|
|
impl = find_device_from_iohid(IOHIDDevice);
|
|
if (impl) bus_event_queue_device_removed(&event_queue, &impl->unix_device);
|
|
else WARN("failed to find device for iohid device %p\n", IOHIDDevice);
|
|
}
|
|
|
|
NTSTATUS iohid_bus_init(void *args)
|
|
{
|
|
TRACE("args %p\n", args);
|
|
|
|
options = *(struct iohid_bus_options *)args;
|
|
|
|
if (!(hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, 0L)))
|
|
{
|
|
ERR("IOHID manager creation failed\n");
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
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);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS iohid_bus_wait(void *args)
|
|
{
|
|
struct bus_event *result = args;
|
|
CFRunLoopRunResult ret;
|
|
|
|
/* cleanup previously returned event */
|
|
bus_event_cleanup(result);
|
|
|
|
do
|
|
{
|
|
if (bus_event_queue_pop(&event_queue, result)) return STATUS_PENDING;
|
|
pthread_mutex_lock(&iohid_cs);
|
|
ret = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, TRUE);
|
|
pthread_mutex_unlock(&iohid_cs);
|
|
} while (ret != kCFRunLoopRunStopped);
|
|
|
|
TRACE("IOHID main loop exiting\n");
|
|
bus_event_queue_destroy(&event_queue);
|
|
IOHIDManagerRegisterDeviceMatchingCallback(hid_manager, NULL, NULL);
|
|
IOHIDManagerRegisterDeviceRemovalCallback(hid_manager, NULL, NULL);
|
|
CFRelease(hid_manager);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS iohid_bus_stop(void *args)
|
|
{
|
|
if (!run_loop) return STATUS_SUCCESS;
|
|
|
|
IOHIDManagerUnscheduleFromRunLoop(hid_manager, run_loop, kCFRunLoopDefaultMode);
|
|
CFRunLoopStop(run_loop);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
#else
|
|
|
|
NTSTATUS iohid_bus_init(void *args)
|
|
{
|
|
WARN("IOHID support not compiled in!\n");
|
|
return STATUS_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NTSTATUS iohid_bus_wait(void *args)
|
|
{
|
|
WARN("IOHID support not compiled in!\n");
|
|
return STATUS_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NTSTATUS iohid_bus_stop(void *args)
|
|
{
|
|
WARN("IOHID support not compiled in!\n");
|
|
return STATUS_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
#endif /* HAVE_IOHIDMANAGERCREATE */
|