//
// 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>
int monitor(int argc, const char * argv[]);
static const char MAIN_OPTIONS_SHORT[] = "p:cm:i:s:t:h";
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' },
{ "help", 0, NULL, 'h' },
{ NULL, 0, NULL, 0 }
};
const char monitorUsage[] =
"\nMonitor HID Event System events\n"
"\nUsage:\n\n"
" hidutil monitor [ --info <eventType | eventName> ][ --predicate <predicate> ][ --show <varibles> ][ --type <event type> ][ --matching <matching> ][ --children ]\n"
"\nFlags:\n\n"
" -i --info..................print predicate info for an event type\n"
" -p --predicate.............filter events 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"
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 NSArray *_variables = NULL;
static void printClassInfo(Class cls) {
objc_property_t *properties = NULL;
unsigned int outCount, i;
properties = class_copyPropertyList(cls, &outCount);
for(i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
printf(" }
free(properties);
}
static void printEventInfo(char *arg) {
NSString *eventString = [NSString stringWithUTF8String:arg];
NSNumberFormatter *nf = [[NSNumberFormatter alloc] init];
BOOL isNum = [nf numberFromString:eventString] != nil;
IOHIDEventRef dummyEvent = NULL;
HIDEvent *event = [[HIDEvent alloc] init];
IOHIDEventType eventType = kIOHIDEventTypeCount;
// get event type from string
if (isNum) {
eventType = [eventString intValue];
} else {
for (uint32_t i = 0; i < kIOHIDEventTypeCount; i++) {
NSString *eventTypeString = [NSString stringWithUTF8String:IOHIDEventGetTypeString(i)];
if ([[eventTypeString lowercaseString] containsString:eventString]) {
eventType = i;
break;
}
}
}
dummyEvent = IOHIDEventCreate(kCFAllocatorDefault, eventType, 0, 0);
printf("base event predicates:\n");
printClassInfo([event class]);
if (eventType < kIOHIDEventTypeCount) {
event = createHIDEvent(dummyEvent);
printf("\n printClassInfo([event class]);
}
CFRelease(dummyEvent);
}
static void printEventVariables(HIDEvent *event) {
NSString *eventString = [[NSString alloc] init];
for (NSString *variable in _variables) {
@try {
if (variable == _variables.lastObject) {
eventString = [eventString stringByAppendingFormat:@" } else {
eventString = [eventString stringByAppendingFormat:@" }
} @ catch(...) {
}
}
if (eventString == nil) {
printf(" } else {
printf(" }
}
static void eventCallback(void *target __unused, void *refcon __unused, void *sender __unused, IOHIDEventRef event) {
bool predicated = false;
HIDEvent *hidEvent = createHIDEvent(event);
if (_predicate) {
@try {
if (![_predicate evaluateWithObject:hidEvent]) {
predicated = true;
}
} @ catch(...) {
predicated = true;
}
}
if (!predicated) {
if (_variables) {
printEventVariables(hidEvent);
} else {
printf(" }
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 = createHIDEvent(child);
if (_variables) {
printf(" ");
printEventVariables(childHIDEvent);
} else {
printf(" }
}
}
}
}
static NSPredicate *predicateForEventType(NSString *eventType)
{
if ([eventType rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]].location == NSNotFound) {
// event type string
return [NSPredicate predicateWithFormat:@"typestr contains } else {
// event type number
return [NSPredicate predicateWithFormat:@"typeval == }
}
int monitor(int argc __unused, const char * argv[] __unused) {
int arg;
int status = STATUS_SUCCESS;
IOHIDEventSystemClientRef client = NULL;
bool matching = false;
client = IOHIDEventSystemClientCreateWithType(kCFAllocatorDefault, kIOHIDEventSystemClientTypeMonitor, NULL);
if (!client) {
goto exit;
}
while ((arg = getopt_long(argc, (char **) argv, MAIN_OPTIONS_SHORT, MAIN_OPTIONS, NULL)) != -1) {
switch (arg) {
// --help
case 'h':
printf(" goto exit;
// --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: status = STATUS_ERROR;
goto exit;
}
break;
}
// --children
case 'c':
_children = true;
break;
// --matching
case 'm':
matching = setClientMatching(client, optarg);
if (!matching) {
printf ("bad matching string: }
break;
// --info
case 'i':
printEventInfo(optarg);
goto exit;
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;
}
default:
printf(" goto exit;
break;
}
}
IOHIDEventSystemClientRegisterEventCallback(client, eventCallback, NULL, NULL);
IOHIDEventSystemClientScheduleWithRunLoop(client, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
CFRunLoopRun();
IOHIDEventSystemClientUnscheduleWithRunLoop(client, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
IOHIDEventSystemClientUnregisterEventCallback(client, eventCallback, NULL, NULL);
status = STATUS_SUCCESS;
exit:
if (client) {
CFRelease(client);
}
return status;
}