IOHIDNXEventRouter.cpp [plain text]
#include "IOHIDNXEventRouter.h"
#include "IOHIDEventServiceUserClient.h"
#include "IOHIDEventData.h"
#include <dispatch/private.h>
#include <IOKit/hid/IOHIDUsageTables.h>
#include <IOKit/hid/IOHIDServiceKeys.h>
#include <IOKit/hid/IOHIDKeys.h>
#include <IOKit/hid/IOHIDLibPrivate.h>
#include "IOHIDDebug.h"
__BEGIN_DECLS
#include <asl.h>
#include <mach/mach.h>
#include <mach/mach_interface.h>
#include <IOKit/IOMessage.h>
#include <mach/mach_time.h>
__END_DECLS
extern "C" void * IOHIDNXEventRouterFactory(CFAllocatorRef allocator __unused, CFUUIDRef typeUUID);
void *IOHIDNXEventRouterFactory(CFAllocatorRef allocator __unused, CFUUIDRef typeID)
{
if (CFEqual(typeID, kIOHIDServicePlugInTypeID))
return (void *) IOHIDNXEventRouter::alloc();
return NULL;
}
IOCFPlugInInterface IOHIDNXEventRouter::sIOCFPlugInInterfaceV1 =
{
0,
&IOHIDIUnknown::genericQueryInterface,
&IOHIDIUnknown::genericAddRef,
&IOHIDIUnknown::genericRelease,
1, 0, &IOHIDNXEventRouter::_probe,
&IOHIDNXEventRouter::_start,
&IOHIDNXEventRouter::_stop
};
IOHIDServiceInterface2 IOHIDNXEventRouter::sIOHIDServiceInterface2 =
{
0,
&IOHIDIUnknown::genericQueryInterface,
&IOHIDIUnknown::genericAddRef,
&IOHIDIUnknown::genericRelease,
&IOHIDNXEventRouter::_open,
&IOHIDNXEventRouter::_close,
&IOHIDNXEventRouter::_copyProperty,
&IOHIDNXEventRouter::_setProperty,
&IOHIDNXEventRouter::_setEventCallback,
&IOHIDNXEventRouter::_scheduleWithDispatchQueue,
&IOHIDNXEventRouter::_unscheduleFromDispatchQueue,
&IOHIDNXEventRouter::_copyEvent,
&IOHIDNXEventRouter::_setOutputEvent
};
IOHIDNXEventRouter::IOHIDNXEventRouter() : IOHIDIUnknown(&sIOCFPlugInInterfaceV1)
{
_hidService.pseudoVTable = NULL;
_hidService.obj = this;
_service = MACH_PORT_NULL;
_isOpen = FALSE;
_asyncPort = MACH_PORT_NULL;
_asyncEventSource = NULL;
_serviceProperties = NULL;
_servicePreferences = NULL;
_eventCallback = NULL;
_eventTarget = NULL;
_eventRefcon = NULL;
queue_ = NULL;
}
IOHIDNXEventRouter::~IOHIDNXEventRouter()
{
if (_service) {
IOObjectRelease(_service);
_service = MACH_PORT_NULL;
}
if (_serviceProperties) {
CFRelease(_serviceProperties);
_serviceProperties = NULL;
}
if (_servicePreferences) {
CFRelease(_servicePreferences);
_servicePreferences = NULL;
}
if ( _asyncPort ) {
mach_port_mod_refs(mach_task_self(), _asyncPort, MACH_PORT_RIGHT_RECEIVE, -1);
_asyncPort = MACH_PORT_NULL;
}
if (queue_) {
CFRelease(queue_);
queue_ = NULL;
}
}
IOReturn IOHIDNXEventRouter::_probe(void *self, CFDictionaryRef propertyTable, io_service_t service, SInt32 *order)
{
return getThis(self)->probe(propertyTable, service, order);
}
IOReturn IOHIDNXEventRouter::_start(void *self, CFDictionaryRef propertyTable, io_service_t service)
{
return getThis(self)->start(propertyTable, service);
}
IOReturn IOHIDNXEventRouter::_stop(void *self)
{
return getThis(self)->stop();
}
boolean_t IOHIDNXEventRouter::_open(void * self, IOOptionBits options)
{
return getThis(self)->open(options);
}
void IOHIDNXEventRouter::_close(void * self, IOOptionBits options)
{
getThis(self)->close(options);
}
CFTypeRef IOHIDNXEventRouter::_copyProperty(void * self, CFStringRef key)
{
return getThis(self)->copyProperty(key);
}
boolean_t IOHIDNXEventRouter::_setProperty(void * self, CFStringRef key, CFTypeRef property)
{
return getThis(self)->setProperty(key, property);
}
IOHIDEventRef IOHIDNXEventRouter::_copyEvent(void *self, IOHIDEventType type, IOHIDEventRef matching, IOOptionBits options)
{
return getThis(self)->copyEvent(type, matching, options);
}
IOReturn IOHIDNXEventRouter::_setOutputEvent(void *self, IOHIDEventRef event)
{
return getThis(self)->setOutputEvent(event);
}
void IOHIDNXEventRouter::_setEventCallback(void * self, IOHIDServiceEventCallback callback, void * target, void * refcon)
{
getThis(self)->setEventCallback(callback, target, refcon);
}
void IOHIDNXEventRouter::_scheduleWithDispatchQueue(void *self, dispatch_queue_t queue)
{
return getThis(self)->scheduleWithDispatchQueue(queue);
}
void IOHIDNXEventRouter::_unscheduleFromDispatchQueue(void *self, dispatch_queue_t queue)
{
return getThis(self)->unscheduleFromDispatchQueue(queue);
}
void IOHIDNXEventRouter::_queueEventSourceCallback(void * info)
{
IOHIDNXEventRouter * self = (IOHIDNXEventRouter*)info;
mach_msg_size_t size = sizeof(mach_msg_header_t) + MAX_TRAILER_SIZE;
mach_msg_header_t *msg = (mach_msg_header_t *)CFAllocatorAllocate(kCFAllocatorDefault, size, 0);
msg->msgh_size = size;
for (;;) {
msg->msgh_bits = 0;
msg->msgh_local_port = self->_asyncPort;
msg->msgh_remote_port = MACH_PORT_NULL;
msg->msgh_id = 0;
kern_return_t ret = mach_msg(msg, MACH_RCV_MSG|MACH_RCV_LARGE|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV), 0, msg->msgh_size, self->_asyncPort, 0, MACH_PORT_NULL);
if (MACH_MSG_SUCCESS == ret) break;
if (MACH_RCV_TOO_LARGE != ret) goto user_exit;
uint32_t newSize = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
msg = (mach_msg_header_t*)CFAllocatorReallocate(kCFAllocatorDefault, msg, newSize, 0);
msg->msgh_size = newSize;
}
IOHIDNXEventRouter::_queueCallback(info);
user_exit:
CFAllocatorDeallocate(kCFAllocatorSystemDefault, msg);
}
void IOHIDNXEventRouter::dispatchHIDEvent(IOHIDEventRef event, IOOptionBits options)
{
if ( !_eventCallback )
return;
(*_eventCallback)(_eventTarget, _eventRefcon, (void *)&_hidService, event, options);
}
IOCFPlugInInterface ** IOHIDNXEventRouter::alloc()
{
IOHIDNXEventRouter * self = new IOHIDNXEventRouter;
return self ? (IOCFPlugInInterface **) &self->iunknown.pseudoVTable : NULL;
}
HRESULT IOHIDNXEventRouter::queryInterface(REFIID iid, void **ppv)
{
CFUUIDRef uuid = CFUUIDCreateFromUUIDBytes(NULL, iid);
HRESULT res = S_OK;
if (CFEqual(uuid, IUnknownUUID) || CFEqual(uuid, kIOCFPlugInInterfaceID))
{
*ppv = &iunknown;
addRef();
}
else if (CFEqual(uuid, kIOHIDServiceInterface2ID))
{
_hidService.pseudoVTable = (IUnknownVTbl *) &sIOHIDServiceInterface2;
_hidService.obj = this;
*ppv = &_hidService;
addRef();
}
else {
*ppv = 0;
}
if (!*ppv)
res = E_NOINTERFACE;
CFRelease(uuid);
return res;
}
IOReturn IOHIDNXEventRouter::probe(CFDictionaryRef propertyTable __unused, io_service_t service, SInt32 * order __unused)
{
if (!service || !IOObjectConformsTo(service, "IOHIDSystem"))
return kIOReturnBadArgument;
return kIOReturnSuccess;
}
#define GET_AND_SET_SERVICE_PROPERTY(reg,regKey,dict,propKey) \
{ \
CFTypeRef typeRef = IORegistryEntryCreateCFProperty(reg,regKey, kCFAllocatorDefault, kNilOptions);\
if (typeRef) \
{ \
CFDictionarySetValue(dict,propKey,typeRef); \
CFRelease(typeRef); \
} \
}
#define GET_AND_SET_PROPERTY(prop,regKey,dict,propKey) \
{ \
CFTypeRef typeRef = CFDictionaryGetValue(prop,regKey); \
if (typeRef) \
CFDictionarySetValue(dict,propKey,typeRef); \
}
IOReturn IOHIDNXEventRouter::start(CFDictionaryRef propertyTable __unused, io_service_t service)
{
IOReturn ret = kIOReturnError;
CFMutableDictionaryRef serviceProps = NULL;
do {
ret = IOObjectRetain(service);
if (ret != kIOReturnSuccess) {
HIDLogError("IOHIDNXEventRouter failed to retain service object err : 0x%x",ret);
break;
}
_service = service;
_serviceProperties = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if ( !_serviceProperties ) {
ret = kIOReturnNoMemory;
break;
}
IORegistryEntryCreateCFProperties(service, &serviceProps, kCFAllocatorDefault, 0);
if ( !serviceProps )
break;
GET_AND_SET_PROPERTY(serviceProps, CFSTR(kIOHIDServiceGlobalModifiersUsageKey), _serviceProperties, CFSTR(kIOHIDServiceGlobalModifiersUsageKey));
GET_AND_SET_PROPERTY(serviceProps, CFSTR(kIOHIDPrimaryUsagePageKey), _serviceProperties, CFSTR(kIOHIDServicePrimaryUsagePageKey));
GET_AND_SET_PROPERTY(serviceProps, CFSTR(kIOHIDPrimaryUsageKey), _serviceProperties, CFSTR(kIOHIDServicePrimaryUsageKey));
GET_AND_SET_PROPERTY(serviceProps, CFSTR(kIOHIDDeviceUsagePairsKey), _serviceProperties, CFSTR(kIOHIDServiceDeviceUsagePairsKey));
CFRelease(serviceProps);
queue_ = IOHIDEventQueueCreate (kCFAllocatorDefault, kIOHIDEventQueueTypeKernel, 1024 * 16, 0);
if (queue_ == NULL) {
ret = kIOReturnNoMemory;
break;
}
return kIOReturnSuccess;
} while (0);
if ( _service ) {
IOObjectRelease(_service);
_service = NULL;
}
return ret;
}
IOReturn IOHIDNXEventRouter::stop()
{
return kIOReturnSuccess;
}
boolean_t IOHIDNXEventRouter::open(IOOptionBits options __unused)
{
bool ret = true;
if ( !_isOpen ) {
IOHIDEventQueueStart(queue_);
_isOpen = true;
}
return ret;
}
void IOHIDNXEventRouter::close(IOOptionBits options __unused)
{
if ( _isOpen ) {
IOHIDEventQueueStop(queue_);
_isOpen = false;
}
}
CFTypeRef IOHIDNXEventRouter::copyProperty(CFStringRef key)
{
CFTypeRef value = CFDictionaryGetValue(_serviceProperties, key);
if ( value ) {
CFRetain(value);
} else {
value = IORegistryEntrySearchCFProperty(_service, kIOServicePlane, key, kCFAllocatorDefault, kIORegistryIterateRecursively| kIORegistryIterateParents);
}
return value;
}
boolean_t IOHIDNXEventRouter::setProperty(CFStringRef key, CFTypeRef property)
{
boolean_t retVal = true;
CFDictionarySetValue(_serviceProperties, key, property);
if (CFEqual(key, CFSTR(kIOHIDKeyboardGlobalModifiersKey))) {
retVal = (IORegistryEntrySetCFProperty(_service, key, property) == kIOReturnSuccess);
}
return retVal;
}
IOHIDEventRef IOHIDNXEventRouter::copyEvent(IOHIDEventType eventType __unused, IOHIDEventRef matching __unused, IOOptionBits options __unused)
{
IOHIDEventRef event = NULL;
return event;
}
IOReturn IOHIDNXEventRouter::setOutputEvent(IOHIDEventRef event __unused)
{
IOReturn result = kIOReturnUnsupported;
return result;
}
void IOHIDNXEventRouter::setEventCallback(IOHIDServiceEventCallback callback, void * target, void * refcon)
{
_eventCallback = callback;
_eventTarget = target;
_eventRefcon = refcon;
}
void IOHIDNXEventRouter::scheduleWithDispatchQueue(dispatch_queue_t dispatchQueue)
{
if ( !_asyncPort ) {
_asyncPort = IODataQueueAllocateNotificationPort();
if (!_asyncPort) {
return;
}
IOHIDEventQueueSetNotificationPort (queue_, _asyncPort);
}
if ( !_asyncEventSource ) {
_asyncEventSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, _asyncPort, 0, dispatchQueue);
if ( !_asyncEventSource ) {
return;
}
dispatch_set_context(_asyncEventSource, this);
dispatch_source_set_event_handler_f(_asyncEventSource, _queueEventSourceCallback);
}
dispatch_resume(_asyncEventSource);
dispatch_async(dispatchQueue, ^{
_queueCallback(this);
});
}
void IOHIDNXEventRouter::unscheduleFromDispatchQueue(dispatch_queue_t queue __unused)
{
if ( _asyncEventSource ) {
dispatch_source_cancel(_asyncEventSource);
dispatch_release(_asyncEventSource);
_asyncEventSource = NULL;
}
}
void IOHIDNXEventRouter::_queueCallback(void * info)
{
IOHIDEventRef event;
IOHIDNXEventRouter *self = (IOHIDNXEventRouter*)info;
if ( !info )
return;
while ( (event = IOHIDEventQueueDequeueCopy(self->queue_)) ) {
self->dispatchHIDEvent(event);
CFRelease(event);
}
}