void PsychHIDInitializeHIDStandardInterfaces(void)
{
    long KbDeviceUsagePages[NUMDEVICEUSAGES] = {kHIDPage_GenericDesktop, kHIDPage_GenericDesktop, kHIDPage_GenericDesktop, kHIDPage_GenericDesktop, kHIDPage_GenericDesktop, kHIDPage_GenericDesktop, kHIDPage_GenericDesktop};
    long KbDeviceUsages[NUMDEVICEUSAGES] = {kHIDUsage_GD_Keyboard, kHIDUsage_GD_Keypad, kHIDUsage_GD_Mouse, kHIDUsage_GD_Pointer, kHIDUsage_GD_Joystick, kHIDUsage_GD_GamePad, kHIDUsage_GD_MultiAxisController};
    int	numDeviceUsages = NUMDEVICEUSAGES;
	int rc, i;

    for (i = 0; i < PSYCH_HID_MAX_DEVICES; i++) {
        queue[i] = NULL;
        psychHIDKbQueueCFRunLoopRef[i] = NULL;
        modifierKeyState[i] = 0;
        KbQueueThread[i] = NULL;
        queueIsAKeyboard[i] = FALSE;
    }

	// Init keyboard queue arrays:
	memset(&psychHIDKbQueueFirstPress[0], 0, sizeof(psychHIDKbQueueFirstPress));
	memset(&psychHIDKbQueueFirstRelease[0], 0, sizeof(psychHIDKbQueueFirstRelease));
	memset(&psychHIDKbQueueLastPress[0], 0, sizeof(psychHIDKbQueueLastPress));
	memset(&psychHIDKbQueueLastRelease[0], 0, sizeof(psychHIDKbQueueLastRelease));
	memset(&psychHIDKbQueueActive[0], 0, sizeof(psychHIDKbQueueActive));
	memset(&psychHIDKbQueueScanKeys[0], 0, sizeof(psychHIDKbQueueScanKeys));

    // Enumerate all possible candidates for default keyboard device:
    PsychHIDGetDeviceListByUsages(numDeviceUsages, KbDeviceUsagePages, KbDeviceUsages, &ndevices, deviceIndices, deviceRecords);

    // Nothing?
    if (ndevices == 0) printf("PTB-WARNING: No keyboard, keypad, mouse, touchpad, joystick etc. input devices detected! KbQueues won't work.\n");

	// Create keyboard queue mutex for later use:
	PsychInitMutex(&KbQueueMutex);
	PsychInitCondition(&KbQueueCondition, NULL);

	return;
}
/* FIXME: void* is wrong return argument type for start_routine!!! Works on Win32, but would crash on Win64!!! */
int PsychCreateThread(psych_thread* threadhandle, void* threadparams, void *(*start_routine)(void *), void *arg)
{
	// threadparams not yet used, this line just to make compiler happy:
	(void*) threadparams;
	
	*threadhandle = (psych_thread) malloc(sizeof(struct psych_threadstruct));
	if (*threadhandle == NULL) PsychErrorExitMsg(PsychError_outofMemory, "Insufficient free RAM memory when trying to create processing thread!");
	(*threadhandle)->handle = NULL;
	(*threadhandle)->threadId = 0;
	(*threadhandle)->taskHandleMMCS = NULL;

	// Create termination event for thread: It can be set to signalled via PsychAbortThread() and
	// threads can test for its state via PsychTestCancelThread(), which will exit the thread cleanly
	// if the event is signalled.
	(*threadhandle)->terminateReq = NULL;
	if (PsychInitCondition(&((*threadhandle)->terminateReq), NULL)) PsychErrorExitMsg(PsychError_system, "Failed to initialize associated condition/signal object when trying to create processing thread!");

	// Create thread, running, with default system settings, assign thread handle:
	(*threadhandle)->handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) start_routine, arg, 0, &((*threadhandle)->threadId));

	// Successfully created?
	if ((*threadhandle)->handle != NULL) {
		// Yes. On pre-MS-Vista systems, we lock the thread to cpu core 0 to prevent possible TSC multi-core sync
		// problems. On Vista and later, we faithfully hope that Microsoft and the vendors of "MS-Vista-Ready"
		// PC hardware have actually solved that mess, i.e., non-broken hardware (or hardware with a HPET as primary
		// time source) and proper HPET support and error handling in Vista et al's timing core. On such systems we
		// don't lock to a core, so we can benefit from multi-core processing. Our consistency checks in PsychGetPrecisionTimerSeconds()
		// should be able to eventually detect multi-core sync problems if they happen:
		if (!PsychIsMSVista()) {
			// Pre-Vista - Lock to single core:
			if (SetThreadAffinityMask(GetCurrentThread(), 1) == 0) {
				// Binding failed! Output warning:
				printf("PTBCRITICAL-ERROR: PsychTimeGlue - Win32 syscall SetThreadAffinityMask() for new child thread failed!!! Timing could be inaccurate.\n");
				printf("PTBCRITICAL-ERROR: Time measurement may be highly unreliable - or even false!!!\n");
				printf("PTBCRITICAL-ERROR: FIX YOUR SYSTEM! In its current state its not useable for conduction of studies!!!\n");
				printf("PTBCRITICAL-ERROR: Check the FAQ section of the Psychtoolbox Wiki for more information.\n");
			}
		}

		// Return success:
		return(0);
	}
	
	// Failed! Return 1:
	return(1);
}
Example #3
0
psych_bool PsychHIDCreateEventBuffer(int deviceIndex, int numValuators, int numSlots)
{
    unsigned int bufferSize;

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

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

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

    bufferSize = hidEventBufferCapacity[deviceIndex];

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

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

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

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

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

    return(TRUE);
}
Example #4
0
psych_bool PsychHIDCreateEventBuffer(int deviceIndex)
{
	unsigned int bufferSize;

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

	bufferSize = hidEventBufferCapacity[deviceIndex];

	// Already created? If so, nothing to do:
	if (hidEventBuffer[deviceIndex] || (bufferSize < 1)) return(FALSE);
	
	hidEventBuffer[deviceIndex] = (PsychHIDEventRecord*) calloc(sizeof(PsychHIDEventRecord), bufferSize);
	if (NULL == hidEventBuffer[deviceIndex]) PsychErrorExitMsg(PsychError_outofMemory, "Insufficient memory to create KbQueue event buffer!");
	
	// Prepare mutex for buffer:
	PsychInitMutex(&hidEventBufferMutex[deviceIndex]);
	PsychInitCondition(&hidEventBufferCondition[deviceIndex], NULL);

	// Flush it:
	PsychHIDFlushEventBuffer(deviceIndex);
	
	return(TRUE);
}
/*
 *      PsychGSCreateMovie() -- Create a movie object.
 *
 *      This function tries to open a moviefile (with or without audio/video tracks)
 *      and create an associated movie object for it.
 *
 *      win = Pointer to window record of associated onscreen window.
 *      moviename = char* with the name of the moviefile.
 *      preloadSecs = How many seconds of the movie should be preloaded/prefetched into RAM at movie open time?
 *      moviehandle = handle to the new movie.
 */
void PsychGSCreateMovie(PsychWindowRecordType *win, const char* moviename, double preloadSecs, int* moviehandle)
{
    GstCaps                     *colorcaps;
    GstElement			*theMovie = NULL;
    GMainLoop			*MovieContext = NULL;
    GstBus			*bus = NULL;
    GstFormat			fmt;
    GstElement      *videosink;
    gint64			length_format;
    GstPad			*pad, *peerpad;
    const GstCaps		*caps;
    GstStructure		*str;
    gint			width,height;
    gint			rate1, rate2;
    int				i, slotid;
    GError			*error = NULL;
    char			movieLocation[FILENAME_MAX];
    psych_bool			trueValue = TRUE;
    char			msgerr[10000];
    char			errdesc[1000];
    psych_bool			printErrors;

    // Suppress output of error-messages if moviehandle == 1000. That means we
    // run in our own Posix-Thread, not in the Matlab-Thread. Printing via Matlabs
    // printing facilities would likely cause a terrible crash.
    printErrors = (*moviehandle == -1000) ? FALSE : TRUE;
    
    // Set movie handle to "failed" initially:
    *moviehandle = -1;

    // We start GStreamer only on first invocation.
    if (firsttime) {        
        // Initialize GStreamer: The routine is defined in PsychVideoCaptureSupportGStreamer.c
		PsychGSCheckInit("movie playback");
        firsttime = FALSE;
    }

    if (win && !PsychIsOnscreenWindow(win)) {
        if (printErrors) PsychErrorExitMsg(PsychError_user, "Provided windowPtr is not an onscreen window."); else return;
    }

    if (NULL == moviename) {
        if (printErrors) PsychErrorExitMsg(PsychError_internal, "NULL-Ptr instead of moviename passed!"); else return;
    }

    if (numMovieRecords >= PSYCH_MAX_MOVIES) {
        *moviehandle = -2;
        if (printErrors) PsychErrorExitMsg(PsychError_user, "Allowed maximum number of simultaneously open movies exceeded!"); else return;
    }

    // Search first free slot in movieRecordBANK:
    for (i=0; (i < PSYCH_MAX_MOVIES) && (movieRecordBANK[i].theMovie); i++);
    if (i>=PSYCH_MAX_MOVIES) {
        *moviehandle = -2;
        if (printErrors) PsychErrorExitMsg(PsychError_user, "Allowed maximum number of simultaneously open movies exceeded!"); else return;
    }

    // Slot slotid will contain the movie record for our new movie object:
    slotid=i;

    // Zero-out new record in moviebank:
    memset(&movieRecordBANK[slotid], 0, sizeof(PsychMovieRecordType));
    
    // Create name-string for moviename: If an URI qualifier is at the beginning,
    // we're fine and just pass the URI as-is. Otherwise we add the file:// URI prefix.
    if (strstr(moviename, "://") || ((strstr(moviename, "v4l") == moviename) && strstr(moviename, "//"))) {
	snprintf(movieLocation, sizeof(movieLocation)-1, "%s", moviename);
    } else {
	snprintf(movieLocation, sizeof(movieLocation)-1, "file:///%s", moviename);
    }
    strncpy(movieRecordBANK[slotid].movieLocation, movieLocation, FILENAME_MAX);
    strncpy(movieRecordBANK[slotid].movieName, moviename, FILENAME_MAX);

    // Create movie playback pipeline:
    theMovie = gst_element_factory_make ("playbin2", "ptbmovieplaybackpipeline");

    // Assign name of movie to play:
    g_object_set(G_OBJECT(theMovie), "uri", movieLocation, NULL);

    // Connect callback to about-to-finish signal: Signal is emitted as soon as
    // end of current playback iteration is approaching. The callback checks if
    // looped playback is requested. If so, it schedules a new playback iteration.
    g_signal_connect(G_OBJECT(theMovie), "about-to-finish", G_CALLBACK(PsychMovieAboutToFinishCB), &(movieRecordBANK[slotid]));

    // Assign message context, message bus and message callback for
    // the pipeline to report events and state changes, errors etc.:    
    MovieContext = g_main_loop_new (NULL, FALSE);
    movieRecordBANK[slotid].MovieContext = MovieContext;
    bus = gst_pipeline_get_bus(GST_PIPELINE(theMovie));
    // Didn't work: g_signal_connect (G_OBJECT(bus), "message::error", G_CALLBACK(PsychMessageErrorCB), NULL);
    //              g_signal_connect (G_OBJECT(bus), "message::warning", G_CALLBACK(PsychMessageErrorCB), NULL);
    gst_bus_add_watch(bus, PsychMovieBusCallback, &(movieRecordBANK[slotid]));
    gst_object_unref(bus);

    // Assign a fakesink named "ptbsink0" as destination video-sink for
    // all video content. This allows us to get hold of the video frame buffers for
    // converting them into PTB OpenGL textures:
    videosink = gst_element_factory_make ("appsink", "ptbsink0");
    if (!videosink) {
	printf("PTB-ERROR: Failed to create video-sink appsink ptbsink!\n");
	PsychGSProcessMovieContext(movieRecordBANK[slotid].MovieContext, TRUE);
	PsychErrorExitMsg(PsychError_system, "Opening the movie failed. Reason hopefully given above.");
    };

    movieRecordBANK[slotid].videosink = videosink;

    // Our OpenGL texture creation routine needs GL_BGRA8 data in G_UNSIGNED_8_8_8_8_REV
    // format, but the pipeline usually delivers YUV data in planar format. Therefore
    // need to perform colorspace/colorformat conversion. We build a little videobin
    // which consists of a ffmpegcolorspace converter plugin connected to our appsink
    // plugin which will deliver video data to us for conversion into textures.
    // The "sink" pad of the converter plugin is connected as the "sink" pad of our
    // videobin, and the videobin is connected to the video-sink output of the pipeline,
    // thereby receiving decoded video data. We place a videocaps filter inbetween the
    // converter and the appsink to enforce a color format conversion to the "colorcaps"
    // we need. colorcaps define the needed data format for efficient conversion into
    // a RGBA8 texture:
    colorcaps = gst_caps_new_simple (   "video/x-raw-rgb",
					"bpp", G_TYPE_INT, 32,
					"depth", G_TYPE_INT, 32,
					"alpha_mask", G_TYPE_INT, 0x000000FF,
					"red_mask", G_TYPE_INT,   0x0000FF00,
					"green_mask", G_TYPE_INT, 0x00FF0000,
					"blue_mask", G_TYPE_INT,  0xFF000000,
					NULL);

    /*
    // Old style method: Only left here for documentation to show how one can create
    // video sub-pipelines via bin's and connect them to each other via ghostpads: 

    GstElement *videobin = gst_bin_new ("video_output_bin");
    GstElement *videocon = gst_element_factory_make ("ffmpegcolorspace", "color_converter");
    gst_bin_add_many(GST_BIN(videobin), videocon, videosink, NULL);

    GstPad *ghostpad = gst_ghost_pad_new("Video_Ghostsink", gst_element_get_pad(videocon, "sink"));
    gst_element_add_pad(videobin, ghostpad);

    gst_element_link_filtered(videocon, videosink, colorcaps);

    // Assign our special videobin as video-sink of the pipeline:
    g_object_set(G_OBJECT(theMovie), "video-sink", videobin, NULL);
    */

    // New style method: Leaves the freedom of choice of color converter (if any)
    // to the auto-plugger.

    // Assign 'colorcaps' as caps to our videosink. This marks the videosink so
    // that it can only receive video image data in the format defined by colorcaps,
    // i.e., a format that is easy to consume for OpenGL's texture creation on std.
    // gpu's. It is the job of the video pipeline's autoplugger to plug in proper
    // color & format conversion plugins to satisfy videosink's needs.
    gst_app_sink_set_caps(GST_APP_SINK(videosink), colorcaps);

    // Assign our special appsink 'videosink' as video-sink of the pipeline:
    g_object_set(G_OBJECT(theMovie), "video-sink", videosink, NULL);
    gst_caps_unref(colorcaps);

    // Get the pad from the final sink for probing width x height of movie frames and nominal framerate of movie:	
    pad = gst_element_get_pad(videosink, "sink");

    PsychGSProcessMovieContext(movieRecordBANK[slotid].MovieContext, FALSE);

    // Should we preroll / preload?	
    if ((preloadSecs > 0) || (preloadSecs == -1)) {
	// Preload / Preroll the pipeline:
	if (!PsychMoviePipelineSetState(theMovie, GST_STATE_PAUSED, 30.0)) {
		PsychGSProcessMovieContext(movieRecordBANK[slotid].MovieContext, TRUE);
		PsychErrorExitMsg(PsychError_user, "In OpenMovie: Opening the movie failed. Reason given above.");
	}
    } else {
	// Ready the pipeline:
	if (!PsychMoviePipelineSetState(theMovie, GST_STATE_READY, 30.0)) {
		PsychGSProcessMovieContext(movieRecordBANK[slotid].MovieContext, TRUE);
		PsychErrorExitMsg(PsychError_user, "In OpenMovie: Opening the movie failed. Reason given above.");
	}    
    }

    // Query number of available video and audio tracks in movie:
    g_object_get (G_OBJECT(theMovie),
               "n-video", &movieRecordBANK[slotid].nrVideoTracks,
               "n-audio", &movieRecordBANK[slotid].nrAudioTracks,
                NULL);

    // We need a valid onscreen window handle for real video playback:
    if ((NULL == win) && (movieRecordBANK[slotid].nrVideoTracks > 0)) {
        if (printErrors) PsychErrorExitMsg(PsychError_user, "No windowPtr to an onscreen window provided. Must do so for movies with videotrack!"); else return;
    }
 
    PsychGSProcessMovieContext(movieRecordBANK[slotid].MovieContext, FALSE);

    PsychInitMutex(&movieRecordBANK[slotid].mutex);
    PsychInitCondition(&movieRecordBANK[slotid].condition, NULL);

    if (oldstyle) {
	// Install the probe callback for reception of video frames from engine at the sink-pad itself:
	gst_pad_add_buffer_probe(pad, G_CALLBACK(PsychHaveVideoDataCallback), &(movieRecordBANK[slotid]));
    } else {
	// Install callbacks used by the videosink (appsink) to announce various events:
	gst_app_sink_set_callbacks(GST_APP_SINK(videosink), &videosinkCallbacks, &(movieRecordBANK[slotid]), PsychDestroyNotifyCallback);
    }

    // Drop frames if callback can't pull buffers fast enough:
    // This together with the max queue lengths of 1 allows to
    // maintain audio-video sync by framedropping if needed.
    gst_app_sink_set_drop(GST_APP_SINK(videosink), TRUE);

    // Only allow one queued buffer before dropping:
    gst_app_sink_set_max_buffers(GST_APP_SINK(videosink), 1);

    // Assign harmless initial settings for fps and frame size:
    rate1 = 0;
    rate2 = 1;
    width = height = 0;

    // Videotrack available?
    if (movieRecordBANK[slotid].nrVideoTracks > 0) {
	// Yes: Query size and framerate of movie:
	peerpad = gst_pad_get_peer(pad);
	caps=gst_pad_get_negotiated_caps(peerpad);
	if (caps) {
		str=gst_caps_get_structure(caps,0);

		/* Get some data about the frame */
		rate1 = 1; rate2 = 1;
		gst_structure_get_fraction(str, "pixel-aspect-ratio", &rate1, &rate2);
		movieRecordBANK[slotid].aspectRatio = (double) rate1 / (double) rate2;
		gst_structure_get_int(str,"width",&width);
		gst_structure_get_int(str,"height",&height);
		rate1 = 0; rate2 = 1;
		gst_structure_get_fraction(str, "framerate", &rate1, &rate2);

	 } else {
		printf("PTB-DEBUG: No frame info available after preroll.\n");	
	 }
    }

    if (strstr(moviename, "v4l2:")) {
	// Special case: The "movie" is actually a video4linux2 live source.
	// Need to make parameters up for now, so it to work as "movie":
	rate1 = 30; width = 640; height = 480;
	movieRecordBANK[slotid].nrVideoTracks = 1;

	// Uglyness at its best ;-)
	if (strstr(moviename, "320")) { width = 320; height = 240; };
    }

    // Release the pad:
    gst_object_unref(pad);

    // Assign new record in moviebank:
    movieRecordBANK[slotid].theMovie = theMovie;
    movieRecordBANK[slotid].loopflag = 0;
    movieRecordBANK[slotid].frameAvail = 0;
    movieRecordBANK[slotid].imageBuffer = NULL;

    *moviehandle = slotid;

    // Increase counter:
    numMovieRecords++;

    // Compute basic movie properties - Duration and fps as well as image size:
    
    // Retrieve duration in seconds:
    fmt = GST_FORMAT_TIME;
    if (gst_element_query_duration(theMovie, &fmt, &length_format)) {
	// This returns nsecs, so convert to seconds:
    	movieRecordBANK[slotid].movieduration = (double) length_format / (double) 1e9;
	//printf("PTB-DEBUG: Duration of movie %i [%s] is %lf seconds.\n", slotid, moviename, movieRecordBANK[slotid].movieduration);
    } else {
	movieRecordBANK[slotid].movieduration = DBL_MAX;
	printf("PTB-WARNING: Could not query duration of movie %i [%s] in seconds. Returning infinity.\n", slotid, moviename);
    }

    // Assign expected framerate, assuming a linear spacing between frames:
    movieRecordBANK[slotid].fps = (double) rate1 / (double) rate2;
    //printf("PTB-DEBUG: Framerate fps of movie %i [%s] is %lf fps.\n", slotid, moviename, movieRecordBANK[slotid].fps);

    // Compute framecount from fps and duration:
    movieRecordBANK[slotid].nrframes = (int)(movieRecordBANK[slotid].fps * movieRecordBANK[slotid].movieduration + 0.5);
    //printf("PTB-DEBUG: Number of frames in movie %i [%s] is %i.\n", slotid, moviename, movieRecordBANK[slotid].nrframes);

    // Define size of images in movie:
    movieRecordBANK[slotid].width = width;
    movieRecordBANK[slotid].height = height;

    // Ready to rock!
    return;
}
void PsychHIDInitializeHIDStandardInterfaces(void)
{
    int i;
    HRESULT rc;
    HINSTANCE modulehandle = NULL;
    dinput = NULL;
    ndevices = 0;

    // Init x_dev array:
    for (i = 0; i < PSYCH_HID_MAX_DEVICES; i++) x_dev[i] = NULL;

    // Init keyboard queue arrays:
    memset(&psychHIDKbQueueFirstPress[0], 0, sizeof(psychHIDKbQueueFirstPress));
    memset(&psychHIDKbQueueFirstRelease[0], 0, sizeof(psychHIDKbQueueFirstRelease));
    memset(&psychHIDKbQueueLastPress[0], 0, sizeof(psychHIDKbQueueLastPress));
    memset(&psychHIDKbQueueLastRelease[0], 0, sizeof(psychHIDKbQueueLastRelease));
    memset(&psychHIDKbQueueActive[0], 0, sizeof(psychHIDKbQueueActive));
    memset(&psychHIDKbQueueScanKeys[0], 0, sizeof(psychHIDKbQueueScanKeys));

    // We need the module instance handle of ourselves, ie., the PsychHID mex file to
    // open a DirectInput-8 interface, so the OS can apply backwards compatibility fixes
    // specific to the way our mex file DLL was built. For this we need the name of the
    // mex file, which is dependent on Octave vs. Matlab and 32-Bit vs. 64-Bit:
#ifndef PTBOCTAVE3MEX
    // Matlab: 64-Bit or 32-Bit mex file?
#if defined(__LP64__) || defined(_M_IA64) || defined(_WIN64)
    // 64-Bit:
    modulehandle = GetModuleHandle("PsychHID.mexw64");
#else
    // 32-Bit:
    modulehandle = GetModuleHandle("PsychHID.mexw32");
#endif
#else
    // Octave: Same mex file file-extension for 32/64-Bit:
    modulehandle = GetModuleHandle("PsychHID.mex");
#endif

    // If this doesn't work, try with application module handle as fallback. This works usually on
    // Windows XP/Vista/7, but may fail catastrophically on Windows-8 and later:
    if (NULL == modulehandle) {
        printf("PsychHID-WARNING: Could not get module handle to PsychHID mex file. Did you rename it? Please don't do that!\n");
        printf("PsychHID-WARNING: Will try application module handle as fallback. This may end badly, e.g., with a crash. Cross your fingers!\n");
        modulehandle = GetModuleHandle(NULL);
    }

    if (NULL == modulehandle) PsychErrorExitMsg(PsychError_system, "PsychHID: FATAL ERROR: Couldn't get module handle to create interface to Microsoft DirectInput-8! Game over!");

    // Open a DirectInput-8 interface:
    rc = DirectInput8Create(modulehandle, DIRECTINPUT_VERSION, IID_IDirectInput8, (LPVOID*)&dinput, NULL);

    if (DI_OK != rc) {
        printf("PsychHID-ERROR: Error return from DirectInput8Create: %x\n", (int) rc);
        if (rc == DIERR_OLDDIRECTINPUTVERSION) printf("PsychHID-ERROR: You need to install a more recent version of DirectX -- at least DirectX-8.\n");
        PsychErrorExitMsg(PsychError_system, "PsychHID: FATAL ERROR: Couldn't create interface to Microsoft DirectInput-8! Game over!");
    }

    // Enumerate all DirectInput keyboard(-like) devices:
    rc = dinput->EnumDevices(DI8DEVCLASS_KEYBOARD, (LPDIENUMDEVICESCALLBACK) keyboardEnumCallback, NULL, DIEDFL_ATTACHEDONLY | DIEDFL_INCLUDEHIDDEN);
    if (DI_OK != rc) {
        printf("PsychHID-ERROR: Error return from DirectInput8 EnumDevices(): %x! Game over!\n", (int) rc);
        goto out;
    }

    // Enumerate all DirectInput mouse(-like) devices:
    rc = dinput->EnumDevices(DI8DEVCLASS_POINTER, (LPDIENUMDEVICESCALLBACK) keyboardEnumCallback, NULL, DIEDFL_ATTACHEDONLY | DIEDFL_INCLUDEHIDDEN);
    if (DI_OK != rc) {
        printf("PsychHID-ERROR: Error return from DirectInput8 EnumDevices(): %x! Game over!\n", (int) rc);
        goto out;
    }

    // Enumerate all DirectInput joystick/gamepad(-like) devices:
    rc = dinput->EnumDevices(DI8DEVCLASS_GAMECTRL, (LPDIENUMDEVICESCALLBACK) keyboardEnumCallback, NULL, DIEDFL_ATTACHEDONLY | DIEDFL_INCLUDEHIDDEN);
    if (DI_OK != rc) {
        printf("PsychHID-ERROR: Error return from DirectInput8 EnumDevices(): %x! Game over!\n", (int) rc);
        goto out;
    }

    // Create keyboard queue mutex for later use:
    KbQueueThreadTerminate = FALSE;
    PsychInitMutex(&KbQueueMutex);
    PsychInitCondition(&KbQueueCondition, NULL);

    // Create event object for signalling device state changes:
    hEvent = CreateEvent(   NULL,	// default security attributes
                            FALSE,	// auto-reset event: This would need to be set TRUE for PsychBroadcastCondition() to work on Windows!
                            FALSE,	// initial state is nonsignaled
                            NULL	// no object name
                        );

    // Ready.
    return;

out:
    ndevices = 0;

    // Close our dedicated x-display connection and we are done:
    if (dinput) dinput->Release();
    dinput = NULL;

    PsychErrorExitMsg(PsychError_system, "PsychHID: FATAL ERROR: X Input extension version 2.0 or later not available! Game over!");
}