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); }
/* Send abort request to thread: */ int PsychAbortThread(psych_thread* threadhandle) { // This is an emergency abort call! Maybe should think about a "softer" solution for Windows? // This is more like an option for a future PsychKillThread(): return( TerminateThread((*threadhandle)->handle, 0) ); // Signal the terminateReq condition variable/signal to politely ask the thread to terminate: return(PsychSignalCondition(&((*threadhandle)->terminateReq))); }
/* 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); }
/* 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; }
/* 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); }
// 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; }
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. }