HIDFrameworkExamples.m [plain text]
//
// HIDFrameworkExamples.m
//
//
// Created by dekom on 11/11/17.
// Copyright © 2017 Apple. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <HID/HID.h>
static uint8_t kbdDescriptor[] = {
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x06, // Usage (Keyboard)
0xA1, 0x01, // Collection (Application)
0x05, 0x07, // Usage Page (Keyboard/Keypad)
0x19, 0xE0, // Usage Minimum........... (224)
0x29, 0xE7, // Usage Maximum........... (231)
0x15, 0x00, // Logical Minimum......... (0)
0x25, 0x01, // Logical Maximum......... (1)
0x75, 0x01, // Report Size............. (1)
0x95, 0x08, // Report Count............ (8)
0x81, 0x02, // Input...................(Data, Variable, Absolute)
0x95, 0x01, // Report Count............ (1)
0x75, 0x08, // Report Size............. (8)
0x81, 0x01, // Input...................(Constant)
0x05, 0x08, // Usage Page (LED)
0x19, 0x01, // Usage Minimum........... (1)
0x29, 0x05, // Usage Maximum........... (5)
0x95, 0x05, // Report Count............ (5)
0x75, 0x01, // Report Size............. (1)
0x91, 0x02, // Output..................(Data, Variable, Absolute)
0x95, 0x01, // Report Count............ (1)
0x75, 0x03, // Report Size............. (3)
0x91, 0x01, // Output..................(Constant)
0x05, 0x07, // Usage Page (Keyboard/Keypad)
0x19, 0x00, // Usage Minimum........... (0)
0x2A, 0xFF, 0x00, // Usage Maximum........... (255)
0x95, 0x05, // Report Count............ (5)
0x75, 0x08, // Report Size............. (8)
0x15, 0x00, // Logical Minimum......... (0)
0x26, 0xFF, 0x00, // Logical Maximum......... (255)
0x81, 0x00, // Input...................(Data, Array, Absolute)
0xC0, // End Collection
};
typedef struct __attribute__((packed))
{
uint8_t leftControl : 1;
uint8_t leftShift : 1;
uint8_t leftAlt : 1;
uint8_t leftGUI : 1;
uint8_t rightControl : 1;
uint8_t rightShift : 1;
uint8_t rightAlt : 1;
uint8_t rightGUI : 1;
uint8_t : 8;
uint8_t keys[5];
} kbdInputReport;
NSInteger _kbdInputReportLength = sizeof(kbdInputReport); // 7 bytes
typedef struct __attribute__((packed))
{
uint8_t numLock : 1;
uint8_t capsLock : 1;
uint8_t scrollLock : 1;
uint8_t compose : 1;
uint8_t kana : 1;
uint8_t : 3;
} kbdOutputReport;
NSInteger _kbdOutReportLength = sizeof(kbdOutputReport); // 1 bytes
dispatch_queue_t _dispatchQueue;
NSString *_uniqueID;
HIDUserDevice *_userDevice;
HIDDevice *_device;
HIDElement *_ledElement;
HIDManager *_manager;
HIDEventSystemClient *_client;
HIDServiceClient *_serviceClient;
void createUserDevice() {
NSData *descriptor = [[NSData alloc] initWithBytes:kbdDescriptor
length:sizeof(kbdDescriptor)];
NSMutableDictionary *properties = [[NSMutableDictionary alloc] init];
properties[@kIOHIDReportDescriptorKey] = descriptor;
properties[@kIOHIDPhysicalDeviceUniqueIDKey] = _uniqueID;
_userDevice = [[HIDUserDevice alloc] initWithProperties:properties];
assert(_userDevice);
[_userDevice setGetReportHandler:^IOReturn(IOHIDReportType type,
uint32_t reportID,
uint8_t *report,
NSUInteger *reportLength) {
kbdOutputReport outReport = { 0 };
assert(type == kIOHIDReportTypeOutput);
outReport.capsLock = 1;
bcopy((const void *)&outReport, report, _kbdOutReportLength);
NSData *reportData = [[NSData alloc] initWithBytes:report
length:*reportLength];
NSLog(@"HIDUserDevice getReport type: type, reportID, reportData);
return kIOReturnSuccess;
}];
[_userDevice setSetReportHandler:^IOReturn(IOHIDReportType type,
uint32_t reportID,
uint8_t *report,
NSUInteger reportLength) {
kbdOutputReport *outReport;
assert(type == kIOHIDReportTypeOutput);
NSData *reportData = [[NSData alloc] initWithBytes:report
length:reportLength];
outReport = (kbdOutputReport *)[reportData bytes];
assert(outReport->capsLock == 1);
NSLog(@"HIDUserDevice setReport type: type, reportID, reportData);
return kIOReturnSuccess;
}];
[_userDevice setCancelHandler:^{
NSLog(@"HIDUserDevice cancel handler called");
_userDevice = nil;
}];
[_userDevice setDispatchQueue:_dispatchQueue];
// all handler / matching calls should happene before activate
[_userDevice activate];
// allow some time for enumeration
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, false);
}
void destroyUserDevice() {
[_userDevice cancel];
// allow some time for cancellation
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, false);
assert(_userDevice == nil);
}
void createDevice() {
NSDictionary *matching = nil;
_device = [[HIDDevice alloc] initWithService:_userDevice.service];
assert(_device);
// Find caps lock LED element. This will be an output element type
// so we can call set/getReport against it.
matching = @{ @kIOHIDElementUsagePageKey : @(kHIDPage_LEDs),
@kIOHIDElementUsageKey : @(kHIDUsage_LED_CapsLock) };
_ledElement = [[_device copyMatchingElements:matching] objectAtIndex:0];
assert(_ledElement);
// match against caps lock key. This will be an input element type
// so we can use it with the input element handler.
matching = @{ @kIOHIDElementUsagePageKey : @(kHIDPage_KeyboardOrKeypad),
@kIOHIDElementUsageKey : @(kHIDUsage_KeyboardCapsLock) };
[_device setInputElementMatching:matching];
[_device setInputElementHandler:^(HIDDevice *sender, HIDElement *element)
{
NSLog(@"HIDDevice input element sender: sender, element);
assert(element.usagePage == kHIDPage_KeyboardOrKeypad &&
element.usage == kHIDUsage_KeyboardCapsLock);
}];
[_device setCancelHandler:^{
NSLog(@"HIDDevice cancel handler called");
_device = nil;
}];
[_device setRemovalHandler:^{
NSLog(@"HIDDevice removal handler called");
}];
[_device setInputReportHandler:^(HIDDevice *sender,
IOHIDReportType type,
uint32_t reportID,
uint8_t *report,
NSUInteger reportLength)
{
kbdInputReport *inReport;
NSData *reportData = [[NSData alloc] initWithBytes:report
length:reportLength];
inReport = (kbdInputReport *)[reportData bytes];
NSLog(@"HIDDevice report sender: sender, type, reportID, reportData);
assert(reportLength == 7 &&
inReport->keys[0] == kHIDUsage_KeyboardCapsLock);
}];
[_device setDispatchQueue:_dispatchQueue];
[_device open];
// all handler / matching calls should happene before activate
[_device activate];
}
void destroyDevice() {
[_device close];
[_device cancel];
// allow some time for cancellation
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, false);
assert(_device == nil);
}
void createManager() {
NSDictionary *matching = nil;
_manager = [[HIDManager alloc] init];
assert(_manager);
// match against caps lock key. This will be an input element type
// so we can use it with the input element handler.
matching = @{ @kIOHIDElementUsagePageKey : @(kHIDPage_KeyboardOrKeypad),
@kIOHIDElementUsageKey : @(kHIDUsage_KeyboardCapsLock) };
[_manager setInputElementMatching:matching];
[_manager setInputElementHandler:^(HIDDevice *sender, HIDElement *element)
{
NSLog(@"HIDManager input element sender: sender, element);
assert(element.usagePage == kHIDPage_KeyboardOrKeypad &&
element.usage == kHIDUsage_KeyboardCapsLock);
}];
matching = @{ @kIOHIDPhysicalDeviceUniqueIDKey : _uniqueID };
[_manager setDeviceMatching:matching];
[_manager setDeviceNotificationHandler:^(HIDDevice * _Nonnull device,
bool added)
{
NSString *deviceID = nil;
NSLog(@"HIDManager device notification device: device, added);
deviceID = [_device getProperty:@kIOHIDPhysicalDeviceUniqueIDKey];
assert(deviceID && [deviceID isEqualToString:_uniqueID]);
}];
[_manager setInputReportHandler:^(HIDDevice *sender,
IOHIDReportType type,
uint32_t reportID,
uint8_t *report,
NSUInteger reportLength)
{
kbdInputReport *inReport;
NSData *reportData = [[NSData alloc] initWithBytes:report
length:reportLength];
inReport = (kbdInputReport *)[reportData bytes];
NSLog(@"HIDManager report sender: sender, type, reportID, reportData);
assert(reportLength == 7 &&
inReport->keys[0] == kHIDUsage_KeyboardCapsLock);
}];
[_manager setCancelHandler:^{
NSLog(@"HIDManager cancel handler called");
_manager = nil;
}];
[_manager setDispatchQueue:_dispatchQueue];
[_manager open];
// all handler / matching calls should happene before activate
[_manager activate];
}
void destroyManager() {
[_manager close];
[_manager cancel];
// allow some time for cancellation
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, false);
assert(_manager == nil);
}
void createClient() {
_client = [[HIDEventSystemClient alloc] initWithType:kHIDEventSystemClientTypeMonitor];
assert(_client);
NSDictionary *matching = @{ @kIOHIDPhysicalDeviceUniqueIDKey : _uniqueID };
[_client setMatching:matching];
[_client setCancelHandler:^{
NSLog(@"Client cancel handler called");
_client = nil;
}];
[_client setDispatchQueue:_dispatchQueue];
[_client setEventHandler:^(HIDServiceClient *service, HIDEvent *event) {
NSLog(@"Received event:
NSLog(@"type: (long)[event getIntegerField:kIOHIDEventFieldKeyboardUsage],
(long)[event getIntegerField:kIOHIDEventFieldKeyboardUsagePage],
(long)[event getIntegerField:kIOHIDEventFieldKeyboardDown]);
}];
[_client setResetHandler:^{
NSLog(@"Client reset handler called");
}];
[_client setEventFilterHandler:^BOOL(HIDServiceClient *service,
HIDEvent *event) {
bool filter = false;
if (event.type == kIOHIDEventTypeNULL) {
filter = true;
}
NSLog(@"Client filter handler called for event: event, service);
return filter;
}];
_serviceClient = nil;
[_client setServiceNotificationHandler:^(HIDServiceClient *service) {
NSLog(@"Received service added notification");
_serviceClient = service;
[_serviceClient setRemovalHandler:^{
NSLog(@"Received service removal notification");
_serviceClient = nil;
}];
}];
// all handler / matching calls should happene before activate
[_client activate];
}
void destroyClient() {
[_client cancel];
// allow some time for cancellation
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, false);
assert(_client == nil);
}
void testDevice() {
NSLog(@"+ Test Device");
createUserDevice();
createDevice();
assert([_device conformsTo:kHIDPage_GenericDesktop
usage:kHIDUsage_GD_Keyboard]);
NSString *deviceID = [_device getProperty:@kIOHIDPhysicalDeviceUniqueIDKey];
assert(deviceID && [deviceID isEqualToString:_uniqueID]);
// test set/getReport
kbdOutputReport outReport = { 0 };
outReport.capsLock = 1;
IOReturn ret = [_device setReport:kIOHIDReportTypeOutput
reportID:0
report:(uint8_t *)&outReport
reportLength:_kbdOutReportLength];
assert(ret == kIOReturnSuccess);
outReport.capsLock = 0;
ret = [_device getReport:kIOHIDReportTypeOutput
reportID:0
report:(uint8_t *)&outReport
reportLength:&_kbdOutReportLength];
assert(ret == kIOReturnSuccess && outReport.capsLock == 1);
// test set/get element
_ledElement.integerValue = 1;
// this will call setReport
ret = [_device setElement:_ledElement];
assert(ret == kIOReturnSuccess);
_ledElement.integerValue = 0;
ret = [_device updateElement:_ledElement options:0];
assert(ret == kIOReturnSuccess && _ledElement.integerValue == 1);
// test input reports / input elements
kbdInputReport inReport = { 0 };
inReport.keys[0] = kHIDUsage_KeyboardCapsLock;
[_userDevice handleReport:(uint8_t *)&inReport
reportLength:_kbdInputReportLength];
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, false);
destroyUserDevice();
destroyDevice();
NSLog(@"- Test Device");
}
void testManager() {
NSLog(@"+ Test Manager");
createUserDevice();
createDevice();
createManager();
NSArray *devices = [_manager copyDevices];
assert(devices && devices.count == 1);
HIDDevice *device = [devices objectAtIndex:0];
NSString *deviceID = [device getProperty:@kIOHIDPhysicalDeviceUniqueIDKey];
assert(deviceID && [deviceID isEqualToString:_uniqueID]);
// test input reports / input elements
kbdInputReport inReport = { 0 };
inReport.keys[0] = kHIDUsage_KeyboardCapsLock;
[_userDevice handleReport:(uint8_t *)&inReport
reportLength:_kbdInputReportLength];
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, false);
destroyUserDevice();
destroyDevice();
destroyManager();
NSLog(@"- Test Manager");
}
void testTransaction() {
NSLog(@"+ Test Transaction");
HIDTransaction *transaction = nil;
createUserDevice();
createDevice();
transaction = [[HIDTransaction alloc] initWithDevice:_device];
assert(transaction);
_ledElement.integerValue = 1;
// test output transaction
transaction.direction = kIOHIDTransactionDirectionTypeOutput;
// this will call setReport
IOReturn ret = [transaction commit:[NSArray arrayWithObject:_ledElement]];
assert(ret == kIOReturnSuccess);
// test input transaction
transaction.direction = kIOHIDTransactionDirectionTypeInput;
_ledElement.integerValue = 0;
// this will call getReport
ret = [transaction commit:[NSArray arrayWithObject:_ledElement]];
assert(ret == kIOReturnSuccess && _ledElement.integerValue == 1);
destroyDevice();
destroyUserDevice();
NSLog(@"- Test Transaction");
}
void testClient() {
NSString *uniqueID;
NSLog(@"+ Test Client");
createClient();
createUserDevice();
assert(_serviceClient && _serviceClient.serviceID != 0);
uniqueID = [_serviceClient copyProperty:@kIOHIDPhysicalDeviceUniqueIDKey];
assert([uniqueID isEqualToString:_uniqueID]);
assert([_serviceClient setProperty:@"TestProperty" value:@123]);
NSNumber *prop = [_serviceClient copyProperty:@"TestProperty"];
assert([prop unsignedIntValue] == 123);
assert([_client setProperty:@"TestProperty" value:@123]);
prop = [_client copyProperty:@"TestProperty"];
assert([prop unsignedIntValue] == 123);
NSArray *services = [_client copyServices];
assert(services.count == 1);
HIDServiceClient *kbdService = [services objectAtIndex:0];
assert([kbdService conformsTo:kHIDPage_GenericDesktop
usage:kHIDUsage_GD_Keyboard]);
assert([kbdService isEqualToHIDServiceClient:_serviceClient]);
kbdInputReport inReport = { 0 };
inReport.keys[0] = kHIDUsage_KeyboardCapsLock;
[_userDevice handleReport:(uint8_t *)&inReport
reportLength:_kbdInputReportLength];
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, false);
inReport.keys[0] = 0;
[_userDevice handleReport:(uint8_t *)&inReport
reportLength:_kbdInputReportLength];
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, false);
destroyUserDevice();
assert(_serviceClient == nil);
destroyClient();
NSLog(@"- Test Client");
}
int main(int argc, const char * argv[]) {
_dispatchQueue = dispatch_queue_create(0, DISPATCH_QUEUE_SERIAL);
_uniqueID = [[[NSUUID alloc] init] UUIDString];
testDevice();
testManager();
testTransaction();
testClient();
return 0;
}