IOHIDObsoleteDeviceClass.m   [plain text]


/*
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * Copyright (c) 2017 Apple Computer, Inc.  All Rights Reserved.
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_LICENSE_HEADER_END@
 */

#import <Foundation/Foundation.h>
#import "IOHIDObsoleteDeviceClass.h"
#import "IOHIDQueueClass.h"
#import "HIDLibElement.h"
#import <IOKit/hid/IOHIDLibPrivate.h>
#import "IOHIDDebug.h"
#import <AssertMacros.h>
#import "IOHIDTransactionClass.h"

@implementation IOHIDObsoleteDeviceClass

- (HRESULT)queryInterface:(REFIID)uuidBytes
             outInterface:(LPVOID *)outInterface
{
    CFUUIDRef uuid = CFUUIDCreateFromUUIDBytes(NULL, uuidBytes);
    HRESULT result = E_NOINTERFACE;
    
    if (CFEqual(uuid, IUnknownUUID) || CFEqual(uuid, kIOCFPlugInInterfaceID)) {
        *outInterface = &self->_plugin;
        CFRetain((__bridge CFTypeRef)self);
        result = S_OK;
    } else if (CFEqual(uuid, kIOHIDDeviceInterfaceID) ||
               CFEqual(uuid, kIOHIDDeviceInterfaceID122)) {
        *outInterface = (LPVOID *)&_interface;
        CFRetain((__bridge CFTypeRef)self);
        result = S_OK;
    } else if (CFEqual(uuid, kIOHIDQueueInterfaceID)) {
        IOHIDObsoleteQueueClass *queue = [[IOHIDObsoleteQueueClass alloc]
                                          initWithDevice:self];
        result = [queue queryInterface:uuidBytes outInterface:outInterface];
    } else if (CFEqual(uuid, kIOHIDOutputTransactionInterfaceID)) {
        IOHIDOutputTransactionClass *transaction;
        transaction = [[IOHIDOutputTransactionClass alloc] initWithDevice:self];
        result = [transaction queryInterface:uuidBytes
                                outInterface:outInterface];
    }
    
    if (uuid) {
        CFRelease(uuid);
    }
    
    return result;
}

static IOReturn _createAsyncEventSource(void *iunknown,
                                        CFRunLoopSourceRef *source)
{
    IUnknownVTbl *vtbl = *((IUnknownVTbl**)iunknown);
    IOHIDObsoleteDeviceClass *me = (__bridge id)vtbl->_reserved;
    
    if (!source) {
        return kIOReturnBadArgument;
    }
    
    CFRetain(me->_runLoopSource);
    *source = me->_runLoopSource;
    
    return kIOReturnSuccess;
}

static CFRunLoopSourceRef _getAsyncEventSource(void *iunknown)
{
    IUnknownVTbl *vtbl = *((IUnknownVTbl**)iunknown);
    IOHIDObsoleteDeviceClass *me = (__bridge id)vtbl->_reserved;
    
    return me->_runLoopSource;
}

static IOReturn _createAsyncPort(void *iunknown, mach_port_t *port)
{
    IUnknownVTbl *vtbl = *((IUnknownVTbl**)iunknown);
    IOHIDObsoleteDeviceClass *me = (__bridge id)vtbl->_reserved;
    
    *port = me->_port;
    return kIOReturnSuccess;
}

static mach_port_t _getAsyncPort(void *iunknown)
{
    IUnknownVTbl *vtbl = *((IUnknownVTbl**)iunknown);
    IOHIDObsoleteDeviceClass *me = (__bridge id)vtbl->_reserved;
    
    return me->_port;
}

static IOReturn _open(void *iunknown, IOOptionBits flags)
{
    IUnknownVTbl *vtbl = *((IUnknownVTbl**)iunknown);
    IOHIDObsoleteDeviceClass *me = (__bridge id)vtbl->_reserved;
    
    return [me open:flags];
}

static IOReturn _close(void *iunknown)
{
    IUnknownVTbl *vtbl = *((IUnknownVTbl**)iunknown);
    IOHIDObsoleteDeviceClass *me = (__bridge id)vtbl->_reserved;
    
    return [me close:0];
}

static void interestCallback(void *refcon,
                             io_service_t service __unused,
                             uint32_t messageType,
                             void *messageArgument __unused)
{
    IOHIDObsoleteDeviceClass *me = (__bridge id)refcon;
    
    if (!me || messageType != kIOMessageServiceIsTerminated) {
        return;
    }
    
    if (me->_removalCallback) {
        (*me->_removalCallback)(me->_removalTarget,
                                kIOReturnSuccess,
                                me->_removalRefcon,
                                (void *)&me->_interface);
    }
}

static IOReturn _setRemovalCallback(void *iunknown,
                                    IOHIDCallbackFunction removalCallback,
                                    void *removalTarget,
                                    void *removalRefcon)
{
    IUnknownVTbl *vtbl = *((IUnknownVTbl**)iunknown);
    IOHIDObsoleteDeviceClass *me = (__bridge id)vtbl->_reserved;
    
    return [me setRemovalCallback:removalCallback
                    removalTarget:removalTarget
                    removalRefcon:removalRefcon];
}

- (IOReturn)setRemovalCallback:(IOHIDCallbackFunction)removalCallback
                 removalTarget:(void *)removalTarget
                 removalRefcon:(void *)removalRefcon
{
    IOReturn ret = kIOReturnSuccess;
    
    _removalTarget = removalTarget;
    _removalRefcon = removalRefcon;
    _removalCallback = removalCallback;
    
    if (!_notification) {
        ret = IOServiceAddInterestNotification(_notifyPort,
                                               _service,
                                               kIOGeneralInterest,
                                               interestCallback,
                                               (__bridge void *)self,
                                               &_notification);
    }
    
    return ret;
}

static IOReturn _getElementValue(void *iunknown,
                                 IOHIDElementCookie elementCookie,
                                 IOHIDEventStruct *valueEvent)
{
    IUnknownVTbl *vtbl = *((IUnknownVTbl**)iunknown);
    IOHIDObsoleteDeviceClass *me = (__bridge id)vtbl->_reserved;
    
    return [me getElementValue:elementCookie value:valueEvent options:0];
}

- (IOReturn)getElementValue:(IOHIDElementCookie)elementCookie
                      value:(IOHIDEventStruct *)eventStruct
                    options:(IOOptionBits)options
{
    if (!eventStruct) {
        return kIOReturnBadArgument;
    }
    
    IOReturn ret = kIOReturnError;
    IOHIDElementRef elementRef = [super getElement:(uint32_t)elementCookie];
    HIDLibElement *element;
    IOHIDValueRef value;
    uint32_t length;
    
    ret = [super getValue:elementRef
                    value:&value
                  timeout:0
                 callback:nil
                  context:nil
                  options:options];
    require_noerr(ret, exit);
    
    elementRef = IOHIDValueGetElement(value);
    element = [[HIDLibElement alloc] initWithElementRef:elementRef];
    element.valueRef = value;
    
    length = (uint32_t)element.length;
    eventStruct->type = element.type;
    eventStruct->elementCookie = (IOHIDElementCookie)element.elementCookie;
    *(UInt64 *)&eventStruct->timestamp = element.timestamp;
    
    if (length > sizeof(uint32_t)) {
        eventStruct->longValueSize = length;
        eventStruct->longValue = malloc(length);
        bcopy(IOHIDValueGetBytePtr(value), eventStruct->longValue, length);
    } else {
        eventStruct->longValueSize = 0;
        eventStruct->longValue = NULL;
        eventStruct->value = (int32_t)element.integerValue;
    }
    
exit:
    return ret;
}

static IOReturn _setElementValue(void *iunknown,
                                 IOHIDElementCookie elementCookie,
                                 IOHIDEventStruct *valueEvent,
                                 uint32_t timeoutMS __unused,
                                 IOHIDElementCallbackFunction callback __unused,
                                 void *callbackTarget __unused,
                                 void *callbackRefcon __unused)
{
    IUnknownVTbl *vtbl = *((IUnknownVTbl**)iunknown);
    IOHIDObsoleteDeviceClass *me = (__bridge id)vtbl->_reserved;
    
    return [me setElementValue:elementCookie value:valueEvent];
}

- (IOReturn)setElementValue:(IOHIDElementCookie)elementCookie
                      value:(IOHIDEventStruct *)eventStruct
{
    if (!eventStruct) {
        return kIOReturnBadArgument;
    }
    
    IOReturn ret = kIOReturnError;
    IOHIDElementRef elementRef = [super getElement:(uint32_t)elementCookie];
    IOHIDValueRef value = _IOHIDValueCreateWithStruct(kCFAllocatorDefault,
                                                      elementRef,
                                                      eventStruct);
    
    require(elementRef && value, exit);
    
    ret = [super setValue:elementRef
                    value:value
                  timeout:0
                 callback:nil
                  context:nil
                  options:0];
    
exit:
    if (value) {
        CFRelease(value);
    }
    
    return ret;
}

static IOReturn _queryElementValue(void *iunknown,
                                   IOHIDElementCookie elementCookie,
                                   IOHIDEventStruct *valueEvent,
                                   uint32_t timeoutMS __unused,
                                   IOHIDElementCallbackFunction callback __unused,
                                   void *callbackTarget __unused,
                                   void *callbackRefcon __unused)
{
    IUnknownVTbl *vtbl = *((IUnknownVTbl**)iunknown);
    IOHIDObsoleteDeviceClass *me = (__bridge id)vtbl->_reserved;
    
    return [me getElementValue:elementCookie
                         value:valueEvent
                       options:kHIDGetElementValueForcePoll];
}

static IOReturn _startAllQueues(void *iunknown)
{
    IUnknownVTbl *vtbl = *((IUnknownVTbl**)iunknown);
    IOHIDObsoleteDeviceClass *me = (__bridge id)vtbl->_reserved;
    
    return [me->_queue start];
}

static IOReturn _stopAllQueues(void *iunknown)
{
    IUnknownVTbl *vtbl = *((IUnknownVTbl**)iunknown);
    IOHIDObsoleteDeviceClass *me = (__bridge id)vtbl->_reserved;
    
    return [me->_queue stop];
}

static IOHIDQueueInterface **_allocQueue(void *iunknown)
{
    IUnknownVTbl *vtbl = *((IUnknownVTbl**)iunknown);
    IOHIDObsoleteDeviceClass *me = (__bridge id)vtbl->_reserved;
    
    return [me allocQueue];
}

- (IOHIDQueueInterface **)allocQueue
{
    IOHIDQueueInterface **queue = NULL;
    
    [self queryInterface:CFUUIDGetUUIDBytes(kIOHIDQueueInterfaceID)
            outInterface:(LPVOID *)&queue];
    
    return queue;
}

static IOHIDOutputTransactionInterface **_allocOutputTransaction(void *iunknown)
{
    IUnknownVTbl *vtbl = *((IUnknownVTbl**)iunknown);
    IOHIDObsoleteDeviceClass *me = (__bridge id)vtbl->_reserved;
    
    return [me allocOutputTransaction];
}

- (IOHIDOutputTransactionInterface **)allocOutputTransaction
{
    IOHIDOutputTransactionInterface **transaction = NULL;
    
    [self queryInterface:CFUUIDGetUUIDBytes(kIOHIDOutputTransactionInterfaceID)
            outInterface:(LPVOID *)&transaction];
    
    return transaction;
}

static IOReturn _setReport(void *iunknown,
                           IOHIDReportType reportType,
                           uint32_t reportID,
                           void *reportBuffer,
                           uint32_t reportBufferSize,
                           uint32_t timeoutMS,
                           IOHIDReportCallbackFunction callback __unused,
                           void *callbackTarget __unused,
                           void *callbackRefcon __unused)
{
    IUnknownVTbl *vtbl = *((IUnknownVTbl**)iunknown);
    IOHIDObsoleteDeviceClass *me = (__bridge id)vtbl->_reserved;
    
    return [me setReport:reportType
                reportID:reportID
                  report:reportBuffer
            reportLength:reportBufferSize
                 timeout:timeoutMS
                callback:nil
                 context:nil
                 options:0];
}

static IOReturn _getReport(void *iunknown,
                           IOHIDReportType reportType,
                           uint32_t reportID,
                           void *reportBuffer,
                           uint32_t *reportBufferSize,
                           uint32_t timeoutMS,
                           IOHIDReportCallbackFunction callback __unused,
                           void *callbackTarget __unused,
                           void *callbackRefcon __unused)
{
    IUnknownVTbl *vtbl = *((IUnknownVTbl**)iunknown);
    IOHIDObsoleteDeviceClass *me = (__bridge id)vtbl->_reserved;
    IOReturn ret = kIOReturnError;
    CFIndex reportLength = reportBufferSize ? *reportBufferSize : 0;
    
    ret = [me getReport:reportType
               reportID:reportID
                 report:reportBuffer
           reportLength:&reportLength
                timeout:timeoutMS
               callback:nil
                context:nil
                options:0];
    
    if (reportBufferSize) {
        *reportBufferSize = (uint32_t)reportLength;
    }
    
    return ret;
}

static IOReturn _copyMatchingElements(void *iunknown,
                                      CFDictionaryRef matchingDict,
                                      CFArrayRef *elements)
{
    IUnknownVTbl *vtbl = *((IUnknownVTbl**)iunknown);
    IOHIDObsoleteDeviceClass *me = (__bridge id)vtbl->_reserved;
    
    return [me copyMatchingElements:matchingDict element:elements];
}

- (IOReturn)copyMatchingElements:(CFDictionaryRef)matchingDict
                         element:(CFArrayRef *)elements
{
    return [super copyMatchingElements:(__bridge NSDictionary *)matchingDict
                             elements:elements
                              options:kHIDCopyMatchingElementsDictionary];
}

static void inputReportCallback(void *context,
                                IOReturn result,
                                void *sender,
                                IOHIDReportType type __unused,
                                uint32_t reportID __unused,
                                uint8_t *report __unused,
                                CFIndex reportLength)
{
    IOHIDObsoleteDeviceClass *me = (__bridge id)context;
    
    if (me->_reportCallback) {
        (*me->_reportCallback)(me->_reportCallbackTarget,
                               result,
                               me->_reportCallbackRefcon,
                               sender,
                               (uint32_t)reportLength);
    }
}

static IOReturn _setInterruptReportHandlerCallback(void *iunknown,
                                                   void *reportBuffer,
                                                   uint32_t reportBufferSize,
                                                   IOHIDReportCallbackFunction callback,
                                                   void *callbackTarget,
                                                   void *callbackRefcon)
{
    IUnknownVTbl *vtbl = *((IUnknownVTbl**)iunknown);
    IOHIDObsoleteDeviceClass *me = (__bridge id)vtbl->_reserved;
    
    return [me setInterruptReportHandlerCallback:reportBuffer
                                reportBufferSize:reportBufferSize
                                        callback:callback
                                  callbackTarget:callbackTarget
                                  callbackRefcon:callbackRefcon];
}

- (IOReturn)setInterruptReportHandlerCallback:(void *)reportBuffer
                             reportBufferSize:(uint32_t)reportBufferSize
                                     callback:(IOHIDReportCallbackFunction)callback
                               callbackTarget:(void *)callbackTarget
                               callbackRefcon:(void *)callbackRefcon
{
    _reportCallbackTarget = callbackTarget;
    _reportCallbackRefcon = callbackRefcon;
    _reportCallback = callback;
    
    return [self setInputReportCallback:(uint8_t *)reportBuffer
                           reportLength:reportBufferSize
                               callback:inputReportCallback
                                context:(__bridge void *)self
                                options:0];
}

- (IOReturn)start:(NSDictionary *)properties
          service:(io_service_t)service
{
    [super start:properties service:service];
    [self initQueue];
    
    return kIOReturnSuccess;
}

- (instancetype)init
{
    self = [super init];
    
    if (!self) {
        return nil;
    }
    
    _interface = (IOHIDDeviceInterface122 *)malloc(sizeof(*_interface));
    
    *_interface = (IOHIDDeviceInterface122) {
        // IUNKNOWN_C_GUTS
        ._reserved = (__bridge void *)self,
        .QueryInterface = self->_vtbl->QueryInterface,
        .AddRef = self->_vtbl->AddRef,
        .Release = self->_vtbl->Release,
        
        // IOHIDDeviceInterface122
        .createAsyncEventSource = _createAsyncEventSource,
        .getAsyncEventSource = _getAsyncEventSource,
        .createAsyncPort = _createAsyncPort,
        .getAsyncPort = _getAsyncPort,
        .open = _open,
        .close = _close,
        .setRemovalCallback = _setRemovalCallback,
        .getElementValue = _getElementValue,
        .setElementValue = _setElementValue,
        .queryElementValue = _queryElementValue,
        .startAllQueues = _startAllQueues,
        .stopAllQueues = _stopAllQueues,
        .allocQueue = _allocQueue,
        .allocOutputTransaction = _allocOutputTransaction,
        .setReport = _setReport,
        .getReport = _getReport,
        .copyMatchingElements = _copyMatchingElements,
        .setInterruptReportHandlerCallback = _setInterruptReportHandlerCallback
    };
    
    _notifyPort = IONotificationPortCreate(kIOMasterPortDefault);
    IONotificationPortSetDispatchQueue(_notifyPort, dispatch_get_main_queue());
    
    return self;
}

- (void)dealloc
{
    free(_interface);
    
    if (_notifyPort) {
        IONotificationPortDestroy(_notifyPort);
    }
    
    if (_notification) {
        IOObjectRelease(_notification);
    }
}

@end