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; }
PsychError SCREENOpenMovie(void) { PsychWindowRecordType *windowRecord; char *moviefile; int moviehandle = -1; int framecount; double durationsecs; double framerate; double aspectRatio; int width; int height; int asyncFlag = 0; int specialFlags1 = 0; static psych_bool firstTime = TRUE; double preloadSecs = 1; int rc; int pixelFormat = 4; int maxNumberThreads = -1; if (firstTime) { // Setup asyncopeninfo on first invocation: firstTime = FALSE; asyncmovieinfo.asyncstate = 0; // State = No async open in progress. } // All sub functions should have these two lines PsychPushHelp(useString, synopsisString, seeAlsoString); if(PsychIsGiveHelp()) {PsychGiveHelp(); return(PsychError_none);}; PsychErrorExit(PsychCapNumInputArgs(7)); // Max. 7 input args. PsychErrorExit(PsychRequireNumInputArgs(1)); // Min. 1 input args required. PsychErrorExit(PsychCapNumOutputArgs(7)); // Max. 7 output args. // Get the window record from the window record argument and get info from the window record windowRecord = NULL; PsychAllocInWindowRecordArg(kPsychUseDefaultArgPosition, FALSE, &windowRecord); // Only onscreen windows allowed: if(windowRecord && !PsychIsOnscreenWindow(windowRecord)) { PsychErrorExitMsg(PsychError_user, "OpenMovie called on something else than an onscreen window."); } // Get the movie name string: moviefile = NULL; PsychAllocInCharArg(2, kPsychArgRequired, &moviefile); // Get the (optional) asyncFlag: PsychCopyInIntegerArg(3, FALSE, &asyncFlag); PsychCopyInDoubleArg(4, FALSE, &preloadSecs); if (preloadSecs < 0 && preloadSecs!= -1 && preloadSecs!= -2) PsychErrorExitMsg(PsychError_user, "OpenMovie called with invalid (negative, but not equal -1) 'preloadSecs' argument!"); // Get the (optional) specialFlags1: PsychCopyInIntegerArg(5, FALSE, &specialFlags1); if (specialFlags1 < 0) PsychErrorExitMsg(PsychError_user, "OpenMovie called with invalid 'specialFlags1' setting! Only positive values allowed."); // Get the (optional) pixelFormat: PsychCopyInIntegerArg(6, FALSE, &pixelFormat); if (pixelFormat < 1 || pixelFormat > 8) PsychErrorExitMsg(PsychError_user, "OpenMovie called with invalid 'pixelFormat' setting! Only values 1 to 8 are allowed."); // Get the (optional) maxNumberThreads: PsychCopyInIntegerArg(7, FALSE, &maxNumberThreads); if (maxNumberThreads < -1) PsychErrorExitMsg(PsychError_user, "OpenMovie called with invalid 'maxNumberThreads' setting! Only values of -1 or greater are allowed."); // Queueing of a new movie for seamless playback requested? if (asyncFlag & 2) { // Yes. Do a special call, just passing the moviename of the next // movie to play. Pass the relevant moviehandle as retrieved from // preloadSecs: moviehandle = (int) preloadSecs; preloadSecs = 0; PsychCreateMovie(windowRecord, moviefile, preloadSecs, &moviehandle, asyncFlag, specialFlags1, pixelFormat, maxNumberThreads); if (moviehandle == -1) PsychErrorExitMsg(PsychError_user, "Could not queue new moviefile for gapless playback."); return(PsychError_none); } // Asynchronous Open operation in progress or requested? if ((asyncmovieinfo.asyncstate == 0) && !(asyncFlag & 1)) { // No. We should just synchronously open the movie: // Try to open the named 'moviefile' and create & initialize a corresponding movie object. // A MATLAB handle to the movie object is returned upon successfull operation. PsychCreateMovie(windowRecord, moviefile, preloadSecs, &moviehandle, asyncFlag, specialFlags1, pixelFormat, maxNumberThreads); } else { // Asynchronous open operation requested or running: switch(asyncmovieinfo.asyncstate) { case 0: // No async open running, but async open requested // Fill all information needed for opening the movie into the info struct: asyncmovieinfo.asyncstate = 1; // Mark state as "Operation in progress" asyncmovieinfo.moviename = strdup(moviefile); asyncmovieinfo.preloadSecs = preloadSecs; asyncmovieinfo.asyncFlag = asyncFlag; asyncmovieinfo.specialFlags1 = specialFlags1; asyncmovieinfo.pixelFormat = pixelFormat; asyncmovieinfo.maxNumberThreads = maxNumberThreads; if (windowRecord) { memcpy(&asyncmovieinfo.windowRecord, windowRecord, sizeof(PsychWindowRecordType)); } else { memcpy(&asyncmovieinfo.windowRecord, 0, sizeof(PsychWindowRecordType)); } asyncmovieinfo.moviehandle = -1; // Increase our scheduling priority to basic RT priority: This way we should get // more cpu time for our PTB main thread than the async. background prefetch-thread: // On Windows we must not go higher than basePriority 1 (HIGH PRIORITY) or bad interference can happen. // On OS/X we use basePriority 2 for robust realtime, using up to (4+1) == 5 msecs of time in every 10 msecs slice, allowing for up to 1 msec jitter/latency for ops. // On Linux we just use standard basePriority 2 RT-FIFO scheduling and trust the os to do the right thing. if ((rc=PsychSetThreadPriority(NULL, ((PSYCH_SYSTEM == PSYCH_WINDOWS) ? 1 : 2), ((PSYCH_SYSTEM == PSYCH_OSX) ? 4 : 0)))!=0) { printf("PTB-WARNING: In OpenMovie(): Failed to raise priority of main thread [System error %i]. Expect movie timing problems.\n", rc); } // Start our own movie loader Posix-Thread: PsychCreateThread(&asyncmovieinfo.pid, NULL, PsychAsyncCreateMovie, &asyncmovieinfo); // Async movie open initiated. We return control to host environment: return(PsychError_none); break; case 1: // Async open operation in progress, but not yet finished. // Should we wait for completion or just return? if (asyncFlag & 1) { // Async poll requested. We just return -1 to signal that open isn't finished yet: PsychCopyOutDoubleArg(1, TRUE, -1); return(PsychError_none); } // We fall through to case 2 - Wait for "Load operation successfully finished." case 2: // Async open operation successfully finished. Parse asyncinfo struct and return it to host environment: // We need to join our terminated worker thread to release its ressources. If the worker-thread // isn't done yet (fallthrough from case 1 for sync. wait), this join will block us until worker // completes: PsychDeleteThread(&asyncmovieinfo.pid); asyncmovieinfo.asyncstate = 0; // Reset state to idle: moviehandle = asyncmovieinfo.moviehandle; // Movie successfully opened? if (moviehandle < 0) { // Movie loading failed for some reason. printf("PTB-ERROR: When trying to asynchronously load movie %s, the operation failed: ", asyncmovieinfo.moviename); #if PSYCH_SYSTEM == PSYCH_OSX switch(moviehandle) { case -2000: case -50: case -43: printf("File not found."); break; case -2048: printf("This is not a file that Quicktime understands."); break; case -2003: printf("Can't find media handler (codec) for this movie."); break; case -2: printf("Maximum allowed number of simultaneously open movie files exceeded!"); break; case -1: printf("Internal error: Failure in PTB's movie playback engine!"); break; default: printf("Unknown error (Quicktime error %i): Check http://developer.apple.com/documentation/QuickTime/APIREF/ErrorCodes.htm#//apple_ref/doc/constant_group/Error_Codes", moviehandle); } printf("\n\n"); #endif PsychErrorExitMsg(PsychError_user, "Asynchronous loading of the Quicktime movie failed."); } // We can fall out of the switch statement and continue with the standard synchronous load code as if // the movie had been loaded synchronously. break; default: PsychErrorExitMsg(PsychError_internal, "Unhandled async movie state condition encountered! BUG!!"); } } // Upon sucessfull completion, we'll have a valid handle in 'moviehandle'. // Return it to Matlab-world: PsychCopyOutDoubleArg(1, TRUE, (double) moviehandle); // Retrieve infos about new movie: // Is the "count" output argument (total number of frames) requested by user? if (PsychGetNumOutputArgs() > 5) { // Yes. Query the framecount (expensive!) and return it: PsychGetMovieInfos(moviehandle, &width, &height, &framecount, &durationsecs, &framerate, NULL, &aspectRatio); PsychCopyOutDoubleArg(6, TRUE, (double) framecount); } else { // No. Don't compute and return it. PsychGetMovieInfos(moviehandle, &width, &height, NULL, &durationsecs, &framerate, NULL, &aspectRatio); } PsychCopyOutDoubleArg(2, FALSE, (double) durationsecs); PsychCopyOutDoubleArg(3, FALSE, (double) framerate); PsychCopyOutDoubleArg(4, FALSE, (double) width); PsychCopyOutDoubleArg(5, FALSE, (double) height); PsychCopyOutDoubleArg(7, FALSE, (double) aspectRatio); // Ready! return(PsychError_none); }
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); }