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); }
psych_bool PsychHIDCreateEventBuffer(int deviceIndex, int numValuators, int numSlots) { unsigned int bufferSize; if (deviceIndex < 0) deviceIndex = PsychHIDGetDefaultKbQueueDevice(); if (numSlots < 0) { printf("PTB-ERROR: PsychHIDCreateEventBuffer(): numSlots %i invalid. Must be at least 0.\n", numSlots); return(FALSE); } // Non-zero numSlots ==> Set new capacity, otherwise leave at default/last capacity: if (numSlots > 0) hidEventBufferCapacity[deviceIndex] = numSlots; bufferSize = hidEventBufferCapacity[deviceIndex]; // Already created? If so, nothing to do: if (hidEventBuffer[deviceIndex] || (bufferSize < 1)) return(FALSE); if (numValuators > PSYCH_HID_MAX_VALUATORS) { printf("PTB-ERROR: PsychHIDCreateEventBuffer(): numValuators %i > current compile time maximum of %i!\n", numValuators, PSYCH_HID_MAX_VALUATORS); return(FALSE); } hidEventBuffer[deviceIndex] = (PsychHIDEventRecord*) calloc(sizeof(PsychHIDEventRecord), bufferSize); if (NULL==hidEventBuffer[deviceIndex]) { printf("PTB-ERROR: PsychHIDCreateEventBuffer(): Insufficient memory to create KbQueue event buffer!"); return(FALSE); } // Prepare mutex for buffer: PsychInitMutex(&hidEventBufferMutex[deviceIndex]); PsychInitCondition(&hidEventBufferCondition[deviceIndex], NULL); // Init & Flush it: hidEventBufferWritePos[deviceIndex] = 0; PsychHIDFlushEventBuffer(deviceIndex); return(TRUE); }
psych_bool 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!"); }