1// 2// IOHIDEventSystemMonitor.c 3// IOHIDFamily 4// 5// Created by Rob Yepez on 9/16/12. 6// 7// 8 9#include <AssertMacros.h> 10#include <pthread.h> 11#include <mach/mach.h> 12#include <mach/mach_time.h> 13#include <CoreFoundation/CoreFoundation.h> 14#include <IOKit/hid/IOHIDEventSystemClientPrivate.h> 15#include <IOKit/hid/IOHIDServiceClient.h> 16#include <IOKit/hid/IOHIDEventSystem.h> 17#include <IOKit/hid/IOHIDService.h> 18#include <IOKit/hid/IOHIDNotification.h> 19#include <IOKit/hid/IOHIDKeys.h> 20 21static const char kServiceAdded[] = "ADDED"; 22static const char kServiceRemoved[] = "REMOVED"; 23 24static uint32_t __persist = 0; 25static uint32_t __dispatchOnly = 0; 26static uint32_t __dispatchEventMask = 0; 27static uint32_t __eventMask = -1; 28static IOHIDEventSystemClientType __clientType = kIOHIDEventSystemClientTypeMonitor; 29static uint32_t __matchingUsagePage = 0; 30static uint32_t __matchingUsage = 0; 31static uint32_t __matchingInterval = 0; 32static uint32_t __timeout = 0; 33static uint64_t __eventCounts[kIOHIDEventTypeCount] = {}; 34static uint64_t __eventCount = 0; 35static uint64_t __eventLatencyTotal = 0; 36 37static boolean_t eventCallback(void * target, void * refcon, void * sender, IOHIDEventRef event) 38{ 39 __eventCount++; 40 __eventCounts[IOHIDEventGetType(event)]++; 41 __eventLatencyTotal += IOHIDEventGetLatency(event, kMicrosecondScale); 42 43 if ( ((1<<IOHIDEventGetType(event)) & __eventMask) != 0 ) { 44 45 CFStringRef outputString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@\n"), event); 46 47 if ( outputString ) { 48 printf("%s", CFStringGetCStringPtr(outputString, kCFStringEncodingMacRoman)); 49 CFRelease(outputString); 50 } 51 } 52 53 return false; 54} 55 56static void serviceClientCallback(void * target, void * refcon, IOHIDServiceClientRef service) 57{ 58 CFStringRef string; 59 60 if ( refcon == kServiceAdded ) { 61 IOHIDServiceClientRegisterRemovalCallback(service, serviceClientCallback, NULL, (void*)kServiceRemoved); 62 } 63 64 printf("SERVICE %s:\n", (char *)refcon); 65 66 string = CFCopyDescription(service); 67 if ( string ) { 68 printf("%s\n", CFStringGetCStringPtr(string, kCFStringEncodingMacRoman)); 69 CFRelease(string); 70 } 71 72 if ( __clientType == kIOHIDEventSystemClientTypeRateControlled ) { 73 CFNumberRef number; 74 uint32_t primaryUsagePage=0, primaryUsage=0; 75 76 number = IOHIDServiceClientCopyProperty(service, CFSTR(kIOHIDServicePrimaryUsagePageKey)); 77 if ( number ) { 78 CFNumberGetValue(number, kCFNumberSInt32Type, &primaryUsagePage); 79 CFRelease(number); 80 } 81 82 number = IOHIDServiceClientCopyProperty(service, CFSTR(kIOHIDServicePrimaryUsageKey)); 83 if ( number ) { 84 CFNumberGetValue(number, kCFNumberSInt32Type, &primaryUsage); 85 CFRelease(number); 86 } 87 88 if ( primaryUsagePage == __matchingUsagePage && primaryUsage == __matchingUsage ) { 89 number = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &__matchingInterval); 90 if ( number ) { 91 IOHIDServiceClientSetProperty(service, CFSTR(kIOHIDServiceReportIntervalKey), number); 92 CFRelease(number); 93 } 94 } 95 } 96 97} 98 99static CFMutableDictionaryRef __serviceNotifications = NULL; 100void serviceRemovalCallback(void * target, void * refcon, IOHIDServiceRef service) 101{ 102 CFStringRef string; 103 104 CFDictionaryRemoveValue(__serviceNotifications, service); 105 106 printf("SERVICE %s:\n", (char *)kServiceRemoved); 107 108 string = CFCopyDescription(service); 109 if ( string ) { 110 printf("%s\n", CFStringGetCStringPtr(string, kCFStringEncodingMacRoman)); 111 CFRelease(string); 112 } 113} 114 115static void servicesAddedCallback(void * target, void * refcon, void * sender, CFArrayRef services) 116{ 117 CFIndex index, count; 118 119 for(index=0, count = CFArrayGetCount(services); index<count; index++) { 120 IOHIDServiceRef service = (IOHIDServiceRef)CFArrayGetValueAtIndex(services, index); 121 CFStringRef string; 122 123 IOHIDNotificationRef notification = IOHIDServiceCreateRemovalNotification(service, serviceRemovalCallback, NULL, NULL); 124 if ( notification ) { 125 CFDictionaryAddValue(__serviceNotifications, service, notification); 126 CFRelease(notification); 127 } 128 129 printf("SERVICE %s:\n", (char *)kServiceAdded); 130 131 string = CFCopyDescription(service); 132 if ( string ) { 133 printf("%s\n", CFStringGetCStringPtr(string, kCFStringEncodingMacRoman)); 134 CFRelease(string); 135 } 136 } 137 138} 139 140static void dispatchClientEvents(IOHIDEventSystemClientRef system, uint32_t mask) 141{ 142 do { 143 144 for ( uint32_t index=0; index<kIOHIDEventTypeCount; index++ ) { 145 IOHIDEventRef event; 146 147 if ( ((1<<index) & mask) == 0 ) 148 continue; 149 150 event = IOHIDEventCreate(kCFAllocatorDefault, index, mach_absolute_time(), 0); 151 if ( !event ) 152 continue; 153 154 IOHIDEventSetSenderID(event, 0xDEFACEDBEEFFECE5); 155 156 IOHIDEventSystemClientDispatchEvent(system, event); 157 158 CFRelease(event); 159 } 160 161 if ( !__persist ) 162 continue; 163 164 printf("hit return to redispatch\n"); 165 166 while (getchar() != '\n'); 167 168 } while (__persist); 169} 170 171static void * dispatchClientThread(void * context) 172{ 173 IOHIDEventSystemClientRef eventSystem = (IOHIDEventSystemClientRef)context; 174 175 dispatchClientEvents(eventSystem, __dispatchEventMask); 176 177 return NULL; 178} 179 180 181static void runClient() 182{ 183 IOHIDEventSystemClientRef eventSystem = IOHIDEventSystemClientCreateWithType(kCFAllocatorDefault, __clientType, NULL); 184 185 require_action(eventSystem, exit, printf("Unable to create client")); 186 187 require_action(!__dispatchOnly, exit, dispatchClientEvents(eventSystem, __dispatchEventMask)); 188 189 if ( __dispatchEventMask ) { 190 pthread_attr_t attr; 191 pthread_t tid; 192 193 assert(!pthread_attr_init(&attr)); 194 assert(!pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE)); 195 assert(!pthread_create(&tid, &attr, &dispatchClientThread, eventSystem)); 196 assert(!pthread_attr_destroy(&attr)); 197 } 198 199 IOHIDEventSystemClientScheduleWithRunLoop(eventSystem, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); 200 201 IOHIDEventSystemClientRegisterEventCallback(eventSystem, (IOHIDEventCallback)eventCallback, NULL, NULL); 202 203 IOHIDEventSystemClientRegisterDeviceMatchingCallback(eventSystem, serviceClientCallback, NULL, (void*)kServiceAdded); 204 205 CFArrayRef services = IOHIDEventSystemClientCopyServices(eventSystem); 206 if ( services ) { 207 CFIndex index, count; 208 209 for(index=0, count = CFArrayGetCount(services); index<count; index++) 210 serviceClientCallback(NULL, (void*)kServiceAdded, (IOHIDServiceClientRef)CFArrayGetValueAtIndex(services, index)); 211 212 CFRelease(services); 213 214 } 215 216 CFRunLoopRun(); 217exit: 218 if ( eventSystem ) 219 CFRelease(eventSystem); 220} 221 222static void runServer() 223{ 224 IOHIDEventSystemRef eventSystem = IOHIDEventSystemCreate(kCFAllocatorDefault); 225 IOHIDNotificationRef notification = NULL; 226 227 require(eventSystem, exit); 228 229 IOHIDEventSystemOpen(eventSystem, eventCallback, NULL, NULL, 0); 230 231 if ( !__serviceNotifications ) 232 __serviceNotifications = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 233 234 CFArrayRef services = IOHIDEventSystemCopyMatchingServices(eventSystem, NULL, servicesAddedCallback, NULL, NULL, ¬ification); 235 if ( services ) { 236 servicesAddedCallback(NULL, NULL, NULL, services); 237 CFRelease(services); 238 239 } 240 241 CFRunLoopRun(); 242exit: 243 if ( eventSystem ) 244 CFRelease(eventSystem); 245 246} 247 248static void listClients() 249{ 250 IOHIDEventSystemClientRef eventSystem = IOHIDEventSystemClientCreateWithType(kCFAllocatorDefault, kIOHIDEventSystemClientTypeAdmin, NULL); 251 CFIndex types; 252 253 require(eventSystem, exit); 254 255 for ( types=kIOHIDEventSystemClientTypeAdmin; types<=kIOHIDEventSystemClientTypeRateControlled; types++ ) { 256 CFArrayRef clients = _IOHIDEventSystemClientCopyClientDescriptions(eventSystem, (IOHIDEventSystemClientType)types); 257 CFIndex index; 258 259 if ( !clients ) 260 continue; 261 262 for ( index=0; index<CFArrayGetCount(clients); index++ ) { 263 CFStringRef clientDebugDesc = (CFStringRef)CFArrayGetValueAtIndex(clients, index); 264 printf("%s\n", CFStringGetCStringPtr(clientDebugDesc, CFStringGetSystemEncoding())); 265 } 266 267 CFRelease(clients); 268 } 269 270exit: 271 if (eventSystem) 272 CFRelease(eventSystem); 273} 274 275static void listServices() 276{ 277 IOHIDEventSystemClientRef eventSystem = IOHIDEventSystemClientCreateWithType(kCFAllocatorDefault, kIOHIDEventSystemClientTypeAdmin, NULL); 278 CFArrayRef services = NULL; 279 CFIndex index; 280 281 require(eventSystem, exit); 282 283 services = _IOHIDEventSystemClientCopyServiceDescriptions(eventSystem); 284 require(services, exit); 285 286 for ( index=0; index<CFArrayGetCount(services); index++ ) { 287 CFStringRef serviceDebugDesc = (CFStringRef)CFArrayGetValueAtIndex(services, index); 288 printf("%s\n", CFStringGetCStringPtr(serviceDebugDesc, CFStringGetSystemEncoding())); 289 } 290 291 292exit: 293 if ( services ) 294 CFRelease(services); 295 296 if (eventSystem) 297 CFRelease(eventSystem); 298} 299 300static void exitTimerCallback(CFRunLoopTimerRef timer, void *info) 301{ 302 printf("***************************************************************************\n"); 303 printf("Event Statistics over %d seconds\n", __timeout); 304 printf("***************************************************************************\n"); 305 for (uint32_t index=0; index<kIOHIDEventTypeCount; index++) { 306 printf("%-20.20s: %llu\n", IOHIDEventGetTypeString(index), __eventCounts[index]); 307 } 308 printf("\n"); 309 printf("Average latency: %10.3f us\n", __eventLatencyTotal ? (double)__eventLatencyTotal/__eventCount : 0); 310 exit(0); 311} 312 313 314static void printHelp() 315{ 316 printf("\n"); 317 printf("hidEventSystemMonitor usage:\n\n"); 318 printf("\t-m <event type mask ...>\t: monitor all events contained in mask\n"); 319 printf("\t-e <event type number ...>\t: monitor all events of the passed type\n"); 320 printf("\t-nm <event type mask>\t\t: monitor all events except those contained in mask\n"); 321 printf("\t-ne <event type number>\t\t: monitor all events except those of the passed type\n"); 322 printf("\t-d <event type number>\t\t: dispatch event of the passed type\n"); 323 printf("\t-dm <event type number>\t\t: dispatch events of the passed mask\n"); 324 printf("\t-p\t\t\t\t: persist event dispatch\n"); 325 printf("\n"); 326 printf("\t-a\t\t\t\t: Admin (Unfiltered event stream)\n"); 327 printf("\t-r\t\t\t\t: Rate Controlled\n"); 328 printf("\n"); 329 printf("\t-s\t\t\t\t: Instantiate HID event server\n"); 330 printf("\n"); 331 printf("\t-lc\t\t\t\t: List clients\n"); 332 printf("\t-ls\t\t\t\t: List services\n"); 333 printf("\n\tAvailable Event Types:\n"); 334 335 for (int type = kIOHIDEventTypeNULL; type<kIOHIDEventTypeCount; type++) { 336 printf("\t\t%2d: %s\n", type, CFStringGetCStringPtr(IOHIDEventTypeGetName(type), kCFStringEncodingMacRoman)); 337 } 338} 339 340typedef enum { 341 kEventRegistrationTypeNone, 342 kEventRegistrationTypeReplaceMask, 343 kEventRegistrationTypeRemoveMask, 344 kEventRegistrationTypeAdd, 345 kEventRegistrationTypeRemove, 346 kEventRegistrationTypeDispatch, 347 kEventRegistrationTypeDispatchMask, 348 kEventRegistrationTypeUsagePage, 349 kEventRegistrationTypeUsage, 350 kEventRegistrationTypeInterval, 351 kEventRegistrationTypeTimeout, 352} EventRegistrationType; 353 354int main (int argc __unused, const char * argv[] __unused) 355{ 356 bool runAsClient = true; 357 358 if ( argc > 1 ) { 359 EventRegistrationType registrationType=kEventRegistrationTypeNone; 360 361 for ( int index=1; index<argc; index++) { 362 const char * arg = argv[index]; 363 if ( !strcmp("-a", arg ) ) { 364 __clientType = kIOHIDEventSystemClientTypeAdmin; 365 } 366 else if ( !strcmp("-r", arg ) ) { 367 __clientType = kIOHIDEventSystemClientTypeRateControlled; 368 } 369 else if ( !strcmp("-up", arg ) ) { 370 registrationType = kEventRegistrationTypeUsagePage; 371 } 372 else if ( !strcmp("-u", arg ) ) { 373 registrationType = kEventRegistrationTypeUsage; 374 } 375 else if ( !strcmp("-i", arg ) ) { 376 registrationType = kEventRegistrationTypeInterval; 377 } 378 else if ( !strcmp("-s", arg ) ) { 379 runAsClient = false; 380 } 381 else if ( !strcmp("-m", arg ) ) { 382 registrationType = kEventRegistrationTypeReplaceMask; 383 } 384 else if ( !strcmp("-e", arg ) ) { 385 registrationType = kEventRegistrationTypeAdd; 386 __eventMask = 0; 387 } 388 else if ( !strcmp("-nm", arg ) ) { 389 registrationType = kEventRegistrationTypeRemoveMask; 390 } 391 else if ( !strcmp("-ne", arg ) ) { 392 registrationType = kEventRegistrationTypeRemove; 393 } 394 else if ( !strcmp("-do", arg ) ) { 395 __dispatchOnly = 1; 396 } 397 else if ( !strcmp("-lc", arg ) ) { 398 listClients(); 399 goto exit; 400 } 401 else if ( !strcmp("-ls", arg ) ) { 402 listServices(); 403 goto exit; 404 } 405 else if ( !strcmp("-d", arg) ) { 406 registrationType = kEventRegistrationTypeDispatch; 407 } 408 else if ( !strcmp("-dm", arg) ) { 409 registrationType = kEventRegistrationTypeDispatchMask; 410 } 411 else if ( !strcmp("-p", arg) ) { 412 __persist = 1; 413 } 414 else if ( !strcmp("-t", arg) ) { 415 registrationType = kEventRegistrationTypeTimeout; 416 } 417 else if ( registrationType == kEventRegistrationTypeReplaceMask ) { 418 __eventMask = (uint32_t)strtoul(arg, NULL, 16); 419 } 420 else if ( registrationType == kEventRegistrationTypeRemoveMask ) { 421 __eventMask &= ~(strtoul(arg, NULL, 16)); 422 } 423 else if ( registrationType == kEventRegistrationTypeAdd ) { 424 __eventMask |= (1<<strtoul(arg, NULL, 10)); 425 } 426 else if ( registrationType == kEventRegistrationTypeRemove ) { 427 __eventMask &= ~(1<<strtoul(arg, NULL, 10)); 428 } 429 else if ( registrationType == kEventRegistrationTypeDispatch ) { 430 __dispatchEventMask = (1<<strtoul(arg, NULL, 10)); 431 } 432 else if ( registrationType == kEventRegistrationTypeDispatchMask ) { 433 __dispatchEventMask = (uint32_t)strtoul(arg, NULL, 16); 434 } 435 else if ( registrationType == kEventRegistrationTypeUsagePage ) { 436 __matchingUsagePage = (uint32_t)strtoul(arg, NULL, 10); 437 } 438 else if ( registrationType == kEventRegistrationTypeUsage ) { 439 __matchingUsage = (uint32_t)strtoul(arg, NULL, 10); 440 } 441 else if ( registrationType == kEventRegistrationTypeInterval ) { 442 __matchingInterval = (uint32_t)strtoul(arg, NULL, 10); 443 } 444 else if ( registrationType == kEventRegistrationTypeTimeout ) { 445 __timeout = (uint32_t)strtoul(arg, NULL, 10); 446 } 447 else if ( !strcmp("-h", arg ) ) { 448 printHelp(); 449 return 0; 450 } 451 } 452 } 453 454 if ( __timeout ) { 455 CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + __timeout, 0, 0, 0, exitTimerCallback, NULL); 456 if ( timer ) { 457 CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode); 458 CFRelease(timer); 459 } 460 } 461 462 if ( runAsClient ) { 463 printf("***************************************************************************\n"); 464 printf("Running as a %s client\n", IOHIDEventSystemClientGetTypeString(__clientType)); 465 printf("***************************************************************************\n"); 466 runClient(); 467 } else { 468 printf("***************************************************************************\n"); 469 printf("Running as server\n"); 470 printf("***************************************************************************\n"); 471 runServer(); 472 } 473 474exit: 475 476 return 0; 477} 478