1#include <CoreFoundation/CoreFoundation.h>
2#include <IOKit/hid/IOHIDLib.h>
3#include "IOHIDReportDescriptorParser.h"
4
5static CFTimeInterval   gPollInterval       = 0.0;
6static bool             gReport             = TRUE;
7static bool             gValue              = FALSE;
8static bool             gSend               = FALSE;
9static bool             gSendTransaction    = FALSE;
10static bool             gPrintDescriptor    = FALSE;
11
12static CFMutableDictionaryRef   gOutputElements = NULL;
13
14static char * getReportTypeString(IOHIDReportType type)
15{
16    switch ( type ) {
17        case kIOHIDReportTypeInput:
18            return "INPUT";
19        case kIOHIDReportTypeOutput:
20            return "OUTPUT";
21        case kIOHIDReportTypeFeature:
22            return "FEATURE";
23        default:
24            return "DUH";
25    }
26}
27
28static void __deviceReportCallback(void * context, IOReturn result, void * sender, IOHIDReportType type, uint32_t reportID, uint8_t * report, CFIndex reportLength)
29{
30    int index;
31
32    printf("IOHIDDeviceRef[%p]: result=0x%08x reportType=%s reportID=%d reportLength=%ld: ", sender, result, getReportTypeString(type), reportID, reportLength);
33    for (index=0; result==kIOReturnSuccess && index<reportLength; index++)
34        printf("%02x ", report[index]);
35    printf("\n");
36
37    // toggle a report
38    if ( gSend || gSendTransaction ) {
39        CFArrayRef outputElements  = NULL;
40
41        outputElements = CFDictionaryGetValue(gOutputElements, sender);
42
43        if ( outputElements ) {
44            static uint8_t      value       = 0;
45            IOHIDTransactionRef transaction = NULL;
46
47            transaction = IOHIDTransactionCreate(kCFAllocatorDefault, (IOHIDDeviceRef)sender, kIOHIDTransactionDirectionTypeOutput, 0);
48            if ( transaction ) {
49                IOReturn    ret;
50                CFIndex     index, count;
51
52                for ( index=0, count = CFArrayGetCount(outputElements); index<count; index++) {
53
54                    IOHIDElementRef element     = (IOHIDElementRef)CFArrayGetValueAtIndex(outputElements, index);
55                    IOHIDValueRef   hidValue    = 0;
56
57                    if ( !element )
58                        continue;
59
60                    IOHIDTransactionAddElement(transaction, element);
61
62                    hidValue = IOHIDValueCreateWithIntegerValue(kCFAllocatorDefault, element, 0, value);
63                    if ( !hidValue )
64                        continue;
65
66                    if ( gSendTransaction ) {
67                        IOHIDTransactionSetValue(transaction, element, hidValue, 0);
68                    } else {
69                        ret = IOHIDDeviceSetValue((IOHIDDeviceRef)sender, element, hidValue);
70                        printf("Attempt to send value. Ret = 0x%08x\n", ret);
71                    }
72                    CFRelease(hidValue);
73
74                }
75
76                if ( gSendTransaction ) {
77                    ret = IOHIDTransactionCommit(transaction);
78                    printf("Attempt to send transaction. Ret = 0x%08x\n", ret);
79                }
80                value = value+1 % 2;
81
82                CFRelease(transaction);
83            }
84        }
85    }
86}
87
88void __deviceValueCallback (void * context, IOReturn result, void * sender, IOHIDValueRef value)
89{
90    IOHIDElementRef element = IOHIDValueGetElement(value);
91
92    printf("IOHIDDeviceRef[%p]: value=%p timestamp=%lld cookie=%d usagePage=0x%02X usage=0x%02X intValue=%ld\n", sender, value, IOHIDValueGetTimeStamp(value), (uint32_t)IOHIDElementGetCookie(element), IOHIDElementGetUsagePage(element), IOHIDElementGetUsage(element), IOHIDValueGetIntegerValue(value));
93}
94
95static void __timerCallback(CFRunLoopTimerRef timer, void *info)
96{
97    IOHIDDeviceRef  device = (IOHIDDeviceRef)info;
98
99    CFNumberRef     number      = NULL;
100    CFIndex         reportSize  = 0;
101    IOReturn        result;
102
103    number = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxInputReportSizeKey));
104    if ( !number )
105        return;
106
107    CFNumberGetValue(number, kCFNumberCFIndexType, &reportSize);
108
109    uint8_t report[reportSize];
110
111    bzero(report, reportSize);
112
113    result = IOHIDDeviceGetReport(device, kIOHIDReportTypeInput, 0, report, &reportSize);
114
115    __deviceReportCallback(NULL, result, device, kIOHIDReportTypeInput, 0, report, reportSize);
116}
117
118static void __deviceCallback(void * context, IOReturn result, void * sender, IOHIDDeviceRef device)
119{
120
121    boolean_t   terminated  = context == 0;
122    CFStringRef debugString = CFCopyDescription(device);
123
124    static CFMutableDictionaryRef s_timers = NULL;
125
126    if ( !s_timers )
127        s_timers = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
128
129    printf("%-10.10s: %s\n", terminated ? "terminated" : "matched", debugString ? CFStringGetCStringPtr(debugString, CFStringGetSystemEncoding()) : "");
130
131    if ( debugString )
132        CFRelease(debugString);
133
134    if ( terminated ) {
135        CFDictionaryRemoveValue(gOutputElements, device);
136
137
138        CFRunLoopTimerRef timer = (CFRunLoopTimerRef)CFDictionaryGetValue(s_timers, device);
139        if ( timer ) {
140            CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopCommonModes);
141        }
142        CFDictionaryRemoveValue(s_timers, device);
143
144    } else {
145        CFArrayRef              outputElements  = NULL;
146        CFMutableDictionaryRef  matching        = NULL;
147
148        if ( gPrintDescriptor ) {
149            CFDataRef descriptor = NULL;
150
151            descriptor = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDReportDescriptorKey));
152            if ( descriptor ) {
153                PrintHIDDescriptor(CFDataGetBytePtr(descriptor), CFDataGetLength(descriptor));
154            }
155        }
156
157        if ( gPollInterval != 0.0 ) {
158            CFRunLoopTimerContext   context = {.info=device};
159            CFRunLoopTimerRef       timer   = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent(), gPollInterval, 0, 0, __timerCallback, &context);
160
161            CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopCommonModes);
162
163            CFDictionaryAddValue(s_timers, device, timer);
164            CFRelease(timer);
165
166            printf("Adding polling timer @ %4.6f s\n", gPollInterval);
167        }
168
169        matching = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
170        if ( matching ) {
171            uint32_t    value   = kIOHIDElementTypeOutput;
172            CFNumberRef number  = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &value);
173
174            if ( number ) {
175                CFDictionarySetValue(matching, CFSTR(kIOHIDElementTypeKey), number);
176
177                outputElements = IOHIDDeviceCopyMatchingElements(device, matching, 0);
178                if ( outputElements ) {
179                    CFDictionarySetValue(gOutputElements, device, outputElements);
180                    CFRelease(outputElements);
181                }
182
183                CFRelease(number);
184            }
185            CFRelease(matching);
186        }
187    }
188
189
190}
191
192static void printHelp()
193{
194    printf("\n");
195    printf("hidReportTest usage:\n\n");
196    printf("\t-p    Parse descriptor data\n");
197    printf("\t-i    Manually poll at a given interval (s)");
198    printf("\t--usage <usage>\n");
199    printf("\t--usagepage <usage page>\n");
200    printf("\t--transport <transport string value\n>");
201    printf("\t--vid <vendor id>\n");
202    printf("\t--pid <product id>\n");
203    printf("\t--transport <transport string value>\n");
204    printf("\n");
205}
206
207int main (int argc, const char * argv[]) {
208
209    IOHIDManagerRef         manager     = IOHIDManagerCreate(kCFAllocatorDefault, 0);
210    CFMutableDictionaryRef  matching    = NULL;
211
212    int argi;
213    for (argi=1; argi<argc; argi++) {
214        if ( 0 == strcmp("-v", argv[argi]) ) {
215            gValue = TRUE;
216        }
217        else if ( 0 == strcmp("-p", argv[argi]) ) {
218            gPrintDescriptor = TRUE;
219        }
220        else if ( 0 == strcmp("-s", argv[argi]) ) {
221            gSend = TRUE;
222        }
223        else if ( 0 == strcmp("-st", argv[argi]) ) {
224            gSendTransaction = TRUE;
225        }
226        else if ( 0 == strcmp("-nr", argv[argi]) ) {
227            gReport = FALSE;
228        }
229        else if ( !strcmp("-i", argv[argi]) && (argi+1) < argc) {
230            gPollInterval = atof(argv[++argi]);
231            printf("gPollInterval = %f seconds\n", gPollInterval);
232        }
233        else if ( !strcmp("--usage", argv[argi]) && (argi+1) < argc) {
234            long value = atol(argv[++argi]);
235            CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongType, &value);
236            if ( number ) {
237                if ( !matching )
238                    matching = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
239                CFDictionarySetValue(matching, CFSTR(kIOHIDDeviceUsageKey), number);
240                CFRelease(number);
241            }
242        }
243        else if ( !strcmp("--usagepage", argv[argi]) && (argi+1) < argc) {
244            long value = atol(argv[++argi]);
245            CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongType, &value);
246            if ( number ) {
247                if ( !matching )
248                    matching = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
249                CFDictionarySetValue(matching, CFSTR(kIOHIDDeviceUsagePageKey), number);
250                CFRelease(number);
251            }
252        }
253        else if ( !strcmp("--vid", argv[argi]) && (argi+1) < argc) {
254            long value = atol(argv[++argi]);
255            CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongType, &value);
256            if ( number ) {
257                if ( !matching )
258                    matching = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
259                CFDictionarySetValue(matching, CFSTR(kIOHIDVendorIDKey), number);
260                CFRelease(number);
261            }
262        }
263        else if ( !strcmp("--pid", argv[argi]) && (argi+1) < argc) {
264            long value = atol(argv[++argi]);
265            CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongType, &value);
266            if ( number ) {
267                if ( !matching )
268                    matching = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
269                CFDictionarySetValue(matching, CFSTR(kIOHIDProductIDKey), number);
270                CFRelease(number);
271            }
272        }
273        else if ( !strcmp("--transport", argv[argi]) && (argi+1) < argc) {
274            CFStringRef string = CFStringCreateWithCString(kCFAllocatorDefault, argv[++argi], CFStringGetSystemEncoding());
275            if ( string ) {
276                if ( !matching )
277                    matching = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
278                CFDictionarySetValue(matching, CFSTR(kIOHIDTransportKey), string);
279                CFRelease(string);
280            }
281        }
282        else {
283            printHelp();
284            return 0;
285        }
286    }
287
288    gOutputElements = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
289
290    IOHIDManagerRegisterDeviceMatchingCallback(manager, __deviceCallback, (void*)TRUE);
291    IOHIDManagerRegisterDeviceRemovalCallback(manager, __deviceCallback, (void*)FALSE);
292
293    if ( gReport ) {
294
295        if ( gPollInterval == 0.0 ) {
296            IOHIDManagerRegisterInputReportCallback(manager, __deviceReportCallback, NULL);
297        }
298
299    }
300    if ( gValue )
301        IOHIDManagerRegisterInputValueCallback(manager, __deviceValueCallback, NULL);
302
303    IOHIDManagerScheduleWithRunLoop(manager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
304    IOHIDManagerSetDeviceMatching(manager, matching);
305    IOHIDManagerOpen(manager, 0);
306
307    CFRunLoopRun();
308
309    if ( matching )
310        CFRelease(matching);
311
312    return 0;
313}
314