IOHIDUserDeviceTest.c [plain text]
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/hid/IOHIDUserDevice.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <AssertMacros.h>
#include "IOHIDReportDescriptorParser.h"
typedef struct GenericLEDKeyboardDescriptor {
UInt8 devUsagePageOp;
UInt8 devUsagePageNum;
UInt8 devUsageOp;
UInt8 devUsageNum;
UInt8 appCollectionOp;
UInt8 appCollectionNum;
UInt8 modUsagePageOp;
UInt8 modUsagePageNum;
UInt8 modUsageMinOp;
UInt8 modUsageMinNum;
UInt8 modUsageMaxOp;
UInt8 modUsageMaxNum;
UInt8 modLogMinOp;
UInt8 modLogMinNum;
UInt8 modLogMaxOp;
UInt8 modLogMaxNum;
UInt8 modRptCountOp;
UInt8 modRptCountNum;
UInt8 modRptSizeOp;
UInt8 modRptSizeNum;
UInt8 modInputOp;
UInt8 modInputNum;
UInt8 rsrvCountOp;
UInt8 rsrvCountNum;
UInt8 rsrvSizeOp;
UInt8 rsrvSizeNum;
UInt8 rsrvInputOp;
UInt8 rsrvInputNum;
UInt8 ledRptCountOp;
UInt8 ledRptCountNum;
UInt8 ledRptSizeOp;
UInt8 ledRptSizeNum;
UInt8 ledUsagePageOp;
UInt8 ledUsagePageNum;
UInt8 ledUsageMinOp;
UInt8 ledUsageMinNum;
UInt8 ledUsageMaxOp;
UInt8 ledUsageMaxNum;
UInt8 ledInputOp;
UInt8 ledInputNum;
UInt8 fillRptCountOp;
UInt8 fillRptCountNum;
UInt8 fillRptSizeOp;
UInt8 fillRptSizeNum;
UInt8 fillInputOp;
UInt8 fillInputNum;
UInt8 keyRptCountOp;
UInt8 keyRptCountNum;
UInt8 keyRptSizeOp;
UInt8 keyRptSizeNum;
UInt8 keyLogMinOp;
UInt8 keyLogMinNum;
UInt8 keyLogMaxOp;
UInt16 keyLogMaxNum;
UInt8 keyUsagePageOp;
UInt8 keyUsagePageNum;
UInt8 keyUsageMinOp;
UInt8 keyUsageMinNum;
UInt8 keyUsageMaxOp;
UInt8 keyUsageMaxNum;
UInt8 keyInputOp;
UInt8 keyInputNum;
UInt8 appCollectionEnd;
} GenericLEDKeyboardDescriptor;
typedef struct GenericKeyboardRpt {
UInt8 modifiers;
UInt8 reserved;
UInt8 keys[6];
} GenericKeyboardRpt;
static UInt8 gGenLEDKeyboardDesc[] = {
0x05, 0x01,
0x09, 0x06,
0xA1, 0x01,
0x05, 0x07,
0x19, 0xe0,
0x29, 0xe7,
0x15, 0x00,
0x25, 0x01,
0x75, 0x01,
0x95, 0x08,
0x81, 0x02,
0x95, 0x01,
0x75, 0x08,
0x81, 0x01,
0x95, 0x02,
0x75, 0x01,
0x05, 0x08,
0x19, 0x01,
0x29, 0x02,
0x91, 0x02,
0x95, 0x01,
0x75, 0x06,
0x91, 0x01,
0x95, 0x06,
0x75, 0x08,
0x15, 0x00,
0x26, 0xff, 0x00,
0x05, 0x07,
0x19, 0x00,
0x29, 0xff,
0x81, 0x00,
0xC0
};
static IOHIDUserDeviceRef gDevice = NULL;
static pthread_mutex_t gMuxtex = PTHREAD_MUTEX_INITIALIZER;
static void printReport(uint8_t * report, CFIndex reportLength, bool rcv)
{
int index;
printf("%s report: reportLength=%ld: ", rcv ? "Received" : "Dispatching", reportLength);
for (index=0; index<reportLength; index++)
printf("%02x ", report[index]);
printf("\n");
}
#define kKeyboardInterval 0.008
static void dispatchKeyboardEvent(char c)
{
GenericKeyboardRpt report;
static CFAbsoluteTime sDeadline = 0;
CFAbsoluteTime delta;
bzero(&report, sizeof(report));
if ( c < 'a' || c > 'z' )
return;
printf("dispatching keyboard event for '%c'\n", c);
pthread_mutex_lock(&gMuxtex);
delta = sDeadline - CFAbsoluteTimeGetCurrent();
if ( delta > 0 )
usleep(delta*1000000);
report.keys[0] = 4 + c - 97;
printReport((uint8_t*)&report, sizeof(report), 0);
IOHIDUserDeviceHandleReport(gDevice, (uint8_t*)&report, sizeof(report));
usleep(kKeyboardInterval*1000000);
report.keys[0] = 0;
printReport((uint8_t*)&report, sizeof(report), 0);
IOHIDUserDeviceHandleReport(gDevice, (uint8_t*)&report, sizeof(report));
sDeadline = CFAbsoluteTimeGetCurrent() + kKeyboardInterval;
pthread_mutex_unlock(&gMuxtex);
}
static void * getKeyboardCharThread(void *arg)
{
printf("This virtual keyboard supports dispatching typed characters within the range of 'a' - 'z'\n");
while (1) {
dispatchKeyboardEvent(getchar());
}
return arg;
}
static void * getReportCharThread(void *arg)
{
char str[256];
printf("Please enter report data: 00 11 22 33 ...\n");
while (1) {
bzero(str, sizeof(str));
if ( fgets(str, sizeof(str), stdin) ) {
CFStringRef rawString = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, (uint8_t *)str, sizeof(str), kCFStringEncodingMacRoman, false, kCFAllocatorNull);
if ( rawString ) {
CFArrayRef rawArray = CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault, rawString, CFSTR("\n"));
if ( rawArray ) {
if ( CFArrayGetCount(rawArray) > 1 ) {
CFArrayRef array = CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault, CFArrayGetValueAtIndex(rawArray, 0), CFSTR(" "));
if ( array ) {
uint8_t report[64] = {};
uint32_t reportIndex = 0;
for ( int index=0; index<CFArrayGetCount(array) && reportIndex<sizeof(report); index++) {
CFStringRef substring = (CFStringRef)CFArrayGetValueAtIndex(array, index);
const char * substr;
if ( !substring )
continue;
substr = CFStringGetCStringPtr(substring, kCFStringEncodingMacRoman);
if ( !substr )
continue;
report[reportIndex++] = strtoul(substr, NULL, 16);
}
pthread_mutex_lock(&gMuxtex);
printReport(report, reportIndex, 0);
IOHIDUserDeviceHandleReport(gDevice, report, reportIndex);
pthread_mutex_unlock(&gMuxtex);
CFRelease(array);
}
}
CFRelease(rawArray);
}
CFRelease(rawString);
}
}
}
return arg;
}
static IOReturn setReportCallback(void * refcon __unused, IOHIDReportType type __unused, uint32_t reportID __unused, uint8_t * report, CFIndex reportLength)
{
printReport(report, reportLength, 1);
return kIOReturnSuccess;
}
typedef void * (*UserInputCallback)(void * refcon);
static void startDevice(CFMutableDictionaryRef properties, const uint8_t * descriptor, uint32_t descriptorLength, UserInputCallback userInputCallback, IOHIDUserDeviceReportCallback outputReportCallback)
{
CFDataRef descriptorData = NULL;
descriptorData = CFDataCreate(kCFAllocatorDefault, descriptor, descriptorLength);
require(descriptorData, finish);
require(properties, finish);
CFDictionarySetValue(properties, CFSTR(kIOHIDReportDescriptorKey), descriptorData);
gDevice = IOHIDUserDeviceCreate(kCFAllocatorDefault, properties);
require(gDevice, finish);
IOHIDUserDeviceScheduleWithRunLoop(gDevice, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
if ( outputReportCallback )
IOHIDUserDeviceRegisterSetReportCallback(gDevice, outputReportCallback, NULL);
if ( userInputCallback ) {
pthread_t tid;
pthread_attr_t attr;
assert(!pthread_attr_init(&attr));
assert(!pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED));
assert(!pthread_create( &tid, &attr, userInputCallback, NULL));
assert(!pthread_attr_destroy(&attr));
}
CFRunLoopRun();
finish:
if ( gDevice )
CFRelease(gDevice);
if ( descriptorData )
CFRelease(descriptorData);
}
static void printHelp()
{
printf("\n");
printf("hidUserDeviceTest usage:\n\n");
printf("\t-d <descriptor data: 00 11 22...>\t: create device with descriptor data\n");
printf("\t-p Parse descriptor data\n");
printf("\t-k Create generic keyboard device\n");
printf("\t--vid <vendor id>\n");
printf("\t--pid <product id>\n");
printf("\n");
}
int main (int argc, const char * argv[])
{
bool handled = false;
bool parse = false;
uint8_t * data = NULL;
uint32_t dataSize = 0;
uint32_t dataIndex = 0;
UserInputCallback userInputCallback = NULL;
IOHIDUserDeviceReportCallback outputReportCallback = NULL;
CFMutableDictionaryRef properties = NULL;
properties = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
for ( int argi=1; argi<argc; argi++ ) {
if ( argv[argi][0] == '-' ) {
if ( !strcmp("-k", argv[argi] ) ) {
if ( data ) {
free(data);
}
dataSize = sizeof(gGenLEDKeyboardDesc);
data = malloc(dataSize);
userInputCallback = getKeyboardCharThread;
outputReportCallback = setReportCallback;
handled = true;
bcopy(gGenLEDKeyboardDesc, data, dataSize);
dataIndex = dataSize;
}
else if ( !strcmp("-d", argv[argi]) ) {
if ( !data ) {
dataSize = argc-argi+1;
data = malloc(dataSize);
}
userInputCallback = getReportCharThread;
outputReportCallback = setReportCallback;
handled = true;
}
else if ( !strcmp("-p", argv[argi]) ) {
parse = true;
if ( !data ) {
dataSize = argc-argi+1;
data = malloc(argc-argi+1);
}
}
else if ( !strcmp("--vid", argv[argi]) && (argi+1) < argc) {
int value = strtol(argv[++argi], NULL, 10);
CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &value);
if ( number ) {
CFDictionarySetValue(properties, CFSTR(kIOHIDVendorIDKey), number);
CFRelease(number);
}
}
else if ( !strcmp("--pid", argv[argi]) && (argi+1) < argc) {
int value = strtol(argv[++argi], NULL, 10);
CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &value);
if ( number ) {
CFDictionarySetValue(properties, CFSTR(kIOHIDProductIDKey), number);
CFRelease(number);
}
}
}
else if ( data && dataIndex < dataSize ) {
data[dataIndex++] = strtoul(argv[argi], NULL, 16);
}
}
if ( data ) {
if ( parse )
PrintHIDReport(data, dataSize);
if ( handled ) {
startDevice(properties, data, dataIndex, userInputCallback, outputReportCallback);
}
free(data);
} else {
printHelp();
}
if ( properties )
CFRelease(properties);
return 0;
}