// Async processing thread for keyboard events: void* KbQueueWorkerThreadMain(void* dummy) { int rc; // Try to raise our priority: We ask to switch ourselves (NULL) to priority class 1 aka // realtime scheduling, with a tweakPriority of +1, ie., raise the relative // priority level by +1 wrt. to the current level: if ((rc = PsychSetThreadPriority(NULL, 1, 1)) > 0) { printf("PsychHID: KbQueueStart: Failed to switch to realtime priority [%s].\n", strerror(rc)); } while (1) { PsychLockMutex(&KbQueueMutex); // Check if we should terminate: if (KbQueueThreadTerminate) break; PsychUnlockMutex(&KbQueueMutex); // Perform event processing until no more events are pending: KbQueueProcessEvents(TRUE); } // Done. Unlock the mutex: PsychUnlockMutex(&KbQueueMutex); // printf("DEBUG: THREAD TERMINATING...\n"); fflush(NULL); // Return and terminate: return(NULL); }
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); }
/* 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); }
/* Called at each end-of-stream event at end of playback: */ static void PsychEOSCallback(GstAppSink *sink, gpointer user_data) { PsychMovieRecordType* movie = (PsychMovieRecordType*) user_data; PsychLockMutex(&movie->mutex); //printf("PTB-DEBUG: Videosink reached EOS.\n"); PsychUnlockMutex(&movie->mutex); PsychSignalCondition(&movie->condition); return; }
/* Not used by us, but needs to be defined as no-op anyway: */ static GstFlowReturn PsychNewBufferListCallback(GstAppSink *sink, gpointer user_data) { PsychMovieRecordType* movie = (PsychMovieRecordType*) user_data; PsychLockMutex(&movie->mutex); //printf("PTB-DEBUG: New Bufferlist received.\n"); PsychUnlockMutex(&movie->mutex); PsychSignalCondition(&movie->condition); return(GST_FLOW_OK); }
psych_bool PsychHIDFlushEventBuffer(int deviceIndex) { if (deviceIndex < 0) deviceIndex = PsychHIDGetDefaultKbQueueDevice(); if (!hidEventBuffer[deviceIndex]) return(FALSE); PsychLockMutex(&hidEventBufferMutex[deviceIndex]); hidEventBufferReadPos[deviceIndex] = hidEventBufferWritePos[deviceIndex] = 0; PsychUnlockMutex(&hidEventBufferMutex[deviceIndex]); return(TRUE); }
void PsychHIDOSKbQueueRelease(int deviceIndex) { // Get true keyboardqueue index assigned to deviceIndex from original user provided deviceIndex: deviceIndex = PsychHIDOSGetKbQueueDevice(deviceIndex, NULL); // Keyboard queue for this deviceIndex already exists? if (NULL == psychHIDKbQueueFirstPress[deviceIndex]) { // No. Nothing to do then. return; } // Ok, we have a keyboard queue. Stop any operation on it first: PsychHIDOSKbQueueStop(deviceIndex); // The mutex will be automatically unlocked and destroyed by the CFRunLoop thread // so it isn't even declared in this routine if (psychHIDKbQueueCFRunLoopRef[deviceIndex]) { // Shutdown the processing thread for this queue: PsychLockMutex(&KbQueueMutex); // Stop the CFRunLoop, which will allow its associated thread to exit: CFRunLoopStop(psychHIDKbQueueCFRunLoopRef[deviceIndex]); // Done. PsychUnlockMutex(&KbQueueMutex); // Shutdown the thread, wait for its termination: PsychDeleteThread(&KbQueueThread[deviceIndex]); KbQueueThread[deviceIndex] = NULL; // Release the CFRunLoop for this queue: CFRelease(psychHIDKbQueueCFRunLoopRef[deviceIndex]); psychHIDKbQueueCFRunLoopRef[deviceIndex] = NULL; // Release queue object: CFRelease(queue[deviceIndex]); queue[deviceIndex] = NULL; } // Release its data structures: free(psychHIDKbQueueFirstPress[deviceIndex]); psychHIDKbQueueFirstPress[deviceIndex] = NULL; free(psychHIDKbQueueFirstRelease[deviceIndex]); psychHIDKbQueueFirstRelease[deviceIndex] = NULL; free(psychHIDKbQueueLastPress[deviceIndex]); psychHIDKbQueueLastPress[deviceIndex] = NULL; free(psychHIDKbQueueLastRelease[deviceIndex]); psychHIDKbQueueLastRelease[deviceIndex] = NULL; free(psychHIDKbQueueScanKeys[deviceIndex]); psychHIDKbQueueScanKeys[deviceIndex] = NULL; // Release kbqueue event buffer: PsychHIDDeleteEventBuffer(deviceIndex); // Done. return; }
unsigned int PsychHIDAvailEventBuffer(int deviceIndex) { unsigned int navail; if (deviceIndex < 0) deviceIndex = PsychHIDGetDefaultKbQueueDevice(); if (!hidEventBuffer[deviceIndex]) return(0); PsychLockMutex(&hidEventBufferMutex[deviceIndex]); navail = hidEventBufferWritePos[deviceIndex] - hidEventBufferReadPos[deviceIndex]; PsychUnlockMutex(&hidEventBufferMutex[deviceIndex]); return(navail); }
void PsychHIDOSKbQueueStart(int deviceIndex) { psych_bool queueActive; int i; // Get true keyboardqueue index assigned to deviceIndex from original user provided deviceIndex: deviceIndex = PsychHIDOSGetKbQueueDevice(deviceIndex, NULL); // Does Keyboard queue for this deviceIndex already exist? if (NULL == psychHIDKbQueueFirstPress[deviceIndex]) { // No. Bad bad... printf("PsychHID-ERROR: Tried to start processing on non-existent keyboard queue for deviceIndex %i! Call KbQueueCreate first!\n", deviceIndex); PsychErrorExitMsg(PsychError_user, "Invalid keyboard 'deviceIndex' specified. No queue for that device yet!"); } // Keyboard queue already stopped? Then we ain't nothing to do: if (psychHIDKbQueueActive[deviceIndex]) return; // Queue is inactive. Start it: // Will this be the first active queue, ie., aren't there any queues running so far? queueActive = FALSE; for (i = 0; i < PSYCH_HID_MAX_DEVICES; i++) { queueActive |= psychHIDKbQueueActive[i]; } PsychLockMutex(&KbQueueMutex); // Clear out current state for this queue: memset(psychHIDKbQueueFirstPress[deviceIndex] , 0, (256 * sizeof(double))); memset(psychHIDKbQueueFirstRelease[deviceIndex] , 0, (256 * sizeof(double))); memset(psychHIDKbQueueLastPress[deviceIndex] , 0, (256 * sizeof(double))); memset(psychHIDKbQueueLastRelease[deviceIndex] , 0, (256 * sizeof(double))); modifierKeyState[deviceIndex] = 0; // Start event collection in the queue: IOHIDQueueStart(queue[deviceIndex]); // Mark this queue as logically started: psychHIDKbQueueActive[deviceIndex] = TRUE; // Queue started. PsychUnlockMutex(&KbQueueMutex); return; }
int PsychHIDReturnEventFromEventBuffer(int deviceIndex, int outArgIndex, double maxWaitTimeSecs) { unsigned int navail; PsychHIDEventRecord evt; PsychGenericScriptType *retevent; double* foo = NULL; const char *FieldNames[] = { "Time", "Pressed", "Keycode", "CookedKey" }; 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: PsychAllocOutStructArray(outArgIndex, kPsychArgOptional, 1, 4, FieldNames, &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); return(navail - 1); } else { // Return empty matrix: PsychCopyOutDoubleMatArg(outArgIndex, kPsychArgOptional, 0, 0, 0, foo); return(0); } }
// Display link callback: Needed so we can actually start the display link: // Gets apparently called from a separate high-priority thread, close to vblank // time. "inNow" is the timestamp of last vblank. static CVReturn PsychCVDisplayLinkOutputCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) { double tVBlank; CVTimeStamp tVbl; double tHost; // Retrieve screenId of associated display screen: int screenId = (int) (long int) displayLinkContext; // Extra guard against shutdown races: if (NULL == cvDisplayLink[screenId]) return(kCVReturnSuccess); // Translate CoreVideo inNow timestamp with time of last vbl from gpu time // to host system time, aka our GetSecs() timebase: memset(&tVbl, 0, sizeof(tVbl)); tVbl.version = 0; tVbl.flags = kCVTimeStampHostTimeValid; CVDisplayLinkTranslateTime(displayLink, inNow, &tVbl); tVBlank = (double) tVbl.hostTime / (double) 1000000000; // Store timestamp in our shared data structure, also increment virtual vblank counter: PsychLockMutex(&(cvDisplayLinkData[screenId].mutex)); cvDisplayLinkData[screenId].vblCount++; cvDisplayLinkData[screenId].vblTimestamp = tVBlank; PsychUnlockMutex(&(cvDisplayLinkData[screenId].mutex)); // Low-level timestamp debugging requested? if (PsychPrefStateGet_Verbosity() > 20) { // Compare CV timestamps against host time for correctness check. We wait 4 msecs, // then take tHost and hopefully tHost will be at least 4 msecs later than the // computed vblank timestamp tVBlank: PsychWaitIntervalSeconds(0.004); PsychGetAdjustedPrecisionTimerSeconds(&tHost); // Caution: Don't run from Matlab GUI! This printf will crash Matlab otherwise. printf("CVCallback: %i : tHost = %lf secs, tVBlank = %lf secs. tHost - tVBlank = %lf secs.\n", screenId, tHost, tVBlank, tHost - tVBlank); } return(kCVReturnSuccess); }
void PsychHIDOSKbQueueFlush(int deviceIndex) { LPDIRECTINPUTDEVICE8 kb; HRESULT rc; DWORD dwItems = INFINITE; if (deviceIndex < 0) { deviceIndex = PsychHIDGetDefaultKbQueueDevice(); // Ok, deviceIndex now contains our default keyboard to use - The first suitable keyboard. } if ((deviceIndex < 0) || (deviceIndex >= ndevices)) { // Out of range index: PsychErrorExitMsg(PsychError_user, "Invalid 'deviceIndex' specified. No such device!"); } // Does Keyboard queue for this deviceIndex already exist? if (NULL == psychHIDKbQueueFirstPress[deviceIndex]) { // No. Bad bad... printf("PsychHID-ERROR: Tried to flush non-existent keyboard queue for deviceIndex %i! Call KbQueueCreate first!\n", deviceIndex); PsychErrorExitMsg(PsychError_user, "Invalid 'deviceIndex' specified. No queue for that device yet!"); } kb = GetXDevice(deviceIndex); // Clear out current state for this queue: PsychLockMutex(&KbQueueMutex); // Flush device buffer: rc = kb->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), NULL, &dwItems, 0); // Clear our buffer: memset(psychHIDKbQueueFirstPress[deviceIndex] , 0, (256 * sizeof(double))); memset(psychHIDKbQueueFirstRelease[deviceIndex] , 0, (256 * sizeof(double))); memset(psychHIDKbQueueLastPress[deviceIndex] , 0, (256 * sizeof(double))); memset(psychHIDKbQueueLastRelease[deviceIndex] , 0, (256 * sizeof(double))); PsychUnlockMutex(&KbQueueMutex); return; }
/* PsychOSGetVBLTimeAndCount() Returns absolute system time of last VBL and current total count of VBL interrupts since startup of gfx-system for the given screen. Returns a time of -1 and a count of 0 if this feature is unavailable on the given OS/Hardware configuration. */ double PsychOSGetVBLTimeAndCount(PsychWindowRecordType *windowRecord, psych_uint64* vblCount) { unsigned int screenid = windowRecord->screenNumber; double cvTime; // Should we use CoreVideo display link timestamping? if (cvDisplayLink[screenid]) { // Yes: Retrieve data from our shared data structure: PsychLockMutex(&(cvDisplayLinkData[screenid].mutex)); *vblCount = cvDisplayLinkData[screenid].vblCount; cvTime = cvDisplayLinkData[screenid].vblTimestamp; PsychUnlockMutex(&(cvDisplayLinkData[screenid].mutex)); return(cvTime); } else { // Unsupported: *vblCount = 0; return(-1); } }
/* Atomically release the 'mutex' lock and go to sleep, waiting for the 'condition' variable * being signalled, then waking up and trying to re-lock the 'mutex'. Will return with * mutex locked. */ int PsychWaitCondition(psych_condition* condition, psych_mutex* mutex) { int rc, rc2; // MS-Windows: Unlock mutex, wait for our event-object to go to signalled // state, then reacquire the mutex: if ((rc = PsychUnlockMutex(mutex))) { printf("PTB-CRITICAL: In call to PsychWaitCondition(%p, %p): PsychUnlockMutex(%p) FAILED [rc=%i]! Expect disaster!!!", condition, mutex, mutex, rc); return(rc); } if ((rc = WaitForSingleObject(*condition, INFINITE)) != WAIT_OBJECT_0) { rc = (int) GetLastError(); printf("PTB-CRITICAL: In call to PsychWaitCondition(%p, %p): WaitForSingleObject(%p) FAILED [GetLastError()=%i]! Expect disaster!!!", condition, mutex, condition, rc); } if ((rc2 = PsychLockMutex(mutex))) { printf("PTB-CRITICAL: In call to PsychWaitCondition(%p, %p): PsychLockMutex(%p) FAILED [rc=%i]! Expect disaster!!!", condition, mutex, mutex, rc2); return(rc2); } return(rc); }
void PsychHIDOSKbQueueFlush(int deviceIndex) { // Get true keyboardqueue index assigned to deviceIndex from original user provided deviceIndex: deviceIndex = PsychHIDOSGetKbQueueDevice(deviceIndex, NULL); // Does Keyboard queue for this deviceIndex already exist? if (NULL == psychHIDKbQueueFirstPress[deviceIndex]) { // No. Bad bad... printf("PsychHID-ERROR: Tried to flush non-existent keyboard queue for deviceIndex %i! Call KbQueueCreate first!\n", deviceIndex); PsychErrorExitMsg(PsychError_user, "Invalid keyboard 'deviceIndex' specified. No queue for that device yet!"); } // Clear out current state for this queue: PsychLockMutex(&KbQueueMutex); memset(psychHIDKbQueueFirstPress[deviceIndex] , 0, (256 * sizeof(double))); memset(psychHIDKbQueueFirstRelease[deviceIndex] , 0, (256 * sizeof(double))); memset(psychHIDKbQueueLastPress[deviceIndex] , 0, (256 * sizeof(double))); memset(psychHIDKbQueueLastRelease[deviceIndex] , 0, (256 * sizeof(double))); modifierKeyState[deviceIndex] = 0; PsychUnlockMutex(&KbQueueMutex); return; }
/* Video data arrived callback: Purely for documentation, because only used if oldstyle == true, that is *never*. */ static gboolean PsychHaveVideoDataCallback(GstPad *pad, GstBuffer *buffer, gpointer dataptr) { unsigned int alloc_size; PsychMovieRecordType* movie = (PsychMovieRecordType*) dataptr; PsychLockMutex(&movie->mutex); if (movie->rate == 0) { PsychUnlockMutex(&movie->mutex); return(TRUE); } /* Perform onetime-init for the buffer */ if (NULL == movie->imageBuffer) { // Allocate the buffer: alloc_size = buffer->size; if ((int) buffer->size < movie->width * movie->height * 4) { alloc_size = movie->width * movie->height * 4; printf("PTB-DEBUG: Overriding unsafe buffer size of %d bytes with %d bytes.\n", buffer->size, alloc_size); } // printf("PTB-DEBUG: Allocating image buffer of %d bytes.\n", alloc_size); movie->imageBuffer = calloc(1, alloc_size); } // Copy new image data to our buffer: memcpy(movie->imageBuffer, buffer->data, buffer->size); movie->frameAvail++; // printf("PTB-DEBUG: New frame %d [size %d] %lf.\n", movie->frameAvail, buffer->size, (double) buffer->timestamp / (double) 1e9); // Fetch presentation timestamp and convert to seconds: movie->pts = (double) buffer->timestamp / (double) 1e9; PsychUnlockMutex(&movie->mutex); PsychSignalCondition(&movie->condition); return(TRUE); }
void PsychHIDOSKbQueueStop(int deviceIndex) { psych_bool queueActive; int i; // Get true keyboardqueue index assigned to deviceIndex from original user provided deviceIndex: deviceIndex = PsychHIDOSGetKbQueueDevice(deviceIndex, NULL); // Keyboard queue for this deviceIndex already exists? if (NULL == psychHIDKbQueueFirstPress[deviceIndex]) { // No. Nothing to do then. return; } // Keyboard queue already stopped? if (!psychHIDKbQueueActive[deviceIndex]) return; // Queue is active. Stop it: PsychLockMutex(&KbQueueMutex); // Stop event collection in the queue: IOHIDQueueStop(queue[deviceIndex]); // Mark queue logically stopped: psychHIDKbQueueActive[deviceIndex] = FALSE; PsychUnlockMutex(&KbQueueMutex); // Was this the last active queue? queueActive = FALSE; for (i = 0; i < PSYCH_HID_MAX_DEVICES; i++) { queueActive |= psychHIDKbQueueActive[i]; } return; }
/* Atomically release the 'mutex' lock and go to sleep, waiting for the 'condition' variable * being signalled, then waking up and trying to re-lock the 'mutex'. Will return with * mutex locked. * * Like PsychWaitCondition, but function will timeout if it fails being signalled before * timeout interval 'maxwaittimesecs' expires. In any case, it will only return after * reacquiring the mutex. It will retun zero on successfull wait, non-zero (WAIT_TIMEOUT) if * timeout was triggered without the condition being signalled. */ int PsychTimedWaitCondition(psych_condition* condition, psych_mutex* mutex, double maxwaittimesecs) { int rc, rc2; int maxmillisecs; if (maxwaittimesecs < 0) { printf("PTB-CRITICAL: In call to PsychTimedWaitCondition(%p, %p, %f): NEGATIVE timeout value passed! Clamping to zero! Expect trouble!!", condition, mutex, maxwaittimesecs); maxmillisecs = 0; } else { // Convert seconds to milliseconds: maxmillisecs = (int) (maxwaittimesecs * 1000.0); } // MS-Windows: Unlock mutex, wait for our event-object to go to signalled // state, then reacquire the mutex: if ((rc = PsychUnlockMutex(mutex))) { printf("PTB-CRITICAL: In call to PsychTimedWaitCondition(%p, %p, %f): PsychUnlockMutex(%p) FAILED [rc=%i]! Expect disaster!!!", condition, mutex, maxwaittimesecs, mutex, rc); return(rc); } rc = (int) WaitForSingleObject(*condition, (DWORD) maxmillisecs); if ((rc != WAIT_OBJECT_0) && (rc != WAIT_TIMEOUT)) { rc = (int) GetLastError(); printf("PTB-CRITICAL: In call to PsychTimedWaitCondition(%p, %p, %f): WaitForSingleObject(%p, %i) FAILED [GetLastError()=%i]! Expect disaster!!!", condition, mutex, maxwaittimesecs, condition, maxmillisecs, rc); } if ((rc2 = PsychLockMutex(mutex))) { printf("PTB-CRITICAL: In call to PsychTimedWaitCondition(%p, %p, %f): PsychLockMutex(%p) FAILED [rc=%i]! Expect disaster!!!", condition, mutex, maxwaittimesecs, mutex, rc2); return(rc2); } // Success: Either in the sense of "signalled" or in the sense of "timeout". // rc will tell the caller what happened: 0 = Signalled, 0x00000102L == WAIT_TIMEOUT for timeout. return(rc); }
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: 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); } }
/* * PsychGSGetTextureFromMovie() -- Create an OpenGL texture map from a specific videoframe from given movie object. * * win = Window pointer of onscreen window for which a OpenGL texture should be created. * moviehandle = Handle to the movie object. * checkForImage = true == Just check if new image available, false == really retrieve the image, blocking if necessary. * timeindex = When not in playback mode, this allows specification of a requested frame by presentation time. * If set to -1, or if in realtime playback mode, this parameter is ignored and the next video frame is returned. * out_texture = Pointer to the Psychtoolbox texture-record where the new texture should be stored. * presentation_timestamp = A ptr to a double variable, where the presentation timestamp of the returned frame should be stored. * * Returns true (1) on success, false (0) if no new image available, -1 if no new image available and there won't be any in future. */ int PsychGSGetTextureFromMovie(PsychWindowRecordType *win, int moviehandle, int checkForImage, double timeindex, PsychWindowRecordType *out_texture, double *presentation_timestamp) { GstElement *theMovie; unsigned int failcount=0; double rate; double targetdelta, realdelta, frames; // PsychRectType outRect; GstBuffer *videoBuffer = NULL; gint64 bufferIndex; double deltaT = 0; GstEvent *event; if (!PsychIsOnscreenWindow(win)) { PsychErrorExitMsg(PsychError_user, "Need onscreen window ptr!!!"); } if (moviehandle < 0 || moviehandle >= PSYCH_MAX_MOVIES) { PsychErrorExitMsg(PsychError_user, "Invalid moviehandle provided."); } if ((timeindex!=-1) && (timeindex < 0 || timeindex >= 10000.0)) { PsychErrorExitMsg(PsychError_user, "Invalid timeindex provided."); } if (NULL == out_texture && !checkForImage) { PsychErrorExitMsg(PsychError_internal, "NULL-Ptr instead of out_texture ptr passed!!!"); } // Fetch references to objects we need: theMovie = movieRecordBANK[moviehandle].theMovie; if (theMovie == NULL) { PsychErrorExitMsg(PsychError_user, "Invalid moviehandle provided. No movie associated with this handle."); } // Allow context task to do its internal bookkeeping and cleanup work: PsychGSProcessMovieContext(movieRecordBANK[moviehandle].MovieContext, FALSE); // If this is a pure audio "movie" with no video tracks, we always return failed, // as those certainly don't have movie frames associated. if (movieRecordBANK[moviehandle].nrVideoTracks == 0) return((checkForImage) ? -1 : FALSE); // Get current playback rate: rate = movieRecordBANK[moviehandle].rate; // Is movie actively playing (automatic async playback, possibly with synced sound)? // If so, then we ignore the 'timeindex' parameter, because the automatic playback // process determines which frames should be delivered to PTB when. This function will // simply wait or poll for arrival/presence of a new frame that hasn't been fetched // in previous calls. if (0 == rate) { // Movie playback inactive. We are in "manual" mode: No automatic async playback, // no synced audio output. The user just wants to manually fetch movie frames into // textures for manual playback in a standard Matlab-loop. // First pass - checking for new image? if (checkForImage) { // Image for specific point in time requested? if (timeindex >= 0) { // Yes. We try to retrieve the next possible image for requested timeindex. // Seek to target timeindex: PsychGSSetMovieTimeIndex(moviehandle, timeindex, FALSE); } else { // No. We just retrieve the next frame, given the current position. // Nothing to do so far... } // Check for frame availability happens down there in the shared check code... } } // Should we just check for new image? If so, just return availability status: if (checkForImage) { PsychLockMutex(&movieRecordBANK[moviehandle].mutex); if ((((0 != rate) && movieRecordBANK[moviehandle].frameAvail) || ((0 == rate) && movieRecordBANK[moviehandle].preRollAvail)) && !gst_app_sink_is_eos(GST_APP_SINK(movieRecordBANK[moviehandle].videosink))) { // New frame available. Unlock and report success: //printf("PTB-DEBUG: NEW FRAME %d\n", movieRecordBANK[moviehandle].frameAvail); PsychUnlockMutex(&movieRecordBANK[moviehandle].mutex); return(true); } // Is this the special case of a movie without video, but only sound? In that case // we always return a 'false' because there ain't no image to return. We check this // indirectly - If the imageBuffer is NULL then the video callback hasn't been called. if (oldstyle && (NULL == movieRecordBANK[moviehandle].imageBuffer)) { PsychUnlockMutex(&movieRecordBANK[moviehandle].mutex); return(false); } // None available. Any chance there will be one in the future? if (gst_app_sink_is_eos(GST_APP_SINK(movieRecordBANK[moviehandle].videosink)) && movieRecordBANK[moviehandle].loopflag == 0) { // No new frame available and there won't be any in the future, because this is a non-looping // movie that has reached its end. PsychUnlockMutex(&movieRecordBANK[moviehandle].mutex); return(-1); } else { // No new frame available yet: PsychUnlockMutex(&movieRecordBANK[moviehandle].mutex); //printf("PTB-DEBUG: NO NEW FRAME\n"); return(false); } } // If we reach this point, then an image fetch is requested. If no new data // is available we shall block: PsychLockMutex(&movieRecordBANK[moviehandle].mutex); // printf("PTB-DEBUG: Blocking fetch start %d\n", movieRecordBANK[moviehandle].frameAvail); if (((0 != rate) && !movieRecordBANK[moviehandle].frameAvail) || ((0 == rate) && !movieRecordBANK[moviehandle].preRollAvail)) { // No new frame available. Perform a blocking wait: PsychTimedWaitCondition(&movieRecordBANK[moviehandle].condition, &movieRecordBANK[moviehandle].mutex, 10.0); // Recheck: if (((0 != rate) && !movieRecordBANK[moviehandle].frameAvail) || ((0 == rate) && !movieRecordBANK[moviehandle].preRollAvail)) { // Game over! Wait timed out after 10 secs. PsychUnlockMutex(&movieRecordBANK[moviehandle].mutex); printf("PTB-ERROR: No new video frame received after timeout of 10 seconds! Something's wrong. Aborting fetch.\n"); return(FALSE); } // At this point we should have at least one frame available. // printf("PTB-DEBUG: After blocking fetch start %d\n", movieRecordBANK[moviehandle].frameAvail); } // We're here with at least one frame available and the mutex lock held. // Preroll case is simple: movieRecordBANK[moviehandle].preRollAvail = 0; // Perform texture fetch & creation: if (oldstyle) { // Reset frame available flag: movieRecordBANK[moviehandle].frameAvail = 0; // This will retrieve an OpenGL compatible pointer to the pixel data and assign it to our texmemptr: out_texture->textureMemory = (GLuint*) movieRecordBANK[moviehandle].imageBuffer; } else { // Active playback mode? if (0 != rate) { // Active playback mode: One less frame available after our fetch: movieRecordBANK[moviehandle].frameAvail--; if (PsychPrefStateGet_Verbosity()>4) printf("PTB-DEBUG: Pulling from videosink, %d buffers avail...\n", movieRecordBANK[moviehandle].frameAvail); // Clamp frameAvail to queue lengths: if ((int) gst_app_sink_get_max_buffers(GST_APP_SINK(movieRecordBANK[moviehandle].videosink)) < movieRecordBANK[moviehandle].frameAvail) { movieRecordBANK[moviehandle].frameAvail = gst_app_sink_get_max_buffers(GST_APP_SINK(movieRecordBANK[moviehandle].videosink)); } // This will pull the oldest video buffer from the videosink. It would block if none were available, // but that won't happen as we wouldn't reach this statement if none were available. It would return // NULL if the stream would be EOS or the pipeline off, but that shouldn't ever happen: videoBuffer = gst_app_sink_pull_buffer(GST_APP_SINK(movieRecordBANK[moviehandle].videosink)); } else { // Passive fetch mode: Use prerolled buffers after seek: // These are available even after eos... videoBuffer = gst_app_sink_pull_preroll(GST_APP_SINK(movieRecordBANK[moviehandle].videosink)); } // We can unlock early, thanks to videosink's internal buffering: PsychUnlockMutex(&movieRecordBANK[moviehandle].mutex); if (videoBuffer) { // Assign pointer to videoBuffer's data directly: Avoids one full data copy compared to oldstyle method. out_texture->textureMemory = (GLuint*) GST_BUFFER_DATA(videoBuffer); // Assign pts presentation timestamp in pipeline stream time and convert to seconds: movieRecordBANK[moviehandle].pts = (double) GST_BUFFER_TIMESTAMP(videoBuffer) / (double) 1e9; if (GST_CLOCK_TIME_IS_VALID(GST_BUFFER_DURATION(videoBuffer))) deltaT = (double) GST_BUFFER_DURATION(videoBuffer) / (double) 1e9; bufferIndex = GST_BUFFER_OFFSET(videoBuffer); } else { printf("PTB-ERROR: No new video frame received in gst_app_sink_pull_buffer! Something's wrong. Aborting fetch.\n"); return(FALSE); } if (PsychPrefStateGet_Verbosity()>4) printf("PTB-DEBUG: ...done.\n"); } // Assign presentation_timestamp: if (presentation_timestamp) *presentation_timestamp = movieRecordBANK[moviehandle].pts; // Activate OpenGL context of target window: PsychSetGLContext(win); #if PSYCH_SYSTEM == PSYCH_OSX // Explicitely disable Apple's Client storage extensions. For now they are not really useful to us. glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_FALSE); #endif // Build a standard PTB texture record: PsychMakeRect(out_texture->rect, 0, 0, movieRecordBANK[moviehandle].width, movieRecordBANK[moviehandle].height); // Set NULL - special texture object as part of the PTB texture record: out_texture->targetSpecific.QuickTimeGLTexture = NULL; // Set texture orientation as if it were an inverted Offscreen window: Upside-down. out_texture->textureOrientation = 3; // We use zero client storage memory bytes: out_texture->textureMemorySizeBytes = 0; // Textures are aligned on 4 Byte boundaries because texels are RGBA8: out_texture->textureByteAligned = 4; // Assign texturehandle of our cached texture, if any, so it gets recycled now: out_texture->textureNumber = movieRecordBANK[moviehandle].cached_texture; // Let PsychCreateTexture() do the rest of the job of creating, setting up and // filling an OpenGL texture with content: PsychCreateTexture(out_texture); // After PsychCreateTexture() the cached texture object from our cache is used // and no longer available for recycling. We mark the cache as empty: // It will be filled with a new textureid for recycling if a texture gets // deleted in PsychMovieDeleteTexture().... movieRecordBANK[moviehandle].cached_texture = 0; // Detection of dropped frames: This is a heuristic. We'll see how well it works out... // TODO: GstBuffer videoBuffer provides special flags that should allow to do a more // robust job, although nothing's wrong with the current approach per se... if (rate && presentation_timestamp) { // Try to check for dropped frames in playback mode: // Expected delta between successive presentation timestamps: targetdelta = 1.0f / (movieRecordBANK[moviehandle].fps * rate); // Compute real delta, given rate and playback direction: if (rate > 0) { realdelta = *presentation_timestamp - movieRecordBANK[moviehandle].last_pts; if (realdelta < 0) realdelta = 0; } else { realdelta = -1.0 * (*presentation_timestamp - movieRecordBANK[moviehandle].last_pts); if (realdelta < 0) realdelta = 0; } frames = realdelta / targetdelta; // Dropped frames? if (frames > 1 && movieRecordBANK[moviehandle].last_pts >= 0) { movieRecordBANK[moviehandle].nr_droppedframes += (int) (frames - 1 + 0.5); } movieRecordBANK[moviehandle].last_pts = *presentation_timestamp; } // Unlock. if (oldstyle) { PsychUnlockMutex(&movieRecordBANK[moviehandle].mutex); } else { gst_buffer_unref(videoBuffer); videoBuffer = NULL; } // Manually advance movie time, if in fetch mode: if (0 == rate) { // We are in manual fetch mode: Need to manually advance movie to next // media sample: event = gst_event_new_step(GST_FORMAT_BUFFERS, 1, 1.0, TRUE, FALSE); gst_element_send_event(theMovie, event); // Block until seek completed, failed, or timeout of 30 seconds reached: gst_element_get_state(theMovie, NULL, NULL, (GstClockTime) (30 * 1e9)); } return(TRUE); }
void PsychHIDOSKbQueueCheck(int deviceIndex) { double *hasKeyBeenDownOutput, *firstPressTimeOutput, *firstReleaseTimeOutput, *lastPressTimeOutput, *lastReleaseTimeOutput; psych_bool isFirstPressSpecified, isFirstReleaseSpecified, isLastPressSpecified, isLastReleaseSpecified; int i; // Get true keyboardqueue index assigned to deviceIndex from original user provided deviceIndex: deviceIndex = PsychHIDOSGetKbQueueDevice(deviceIndex, NULL); // Does Keyboard queue for this deviceIndex already exist? if (NULL == psychHIDKbQueueFirstPress[deviceIndex]) { // No. Bad bad... printf("PsychHID-ERROR: Tried to check non-existent keyboard queue for deviceIndex %i! Call KbQueueCreate first!\n", deviceIndex); PsychErrorExitMsg(PsychError_user, "Invalid keyboard 'deviceIndex' specified. No queue for that device yet!"); } // Allocate output PsychAllocOutDoubleArg(1, FALSE, &hasKeyBeenDownOutput); isFirstPressSpecified = PsychAllocOutDoubleMatArg(2, FALSE, 1, 256, 1, &firstPressTimeOutput); isFirstReleaseSpecified = PsychAllocOutDoubleMatArg(3, FALSE, 1, 256, 1, &firstReleaseTimeOutput); isLastPressSpecified = PsychAllocOutDoubleMatArg(4, FALSE, 1, 256, 1, &lastPressTimeOutput); isLastReleaseSpecified = PsychAllocOutDoubleMatArg(5, FALSE, 1, 256, 1, &lastReleaseTimeOutput); // Initialize output if(isFirstPressSpecified) memset((void*) firstPressTimeOutput, 0, sizeof(double) * 256); if(isFirstReleaseSpecified) memset((void*) firstReleaseTimeOutput, 0, sizeof(double) * 256); if(isLastPressSpecified) memset((void*) lastPressTimeOutput, 0, sizeof(double) * 256); if(isLastReleaseSpecified) memset((void*) lastReleaseTimeOutput, 0, sizeof(double) * 256); *hasKeyBeenDownOutput=0; // Compute and assign output: PsychLockMutex(&KbQueueMutex); for (i = 0; i < 256; i++) { double lastRelease = psychHIDKbQueueLastRelease[deviceIndex][i]; double lastPress = psychHIDKbQueueLastPress[deviceIndex][i]; double firstRelease = psychHIDKbQueueFirstRelease[deviceIndex][i]; double firstPress = psychHIDKbQueueFirstPress[deviceIndex][i]; if (firstPress) { *hasKeyBeenDownOutput=1; if(isFirstPressSpecified) firstPressTimeOutput[i] = firstPress; psychHIDKbQueueFirstPress[deviceIndex][i] = 0; } if (firstRelease) { if(isFirstReleaseSpecified) firstReleaseTimeOutput[i] = firstRelease; psychHIDKbQueueFirstRelease[deviceIndex][i] = 0; } if (lastPress) { if(isLastPressSpecified) lastPressTimeOutput[i] = lastPress; psychHIDKbQueueLastPress[deviceIndex][i] = 0; } if (lastRelease) { if(isLastReleaseSpecified) lastReleaseTimeOutput[i] = lastRelease; psychHIDKbQueueLastRelease[deviceIndex][i] = 0; } } PsychUnlockMutex(&KbQueueMutex); return; }
PsychError PsychHIDOSKbQueueCreate(int deviceIndex, int numScankeys, int* scanKeys) { pRecDevice deviceRecord; // Valid number of keys? if (scanKeys && (numScankeys != 256)) { PsychErrorExitMsg(PsychError_user, "Second argument to KbQueueCreate must be a vector with 256 elements."); } // Do we finally have a valid keyboard or other suitable input device? // PsychHIDOSGetKbQueueDevice() will error out if no suitable device // for deviceIndex can be found. Otherwise it will return the HID // device record and remapped deviceIndex for use with our KbQueues: deviceIndex = PsychHIDOSGetKbQueueDevice(deviceIndex, &deviceRecord); // Keyboard queue for this deviceIndex already created? if (psychHIDKbQueueFirstPress[deviceIndex]) { // Yep. Release it, so we can start from scratch: PsychHIDOSKbQueueRelease(deviceIndex); } // Allocate and zero-init memory for tracking key presses and key releases: psychHIDKbQueueFirstPress[deviceIndex] = calloc(256, sizeof(double)); psychHIDKbQueueFirstRelease[deviceIndex] = calloc(256, sizeof(double)); psychHIDKbQueueLastPress[deviceIndex] = calloc(256, sizeof(double)); psychHIDKbQueueLastRelease[deviceIndex] = calloc(256, sizeof(double)); psychHIDKbQueueScanKeys[deviceIndex] = calloc(256, sizeof(int)); // Assign scanKeys vector, if any: if (scanKeys) { // Copy it: memcpy(psychHIDKbQueueScanKeys[deviceIndex], scanKeys, 256 * sizeof(int)); } else { // None provided. Enable all keys by default: memset(psychHIDKbQueueScanKeys[deviceIndex], 1, 256 * sizeof(int)); } // Create HIDQueue for device: queue[deviceIndex] = IOHIDQueueCreate(kCFAllocatorDefault, deviceRecord, 30, 0); if (NULL == queue[deviceIndex]) PsychErrorExitMsg(PsychError_system, "Failed to create event queue for detecting key press."); // Mark as a non-keyboard device, to start with: queueIsAKeyboard[deviceIndex] = FALSE; // Parse HID device to add all detected and selected keys: { // Add deviceRecord's elements to our queue, filtering unwanted keys via 'scanList'. // This code is almost identical to the enumeration code in PsychHIDKbCheck, to make sure we get // matching performance and behaviour and hopefully that it works on the latest problematic Apple // hardware, e.g., late 2013 MacBookAir and OSX 10.9: { uint32_t usage, usagePage; pRecElement currentElement, lastElement = NULL; // Step through the elements of the device and add matching ones: for (currentElement = HIDGetFirstDeviceElement(deviceRecord, kHIDElementTypeInput | kHIDElementTypeCollection); (currentElement != NULL) && (currentElement != lastElement); currentElement = HIDGetNextDeviceElement(currentElement, kHIDElementTypeInput | kHIDElementTypeCollection)) { // Keep track of last queried element: lastElement = currentElement; usage = IOHIDElementGetUsage(currentElement); usagePage = IOHIDElementGetUsagePage(currentElement); if (getenv("PSYCHHID_TELLME")) { printf("PTB-DEBUG: [KbQueueCreate]: 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) && ( (scanKeys == NULL) || (scanKeys[usage - 1] > 0) )) { // Add it for use in keyboard queue: PsychHIDOSKbElementAdd(tIOHIDElementRef, queue[deviceIndex], deviceIndex); } } } // Done with this currentElement, which was a collection of buttons/keys. // Iterate to next currentElement: continue; } // Classic path for non-collection elements: if(((usagePage == kHIDPage_KeyboardOrKeypad) || (usagePage == kHIDPage_Button)) && (usage <= 256) && (usage >= 1) && ( (scanKeys == NULL) || (scanKeys[usage - 1] > 0) ) ) { // Add it for use in keyboard queue: PsychHIDOSKbElementAdd(currentElement, queue[deviceIndex], deviceIndex); } } } } // Register "queue empty -> non-empty transition" callback: TODO Replace queue by reference to our keyboard queue struct: IOHIDQueueRegisterValueAvailableCallback(queue[deviceIndex], (IOHIDCallback) PsychHIDKbQueueCallbackFunction, (void*) (long) deviceIndex); // Create event buffer: PsychHIDCreateEventBuffer(deviceIndex); // Start the processing thread for this queue: PsychLockMutex(&KbQueueMutex); if (PsychCreateThread(&KbQueueThread[deviceIndex], NULL, KbQueueWorkerThreadMain, (void*) (long) deviceIndex)) { // We are so screwed: // Cleanup the mess: psychHIDKbQueueActive[deviceIndex] = FALSE; PsychUnlockMutex(&KbQueueMutex); // Whine a little bit: printf("PsychHID-ERROR: Start of keyboard queue processing for deviceIndex %i failed!\n", deviceIndex); PsychErrorExitMsg(PsychError_system, "Creation of keyboard queue background processing thread failed!"); } PsychUnlockMutex(&KbQueueMutex); // Ready to use this keybord queue. return(PsychError_none); }
static void PsychHIDKbQueueCallbackFunction(void *target, IOReturn result, void *sender) { // This routine is executed each time the queue transitions from empty to non-empty // The CFRunLoop of the thread in KbQueueWorkerThreadMain() is the one that executes here: IOHIDQueueRef queue = (IOHIDQueueRef) sender; IOHIDValueRef valueRef = NULL; int deviceIndex = (int) target; double timestamp; int eventValue; long keysUsage = -1; PsychHIDEventRecord evt; result=kIOReturnError; if (!queue) return; // Nothing we can do because we can't access queue, (shouldn't happen) while (1) { // This function only gets called when queue transitions from empty to non-empty // Therefore, we must process all available events in this while loop before // it will be possible for this function to be notified again. if (valueRef) { CFRelease(valueRef); valueRef = NULL; } // Dequeue next event from queue in a polling non-blocking fashion: valueRef = IOHIDQueueCopyNextValueWithTimeout(queue, 0.0); // Done? Exit, if so: if (!valueRef) break; // Get event value, e.g., the key state of a key or button 1 = pressed, 0 = released: eventValue = IOHIDValueGetIntegerValue(valueRef); // Get usage value, ie., the identity of the key: IOHIDElementRef element = IOHIDValueGetElement(valueRef); keysUsage = IOHIDElementGetUsage(element); // Get double GetSecs timestamp, computed from returned uint64 nanoseconds timestamp: timestamp = convertTime(IOHIDValueGetTimeStamp(valueRef)); // Don't bother with keysUsage of 0 (meaningless) or 1 (ErrorRollOver) for keyboards: if ((queueIsAKeyboard[deviceIndex]) && (keysUsage <= 1)) continue; // Clear ringbuffer event: memset(&evt, 0 , sizeof(evt)); // Cooked key code defaults to "unhandled", and stays that way for anything but keyboards: evt.cookedEventCode = -1; // For real keyboards we can compute cooked key codes: Requires OSX 10.5 or later. if (queueIsAKeyboard[deviceIndex]) { // Keyboard(ish) device. We can handle this under some conditions. // Init to a default of handled, but unmappable/ignored keycode: evt.cookedEventCode = 0; // Keypress event code available in mapping table? if (keysUsage < kHID2VKCSize) { // Yes: We try to map this to a character code: // Step 1: Map HID usage value to virtual keycode via LUT: uint16_t vcKey = kHID2VKC[keysUsage]; // Keep track of SHIFT keys as modifier keys: Bits 0 == Command, 1 == Shift, 2 == CapsLock, 3 == Alt/Option, 4 == CTRL if ((vcKey == kVKC_Shift || vcKey == kVKC_rShift) && (eventValue != 0)) modifierKeyState[deviceIndex] |= (1 << 1); if ((vcKey == kVKC_Shift || vcKey == kVKC_rShift) && (eventValue == 0)) modifierKeyState[deviceIndex] &= ~(1 << 1); // Keep track of ALT keys as modifier keys: if ((vcKey == kVKC_Option || vcKey == kVKC_rOption) && (eventValue != 0)) modifierKeyState[deviceIndex] |= (1 << 3); if ((vcKey == kVKC_Option || vcKey == kVKC_rOption) && (eventValue == 0)) modifierKeyState[deviceIndex] &= ~(1 << 3); // Keep track of CTRL keys as modifier keys: if ((vcKey == kVKC_Control || vcKey == kVKC_rControl) && (eventValue != 0)) modifierKeyState[deviceIndex] |= (1 << 4); if ((vcKey == kVKC_Control || vcKey == kVKC_rControl) && (eventValue == 0)) modifierKeyState[deviceIndex] &= ~(1 << 4); // Was this a CTRL + C interrupt request? if ((eventValue != 0) && (vcKey == 0x08) && (modifierKeyState[deviceIndex] & (1 << 4))) { // Yes: Tell the console input helper about it, so it can send interrupt // signals to the runtime and reenable keyboard input if appropriate: // Note: Not sure if the mutex exclusion is needed here, but better safe than sorry. PsychLockMutex(&KbQueueMutex); ConsoleInputHelper(-1); PsychUnlockMutex(&KbQueueMutex); } // Key press? if (eventValue != 0) { // Step 2: Translate virtual key code into unicode char: // Ok, this is the usual horrifying complexity of Apple's system. We use code // snippets found on StackOverflow, modified to suit our needs, e.g., we track // modifier keys manually, at least left and right ALT and SHIFT keys. We don't // care about other modifiers. TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); CFDataRef uchr = (CFDataRef) ((currentKeyboard) ? TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData) : NULL); const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout*) ((uchr) ? CFDataGetBytePtr(uchr) : NULL); if (keyboardLayout) { UInt32 deadKeyState = 0; UniCharCount maxStringLength = 255; UniCharCount actualStringLength = 0; UniChar unicodeString[maxStringLength]; OSStatus status = UCKeyTranslate(keyboardLayout, vcKey, kUCKeyActionDown, modifierKeyState[deviceIndex], LMGetKbdType(), 0, &deadKeyState, maxStringLength, &actualStringLength, unicodeString); if ((actualStringLength == 0) && deadKeyState) { status = UCKeyTranslate(keyboardLayout, kVK_Space, kUCKeyActionDown, 0, LMGetKbdType(), 0, &deadKeyState, maxStringLength, &actualStringLength, unicodeString); } if((actualStringLength > 0) && (status == noErr)) { // Assign final cooked / mapped keycode: evt.cookedEventCode = (int) unicodeString[0]; // Send same keystroke character to console input helper. // In kbqueue-based ListenChar(1) mode, the helper will // inject/forward the character into the runtime: // Note: ConsoleInputHelper() should be safe to call without // mutex protection for >= 0 event codes. ConsoleInputHelper(evt.cookedEventCode); } } } } } PsychLockMutex(&KbQueueMutex); // Update records of first and latest key presses and releases if (eventValue != 0) { if (psychHIDKbQueueFirstPress[deviceIndex]) { // First key press timestamp: if (psychHIDKbQueueFirstPress[deviceIndex][keysUsage-1] == 0) { psychHIDKbQueueFirstPress[deviceIndex][keysUsage-1] = timestamp; } } if (psychHIDKbQueueLastPress[deviceIndex]) { // Last key press timestamp: psychHIDKbQueueLastPress[deviceIndex][keysUsage-1] = timestamp; } evt.status |= (1 << 0); } else { if (psychHIDKbQueueFirstRelease[deviceIndex]) { // First key release timestamp: if (psychHIDKbQueueFirstRelease[deviceIndex][keysUsage-1] == 0) psychHIDKbQueueFirstRelease[deviceIndex][keysUsage-1] = timestamp; } if (psychHIDKbQueueLastRelease[deviceIndex]) { // Last key release timestamp: psychHIDKbQueueLastRelease[deviceIndex][keysUsage-1] = timestamp; } evt.status &= ~(1 << 0); } // Update event buffer: evt.timestamp = timestamp; evt.rawEventCode = keysUsage; PsychHIDAddEventToEventBuffer(deviceIndex, &evt); // Tell waiting userspace (under KbQueueMutxex protection for better scheduling) something interesting has changed: PsychSignalCondition(&KbQueueCondition); PsychUnlockMutex(&KbQueueMutex); // Next while loop iteration to dequeue potentially more events: } // Done for this queue transition. Return to runloop. }
void PsychHIDOSKbQueueCheck(int deviceIndex) { double *hasKeyBeenDownOutput, *firstPressTimeOutput, *firstReleaseTimeOutput, *lastPressTimeOutput, *lastReleaseTimeOutput; psych_bool isFirstPressSpecified, isFirstReleaseSpecified, isLastPressSpecified, isLastReleaseSpecified; int i; if (deviceIndex < 0) { deviceIndex = PsychHIDGetDefaultKbQueueDevice(); // Ok, deviceIndex now contains our default keyboard to use - The first suitable keyboard. } if ((deviceIndex < 0) || (deviceIndex >= ndevices)) { // Out of range index: PsychErrorExitMsg(PsychError_user, "Invalid 'deviceIndex' specified. No such device!"); } // Does Keyboard queue for this deviceIndex already exist? if (NULL == psychHIDKbQueueFirstPress[deviceIndex]) { // No. Bad bad... printf("PsychHID-ERROR: Tried to check non-existent keyboard queue for deviceIndex %i! Call KbQueueCreate first!\n", deviceIndex); PsychErrorExitMsg(PsychError_user, "Invalid 'deviceIndex' specified. No queue for that device yet!"); } // Allocate output PsychAllocOutDoubleArg(1, kPsychArgOptional, &hasKeyBeenDownOutput); isFirstPressSpecified = PsychAllocOutDoubleMatArg(2, kPsychArgOptional, 1, 256, 1, &firstPressTimeOutput); isFirstReleaseSpecified = PsychAllocOutDoubleMatArg(3, kPsychArgOptional, 1, 256, 1, &firstReleaseTimeOutput); isLastPressSpecified = PsychAllocOutDoubleMatArg(4, kPsychArgOptional, 1, 256, 1, &lastPressTimeOutput); isLastReleaseSpecified = PsychAllocOutDoubleMatArg(5, kPsychArgOptional, 1, 256, 1, &lastReleaseTimeOutput); // Initialize output if(isFirstPressSpecified) memset((void*) firstPressTimeOutput, 0, sizeof(double) * 256); if(isFirstReleaseSpecified) memset((void*) firstReleaseTimeOutput, 0, sizeof(double) * 256); if(isLastPressSpecified) memset((void*) lastPressTimeOutput, 0, sizeof(double) * 256); if(isLastReleaseSpecified) memset((void*) lastReleaseTimeOutput, 0, sizeof(double) * 256); *hasKeyBeenDownOutput=0; // Compute and assign output: PsychLockMutex(&KbQueueMutex); for (i = 0; i < 256; i++) { double lastRelease = psychHIDKbQueueLastRelease[deviceIndex][i]; double lastPress = psychHIDKbQueueLastPress[deviceIndex][i]; double firstRelease = psychHIDKbQueueFirstRelease[deviceIndex][i]; double firstPress = psychHIDKbQueueFirstPress[deviceIndex][i]; if (firstPress) { *hasKeyBeenDownOutput=1; if(isFirstPressSpecified) firstPressTimeOutput[i] = firstPress; psychHIDKbQueueFirstPress[deviceIndex][i] = 0; } if (firstRelease) { if(isFirstReleaseSpecified) firstReleaseTimeOutput[i] = firstRelease; psychHIDKbQueueFirstRelease[deviceIndex][i] = 0; } if (lastPress) { if(isLastPressSpecified) lastPressTimeOutput[i] = lastPress; psychHIDKbQueueLastPress[deviceIndex][i] = 0; } if (lastRelease) { if(isLastReleaseSpecified) lastReleaseTimeOutput[i] = lastRelease; psychHIDKbQueueLastRelease[deviceIndex][i] = 0; } } PsychUnlockMutex(&KbQueueMutex); return; }
void PsychHIDOSKbQueueStart(int deviceIndex) { LPDIRECTINPUTDEVICE8 kb; DIPROPDWORD dipdw; psych_bool queueActive; int i; if (deviceIndex < 0) { deviceIndex = PsychHIDGetDefaultKbQueueDevice(); // Ok, deviceIndex now contains our default keyboard to use - The first suitable keyboard. } if ((deviceIndex < 0) || (deviceIndex >= ndevices)) { // Out of range index: PsychErrorExitMsg(PsychError_user, "Invalid 'deviceIndex' specified. No such device!"); } // Does Keyboard queue for this deviceIndex already exist? if (NULL == psychHIDKbQueueFirstPress[deviceIndex]) { // No. Bad bad... printf("PsychHID-ERROR: Tried to start processing on non-existent keyboard queue for deviceIndex %i! Call KbQueueCreate first!\n", deviceIndex); PsychErrorExitMsg(PsychError_user, "Invalid keyboard 'deviceIndex' specified. No queue for that device yet!"); } // Keyboard queue already stopped? Then we ain't nothing to do: if (psychHIDKbQueueActive[deviceIndex]) return; // Queue is inactive. Start it: // Will this be the first active queue, ie., aren't there any queues running so far? queueActive = FALSE; for (i = 0; i < PSYCH_HID_MAX_DEVICES; i++) { queueActive |= psychHIDKbQueueActive[i]; } PsychLockMutex(&KbQueueMutex); // Clear out current state for this queue: memset(psychHIDKbQueueFirstPress[deviceIndex] , 0, (256 * sizeof(double))); memset(psychHIDKbQueueFirstRelease[deviceIndex] , 0, (256 * sizeof(double))); memset(psychHIDKbQueueLastPress[deviceIndex] , 0, (256 * sizeof(double))); memset(psychHIDKbQueueLastRelease[deviceIndex] , 0, (256 * sizeof(double))); // Setup event mask, so events from our associated xinput device // get enqueued in our event queue: kb = GetXDevice(deviceIndex); // Device specific data format setup: switch (info[deviceIndex].dwDevType & 0xff) { case DI8DEVTYPE_KEYBOARD: if (DI_OK != kb->SetDataFormat(&c_dfDIKeyboard)) { PsychUnlockMutex(&KbQueueMutex); printf("PsychHID-ERROR: Tried to start processing on keyboard queue for deviceIndex %i, but setting dataformat failed!\n", deviceIndex); PsychErrorExitMsg(PsychError_user, "Starting keyboard queue failed!"); } break; case DI8DEVTYPE_MOUSE: case DI8DEVTYPE_SCREENPOINTER: if (DI_OK != kb->SetDataFormat(&c_dfDIMouse2)) { PsychUnlockMutex(&KbQueueMutex); printf("PsychHID-ERROR: Tried to start processing on keyboard queue for deviceIndex %i, but setting dataformat failed!\n", deviceIndex); PsychErrorExitMsg(PsychError_user, "Starting keyboard queue failed!"); } break; case DI8DEVTYPE_JOYSTICK: if (DI_OK != kb->SetDataFormat(&c_dfDIJoystick2)) { PsychUnlockMutex(&KbQueueMutex); printf("PsychHID-ERROR: Tried to start processing on keyboard queue for deviceIndex %i, but setting dataformat failed!\n", deviceIndex); PsychErrorExitMsg(PsychError_user, "Starting keyboard queue failed!"); } break; } // Set device event buffer size to 256 elements: dipdw.diph.dwSize = sizeof(DIPROPDWORD); dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER); dipdw.diph.dwObj = 0; dipdw.diph.dwHow = DIPH_DEVICE; dipdw.dwData = 256; if (DI_OK != kb->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph)) { PsychUnlockMutex(&KbQueueMutex); printf("PsychHID-ERROR: Tried to start processing on keyboard queue for deviceIndex %i, but setting buffersize on device failed!\n", deviceIndex); PsychErrorExitMsg(PsychError_user, "Starting keyboard queue failed!"); } // Enable state-change event notifications: if (DI_OK != kb->SetEventNotification(hEvent)) { PsychUnlockMutex(&KbQueueMutex); printf("PsychHID-ERROR: Tried to start processing on keyboard queue for deviceIndex %i, but setting device state notifications failed!\n", deviceIndex); PsychErrorExitMsg(PsychError_user, "Starting keyboard queue failed!"); } if (DI_OK != kb->Acquire()) { PsychUnlockMutex(&KbQueueMutex); printf("PsychHID-ERROR: Tried to start processing on keyboard queue for deviceIndex %i, but acquiring device failed!\n", deviceIndex); PsychErrorExitMsg(PsychError_user, "Starting keyboard queue failed!"); } // Mark this queue as logically started: psychHIDKbQueueActive[deviceIndex] = TRUE; // Queue started. PsychUnlockMutex(&KbQueueMutex); // If other queues are already active then we're done: if (queueActive) return; // No other active queues. We are the first one. // Start the common processing thread for all queues: PsychLockMutex(&KbQueueMutex); KbQueueThreadTerminate = FALSE; if (PsychCreateThread(&KbQueueThread, NULL, KbQueueWorkerThreadMain, NULL)) { // We are soo screwed: // Cleanup the mess: psychHIDKbQueueActive[deviceIndex] = FALSE; PsychUnlockMutex(&KbQueueMutex); // Whine a little bit: printf("PsychHID-ERROR: Start of keyboard queue processing failed!\n"); PsychErrorExitMsg(PsychError_system, "Creation of keyboard queue background processing thread failed!"); } // Up and running, we're done! PsychUnlockMutex(&KbQueueMutex); return; }
void PsychHIDOSKbQueueStop(int deviceIndex) { LPDIRECTINPUTDEVICE8 kb; psych_bool queueActive; int i; if (deviceIndex < 0) { deviceIndex = PsychHIDGetDefaultKbQueueDevice(); // Ok, deviceIndex now contains our default keyboard to use - The first suitable keyboard. } if ((deviceIndex < 0) || (deviceIndex >= ndevices)) { // Out of range index: PsychErrorExitMsg(PsychError_user, "Invalid 'deviceIndex' specified. No such device!"); } // Keyboard queue for this deviceIndex already exists? if (NULL == psychHIDKbQueueFirstPress[deviceIndex]) { // No. Nothing to do then. return; } // Keyboard queue already stopped? if (!psychHIDKbQueueActive[deviceIndex]) return; // Get device: kb = GetXDevice(deviceIndex); // Queue is active. Stop it: PsychLockMutex(&KbQueueMutex); // Release the device: if (DI_OK != kb->Unacquire()) { PsychUnlockMutex(&KbQueueMutex); printf("PsychHID-ERROR: Tried to stop processing on keyboard queue for deviceIndex %i, but releasing device failed!\n", deviceIndex); PsychErrorExitMsg(PsychError_user, "Stopping keyboard queue failed!"); } // Disable state-change event notifications: if (DI_OK != kb->SetEventNotification(NULL)) { PsychUnlockMutex(&KbQueueMutex); printf("PsychHID-ERROR: Tried to stop processing on keyboard queue for deviceIndex %i, but disabling device state notifications failed!\n", deviceIndex); PsychErrorExitMsg(PsychError_user, "Stopping keyboard queue failed!"); } // Mark queue logically stopped: psychHIDKbQueueActive[deviceIndex] = FALSE; PsychUnlockMutex(&KbQueueMutex); // Was this the last active queue? queueActive = FALSE; for (i = 0; i < PSYCH_HID_MAX_DEVICES; i++) { queueActive |= psychHIDKbQueueActive[i]; } // If more queues are active then we're done: if (queueActive) return; // No more active queues. Shutdown the common processing thread: PsychLockMutex(&KbQueueMutex); KbQueueThreadTerminate = TRUE; // Done. PsychUnlockMutex(&KbQueueMutex); // Shutdown the thread, wait for its termination: PsychDeleteThread(&KbQueueThread); KbQueueThreadTerminate = FALSE; // printf("DEBUG: THREAD JOINED.\n"); fflush(NULL); return; }
void PsychHIDOSKbTriggerWait(int deviceIndex, int numScankeys, int* scanKeys) { int keyMask[256]; int i; double t, tc; if (deviceIndex < 0) { deviceIndex = PsychHIDGetDefaultKbQueueDevice(); // Ok, deviceIndex now contains our default keyboard to use - The first suitable keyboard. } if ((deviceIndex < 0) || (deviceIndex >= ndevices)) { // Out of range index: PsychErrorExitMsg(PsychError_user, "Invalid 'deviceIndex' specified. No such device!"); } if(psychHIDKbQueueFirstPress[deviceIndex]) PsychErrorExitMsg(PsychError_user, "A queue for this device is already running, you must call KbQueueRelease() before invoking KbTriggerWait."); // Create a keyboard queue for this deviceIndex: memset(&keyMask[0], 0, sizeof(keyMask)); for (i = 0; i < numScankeys; i++) { if (scanKeys[i] < 1 || scanKeys[i] > 256) PsychErrorExitMsg(PsychError_user, "Invalid entry for triggerKey specified. Not in valid range 1 - 256!"); keyMask[scanKeys[i] - 1] = 1; } // Create keyboard queue with proper mask: PsychHIDOSKbQueueCreate(deviceIndex, 256, &keyMask[0]); PsychHIDOSKbQueueStart(deviceIndex); PsychLockMutex(&KbQueueMutex); // Scan for trigger key: while (1) { // Wait until something changes in a keyboard queue: PsychWaitCondition(&KbQueueCondition, &KbQueueMutex); // Check if our queue had one of the dedicated trigger keys pressed: for (i = 0; i < numScankeys; i++) { // Break out of scan loop if key pressed: if (psychHIDKbQueueFirstPress[deviceIndex][scanKeys[i] - 1] != 0) break; } // Triggerkey pressed? if ((i < numScankeys) && (psychHIDKbQueueFirstPress[deviceIndex][scanKeys[i] - 1] != 0)) break; // No change for our trigger keys. Repeat scan loop. } // If we reach this point, we know some triggerkey has been pressed. As we aborted // the scan on detection of the first pressed key, we can't be certain we caught the // key with the earliest key press, maybe one of the untested keys was pressed even // earlier. Therefore do another pass over all keys to find the pressed one with the // earliest (minimum) pressed time: t = DBL_MAX; for (i = 0; i < numScankeys; i++) { tc = psychHIDKbQueueFirstPress[deviceIndex][scanKeys[i] - 1]; if ((tc != 0) && (tc <= t)) t = tc; } // Done. Release the lock: PsychUnlockMutex(&KbQueueMutex); // Stop and release the queue: PsychHIDOSKbQueueStop(deviceIndex); PsychHIDOSKbQueueRelease(deviceIndex); // Return timestamp: PsychCopyOutDoubleArg(1, kPsychArgOptional, t); return; }
// This is the event dequeue & process function which updates // Keyboard queue state. It can be called with 'blockingSinglepass' // set to TRUE to process exactly one event, if called from the // background keyboard queue processing thread. Alternatively it // can be called synchronously from KbQueueCheck with a setting of FALSE // to iterate over all available events and process them instantaneously: void KbQueueProcessEvents(psych_bool blockingSinglepass) { LPDIRECTINPUTDEVICE8 kb; DIDEVICEOBJECTDATA event; HRESULT rc; DWORD dwItems; double tnow; unsigned int i, keycode, keystate; PsychHIDEventRecord evt; WORD asciiValue[2]; UCHAR keyboardState[256]; while (1) { // Single pass or multi-pass? if (blockingSinglepass) { // Wait until at least one event available and dequeue it: // We use a timeout of 100 msecs. WaitForSingleObject(hEvent, 100); } else { // Check if event available, dequeue it, if so. Abort // processing if no new event available, aka queue empty: // TODO if (!XCheckTypedEvent(thread_dpy, GenericEvent, &KbQueue_xevent)) break; } // Take timestamp: PsychGetAdjustedPrecisionTimerSeconds(&tnow); // Need the lock from here on: PsychLockMutex(&KbQueueMutex); // Do a sweep over all keyboard devices whose queues are active: for (i = 0; i < (unsigned int) ndevices; i++) { // Skip this one if inactive: if (!psychHIDKbQueueActive[i]) continue; // Check this device: kb = GetXDevice(i); // Fetch one item from the buffer: // event.dwTimeStamp = Timestamp in msecs of timeGetTime() timebase. // event.dwSequence = Sequence number. // Fetch from this device, item-by-item, until nothing more to fetch: while (TRUE) { // Try one fetch from this device: dwItems = 1; rc = kb->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), &event, &dwItems, 0); // If failed or nothing more to fetch, break out of fetch loop: if (!SUCCEEDED(rc) || (0 == dwItems)) break; // Clear ringbuffer event: memset(&evt, 0 , sizeof(evt)); // Init character code to "unmapped": It will stay like that for anything but real keyboards: evt.cookedEventCode = -1; // Map to key code and key state: keycode = event.dwOfs & 0xff; keystate = event.dwData & 0x80; // Remap keycode into target slot in our arrays, depending on input device: switch (info[i].dwDevType & 0xff) { case DI8DEVTYPE_KEYBOARD: // Try to map scancode to ascii character: memset(keyboardState, 0, sizeof(keyboardState)); if (GetAsyncKeyState(VK_SHIFT)) keyboardState[VK_SHIFT] = 0xff; if ((1 == ToAsciiEx(MapVirtualKeyEx(keycode, 1, GetKeyboardLayout(0)), keycode, keyboardState, (LPWORD) &(asciiValue[0]), 0, GetKeyboardLayout(0)))) { // Mapped to single char: Return it as cooked keycode: evt.cookedEventCode = (int) (asciiValue[0] & 0xff); } else { // Could not map key to valid ascii character: Mark as "not mapped" aka zero: evt.cookedEventCode = 0; } // Map scancode 'keycode' to virtual key code 'keycode': keycode = PsychHIDOSMapKey(keycode); break; case DI8DEVTYPE_MOUSE: case DI8DEVTYPE_SCREENPOINTER: // Button event? Otherwise skip it. if (keycode < 3 * sizeof(LONG)) continue; // Correct for buttons offset in data structure DIMOUSESTATE2: keycode -= 3 * sizeof(LONG); break; case DI8DEVTYPE_JOYSTICK: // Button event? Otherwise skip it. if (keycode < (8 * sizeof(LONG) + 4 * sizeof(DWORD))) continue; // Correct for buttons offset in data structure DIJOYSTATE2: keycode -= (8 * sizeof(LONG) + 4 * sizeof(DWORD)); // Also skip if beyond button array: if (keycode >= 128) continue; break; default: // Unkown device -- Skip it. continue; } // This keyboard queue interested in this keycode? if (psychHIDKbQueueScanKeys[i][keycode] != 0) { // Yes: The queue wants to receive info about this key event. // Press or release? if (keystate) { // Enqueue key press. Always in the "last press" array, because any // press at this time is the best candidate for the last press. // Only enqeue in "first press" if there wasn't any registered before, // ie., the slot is so far empty: if (psychHIDKbQueueFirstPress[i][keycode] == 0) psychHIDKbQueueFirstPress[i][keycode] = tnow; psychHIDKbQueueLastPress[i][keycode] = tnow; evt.status |= (1 << 0); } else { // Enqueue key release. See logic above: if (psychHIDKbQueueFirstRelease[i][keycode] == 0) psychHIDKbQueueFirstRelease[i][keycode] = tnow; psychHIDKbQueueLastRelease[i][keycode] = tnow; evt.status &= ~(1 << 0); // Clear cooked keycode - We don't record key releases this way: if (evt.cookedEventCode > 0) evt.cookedEventCode = 0; } // Update event buffer: evt.timestamp = tnow; evt.rawEventCode = keycode + 1; PsychHIDAddEventToEventBuffer(i, &evt); // Tell waiting userspace (under KbQueueMutex protection for better scheduling) something interesting has changed: PsychSignalCondition(&KbQueueCondition); } // Next fetch iteration for this device... } // Check next device... } // Done with shared data access: PsychUnlockMutex(&KbQueueMutex); // Done if we were only supposed to handle one sweep, which we did: if (blockingSinglepass) break; } return; }