monitor.m   [plain text]

//
// monitor.m
// IOHIDFamily
//
// Created by dekom on 8/2/17.
//

#include <IOKit/hid/IOHIDEventSystemClient.h>
#include <getopt.h>
#include "utility.h"
#import "HIDEvent.h"
#import <objc/runtime.h>
#import <AssertMacros.h>
#import <os/variant_private.h>

#define kOptClientType 1000
#define kOptSerialize 1001

Boolean clientTypeForString (NSString * string, IOHIDEventSystemClientType * type);
int monitor(int argc, const char * argv[]);

static const char MAIN_OPTIONS_SHORT[] = "p:cm:i:s:t:o:hv";
static const struct option MAIN_OPTIONS[] =
{
{ "predicate", required_argument, NULL, 'p' },
{ "children", no_argument, NULL, 'c' },
{ "matching", required_argument, NULL, 'm' },
{ "info", required_argument, NULL, 'i' },
{ "show", required_argument, NULL, 's' },
{ "type", required_argument, NULL, 't' },
{ "output", required_argument, NULL, 'o' },
{ "help", 0, NULL, 'h' },
{ "verbose", 0, NULL, 'v' },
{ "client", required_argument, NULL, kOptClientType },
{ "serialize", 0, NULL, kOptSerialize },
{ NULL, 0, NULL, 0 }
};

const char monitorUsage[] =
"\nMonitor HID Event System events\n"
"\nUsage:\n\n"
" hidutil monitor [ --predicate <predicate> ][ --show <varibles> ][ --type <event type> ][ --matching <matching> ][ --children ]\n"
"\nFlags:\n\n"
" -p --predicate.............filter events output based on predicate\n"
" -s --show..................show only specified variables\n"
" -t --type..................filter by event type, takes integer or string\n"
" -c --children..............show child events\n"
" -v --verbose...............print service enumerated/terminated info\n"
" -o --output................output to file\n"
" --client................event system clien type (admin, passive, ratecontrolled, simple, monitor)\n"
" --serialize.............output events in serialized binary format\n"

MATCHING_HELP
"\nExamples:\n\n"
" hidutil monitor --info keyboard\n"
" hidutil monitor --type keyboard\n"
" hidutil monitor --predicate 'typestr contains \"digit\"' --children\n"
" hidutil monitor --predicate 'usagepage == 7 and latency > 100'\n"
" hidutil monitor --show 'timestamp sender typestr usagepage usage'\n"
" hidutil monitor --matching '{\"ProductID\":0x54c,\"VendorID\":746}'\n";

static NSCompoundPredicate * _predicate = NULL;
static bool _children = false;
static bool _serialize = false;
static NSArray * _variables = NULL;
static char * _fileNameStr;
static char * _clientTypeStr;
static FILE * _file;
static const NSArray *_debugKeys;

static void printEventVariables(HIDEvent *event) {
NSString *eventString = [[NSString alloc] init];

for (NSString *variable in _variables) {
@try {
if (variable == _variables.lastObject) {
eventString = [eventString stringByAppendingFormat:@"%@:%@", variable, [event valueForKey:variable]];
} else {
eventString = [eventString stringByAppendingFormat:@"%@:%@ ", variable, [event valueForKey:variable]];
}
} @ catch(...) {
}
}

if (eventString == nil) {
fprintf(_file, "%s\n", [[event description] UTF8String]);
} else {
fprintf(_file, "%s\n", [eventString UTF8String]);
}
}

static void eventCallback(void *target __unused, void *refcon __unused, void *sender __unused, IOHIDEventRef event) {
bool predicated = false;
HIDEvent *hidEvent = (__bridge HIDEvent *)event;

@autoreleasepool {
if (_predicate) {
@try {
if (![_predicate evaluateWithObject:hidEvent]) {
predicated = true;
}
} @ catch(...) {
predicated = true;
}
}

if (!predicated) {
if (_serialize) {
NSData *data = (NSData *)CFBridgingRelease(IOHIDEventCreateData(kCFAllocatorDefault, event));
NSMutableString *str = [NSMutableString stringWithCapacity:data.length * 2];
for (unsigned int i = 0; i < data.length; i++) {
[str appendFormat:@"%02x", ((uint8_t *)data.bytes)[i]];
}
fprintf(_file, "%s\n", [[str description] UTF8String]);
return;
}

if (_variables) {
printEventVariables(hidEvent);
} else {
fprintf(_file, "%s\n", [[hidEvent description] UTF8String]);
}

if (_children) {
CFArrayRef children = IOHIDEventGetChildren(event);
for (CFIndex index = 0, count = children ? CFArrayGetCount(children) : 0; index < count; index++) {
IOHIDEventRef child = (IOHIDEventRef)CFArrayGetValueAtIndex(children, index);
HIDEvent *childHIDEvent = (__bridge HIDEvent *)child;

if (_variables) {
fprintf(_file, " ");
printEventVariables(childHIDEvent);
} else {
fprintf(_file," %s\n", [[childHIDEvent description] UTF8String]);
}
}
}
}
}
}

static NSPredicate *predicateForEventType(NSString *eventType)
{
if ([eventType rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]].location == NSNotFound) {
// event type string
return [NSPredicate predicateWithFormat:@"typestr contains %@", eventType ];
} else {
// event type number
return [NSPredicate predicateWithFormat:@"typeval == %d", [eventType intValue]];
}
}

Boolean clientTypeForString (NSString * string, IOHIDEventSystemClientType * type)
{
typedef struct {
IOHIDEventSystemClientType type;
NSString * str;
} ClientTypeName;

ClientTypeName table [] = {
{
kIOHIDEventSystemClientTypeAdmin,
@"admin"
},
{
kIOHIDEventSystemClientTypeMonitor,
@"monitor"
},
{
kIOHIDEventSystemClientTypePassive,
@"passive"
},
{
kIOHIDEventSystemClientTypeRateControlled,
@"ratecontrolled"
},
{
kIOHIDEventSystemClientTypeSimple,
@"simple"
},
};

for (size_t index = 0 ; index < sizeof(table) / sizeof (table[0]); index++) {
if ([table[index].str containsString:string]) {
*type = table[index].type;
return true;
}
}

return false;
}

static void _serviceRemovedCallback(void *target __unused,
void *refcon __unused,
IOHIDServiceClientRef service)
{
uint64_t regID = 0;

id serviceID = (__bridge id)IOHIDServiceClientGetRegistryID(service);
if (serviceID) {
regID = [serviceID unsignedLongLongValue];
}

printf("service removed:0x%llx ", regID);
for (NSString *key in _debugKeys) {
id val = CFBridgingRelease(IOHIDServiceClientCopyProperty(service, (__bridge CFStringRef)key));
printf("%s ", [[NSString stringWithFormat:@"%@:%@", key, val] UTF8String]);
}
printf("\n");
}

static void _serviceAddedCallback(void *target __unused,
void *refcon __unused,
IOHIDServiceClientRef service)
{
uint64_t regID = 0;

IOHIDServiceClientRegisterRemovalCallback(service, _serviceRemovedCallback, 0, 0);

id serviceID = (__bridge id)IOHIDServiceClientGetRegistryID(service);
if (serviceID) {
regID = [serviceID unsignedLongLongValue];
}

printf("service added:0x%llx, ", regID);
for (NSString *key in _debugKeys) {
NSString *formatStr = @"%@:%@,";
if ([key isEqualToString:_debugKeys.lastObject]) {
formatStr = @"%@:%@";
}
id val = CFBridgingRelease(IOHIDServiceClientCopyProperty(service, (__bridge CFStringRef)key));
printf("%s ", [[NSString stringWithFormat:formatStr, key, val] UTF8String]);
}
printf("\n");
}

int monitor(int argc __unused, const char * argv[] __unused) {
int arg;
int status = STATUS_SUCCESS;
IOHIDEventSystemClientRef client = NULL;
dispatch_source_t source;
bool verbose = false;
char * matchingStr = NULL;
_debugKeys = @[ @kIOHIDPrimaryUsagePageKey, @kIOHIDPrimaryUsageKey, @kIOHIDVendorIDKey, @kIOHIDProductIDKey,
@kIOHIDTransportKey, @kIOHIDLocationIDKey, @kIOHIDProductKey, @kIOHIDManufacturerKey ];

if(!os_variant_allows_internal_security_policies(nil)) {
return 0;
}

while ((arg = getopt_long(argc, (char **) argv, MAIN_OPTIONS_SHORT, MAIN_OPTIONS, NULL)) != -1) {
switch (arg) {
// --help
case 'h':
printf("%s", monitorUsage);
goto exit;
// --verbose
case 'v':
verbose = true;
break;
// --predicate
case 'p': {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@(optarg)];
if (_predicate) {
_predicate = [NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:_predicate, predicate, nil]];
} else {
_predicate = (NSCompoundPredicate *)predicate;
}

if (!_predicate) {
printf("Bad predicate: %s\n", optarg);
status = STATUS_ERROR;
goto exit;
}
break;
}
// --children
case 'c':
_children = true;
break;
// --output
case 'o':
_fileNameStr = optarg;
break;
// --matching
case 'm':
matchingStr = optarg;
break;
// --show
case 's':
_variables = [[NSString stringWithUTF8String:optarg] componentsSeparatedByString:@" "];
break;
// --type
case 't':
{
NSPredicate *predicate = predicateForEventType([NSString stringWithUTF8String:optarg]);

if (_predicate) {
_predicate = [NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:_predicate, predicate, nil]];
} else {
_predicate = (NSCompoundPredicate *)predicate;
}
}
break;
case kOptClientType:
_clientTypeStr = optarg;
break;
case kOptSerialize:
_serialize = true;
break;
default:
printf("%s", monitorUsage);
goto exit;
break;
}
}

if (_fileNameStr) {
_file = fopen(_fileNameStr, "w+");
require_action_quiet (_file, exit, printf("Unable open ouput file: %s", _fileNameStr));
} else {
_file = stdout;
}

signal(SIGINT, SIG_IGN);
source = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGINT, 0, dispatch_get_global_queue(0, 0));
dispatch_source_set_event_handler(source, ^{
CFRunLoopRef runloop = CFRunLoopGetMain();
CFRunLoopStop(runloop);
CFRunLoopWakeUp(runloop);

});
dispatch_resume(source);

IOHIDEventSystemClientType type = kIOHIDEventSystemClientTypeMonitor;

if (_clientTypeStr && !clientTypeForString ([[NSString stringWithUTF8String:_clientTypeStr] lowercaseString], &type)) {
printf("Unknowwn client type: %s\n", _clientTypeStr);
status = kOptionErr;
goto exit;
}

client = IOHIDEventSystemClientCreateWithType(kCFAllocatorDefault, type, NULL);
require_action_quiet (client, exit, status = kUnknownErr; printf("ERROR!Unable to create event system client\n"));

require_action_quiet (!matchingStr || setClientMatching(client, matchingStr) == true, exit, status = kParamErr; printf("ERROR! bad matching string:%s\n", matchingStr));

if (verbose) {
IOHIDEventSystemClientRegisterDeviceMatchingCallback(client, _serviceAddedCallback, 0, 0);
NSArray *services = CFBridgingRelease(IOHIDEventSystemClientCopyServices(client));
for (id service in services) {
_serviceAddedCallback(0, 0, (__bridge IOHIDServiceClientRef)service);
}
}

IOHIDEventSystemClientRegisterEventCallback(client, eventCallback, NULL, NULL);
IOHIDEventSystemClientScheduleWithRunLoop(client, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);

CFRunLoopRun();

IOHIDEventSystemClientUnscheduleWithRunLoop(client, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
IOHIDEventSystemClientUnregisterEventCallback(client, eventCallback, NULL, NULL);
status = STATUS_SUCCESS;

if (_file) {
fclose(_file);
}

exit:

if (client) {
CFRelease(client);
}

return status;
}