HIDUserDevice.m   [plain text]


/*
*
* @APPLE_LICENSE_HEADER_START@
*
* Copyright (c) 2019 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 "HIDUserDevice.h"
#import <IOKit/hid/IOHIDUserDevice.h>
#import "NSError+IOReturn.h"
#import <os/assumes.h>

NSString * const kHIDUserDevicePropertyCreateInactiveKey = @"HIDUserDeviceCreateInactive";

@implementation HIDUserDevice {
    IOHIDUserDeviceRef              _device;
    HIDUserDeviceGetReportHandler   _getReportHandler;
    HIDUserDeviceSetReportHandler   _setReportHandler;
    io_service_t                    _service;
    HIDBlock                        _cancelHandler;
}

- (instancetype)initWithProperties:(NSDictionary *)properties
{
    IOOptionBits options = 0;
    
    self = [super init];
    
    if (!self) {
        return self;
    }
    
    if (properties[kHIDUserDevicePropertyCreateInactiveKey] && [properties[kHIDUserDevicePropertyCreateInactiveKey] isEqual:@YES]) {
        options |= kIOHIDUserDeviceCreateOptionStartWhenScheduled;
    }
    
    _device = IOHIDUserDeviceCreateWithOptions(kCFAllocatorDefault,
                                               (__bridge CFDictionaryRef)properties,
                                               options
                                               );
    if (!_device) {
        return nil;
    }
    
    _service = IOHIDUserDeviceCopyService(_device);
    
    return self;
}

- (void)dealloc
{
    if (_service) {
        IOObjectRelease(_service);
    }
    if (_device) {
        CFRelease(_device);
    }
}

- (NSString *)description {
    return [NSString stringWithFormat:@"%@", _device];
}

- (id)propertyForKey:(NSString *)key
{
    return (id)CFBridgingRelease(IOHIDUserDeviceCopyProperty(
                                                    _device,
                                                    (__bridge CFStringRef)key));
}

- (BOOL)setProperty:(id)value forKey:(NSString *)key
{
    return IOHIDUserDeviceSetProperty(_device,
                                      (__bridge CFStringRef)key,
                                      (__bridge CFTypeRef)value);
}

- (void)setCancelHandler:(HIDBlock)handler
{
    _cancelHandler = handler;
}

- (void)setDispatchQueue:(dispatch_queue_t)queue
{
    IOHIDUserDeviceSetDispatchQueue(_device, queue);
}

- (void)activate
{
    IOHIDUserDeviceSetCancelHandler(_device, ^{
        // Block captures reference to self while cancellation hasn't completed.
        if (self->_cancelHandler) {
            self->_cancelHandler();
            self->_cancelHandler = nil;
        }
    });

    IOHIDUserDeviceActivate(_device);
}

- (void)cancel
{
    IOHIDUserDeviceCancel(_device);
}

static IOReturn getReportCallback(void *refcon, IOHIDReportType type,
                                  uint32_t reportID, uint8_t *report,
                                  CFIndex *reportLength)
{
    NSInteger length = (NSInteger)*reportLength;
    IOReturn ret;
    HIDUserDevice *me = (__bridge HIDUserDevice *)refcon;
    
    ret = (me->_getReportHandler)((HIDReportType)type,
                                  reportID,
                                  (void *)report,
                                  &length);
    
    *reportLength = (CFIndex)MIN(*reportLength, length);
    
    return ret;
}

- (void)setGetReportHandler:(HIDUserDeviceGetReportHandler)handler
{
    os_assert(!_getReportHandler, "Get report handler already set");
    _getReportHandler = handler;
    
    IOHIDUserDeviceRegisterGetReportWithReturnLengthCallback(
                                                        _device,
                                                        getReportCallback,
                                                        (__bridge void *)self);
}

static IOReturn setReportCallback(void *refcon, IOHIDReportType type,
                                  uint32_t reportID, uint8_t *report,
                                  CFIndex reportLength)
{
    HIDUserDevice *me = (__bridge HIDUserDevice *)refcon;
    
    return (me->_setReportHandler)((HIDReportType)type,
                                   reportID,
                                   (const void *)report,
                                   (NSInteger)reportLength);
}

- (void)setSetReportHandler:(HIDUserDeviceSetReportHandler)handler
{
    os_assert(!_setReportHandler, "Set report handler already set");
    _setReportHandler = handler;
    IOHIDUserDeviceRegisterSetReportCallback(_device,
                                             setReportCallback,
                                             (__bridge void *)self);
}

- (BOOL)handleReport:(NSData *)report
               error:(out NSError **)outError
{
    IOReturn ret = IOHIDUserDeviceHandleReport(_device,
                                               (uint8_t *)[report bytes],
                                               [report length]);
    
    if (ret != kIOReturnSuccess && outError) {
        *outError = [NSError errorWithIOReturn:ret];
    }
    
    return (ret == kIOReturnSuccess);
}

- (BOOL)handleReport:(NSData *)report
       withTimestamp:(uint64_t)timestamp
               error:(out NSError **)outError
{
    IOReturn ret = IOHIDUserDeviceHandleReportWithTimeStamp(_device,
                                                            timestamp,
                                                            (uint8_t *)[report bytes],
                                                            [report length]);

    if (ret != kIOReturnSuccess && outError) {
        *outError = [NSError errorWithIOReturn:ret];
    }

    return (ret == kIOReturnSuccess);
}

- (io_service_t)service
{
    return _service;
}

@end