// 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);
}
Esempio n. 2
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);
}
Esempio n. 3
0
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);
}
Esempio n. 4
0
/* 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);
}
Esempio n. 7
0
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;
}
Esempio n. 11
0
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);
	}
}
Esempio n. 12
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;
}
Esempio n. 14
0
/*
    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);
    }
}
Esempio n. 15
0
/* 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;
}
Esempio n. 19
0
/* 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);
}
Esempio n. 20
0
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;
}