void PsychHIDInitializeHIDStandardInterfaces(void) { long KbDeviceUsagePages[NUMDEVICEUSAGES] = {kHIDPage_GenericDesktop, kHIDPage_GenericDesktop, kHIDPage_GenericDesktop, kHIDPage_GenericDesktop, kHIDPage_GenericDesktop, kHIDPage_GenericDesktop, kHIDPage_GenericDesktop}; long KbDeviceUsages[NUMDEVICEUSAGES] = {kHIDUsage_GD_Keyboard, kHIDUsage_GD_Keypad, kHIDUsage_GD_Mouse, kHIDUsage_GD_Pointer, kHIDUsage_GD_Joystick, kHIDUsage_GD_GamePad, kHIDUsage_GD_MultiAxisController}; int numDeviceUsages = NUMDEVICEUSAGES; int rc, i; for (i = 0; i < PSYCH_HID_MAX_DEVICES; i++) { queue[i] = NULL; psychHIDKbQueueCFRunLoopRef[i] = NULL; modifierKeyState[i] = 0; KbQueueThread[i] = NULL; queueIsAKeyboard[i] = FALSE; } // Init keyboard queue arrays: memset(&psychHIDKbQueueFirstPress[0], 0, sizeof(psychHIDKbQueueFirstPress)); memset(&psychHIDKbQueueFirstRelease[0], 0, sizeof(psychHIDKbQueueFirstRelease)); memset(&psychHIDKbQueueLastPress[0], 0, sizeof(psychHIDKbQueueLastPress)); memset(&psychHIDKbQueueLastRelease[0], 0, sizeof(psychHIDKbQueueLastRelease)); memset(&psychHIDKbQueueActive[0], 0, sizeof(psychHIDKbQueueActive)); memset(&psychHIDKbQueueScanKeys[0], 0, sizeof(psychHIDKbQueueScanKeys)); // Enumerate all possible candidates for default keyboard device: PsychHIDGetDeviceListByUsages(numDeviceUsages, KbDeviceUsagePages, KbDeviceUsages, &ndevices, deviceIndices, deviceRecords); // Nothing? if (ndevices == 0) printf("PTB-WARNING: No keyboard, keypad, mouse, touchpad, joystick etc. input devices detected! KbQueues won't work.\n"); // Create keyboard queue mutex for later use: PsychInitMutex(&KbQueueMutex); PsychInitCondition(&KbQueueCondition, NULL); return; }
PsychError PSYCHHIDKbTriggerWait(void) { pRecDevice deviceRecord; int i, deviceIndex, numDeviceIndices; long KeysUsagePage=0x07; // This is the keyboard usage page long KeysUsage; // This will contain the key code of the trigger key long KbDeviceUsagePages[NUMDEVICEUSAGES]= {0x01,0x01}, KbDeviceUsages[NUMDEVICEUSAGES]={0x06,0x07}; // Generic Desktop page (0x01), keyboard (0x06), keypad (0x07) int numDeviceUsages=NUMDEVICEUSAGES; int deviceIndices[PSYCH_HID_MAX_KEYBOARD_DEVICES]; pRecDevice deviceRecords[PSYCH_HID_MAX_KEYBOARD_DEVICES]; psych_bool isDeviceSpecified, foundUserSpecifiedDevice; double *timeValueOutput; IOHIDQueueInterface **queue; HRESULT result; IOHIDDeviceInterface122** interface=NULL; // This requires Mac OS X 10.3 or higher IOReturn success; IOHIDElementCookie triggerCookie; PsychPushHelp(useString, synopsisString, seeAlsoString); if(PsychIsGiveHelp()){PsychGiveHelp();return(PsychError_none);}; PsychErrorExit(PsychCapNumOutputArgs(1)); PsychErrorExit(PsychCapNumInputArgs(2)); //Specify trigger key code and the deviceNumber of the keyboard or keypad to scan. PsychHIDVerifyInit(); if(hidDataRef!=NULL) PsychErrorExitMsg(PsychError_user, "A queue is already running, you must call KbQueueRelease() before invoking KbTriggerWait."); //Identify the trigger { int KeysUsageInteger; if(!PsychCopyInIntegerArg(1, TRUE, &KeysUsageInteger)){ PsychErrorExitMsg(PsychError_user, "Keycode is required."); } KeysUsage=KeysUsageInteger; } //Choose the device index and its record PsychHIDGetDeviceListByUsages(numDeviceUsages, KbDeviceUsagePages, KbDeviceUsages, &numDeviceIndices, deviceIndices, deviceRecords); isDeviceSpecified=PsychCopyInIntegerArg(2, FALSE, &deviceIndex); if(isDeviceSpecified){ //make sure that the device number provided by the user is really a keyboard or keypad. for(i=0;i<numDeviceIndices;i++){ if(foundUserSpecifiedDevice=(deviceIndices[i]==deviceIndex)) break; } if(!foundUserSpecifiedDevice) PsychErrorExitMsg(PsychError_user, "Specified device number is not a keyboard or keypad device."); }else{ // set the keyboard or keypad device to be the first keyboard device or, if no keyboard, the first keypad i=0; if(numDeviceIndices==0) PsychErrorExitMsg(PsychError_user, "No keyboard or keypad devices detected."); else{ deviceIndex=deviceIndices[i]; } } deviceRecord=deviceRecords[i]; //Allocate and init out return arguments. PsychAllocOutDoubleArg(1, FALSE, &timeValueOutput); if(!timeValueOutput) PsychErrorExitMsg(PsychError_system, "Failed to allocate memory for output."); interface=deviceRecord->interface; if(!interface) PsychErrorExitMsg(PsychError_system, "Could not get interface to device."); // The following bracketed clause will get a cookie corresponding to the // trigger. If multiple keys were of interest, the code could be modified // trivially to iterate over an array of KeysUsage to generate an array of // corresponding cookies { CFArrayRef elements; psych_bool usedDictionary=FALSE; { CFDictionaryRef dict=NULL; // The next few lines define a dictionary describing the key of interest // If they are omitted, the code will still work, but all elements will match // initially rather than just the one key of interest, so the code will be // slower since it will iterate through hundreds of keys CFStringRef keys[2] = {CFSTR("UsagePage"), CFSTR("Usage")}; CFNumberRef values[2]; values[0] = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &KeysUsagePage); values[1] = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &KeysUsage); if(values[0]!=NULL && values[1]!=NULL){ // Even if they are NULL, it will be ok since dict can be NULL at the expense of some loss of efficiency dict = CFDictionaryCreate(kCFAllocatorDefault, (const void**)keys, (const void**)values, 2, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); } // copyMatchinfElements requires IOHIDDeviceInterface122, thus Mac OS X 10.3 or higher // elements would have to be obtained directly from IORegistry for 10.2 or earlier // If dict==NULL, all elements will match, leading to some inefficiency success = (*interface)->copyMatchingElements(interface, dict, &elements); if(dict){ usedDictionary=TRUE; CFRelease(dict); } if(values[0]) CFRelease(values[0]); if(values[1]) CFRelease(values[1]); if(!elements){ PsychErrorExitMsg(PsychError_user, "Specified key code not found on device."); } } { // elements will only contain one element in this implementation, but has the // advantage of generalizing to future derived implementations that listen // for multiple keys CFIndex i; for (i=0; i<CFArrayGetCount(elements); i++) { long number; CFDictionaryRef element= CFArrayGetValueAtIndex(elements, i); CFTypeRef object; if(!element) continue; if(!usedDictionary){ // Verify tht we are dealing with a keypad or keyboard object = CFDictionaryGetValue(element, CFSTR(kIOHIDElementUsageKey)); if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) continue; if (!CFNumberGetValue((CFNumberRef) object, kCFNumberLongType,&number)) continue; if(number!=KeysUsage) continue; // See if element corresponds to the desired key object = CFDictionaryGetValue(element, CFSTR(kIOHIDElementUsagePageKey)); if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) continue; if (!CFNumberGetValue((CFNumberRef) object, kCFNumberLongType, &number)) continue; if(number!=KeysUsagePage) continue; } // Get the cookie for this element object= (CFDictionaryGetValue(element, CFSTR(kIOHIDElementCookieKey))); if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) continue; if(!CFNumberGetValue((CFNumberRef) object, kCFNumberLongType, &number)) continue; triggerCookie = (IOHIDElementCookie) number; break; } if(CFArrayGetCount(elements)==i){ CFRelease(elements); PsychErrorExitMsg(PsychError_user, "Specified key code not found on device."); } CFRelease(elements); } } // Allocate for the queue queue=(*interface)->allocQueue(interface); if(!queue){ PsychErrorExitMsg(PsychError_system, "Failed to allocate event queue for detecting key press."); } // Create the queue result = (*queue)->create(queue, 0, 8); // 8 events can be stored before the earliest event is lost if (kIOReturnSuccess != result){ (*queue)->Release(queue); (*queue)=NULL; PsychErrorExitMsg(PsychError_system, "Failed to create event queue for detecting key press."); } // Add the trigger to the queue // If multiple keys were of interest, their cookies could be added in turn result = (*queue)->addElement(queue, triggerCookie, 0); if (kIOReturnSuccess != result){ result = (*queue)->dispose(queue); (*queue)->Release(queue); (*queue)=NULL; PsychErrorExitMsg(PsychError_system, "Failed to add trigger key to event queues."); } // Start the queue result = (*queue)->start(queue); if (kIOReturnSuccess != result){ result = (*queue)->dispose(queue); (*queue)->Release(queue); (*queue)=NULL; PsychErrorExitMsg(PsychError_system, "Failed to start event queues."); } // Watch for the trigger { IOHIDEventStruct event; while(1){ AbsoluteTime zeroTime = {0,0}; result = (*queue)->getNextEvent(queue, &event, zeroTime, 0); if(kIOReturnSuccess==result) break; PsychWaitIntervalSeconds((double)0.004); //surrender some time to other processes // If it were of interest to trigger selectively on key press or key release, // this could be evaluated by checking event.value (zero versus non-zero) // but this would put more code inside the loop // If multiple keys are registered via addElement (not the case here), the // cookie for the key responsible for the event can be obtained from // event.elementCookie } // If event.longValue is not NULL, the documentation indicates that it is up // to the caller to deallocate it. The circumstances under which a non-NULL // value would be generated are not specified. My guess is that some devices // might return a 64-bit value (e.g., a tracking device coordinate). // Keys, having only two states, shouldn't need this, but check and free to // be safe if ((event.longValueSize != 0) && (event.longValue != NULL)) free(event.longValue); // Set the time, using the same strategy as PsychTimeGlue's PsychGetPrecisionTimerSeconds // For code maintainability, it would be better if this conversion were performed // by a function in PsychTimeGlue { Nanoseconds timeNanoseconds=AbsoluteToNanoseconds(event.timestamp); UInt64 timeUInt64=UnsignedWideToUInt64(timeNanoseconds); double timeDouble=(double)timeUInt64; *timeValueOutput=timeDouble / 1000000000; } } // Clean up result = (*queue)->stop(queue); // Code from Apple sometimes removes elements from queue before disposing and sometimes does not // I can't see any reason to do so for a queue that's one line of code away from destruction // and therefore haven't result = (*queue)->dispose(queue); (*queue)->Release(queue); (*queue)=NULL; // Just in case queue is redefined as static in the future // PsychGetPrecisionTimerSeconds(timeValueOutput); // Less precise strategy than using event.timestamp return(PsychError_none); }
PsychError PsychHIDOSKbCheck(int deviceIndex, double* scanList) { pRecDevice deviceRecord; pRecElement currentElement, lastElement = NULL; int i, debuglevel = 0; static int numDeviceIndices = -1; int numDeviceUsages=NUMDEVICEUSAGES; long KbDeviceUsagePages[NUMDEVICEUSAGES]= {kHIDPage_GenericDesktop, kHIDPage_GenericDesktop, kHIDPage_GenericDesktop, kHIDPage_GenericDesktop, kHIDPage_GenericDesktop, kHIDPage_GenericDesktop, kHIDPage_GenericDesktop}; long KbDeviceUsages[NUMDEVICEUSAGES]={kHIDUsage_GD_Keyboard, kHIDUsage_GD_Keypad, kHIDUsage_GD_Mouse, kHIDUsage_GD_Pointer, kHIDUsage_GD_Joystick, kHIDUsage_GD_GamePad, kHIDUsage_GD_MultiAxisController}; static int deviceIndices[PSYCH_HID_MAX_KEYBOARD_DEVICES]; static pRecDevice deviceRecords[PSYCH_HID_MAX_KEYBOARD_DEVICES]; psych_bool isDeviceSpecified, foundUserSpecifiedDevice; double *timeValueOutput, *isKeyDownOutput, *keyArrayOutput; int m, n, p, nout; double dummyKeyDown; double dummykeyArrayOutput[256]; uint32_t usage, usagePage; int value; // We query keyboard and keypad devices only on first invocation, then cache and recycle the data: if (numDeviceIndices == -1) { PsychHIDVerifyInit(); PsychHIDGetDeviceListByUsages(numDeviceUsages, KbDeviceUsagePages, KbDeviceUsages, &numDeviceIndices, deviceIndices, deviceRecords); } // Choose the device index and its record isDeviceSpecified = (deviceIndex != INT_MAX); if(isDeviceSpecified){ //make sure that the device number provided by the user is really a keyboard or keypad. if (deviceIndex < 0) { debuglevel = 1; deviceIndex = -deviceIndex; } for(i=0;i<numDeviceIndices;i++){ if ((foundUserSpecifiedDevice=(deviceIndices[i]==deviceIndex))) break; } if(!foundUserSpecifiedDevice) PsychErrorExitMsg(PsychError_user, "Specified device number is not a keyboard or keypad device."); } else { // set the keyboard or keypad device to be the first keyboard device or, if no keyboard, the first keypad i=0; if(numDeviceIndices==0) PsychErrorExitMsg(PsychError_user, "No keyboard or keypad devices detected."); else{ deviceIndex=deviceIndices[i]; } } deviceRecord=deviceRecords[i]; // Allocate and init out return arguments. // Either alloc out the arguments, or redirect to // internal dummy variables. This to avoid mxMalloc() call overhead // inside the PsychAllocOutXXX() routines: nout = PsychGetNumNamedOutputArgs(); // keyDown flag: if (nout >= 1) { PsychAllocOutDoubleArg(1, FALSE, &isKeyDownOutput); } else { isKeyDownOutput = &dummyKeyDown; } *isKeyDownOutput= (double) FALSE; // key state vector: if (nout >= 3) { PsychAllocOutDoubleMatArg(3, FALSE, 1, 256, 1, &keyArrayOutput); } else { keyArrayOutput = &dummykeyArrayOutput[0]; } memset((void*) keyArrayOutput, 0, sizeof(double) * 256); // Query timestamp: if (nout >= 2) { PsychAllocOutDoubleArg(2, FALSE, &timeValueOutput); // Get query timestamp: PsychGetPrecisionTimerSeconds(timeValueOutput); } // Make sure our keyboard query mechanism is not blocked for security reasons, e.g., // secure password entry field active in another process, i.e., EnableSecureEventInput() active. if (PsychHIDWarnInputDisabled("PsychHID('KbCheck')")) return(PsychError_none); //step through the elements of the device. Set flags in the return array for down keys. for(currentElement=HIDGetFirstDeviceElement(deviceRecord, kHIDElementTypeInput | kHIDElementTypeCollection); (currentElement != NULL) && (currentElement != lastElement); currentElement=HIDGetNextDeviceElement(currentElement, kHIDElementTypeInput | kHIDElementTypeCollection)) { // Keep track of last queried element: lastElement = currentElement; #ifndef __LP64__ usage = currentElement->usage; usagePage = currentElement->usagePage; #else usage = IOHIDElementGetUsage(currentElement); usagePage = IOHIDElementGetUsagePage(currentElement); // printf("PTB-DEBUG: [KbCheck]: ce %p page %d usage: %d isArray: %d\n", currentElement, usagePage, usage, IOHIDElementIsArray(currentElement)); if (IOHIDElementGetType(currentElement) == kIOHIDElementTypeCollection) { CFArrayRef children = IOHIDElementGetChildren(currentElement); if (!children) continue; CFIndex idx, cnt = CFArrayGetCount(children); for ( idx = 0; idx < cnt; idx++ ) { IOHIDElementRef tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(children, idx); if (tIOHIDElementRef && ((IOHIDElementGetType(tIOHIDElementRef) == kIOHIDElementTypeInput_Button) || (IOHIDElementGetType(tIOHIDElementRef) == kIOHIDElementTypeInput_ScanCodes))) { usage = IOHIDElementGetUsage(tIOHIDElementRef); if ((usage <= 256) && (usage >= 1) && ( (scanList == NULL) || (scanList[usage - 1] > 0) )) { value = (int) IOHIDElement_GetValue(tIOHIDElementRef, kIOHIDValueScaleTypePhysical); if (debuglevel > 0) printf("PTB-DEBUG: [KbCheck]: usage: %x value: %d \n", usage, value); keyArrayOutput[usage - 1] = (value || (int) keyArrayOutput[usage - 1]); *isKeyDownOutput = keyArrayOutput[usage - 1] || *isKeyDownOutput; } } } // Done with this currentElement, which was a collection of buttons/keys. // Iterate to next currentElement: continue; } #endif // Classic path, or 64-Bit path for non-collection elements: if(((usagePage == kHIDPage_KeyboardOrKeypad) || (usagePage == kHIDPage_Button)) && (usage <= 256) && (usage >= 1) && ( (scanList == NULL) || (scanList[usage - 1] > 0) ) ) { #ifndef __LP64__ value = (int) HIDGetElementValue(deviceRecord, currentElement); #else value = (int) IOHIDElement_GetValue(currentElement, kIOHIDValueScaleTypePhysical); #endif if (debuglevel > 0) printf("PTB-DEBUG: [KbCheck]: usage: %x value: %d \n", usage, value); keyArrayOutput[usage - 1]=(value || (int) keyArrayOutput[usage - 1]); *isKeyDownOutput= keyArrayOutput[usage - 1] || *isKeyDownOutput; } } return(PsychError_none); }