1/*
2 * Copyright (c) 1998-2000 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * The contents of this file constitute Original Code as defined in and
7 * are subject to the Apple Public Source License Version 1.1 (the
8 * "License").  You may not use this file except in compliance with the
9 * License.  Please obtain a copy of the License at
10 * http://www.apple.com/publicsource and read it before using this file.
11 *
12 * This Original Code and all software distributed under the License are
13 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
14 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
15 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
17 * License for the specific language governing rights and limitations
18 * under the License.
19 *
20 * @APPLE_LICENSE_HEADER_END@
21 */
22
23#include <CoreFoundation/CoreFoundation.h>
24#include <IOKit/hid/IOHIDUserDevice.h>
25#include <stdio.h>
26#include <string.h>
27#include <pthread.h>
28#include <AssertMacros.h>
29#include "IOHIDReportDescriptorParser.h"
30
31typedef struct GenericLEDKeyboardDescriptor {
32    //05 01: Usage Page (Generic Desktop)
33    UInt8 devUsagePageOp;
34    UInt8 devUsagePageNum;
35    //09 06: Usage (Keyboard)
36    UInt8 devUsageOp;
37    UInt8 devUsageNum;
38    //A1 01: Collection (Application)
39    UInt8 appCollectionOp;
40    UInt8 appCollectionNum;
41    //05 07:    Usage Page (Key Codes)
42    UInt8 modUsagePageOp;
43    UInt8 modUsagePageNum;
44    //19 e0:    Usage Minimum...... (224)
45    UInt8 modUsageMinOp;
46    UInt8 modUsageMinNum;
47    //29 e7:    Usage Maximum...... (231)
48    UInt8 modUsageMaxOp;
49    UInt8 modUsageMaxNum;
50    //15 00:    Logical Minimum.... (0)
51    UInt8 modLogMinOp;
52    UInt8 modLogMinNum;
53    //25 01:    Logical Maximum.... (1)
54    UInt8 modLogMaxOp;
55    UInt8 modLogMaxNum;
56    //95 08:    Report Count....... (8)
57    UInt8 modRptCountOp;
58    UInt8 modRptCountNum;
59    //75 01:    Report Size........ (1)
60    UInt8 modRptSizeOp;
61    UInt8 modRptSizeNum;
62    //81 02:    Input (Data)
63    UInt8 modInputOp;
64    UInt8 modInputNum;
65
66    //95 01:    Report Count....... (1)
67    UInt8 rsrvCountOp;
68    UInt8 rsrvCountNum;
69    //75 08:    Report Size........ (8)
70    UInt8 rsrvSizeOp;
71    UInt8 rsrvSizeNum;
72    //81 01:    Input (Constant)
73    UInt8 rsrvInputOp;
74    UInt8 rsrvInputNum;
75
76
77    //95 02:    Report Count....... (2)
78    UInt8 ledRptCountOp;
79    UInt8 ledRptCountNum;
80    //75 01:    Report Size........ (1)
81    UInt8 ledRptSizeOp;
82    UInt8 ledRptSizeNum;
83    //05 08:    Usage Page (LEDs)
84    UInt8 ledUsagePageOp;
85    UInt8 ledUsagePageNum;
86    //19 01:    Usage Minimum...... (1)
87    UInt8 ledUsageMinOp;
88    UInt8 ledUsageMinNum;
89    //29 02:    Usage Maximum...... (2)
90    UInt8 ledUsageMaxOp;
91    UInt8 ledUsageMaxNum;
92    //91 02:    Output (Data)
93    UInt8 ledInputOp;
94    UInt8 ledInputNum;
95
96    //95 01:    Report Count....... (1)
97    UInt8 fillRptCountOp;
98    UInt8 fillRptCountNum;
99    //75 03:    Report Size........ (3)
100    UInt8 fillRptSizeOp;
101    UInt8 fillRptSizeNum;
102    //91 01:    Output (Constant)
103    UInt8 fillInputOp;
104    UInt8 fillInputNum;
105
106
107    //95 06:    Report Count....... (6)
108    UInt8 keyRptCountOp;
109    UInt8 keyRptCountNum;
110    //75 08:    Report Size........ (8)
111    UInt8 keyRptSizeOp;
112    UInt8 keyRptSizeNum;
113    //15 00:    Logical Minimum.... (0)
114    UInt8 keyLogMinOp;
115    UInt8 keyLogMinNum;
116    //26 ff 00:    Logical Maximum.... (255)
117    UInt8 keyLogMaxOp;
118    UInt16 keyLogMaxNum;
119    //05 07:    Usage Page (Key Codes)
120    UInt8 keyUsagePageOp;
121    UInt8 keyUsagePageNum;
122    //19 00:    Usage Minimum...... (0)
123    UInt8 keyUsageMinOp;
124    UInt8 keyUsageMinNum;
125    //29 ff:    Usage Maximum...... (255)
126    UInt8 keyUsageMaxOp;
127    UInt8 keyUsageMaxNum;
128    //81 00:    Input (Array)
129    UInt8 keyInputOp;
130    UInt8 keyInputNum;
131
132    //C0:    End Collection
133    UInt8 appCollectionEnd;
134} GenericLEDKeyboardDescriptor;
135
136typedef struct GenericKeyboardRpt {
137    UInt8 modifiers;
138    UInt8 reserved;
139    UInt8 keys[6];
140} GenericKeyboardRpt;
141
142static UInt8 gGenLEDKeyboardDesc[] = {
143    0x05, 0x01,
144    0x09, 0x06,
145    0xA1, 0x01,
146    0x05, 0x07,
147    0x19, 0xe0,
148    0x29, 0xe7,
149    0x15, 0x00,
150    0x25, 0x01,
151    0x75, 0x01,
152    0x95, 0x08,
153    0x81, 0x02,
154    0x95, 0x01,
155    0x75, 0x08,
156    0x81, 0x01,
157
158    0x95, 0x02,
159    0x75, 0x01,
160    0x05, 0x08,
161    0x19, 0x01,
162    0x29, 0x02,
163    0x91, 0x02,
164    0x95, 0x01,
165    0x75, 0x06,
166    0x91, 0x01,
167
168    0x95, 0x06,
169    0x75, 0x08,
170    0x15, 0x00,
171    0x26, 0xff, 0x00,
172    0x05, 0x07,
173    0x19, 0x00,
174    0x29, 0xff,
175    0x81, 0x00,
176    0xC0
177};
178
179static IOHIDUserDeviceRef   gDevice     = NULL;
180static pthread_mutex_t      gMuxtex     = PTHREAD_MUTEX_INITIALIZER;
181
182static void printReport(uint8_t * report, CFIndex reportLength, bool rcv)
183{
184    int index;
185
186    printf("%s report: reportLength=%ld: ", rcv ? "Received" : "Dispatching", reportLength);
187    for (index=0; index<reportLength; index++)
188        printf("%02x ", report[index]);
189    printf("\n");
190}
191
192#define kKeyboardInterval 0.008
193
194static void dispatchKeyboardEvent(char c)
195{
196    GenericKeyboardRpt report;
197    static CFAbsoluteTime sDeadline = 0;
198    CFAbsoluteTime delta;
199
200    bzero(&report, sizeof(report));
201    if ( c < 'a' || c > 'z' )
202        return;
203
204    printf("dispatching keyboard event for '%c'\n", c);
205
206    pthread_mutex_lock(&gMuxtex);
207
208    delta = sDeadline - CFAbsoluteTimeGetCurrent();
209    if ( delta > 0 )
210        usleep(delta*1000000);
211
212    report.keys[0] = 4 + c - 97;
213    printReport((uint8_t*)&report, sizeof(report), 0);
214    IOHIDUserDeviceHandleReport(gDevice, (uint8_t*)&report, sizeof(report));
215
216    usleep(kKeyboardInterval*1000000);
217
218    report.keys[0] = 0;
219    printReport((uint8_t*)&report, sizeof(report), 0);
220    IOHIDUserDeviceHandleReport(gDevice, (uint8_t*)&report, sizeof(report));
221
222    sDeadline = CFAbsoluteTimeGetCurrent() + kKeyboardInterval;
223
224    pthread_mutex_unlock(&gMuxtex);
225
226}
227
228static void * getKeyboardCharThread(void *arg)
229{
230    printf("This virtual keyboard supports dispatching typed characters within the range of 'a' - 'z'\n");
231
232    while (1) {
233        dispatchKeyboardEvent(getchar());
234    }
235    return arg;
236}
237
238static void * getReportCharThread(void *arg)
239{
240    char str[256];
241    printf("Please enter report data: 00 11 22 33 ...\n");
242
243    while (1) {
244        bzero(str, sizeof(str));
245
246        if ( fgets(str, sizeof(str), stdin) ) {
247
248            CFStringRef rawString = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, (uint8_t *)str, sizeof(str), kCFStringEncodingMacRoman, false, kCFAllocatorNull);
249
250            if ( rawString ) {
251                CFArrayRef rawArray = CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault, rawString, CFSTR("\n"));
252
253                if ( rawArray ) {
254                    if ( CFArrayGetCount(rawArray) > 1 ) {
255                        CFArrayRef array = CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault, CFArrayGetValueAtIndex(rawArray, 0), CFSTR(" "));
256
257                        if ( array ) {
258
259                            uint8_t     report[64] = {};
260                            uint32_t    reportIndex = 0;
261
262                            for ( int index=0; index<CFArrayGetCount(array) && reportIndex<sizeof(report); index++) {
263                                CFStringRef substring = (CFStringRef)CFArrayGetValueAtIndex(array, index);
264                                const char * substr;
265
266                                if ( !substring )
267                                    continue;
268
269                                substr = CFStringGetCStringPtr(substring, kCFStringEncodingMacRoman);
270                                if ( !substr )
271                                    continue;
272
273                                report[reportIndex++] = strtoul(substr, NULL, 16);
274                            }
275
276                            pthread_mutex_lock(&gMuxtex);
277                            printReport(report, reportIndex, 0);
278                            IOHIDUserDeviceHandleReport(gDevice, report, reportIndex);
279                            pthread_mutex_unlock(&gMuxtex);
280
281                            CFRelease(array);
282                        }
283                    }
284
285                    CFRelease(rawArray);
286                }
287
288                CFRelease(rawString);
289            }
290        }
291
292    }
293    return arg;
294}
295
296
297static IOReturn setReportCallback(void * refcon __unused, IOHIDReportType type __unused, uint32_t reportID __unused, uint8_t * report, CFIndex reportLength)
298{
299    printReport(report, reportLength, 1);
300    return kIOReturnSuccess;
301}
302
303typedef void * (*UserInputCallback)(void * refcon);
304
305
306static void startDevice(CFMutableDictionaryRef properties, const uint8_t * descriptor, uint32_t descriptorLength, UserInputCallback userInputCallback, IOHIDUserDeviceReportCallback outputReportCallback)
307{
308    CFDataRef descriptorData  = NULL;
309
310    descriptorData = CFDataCreate(kCFAllocatorDefault, descriptor, descriptorLength);
311    require(descriptorData, finish);
312
313    require(properties, finish);
314
315    CFDictionarySetValue(properties, CFSTR(kIOHIDReportDescriptorKey), descriptorData);
316
317    gDevice = IOHIDUserDeviceCreate(kCFAllocatorDefault, properties);
318    require(gDevice, finish);
319
320    IOHIDUserDeviceScheduleWithRunLoop(gDevice, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
321
322    if ( outputReportCallback )
323        IOHIDUserDeviceRegisterSetReportCallback(gDevice, outputReportCallback, NULL);
324
325    if ( userInputCallback ) {
326        pthread_t tid;
327        pthread_attr_t attr;
328
329        assert(!pthread_attr_init(&attr));
330        assert(!pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED));
331        assert(!pthread_create( &tid, &attr, userInputCallback, NULL));
332        assert(!pthread_attr_destroy(&attr));
333    }
334
335    CFRunLoopRun();
336
337finish:
338    if ( gDevice )
339        CFRelease(gDevice);
340
341    if ( descriptorData )
342        CFRelease(descriptorData);
343}
344
345static void printHelp()
346{
347    printf("\n");
348    printf("hidUserDeviceTest usage:\n\n");
349    printf("\t-d    <descriptor data: 00 11 22...>\t: create device with descriptor data\n");
350    printf("\t-p    Parse descriptor data\n");
351    printf("\t-k    Create generic keyboard device\n");
352    printf("\t--vid <vendor id>\n");
353    printf("\t--pid <product id>\n");
354    printf("\n");
355}
356
357int main (int argc, const char * argv[])
358{
359    bool                            handled                 = false;
360    bool                            parse                   = false;
361    uint8_t *                       data                    = NULL;
362    uint32_t                        dataSize                = 0;
363    uint32_t                        dataIndex               = 0;
364    UserInputCallback               userInputCallback       = NULL;
365    IOHIDUserDeviceReportCallback   outputReportCallback    = NULL;
366    CFMutableDictionaryRef          properties              = NULL;
367
368    properties = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
369
370    for ( int argi=1; argi<argc; argi++ ) {
371        // command
372        if ( argv[argi][0] == '-' ) {
373            if ( !strcmp("-k", argv[argi] ) ) {
374                if ( data ) {
375                    free(data);
376                }
377
378                dataSize                = sizeof(gGenLEDKeyboardDesc);
379                data                    = malloc(dataSize);
380                userInputCallback       = getKeyboardCharThread;
381                outputReportCallback    = setReportCallback;
382                handled                 = true;
383
384                bcopy(gGenLEDKeyboardDesc, data, dataSize);
385                dataIndex = dataSize;
386            }
387            else if ( !strcmp("-d", argv[argi]) ) {
388                if ( !data ) {
389                    dataSize                = argc-argi+1;
390                    data                    = malloc(dataSize);
391                }
392                userInputCallback       = getReportCharThread;
393                outputReportCallback    = setReportCallback;
394                handled                 = true;
395            }
396            else if ( !strcmp("-p", argv[argi]) ) {
397                parse = true;
398
399                if ( !data ) {
400                    dataSize    = argc-argi+1;
401                    data        = malloc(argc-argi+1);
402                }
403            }
404            else if ( !strcmp("--vid", argv[argi]) && (argi+1) < argc) {
405                int value = strtol(argv[++argi], NULL, 10);
406                CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &value);
407                if ( number ) {
408                    CFDictionarySetValue(properties, CFSTR(kIOHIDVendorIDKey), number);
409                    CFRelease(number);
410                }
411            }
412            else if ( !strcmp("--pid", argv[argi]) && (argi+1) < argc) {
413                int value = strtol(argv[++argi], NULL, 10);
414                CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &value);
415                if ( number ) {
416                    CFDictionarySetValue(properties, CFSTR(kIOHIDProductIDKey), number);
417                    CFRelease(number);
418                }
419            }
420        }
421        // data
422        else if ( data && dataIndex < dataSize ) {
423            data[dataIndex++] = strtoul(argv[argi], NULL, 16);
424        }
425
426    }
427
428    if ( data ) {
429        if ( parse )
430            PrintHIDReport(data, dataSize);
431
432        if ( handled ) {
433            startDevice(properties, data, dataIndex, userInputCallback, outputReportCallback);
434        }
435
436        free(data);
437
438    } else {
439        printHelp();
440    }
441
442    if ( properties )
443        CFRelease(properties);
444
445    return 0;
446}
447