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, &notification);
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