File: PsychHIDHelpers.c

package info (click to toggle)
psychtoolbox-3 3.0.19.14.dfsg1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 86,796 kB
  • sloc: ansic: 176,245; cpp: 20,103; objc: 5,393; sh: 2,753; python: 1,397; php: 384; makefile: 193; java: 113
file content (1038 lines) | stat: -rw-r--r-- 41,788 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
/*
  PsychToolbox/Source/Common/PsychHID/PsychHIDHelpers.c

  PROJECTS: PsychHID

  PLATFORMS:  All

  AUTHORS:
  Allen.Ingling@nyu.edu             awi
  mario.kleiner@tuebingen.mpg.de    mk

  HISTORY:
  5/05/03  awi        Created.
  4/19/05  dgp      cosmetic.
  8/23/07  rpw      added PsychHIDKbQueueRelease() to PsychHIDCleanup()
  4/04/09  mk        added support routines for generic USB devices and usbDeviceRecordBank.

  TO DO:

*/

#include "PsychHID.h"

#if PSYCH_SYSTEM != PSYCH_OSX

// Global variable which holds a reference to the last
// accessed hid_device* on non-OS/X. This is for error
// handling:
hid_device* last_hid_device = NULL;

// List with all known low level USB-HID devices from enumeration:
struct hid_device_info* hidlib_devices = NULL;
pRecDevice hid_devices = NULL;
hid_device* source[MAXDEVICEINDEXS];

#endif

#if PSYCH_SYSTEM == PSYCH_OSX
IOHIDDeviceInterface122** deviceInterfaces[MAXDEVICEINDEXS];
#endif

// Tracker used to maintain references to open generic USB devices.
// PsychUSBDeviceRecord is currently defined in PsychHID.h.
PsychUSBDeviceRecord usbDeviceRecordBank[PSYCH_HID_MAX_GENERIC_USB_DEVICES];

PsychHIDEventRecord* hidEventBuffer[PSYCH_HID_MAX_DEVICES];
unsigned int    hidEventBufferCapacity[PSYCH_HID_MAX_DEVICES];
unsigned int    hidEventBufferReadPos[PSYCH_HID_MAX_DEVICES];
unsigned int    hidEventBufferWritePos[PSYCH_HID_MAX_DEVICES];
psych_mutex     hidEventBufferMutex[PSYCH_HID_MAX_DEVICES];
psych_condition hidEventBufferCondition[PSYCH_HID_MAX_DEVICES];

/* PsychInitializePsychHID()
 *
 * Master init routine - Called at module load time / first time init.
 *
 */
void PsychInitializePsychHID(void)
{
    int i;

    // Initialize the generic USB tracker to "all off" state:
    for (i = 0; i < PSYCH_HID_MAX_GENERIC_USB_DEVICES; i++) {
        usbDeviceRecordBank[i].valid = 0;
    }

    // Setup event ringbuffers:
    for (i = 0; i < PSYCH_HID_MAX_DEVICES; i++) {
        hidEventBuffer[i] = NULL;
        hidEventBufferCapacity[i] = 10000; // Initial capacity of event buffer.
        hidEventBufferReadPos[i] = 0;
        hidEventBufferWritePos[i] = 0;
    }

#if PSYCH_SYSTEM == PSYCH_OSX
    for (i = 0; i < MAXDEVICEINDEXS; i++) deviceInterfaces[i] = NULL;

    // Try to load all bundles from Psychtoolbox/PsychHardware/
    // This loads the HID_Utilities.framework bundle if it is present. The whole point of it is
    // to allow our statically compiled-in version of the library to find the location of
    // the XML file with the database of (vendorId, productId) -> (VendorName, ProductName) and
    // (usagePage, usage) -> (usageName) mappings.
    //
    // In practice, the XML file only serves as a fallback, and one that doesn't contain much
    // useful info for mainstream products, only for a few niche products. Given its limited
    // value, i think we can refrain from shipping the framework as part of Psychtoolbox and
    // just provide the option to use it (== its XML file) if users decide to install it themselves.
    char tmpString[1024];

    sprintf(tmpString, "%sPsychHardware/", PsychRuntimeGetPsychtoolboxRoot(FALSE));
    CFStringRef urlString = CFStringCreateWithCString(kCFAllocatorDefault, tmpString, kCFStringEncodingASCII);
    CFURLRef directoryURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, urlString, kCFURLPOSIXPathStyle, false);
    CFRelease(urlString);
    CFArrayRef bundleArray = CFBundleCreateBundlesFromDirectory(kCFAllocatorDefault, directoryURL, NULL);
    CFRelease(directoryURL);
    CFRelease(bundleArray);

#endif

    // Initialize OS specific interfaces and routines:
    PsychHIDInitializeHIDStandardInterfaces();

    // This sets up data structures for HID report reception inside PsychHIDReceiveReports.c:
    PsychHIDReleaseAllReportMemory();

    return;
}

/* PsychHIDGetFreeUSBDeviceSlot();
 *
 * Return a device record pointer to a free generic USB device
 * slot, as well as the associated numeric usbHandle.
 *
 * Abort with error if no more slots are free.
 */
PsychUSBDeviceRecord* PsychHIDGetFreeUSBDeviceSlot(int* usbHandle)
{
    int i;

    // Find the next available USB slot:
    for (i = 0; i < PSYCH_HID_MAX_GENERIC_USB_DEVICES; i++) {
        if (usbDeviceRecordBank[i].valid == 0) {
            *usbHandle = i;
            return(&(usbDeviceRecordBank[i]));
        }
    }

    // If we reach this point, then all slots are occupied: Fail!
    PsychErrorExitMsg(PsychError_user, "Unable to open another generic USB device! Too many devices open. Please close one and retry.");
    return(NULL);
}

/* PsychHIDGetFreeUSBDeviceSlot();
 *
 * Return a device record pointer to a free generic USB device
 * slot, as well as the associated numeric usbHandle.
 *
 * Abort with error if no more slots are free.
 */
PsychUSBDeviceRecord* PsychHIDGetUSBDevice(int usbHandle)
{
    // Child protection:
    if (usbHandle < 0 || usbHandle >= PSYCH_HID_MAX_GENERIC_USB_DEVICES) PsychErrorExitMsg(PsychError_user, "Invalid generic USB device handle provided! Handle outside valid range.");
    if (usbDeviceRecordBank[usbHandle].valid == 0) PsychErrorExitMsg(PsychError_user, "Invalid generic USB device handle provided! The handle doesn't correspond to an open device.");

    // Valid handle for slot corresponding to an open device. Return PsychUSBDeviceRecord* to it:
    return(&(usbDeviceRecordBank[usbHandle]));
}

void PsychHIDCloseAllUSBDevices(void)
{
    int i;
    for (i = 0; i < PSYCH_HID_MAX_GENERIC_USB_DEVICES; i++) {
        if (usbDeviceRecordBank[i].valid) {
            PsychHIDOSCloseUSBDevice(PsychHIDGetUSBDevice(i));
        }
    }
}

/*
    PsychHIDCleanup()

    Cleanup before flushing the mex file.
*/
PsychError PsychHIDCleanup(void)
{
    pRecDevice curdev = NULL;

    // Disable online help system:
    PsychClearGiveHelp();

    // Disable any kind of low-level stdin<->tty magic for character reception
    // or suppression in console mode (for octave and matlab -nojvm):
    ConsoleInputHelper(-10);

    // Shutdown USB-HID report low-level functions, e.g., for DAQ toolbox on OS/X:
    PsychHIDReceiveReportsCleanup(); // PsychHIDReceiveReport.c

    // Shutdown os specific interfaces and routines:
    PsychHIDShutdownHIDStandardInterfaces();

    // Release all other HID device data structures:
#if PSYCH_SYSTEM == PSYCH_OSX
    // Via Apple HIDUtils:
    int i;
    for (i = 0; i < MAXDEVICEINDEXS; i++) {
        if (deviceInterfaces[i]) {
            IOHIDDeviceInterface** interface = (IOHIDDeviceInterface**)deviceInterfaces[i];
            (*interface)->close(interface);
            (*interface)->Release(interface);
            deviceInterfaces[i] = NULL;
        }
    }

    if (HIDHaveDeviceList()) HIDReleaseDeviceList();
#else
    // Then our high-level list:
    while (hid_devices) {
        // Get current device record to release:
        curdev = hid_devices;

        // Advance to the next one for next loop iteration:
        hid_devices = hid_devices->pNext;

        // Interface attached aka device opened? If so we need to close the device handle:
        if (curdev->interface) hid_close((hid_device*)curdev->interface);

        // Release:
        free(curdev);
    }

    // Reset last hid device for error handling:
    last_hid_device = NULL;

    // Release the HIDLIB low-level device list:
    if (hidlib_devices) hid_free_enumeration(hidlib_devices);
    hidlib_devices = NULL;

    // Shutdown HIDAPI:
    hid_exit();

#endif

    // Close and release all open generic USB devices:
    PsychHIDCloseAllUSBDevices();

    return(PsychError_none);
}

/*
    PsychHIDGetDeviceRecordPtrFromIndex()

    The inverse of PsychHIDGetIndexFromRecord()

    Accept the index from the list of device records and return a pointer to the indicated record.  Externally the list is one-indexed.
*/
pRecDevice PsychHIDGetDeviceRecordPtrFromIndex(int deviceIndex)
{
    int        i;
    pRecDevice currentDevice = NULL;

    PsychHIDVerifyInit();
    i = 1;
    for (currentDevice = HIDGetFirstDevice(); currentDevice != NULL; currentDevice = HIDGetNextDevice(currentDevice)) {
        if (i==deviceIndex) {
#if PSYCH_SYSTEM != PSYCH_OSX
            if (!currentDevice->interface) {
                currentDevice->interface = (void*)hid_open_path(currentDevice->transport);
                if (!currentDevice->interface) PsychErrorExitMsg(PsychError_system, "HIDLIB Failed to open USB device!");

                // Set read ops on device to non-blocking:
                hid_set_nonblocking((hid_device*)currentDevice->interface, 1);
            }
#endif
            return currentDevice;
        }
        ++i;
    }

    PsychErrorExitMsg(PsychError_user, "Invalid device index specified. Has a device been unplugged? Try rebuilding the device list");
    return(NULL);  //make the compiler happy.
}

psych_bool PsychHIDCreateEventBuffer(int deviceIndex, int numValuators, int numSlots)
{
    unsigned int bufferSize;

    if (deviceIndex < 0) deviceIndex = PsychHIDGetDefaultKbQueueDevice();

    if (numSlots < 0) {
        printf("PTB-ERROR: PsychHIDCreateEventBuffer(): numSlots %i invalid. Must be at least 0.\n", numSlots);
        return(FALSE);
    }

    // Non-zero numSlots ==> Set new capacity, otherwise leave at default/last capacity:
    if (numSlots > 0)
        hidEventBufferCapacity[deviceIndex] = numSlots;

    bufferSize = hidEventBufferCapacity[deviceIndex];

    // Already created? If so, nothing to do:
    if (hidEventBuffer[deviceIndex] || (bufferSize < 1)) return(FALSE);

    if (numValuators > PSYCH_HID_MAX_VALUATORS) {
        printf("PTB-ERROR: PsychHIDCreateEventBuffer(): numValuators %i > current compile time maximum of %i!\n",
               numValuators, PSYCH_HID_MAX_VALUATORS);
        return(FALSE);
    }

    hidEventBuffer[deviceIndex] = (PsychHIDEventRecord*) calloc(sizeof(PsychHIDEventRecord), bufferSize);
    if (NULL==hidEventBuffer[deviceIndex]) {
        printf("PTB-ERROR: PsychHIDCreateEventBuffer(): Insufficient memory to create KbQueue event buffer!");
        return(FALSE);
    }

    // Prepare mutex for buffer:
    PsychInitMutex(&hidEventBufferMutex[deviceIndex]);
    PsychInitCondition(&hidEventBufferCondition[deviceIndex], NULL);

    // Init & Flush it:
    hidEventBufferWritePos[deviceIndex] = 0;
    PsychHIDFlushEventBuffer(deviceIndex);

    return(TRUE);
}

psych_bool PsychHIDDeleteEventBuffer(int deviceIndex)
{
    if (deviceIndex < 0) deviceIndex = PsychHIDGetDefaultKbQueueDevice();

    if (hidEventBuffer[deviceIndex]) {
        // Empty the buffer, reset read/writepointers:
        PsychHIDFlushEventBuffer(deviceIndex);

        // Release it:
        free(hidEventBuffer[deviceIndex]);
        hidEventBuffer[deviceIndex] = NULL;
        PsychDestroyMutex(&hidEventBufferMutex[deviceIndex]);
        PsychDestroyCondition(&hidEventBufferCondition[deviceIndex]);
    }

    return TRUE;
}

psych_bool PsychHIDFlushEventBuffer(int deviceIndex)
{
    if (deviceIndex < 0) deviceIndex = PsychHIDGetDefaultKbQueueDevice();

    if (!hidEventBuffer[deviceIndex]) return FALSE;

    PsychLockMutex(&hidEventBufferMutex[deviceIndex]);
    hidEventBufferReadPos[deviceIndex] = hidEventBufferWritePos[deviceIndex];
    PsychUnlockMutex(&hidEventBufferMutex[deviceIndex]);

    return TRUE;
}

/* Return number of events in buffer for 'deviceIndex':
 * flags == 0 -> All events.
 * flags &  1 -> Only keypress events with valid mapped ASCII CookedKey keycode.
 */
unsigned int PsychHIDAvailEventBuffer(int deviceIndex, unsigned int flags)
{
    unsigned int navail, i, j;

    if (deviceIndex < 0) deviceIndex = PsychHIDGetDefaultKbQueueDevice();

    if (!hidEventBuffer[deviceIndex]) return(0);

    PsychLockMutex(&hidEventBufferMutex[deviceIndex]);

    // Compute total number of available events by default:
    navail = hidEventBufferWritePos[deviceIndex] - hidEventBufferReadPos[deviceIndex];

    // Only count of valid "CookedKey" mapped keypress events, e.g., for use by CharAvail(), requested?
    if (flags & 1) {
        // Yes: Iterate over all available events and only count number of keypress events
        // with meaningful 'CookedKey' field:
        navail = 0;
        for (i = hidEventBufferReadPos[deviceIndex]; i < hidEventBufferWritePos[deviceIndex]; i++) {
            j = i % hidEventBufferCapacity[deviceIndex];
            if ((hidEventBuffer[deviceIndex][j].status & (1 << 0)) && (hidEventBuffer[deviceIndex][j].cookedEventCode > 0)) navail++;
        }
    }

    PsychUnlockMutex(&hidEventBufferMutex[deviceIndex]);

    return(navail);
}

int PsychHIDReturnEventFromEventBuffer(int deviceIndex, int outArgIndex, double maxWaitTimeSecs)
{
    unsigned int navail, j;
    PsychHIDEventRecord evt;
    PsychGenericScriptType *retevent;
    double* foo = NULL;
    PsychGenericScriptType *outMat;
    double *v;
    const char *FieldNames[] = { "Type", "Time", "Pressed", "Keycode", "CookedKey", "ButtonStates", "Motion", "X", "Y", "NormX", "NormY", "Valuators" };

    if (deviceIndex < 0) deviceIndex = PsychHIDGetDefaultKbQueueDevice();
    if (!hidEventBuffer[deviceIndex]) return(0);

    PsychLockMutex(&hidEventBufferMutex[deviceIndex]);
    navail = hidEventBufferWritePos[deviceIndex] - hidEventBufferReadPos[deviceIndex];

    // If nothing available and we're asked to wait for something, then wait:
    if ((navail == 0) && (maxWaitTimeSecs > 0)) {
        // Wait for something:
        PsychTimedWaitCondition(&hidEventBufferCondition[deviceIndex], &hidEventBufferMutex[deviceIndex], maxWaitTimeSecs);

        // Recompute number of available events:
        navail = hidEventBufferWritePos[deviceIndex] - hidEventBufferReadPos[deviceIndex];
    }

    // Check if anything available, copy it if so:
    if (navail) {
        memcpy(&evt, &(hidEventBuffer[deviceIndex][hidEventBufferReadPos[deviceIndex] % hidEventBufferCapacity[deviceIndex]]), sizeof(PsychHIDEventRecord));
        hidEventBufferReadPos[deviceIndex]++;
    }

    PsychUnlockMutex(&hidEventBufferMutex[deviceIndex]);

    if (navail) {
        // Return event struct:
        switch (evt.type) {
            case 0: // Press/Release
            case 1: // Motion/Valuator change
                PsychAllocOutStructArray(outArgIndex, kPsychArgOptional, -1, 12, FieldNames, &retevent);
                break;

            case 2: // Touch begin
            case 3: // Touch update/move
            case 4: // Touch end
            case 5: // Touch sequence compromised marker. If this one shows up - with magic touch point
                    // id 0xffffffff btw., then the user script knows the sequence was cut short / aborted
                    // by some higher priority consumer, e.g., some global gesture recognizer.
                PsychAllocOutStructArray(outArgIndex, kPsychArgOptional, -1, 12, FieldNames, &retevent);
                break;

            default:
                PsychErrorExitMsg(PsychError_internal, "Unhandled keyboard queue event type!");
        }

        PsychSetStructArrayDoubleElement("Type",         0, (double) evt.type,                        retevent);
        PsychSetStructArrayDoubleElement("Time",         0, evt.timestamp,                            retevent);
        PsychSetStructArrayDoubleElement("Pressed",      0, (double) (evt.status & (1 << 0)) ? 1 : 0, retevent);
        PsychSetStructArrayDoubleElement("Keycode",      0, (double) evt.rawEventCode,                retevent);
        PsychSetStructArrayDoubleElement("CookedKey",    0, (double) evt.cookedEventCode,             retevent);
        PsychSetStructArrayDoubleElement("ButtonStates", 0, (double) evt.buttonStates,                retevent);
        PsychSetStructArrayDoubleElement("Motion",       0, (double) (evt.status & (1 << 1)) ? 1 : 0, retevent);
        PsychSetStructArrayDoubleElement("X",            0, (double) evt.X,                           retevent);
        PsychSetStructArrayDoubleElement("Y",            0, (double) evt.Y,                           retevent);
        PsychSetStructArrayDoubleElement("NormX",        0, (double) evt.normX,                       retevent);
        PsychSetStructArrayDoubleElement("NormY",        0, (double) evt.normY,                       retevent);

        // Copy out all valuators (including redundant (X,Y) again:
        v = NULL;
        PsychAllocateNativeDoubleMat(1, evt.numValuators, 1, &v, &outMat);
        for (j = 0; j < evt.numValuators; j++)
            *(v++) = (double) evt.valuators[j];
        PsychSetStructArrayNativeElement("Valuators", 0, outMat, retevent);

        return(navail - 1);
    }
    else {
        // Return empty matrix:
        PsychCopyOutDoubleMatArg(outArgIndex, kPsychArgOptional, 0, 0, 0, foo);
        return(0);
    }
}

PsychHIDEventRecord* PsychHIDLastTouchEventFromEventBuffer(int deviceIndex, int touchID)
{
    int nend, current;
    PsychHIDEventRecord *evt;

    if (!hidEventBuffer[deviceIndex]) return(0);

    PsychLockMutex(&hidEventBufferMutex[deviceIndex]);
    nend = (hidEventBufferWritePos[deviceIndex] - 1) % hidEventBufferCapacity[deviceIndex];
    current = nend;

    // Go backwards through all touch events until the most recent one with touchID is found:
    do {
        if ((hidEventBuffer[deviceIndex][current].type >= 2) &&
            (hidEventBuffer[deviceIndex][current].type <= 4) &&
            (hidEventBuffer[deviceIndex][current].rawEventCode == touchID))
            break;

        current = (current - 1) % hidEventBufferCapacity[deviceIndex];
    } while ((current != nend) && (current >= 0));

    if (hidEventBuffer[deviceIndex][current].rawEventCode == touchID)
        evt = &(hidEventBuffer[deviceIndex][current]);
    else
        evt = NULL;

    PsychUnlockMutex(&hidEventBufferMutex[deviceIndex]);

    return (evt);
}

int PsychHIDAddEventToEventBuffer(int deviceIndex, PsychHIDEventRecord* evt)
{
    unsigned int navail;

    if (deviceIndex < 0) deviceIndex = PsychHIDGetDefaultKbQueueDevice();

    if (!hidEventBuffer[deviceIndex]) return 0;

    PsychLockMutex(&hidEventBufferMutex[deviceIndex]);

    navail = hidEventBufferWritePos[deviceIndex] - hidEventBufferReadPos[deviceIndex];
    if (navail < hidEventBufferCapacity[deviceIndex]) {
        memcpy(&(hidEventBuffer[deviceIndex][hidEventBufferWritePos[deviceIndex] % hidEventBufferCapacity[deviceIndex]]), evt, sizeof(PsychHIDEventRecord));
        hidEventBufferWritePos[deviceIndex]++;

        // Announce new event to potential waiters:
        PsychSignalCondition(&hidEventBufferCondition[deviceIndex]);
    }
    else {
        printf("PsychHID: WARNING: KbQueue event buffer is full! Maximum capacity of %i elements reached, will discard future events.\n", hidEventBufferCapacity[deviceIndex]);
    }

    PsychUnlockMutex(&hidEventBufferMutex[deviceIndex]);

    return navail - 1;
}

// Platform specific code starts here:
// ===================================

#if PSYCH_SYSTEM == PSYCH_OSX

/*
    PSYCHHIDCheckInit()

    Check to see if we need to create the USB-HID device list. If it has not been created then create it.
*/
void PsychHIDVerifyInit(void)
{
    // GenericDesktop for mice, keyboards, gamepads etc. vendor defined for things like MCC USB-DAQ boxes...
    UInt32 inUsagePages[2] = {kHIDPage_GenericDesktop, kHIDPage_VendorDefinedStart};
    UInt32 inUsages[2] = {0, 0};
    int inNumDeviceTypes = 2;
    psych_bool success = TRUE;

    // Check for Catalina+ input monitoring permissions being denied.
    // We make a failure here a fatal PsychHID startup error, which will disable PsychHID completely for
    // the time being:
    if (PsychHIDWarnAccessDenied(NULL))
        PsychErrorExitMsg(PsychError_user, "PsychHID-CRITICAL: PsychHID disabled due to macOS security restrictions! Emergency shutdown! 'clear all' or restart your application.\n");

    // Build HID device list if it doesn't already exist:
    if (!HIDHaveDeviceList()) success = (psych_bool)HIDBuildDeviceList(0, 0);

    // This check can only be made against the 64-Bit HID Utilities, as the older 32-Bit
    // version is even more crappy and can't report meaningful error status:
    if (!success) {
        printf("\n");
        printf("PsychHID-ERROR: Could not enumerate and attach to all HID devices (HIDBuildDeviceList(0,0) failed)!\n");
        printf("PsychHID-ERROR: One reason could be that some HID devices are already exclusively claimed by some 3rd party device drivers\n");
        printf("PsychHID-ERROR: or applications. I will now retry to only claim control of a hopefully safe subset of devices like standard\n");
        printf("PsychHID-ERROR: keyboards, mice, gamepads and supported USB-DAQ devices and other vendor defined devices and hope this goes better...\n");

        // User override for special devices provided?
        if (getenv("PSYCHHID_HIDPAGEOVERRIDE")) {
            // Override via environment variable provided: Use it in addition to the generic desktop input devices:
            inUsagePages[1] = atoi(getenv("PSYCHHID_HIDPAGEOVERRIDE"));
            inUsages[1] = atoi(getenv("PSYCHHID_HIDUSAGEOVERRIDE"));
            printf("PsychHID-INFO: User override: Generic desktop page devices + usagePage 0x%x and usage value 0x%x ...\n", inUsagePages[1], inUsages[1]);
        }

        success = (psych_bool)HIDBuildMultiDeviceList(inUsagePages, inUsages, inNumDeviceTypes);
        if (!success) {
            printf("PsychHID-ERROR: Nope. Excluding everything but a few known USB-DAQ devices and standard mice, keyboards etc. Retrying ...\n");
            inUsagePages[1] = kHIDPage_VendorDefinedStart;
            inUsages[1] = 1; // Known combo is usagepage 0xff00 + usage 0x1 for MCC USB 1208-FS USB HID-DAQ device.
            success = (psych_bool)HIDBuildMultiDeviceList(inUsagePages, inUsages, inNumDeviceTypes);
        }

        if (!success) {
            printf("PsychHID-ERROR: Nope. Excluding everything but standard mice, keyboards etc. Retrying ...\n");
            success = (psych_bool)HIDBuildMultiDeviceList(inUsagePages, inUsages, 1);
            if (!success) {
                printf("PsychHID-ERROR: Could not enumerate any HID devices (HIDBuildDeviceList() still failed even for standard generic desktop input devices)!\n");
                printf("PsychHID-ERROR: Reasons can be ranging from bugs in Apples HID software to a buggy HID device driver for some connected device,\n");
                printf("PsychHID-ERROR: to general operating system malfunction. A reboot or device driver update for 3rd party HID devices\n");
                printf("PsychHID-ERROR: maybe could help. Check the OSX system log for possible HID related error messages or hints. Aborting...\n");
                PsychErrorExitMsg(PsychError_system, "HID device enumeration failed due to malfunction in the OSX 64 Bit Apple HID Utilities framework.");
            }
        }
        else {
            printf("PsychHID-INFO: That worked. A subset of regular mouse, keyboard etc. input devices and maybe some vendor defined devices will be available at least.\n");
        }
    }

    // Double-Check to protect against pathetic Apple software:
    if (!HIDHaveDeviceList()) {
        printf("PsychHID-ERROR: Could not enumerate HID devices (HIDBuildDeviceList() success, but HIDHaveDeviceList() still failed)!\n");
        printf("PsychHID-ERROR: Reasons can be ranging from bugs in Apples HID software to a buggy HID device driver for some connected device,\n");
        printf("PsychHID-ERROR: to general operating system malfunction. A reboot or device driver update for 3rd party HID devices\n");
        printf("PsychHID-ERROR: maybe could help. Check the OSX system log for possible HID related error messages or hints. Aborting...\n");
        PsychErrorExitMsg(PsychError_system, "HID device enumeration failed due to malfunction in the OSX Apple HID Utilities framework (II).");
    }

    // Verify no security sensitive application is blocking our low-level access to HID devices:
    PsychHIDWarnInputDisabled(NULL);
}

/*
    PsychHIDWarnInputDisabled()

    Check if HID event input is disabled by external processes, e.g., due to
    secure password entry protection. Return TRUE and output a warning message
    to user if HID input won't work due to active security measures. Return FALSE
    and stay silent if PsychHID can work as expected.

*/
psych_bool PsychHIDWarnInputDisabled(const char* callerName)
{
    if (IsSecureEventInputEnabled()) {
        printf("PTB-WARNING: During %s: Some other running application is preventing me from accessing the keyboard/keypad/mouse/...!\n", (callerName) ? callerName : "PsychHID invocation");
        printf("PTB-WARNING: This is likely a security measure, e.g., to protect some active password entry field.\n");
        printf("PTB-WARNING: Please identify and quit the offending application. E.g., some versions of Firefox are known to cause such problems...\n");
        return TRUE;
    }

    return FALSE;
}

// Device interface setup & query: 10.5 Leopard and later:
//
// ---------------------------------
// This routines is largely transplanted - with modifications - from HID Utilities v1.0:
// Create and open an interface to device, required prior to extracting values or building queues
// Note: application now owns the device and must close and release it prior to exiting

//unsigned long HIDCreateOpenDeviceInterface (UInt32 hidDevice, pRecDevice pDevice)
static IOHIDDeviceInterface122** HIDCreateOpenDeviceInterface(pRecDevice pDevice)
{
    IOReturn result = kIOReturnSuccess;
    HRESULT plugInResult = S_OK;
    SInt32 score = 0;
    IOCFPlugInInterface** ppPlugInInterface = NULL;
    IOHIDDeviceInterface** interface = NULL;
    io_service_t hidDevice = 0L;
    mach_port_t port;

    // Get low-level device for given HIDDeviceRef from HID Utilities v2.0:
    hidDevice = AllocateHIDObjectFromIOHIDDeviceRef((IOHIDDeviceRef)pDevice);
    if (0L == hidDevice) {
        printf("PTB-ERROR: PsychHID: Failed to allocate low-level HID-Object for high-level HID device.\n");
        return(NULL);
    }

    // This code is borrowed from HID Utilities v1.0:
    // Create and open interface for IORegistry device:
    result = IOCreatePlugInInterfaceForService(hidDevice, kIOHIDDeviceUserClientTypeID,
                                               kIOCFPlugInInterfaceID, &ppPlugInInterface, &score);
    if ((kIOReturnSuccess == result) && ppPlugInInterface)
    {
        // Call a method of the intermediate plug-in to create the device interface
        plugInResult = (*ppPlugInInterface)->QueryInterface(ppPlugInInterface,
                                                            CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), (void *) &interface);
        if ((S_OK != plugInResult) || (NULL == interface))
            printf("PTB-ERROR: PsychHID: Couldn't query HID class device interface from plugInInterface: %x.\n", plugInResult);

        // Release ppPlugInInterface in any case - no longer needed:
        IODestroyPlugInInterface(ppPlugInInterface);
    }
    else
        printf("PTB-ERROR: PsychHID: Failed to create **plugInInterface via IOCreatePlugInInterfaceForService: %x.\n", result);

    // Successfully retrieved an interface?
    if (NULL != interface)
    {
        // Yes: Open it.
        result = (*interface)->open(interface, 0);
        if (kIOReturnSuccess != result) {
            printf("PTB-ERROR: PsychHID: Failed to open HID device low-level interface via open: %x.\n", result);
        }
        else {
            // Interface open. Create async mach port for it to deliver interrupt in endpoint data:
            result = (*interface)->createAsyncPort(interface, &port);
            if (kIOReturnSuccess != result) {
                printf("PTB-ERROR: PsychHID: Failed to create async port for HID device low-level interface: %x.\n", result);
            }
        }
    }

    // Release reference to high-level device via HID Utilities v2.0::
    if (hidDevice) FreeHIDObject(hidDevice);

    return((IOHIDDeviceInterface122**)interface);
}

IOHIDDeviceInterface122** PsychHIDGetDeviceInterfacePtrFromIndex(int deviceIndex)
{
    IOHIDDeviceInterface122 **interface = NULL;
    pRecDevice dev = PsychHIDGetDeviceRecordPtrFromIndex(deviceIndex);
    if (dev) {
        // Interface already open?
        if (NULL == deviceInterfaces[deviceIndex]) {
            // No. Need to create it:
            deviceInterfaces[deviceIndex] = HIDCreateOpenDeviceInterface(dev);
        }

        // Assign it:
        interface = deviceInterfaces[deviceIndex];
    }

    return interface;
}

/*
    PsychHIDGetDeviceListByUsage()
*/
void PsychHIDGetDeviceListByUsage(long usagePage, long usage, int *numDeviceIndices, int *deviceIndices, pRecDevice *deviceRecords)
{
    pRecDevice currentDevice;
    int        currentDeviceIndex;

    PsychHIDVerifyInit();
    currentDeviceIndex = 0;
    *numDeviceIndices = 0;
    for (currentDevice = HIDGetFirstDevice(); currentDevice != NULL; currentDevice = HIDGetNextDevice(currentDevice)) {
        ++currentDeviceIndex;
        if (IOHIDDevice_GetUsagePage(currentDevice) == usagePage && IOHIDDevice_GetUsage(currentDevice) == usage) {
            deviceRecords[*numDeviceIndices] = currentDevice;
            deviceIndices[*numDeviceIndices] = currentDeviceIndex;  //the array is 0-indexed, devices are 1-indexed.
            ++(*numDeviceIndices);
        }
    }
}

/*
    PsychHIDGetDeviceListByUsages()
 */
void PsychHIDGetDeviceListByUsages(int numUsages, long *usagePages, long *usages, int *numDeviceIndices, int *deviceIndices, pRecDevice *deviceRecords)
{
    pRecDevice currentDevice;
    int        currentDeviceIndex;
    int        currentUsage;
    long       *usagePage;
    long       *usage;

    PsychHIDVerifyInit();
    *numDeviceIndices = 0;
    for (usagePage = usagePages, usage = usages, currentUsage = 0; currentUsage < numUsages; usagePage++, usage++, currentUsage++) {
        currentDeviceIndex = 0;
        for (currentDevice = HIDGetFirstDevice(); currentDevice != NULL; currentDevice = HIDGetNextDevice(currentDevice)) {
            ++currentDeviceIndex;
            if (IOHIDDevice_GetPrimaryUsagePage(currentDevice) == *usagePage && IOHIDDevice_GetPrimaryUsage(currentDevice) == *usage) {
                deviceRecords[*numDeviceIndices] = currentDevice;
                deviceIndices[*numDeviceIndices] = currentDeviceIndex;  //the array is 0-indexed, devices are 1-indexed.
                ++(*numDeviceIndices);
            }
        }
    }
}


/*
    PsychHIDGetIndexFromRecord()

    The inverse of PsychHIDGetDeviceRecordPtrFromIndex.

    This O(n) where n is the number of device elements.   We could make it O(1) if we modified
    the element structure in the HID Utilities library to include a field specifying the index of the element or
    collection.

    Note that if PsychHIDGetIndexFromRecord() is O(n) then its caller, PsychHIDGetCollections, is O(n^2) for each
    device, whereas if PsychHIDGetIndexFromRecord() is O(1) then psychHIDGetCollections becomes O(n) for each
    device.
*/
int PsychHIDGetIndexFromRecord(pRecDevice deviceRecord, pRecElement elementRecord, HIDElementTypeMask typeMask)
{
    int         elementIndex;
    pRecElement currentElement;

    if (elementRecord == NULL)
        return(0);
    elementIndex = 1;
    for (currentElement = HIDGetFirstDeviceElement(deviceRecord, typeMask);
        currentElement != elementRecord && currentElement != NULL;
        currentElement = HIDGetNextDeviceElement(currentElement, typeMask))
        ++elementIndex;
    if (currentElement == elementRecord)
        return elementIndex;
    else {
        PsychErrorExitMsg(PsychError_internal, "Element record not found within device record");
        return 0; //make the compiler happy
    }
}


pRecElement PsychHIDGetElementRecordFromDeviceRecordAndElementIndex(pRecDevice deviceRecord, int elementIndex)
{
    int         i;
    pRecElement currentElement;

    PsychHIDVerifyInit();
    i = 1;
    for (currentElement = HIDGetFirstDeviceElement(deviceRecord, kHIDElementTypeIO);
        currentElement != NULL;
        currentElement = HIDGetNextDeviceElement(currentElement, kHIDElementTypeIO))
    {
        if (i == elementIndex)
            return currentElement;
        ++i;
    }
    PsychErrorExitMsg(PsychError_internal, "Invalid device index specified.  Has a device has been unplugged? Try rebuilding the device list");
    return NULL;  //make the compiler happy.

}


pRecElement PsychHIDGetCollectionRecordFromDeviceRecordAndCollectionIndex(pRecDevice deviceRecord, int elementIndex)
{
    int         i;
    pRecElement currentElement;

    PsychHIDVerifyInit();
    i = 1;
    for (currentElement = HIDGetFirstDeviceElement(deviceRecord, kHIDElementTypeCollection);
        currentElement != NULL;
        currentElement = HIDGetNextDeviceElement(currentElement, kHIDElementTypeCollection))
    {
        if (i == elementIndex)
            return currentElement;
        ++i;
    }
    PsychErrorExitMsg(PsychError_internal, "Invalid collection index specified.  Has a device has been unplugged? Try rebuilding the device list");
    return NULL;  //make the compiler happy.

}


/*
        PsychHIDQueryOpenDeviceInterfaceFromDeviceIndex()

        Check the interface field of the libHIDUtilities device structure for NULLness.  libHIDUtilities.h seems to indicate that it is neccessary for application
        to invoke HIDCreateOpenDeviceInterface() before accessing a device.  However,
        1) libHIDUtilities provides no way to obtain a value for the required first argument to HIDCreateOpenDeviceInterface().
        2) Apple's example HID Explorer application does not call HIDCreateOpenDeviceInterface().
        3) Internally, libHIDUtilities invokes HIDCreateOpenDeviceInterface() itself when HIDBuildDeviceList() is called.

        Because the call lies within mysterious conditionals there is some uncertainty about whether HIDCreateOpenDeviceInterface() will always
        invoke HIDBuildDeviceList().  Therefore, PsychHID verifies that the device interface has been opened before accessing the elements of a device.

*/
psych_bool PsychHIDQueryOpenDeviceInterfaceFromDeviceIndex(int deviceIndex)
{
    PsychHIDVerifyInit();
    return PsychHIDGetDeviceInterfacePtrFromIndex(deviceIndex) != NULL;
}

void PsychHIDVerifyOpenDeviceInterfaceFromDeviceIndex(int deviceIndex)
{
    if (!PsychHIDQueryOpenDeviceInterfaceFromDeviceIndex(deviceIndex))
        PsychErrorExitMsg(PsychError_internal, "Device interface field is NULL.  libHIDUtilities failed to open the device interface ?");
}

/*
    PsychHIDGetTypeMaskStringFromTypeMask()

    Apple's HID Utilities library uses two different specificationos of the device type:
        - enumerated type HIDElementTypeMask, used to specify which element types to return.
        - unsigned long type identifier constants.

    The mask values will not mask the unsigned long type identifiers.
*/
void PsychHIDGetTypeMaskStringFromTypeMask(HIDElementTypeMask maskValue, char **pStr)
{
    char *maskNames[] = {"input", "output", "feature", "collection", "io", "all",};

    switch (maskValue)
    {
    case kHIDElementTypeInput:
        *pStr = maskNames[0];
        break;
    case kHIDElementTypeOutput:
        *pStr = maskNames[1];
        break;
    case kHIDElementTypeFeature:
        *pStr = maskNames[2];
        break;
    case kHIDElementTypeCollection:
        *pStr = maskNames[3];
        break;
    case kHIDElementTypeIO:
        *pStr = maskNames[4];
        break;
    case kHIDElementTypeAll:
        *pStr = maskNames[5];
        break;
    }
}


/*
    PsychHIDCountCollectionElements()

    Non-recursively count all elements of a collection which are of the specified type.

    HID element records hold three pointers to other element records: pPrevious, pChild and pSibling.  PsychHIDCountCollectionElements()
    operates on the theory that the members of a collection are its child and all of that child's siblings.


*/
int PsychHIDCountCollectionElements(pRecElement collectionRecord, HIDElementTypeMask elementTypeMask)
{
    pRecElement         currentElement;
    int                 numElements = 0;
    CFIndex             i, nmax;
    HIDElementTypeMask  currentElementMaskValue;

    CFArrayRef children = IOHIDElementGetChildren(collectionRecord);
    nmax = CFArrayGetCount(children);
    for (i = 0; i < nmax; i++) {
        currentElement = (pRecElement)CFArrayGetValueAtIndex(children, i);
        currentElementMaskValue = HIDConvertElementTypeToMask(IOHIDElementGetType(currentElement));
        if (currentElementMaskValue & elementTypeMask) ++numElements;
    }
    return numElements;
}


/*
    FindCollectionElements()

    Non-recursively return of a list of a collection's memember elements.

    HID element records hold three pointers to other element records: pPrevious, pChild and pSibling.  FindCollectionElements()
    operates on the theory that the members of a collection are its child and all of that child's siblings.

*/
int PsychHIDFindCollectionElements(pRecElement collectionRecord, HIDElementTypeMask elementTypeMask, pRecElement *collectionMembers, int maxListElements)
{
    pRecElement         currentElement;
    int                 numElements = 0;
    CFIndex             i, nmax;
    HIDElementTypeMask  currentElementMaskValue;

    CFArrayRef children = IOHIDElementGetChildren(collectionRecord);
    nmax = CFArrayGetCount(children);
    for (i = 0; i < nmax; i++) {
        currentElement = (pRecElement)CFArrayGetValueAtIndex(children, i);
        currentElementMaskValue = HIDConvertElementTypeToMask(IOHIDElementGetType(currentElement));
        if (currentElementMaskValue & elementTypeMask) {
            if (numElements == maxListElements) PsychErrorExitMsg(PsychError_internal, "Number of collection elements exceeds allocated storage space.");
            collectionMembers[numElements] = currentElement;
            ++numElements;
        }
    }

    return numElements;
}

#else

/* Linux and MS-Windows support via HIDLIB: */
/* ======================================== */

/*
    PSYCHHIDCheckInit()

    Check to see if we need to create the USB-HID device list. If it has not been created then create it.
*/
void PsychHIDVerifyInit(void)
{
    int busId, devId;
    pRecDevice currentDevice = NULL;
    struct hid_device_info* hid_dev = NULL;

    // If hid_devices list of all HID devices not yet initialized,
    // perform device enumeration:
    if (!hidlib_devices) {
        // Low-Level enumeration by HIDLIB:
        hidlib_devices = hid_enumerate(0x0, 0x0);

        // Build our own higher-level device list filled with info
        // from the low-level list:
        for (hid_dev = hidlib_devices; hid_dev != NULL; hid_dev = hid_dev->next) {
            // Allocate and zero-init high level struct currentDevice:
            currentDevice = calloc(1, sizeof(recDevice));

            // Copy low-level props to corresponding high-level props:
            currentDevice->usagePage = hid_dev->usage_page;
            currentDevice->usage = hid_dev->usage;
            // Abuse the "transport" string for the device path. On OS/X this just contains "USB":
            sprintf(&currentDevice->transport[0], "%s", hid_dev->path);
            currentDevice->vendorID = hid_dev->vendor_id;
            currentDevice->productID = hid_dev->product_id;
            currentDevice->version = hid_dev->release_number;
            if (hid_dev->manufacturer_string) wcstombs(&currentDevice->manufacturer[0], hid_dev->manufacturer_string, 256);
            if (hid_dev->product_string) wcstombs(&currentDevice->product[0], hid_dev->product_string, 256);
            if (hid_dev->serial_number) wcstombs(&currentDevice->serial[0], hid_dev->serial_number, 256);

            // Convert unique device path into unique numeric location id:
            if (PSYCH_SYSTEM == PSYCH_LINUX) {
                // Use USB bus-id and device-id as unique location identifier:
                sscanf(hid_dev->path, "%x:%x", &busId, &devId);
                currentDevice->locID = (double)((busId << 16) + (devId << 0));
            }

            if (PSYCH_SYSTEM == PSYCH_WINDOWS) {
                // Use device container id as unique location identifier.
                // This may only work on Windows-7+ and is a bit of a hack here,
                // the id is a GUID, nothing related to busId or devId. We init
                // devId with the hid_dev pointer value, to get a devId and thereby
                // location id in case proper parsing of a container id doesn't work:
                busId = 0;
                devId = (int)(size_t)hid_dev;
                if (strstr(hid_dev->path, "{")) sscanf(strstr(hid_dev->path, "{"), "{%x-%x", &busId, &devId);
                currentDevice->locID = (double)(((psych_uint64)busId << 32) + devId);
            }

            // Interface number is great for identifying DAQ devices, but not available
            // on OS/X, so this will be a Linux/Windows only thing.
            currentDevice->interfaceId = hid_dev->interface_number;

            // Enqueue record into linked list:
            currentDevice->pNext = hid_devices;
            hid_devices = currentDevice;
        }
    }

    return;
}

pRecDevice HIDGetFirstDevice(void)
{
    return hid_devices;
}

pRecDevice HIDGetNextDevice(pRecDevice pDevice)
{
    return pDevice->pNext;
}

/* HIDCountDevices(): Return count of all enumerated HID devices: */
psych_uint32 HIDCountDevices(void)
{
    pRecDevice cur_dev = hid_devices;
    psych_uint32 count = 0;

    while (cur_dev) {
        count++;
        cur_dev = cur_dev->pNext;
    }

    return count;
}

#endif