diff --git a/app/streaming/input/clipboard.cpp b/app/streaming/input/clipboard.cpp index bdcb7cbc..4851a48e 100644 --- a/app/streaming/input/clipboard.cpp +++ b/app/streaming/input/clipboard.cpp @@ -2,6 +2,34 @@ #include +int SdlInputHandler::clipboardThreadProc(void *ptr) +{ + auto me = (SdlInputHandler*)ptr; + + while (!SDL_AtomicGet(&me->m_ShutdownClipboardThread)) { + QString clipboardData; + + SDL_LockMutex(me->m_ClipboardLock); + for (;;) { + clipboardData = me->m_ClipboardData; + me->m_ClipboardData.clear(); + if (!clipboardData.isEmpty() || SDL_AtomicGet(&me->m_ShutdownClipboardThread)) { + break; + } + SDL_CondWait(me->m_ClipboardHasData, me->m_ClipboardLock); + } + SDL_UnlockMutex(me->m_ClipboardLock); + + // We might get here on shutdown, so don't send text if there's + // nothing to send. + if (!clipboardData.isEmpty()) { + me->sendText(clipboardData); + } + } + + return 0; +} + #define MAP_KEY(c, sc) \ case c: \ event.key.keysym.scancode = sc; \ @@ -13,14 +41,18 @@ event.key.keysym.mod = KMOD_SHIFT; \ break -void SdlInputHandler::sendText(const char* text) +void SdlInputHandler::sendText(QString& string) { - QString string = QString::fromUtf8(text); - for (int i = 0; i < string.size(); i++) { char16_t c = string[i].unicode(); SDL_Event event = {}; + // If we're sending a very long string, we might get a termination request + // while we're in the middle of sending a string. In that case, just bail. + if (SDL_AtomicGet(&m_ShutdownClipboardThread)) { + return; + } + if (c >= 'A' && c <= 'Z') { event.key.keysym.scancode = (SDL_Scancode)((c - 'A') + SDL_SCANCODE_A); event.key.keysym.mod = KMOD_SHIFT; @@ -125,6 +157,16 @@ void SdlInputHandler::sendText(const char* text) } } + if (event.key.keysym.mod & KMOD_SHIFT) { + SDL_Event modifierEvent = {}; + modifierEvent.type = SDL_KEYDOWN; + modifierEvent.key.state = SDL_PRESSED; + modifierEvent.key.keysym.scancode = SDL_SCANCODE_LSHIFT; + handleKeyEvent(&modifierEvent.key); + + SDL_Delay(10); + } + event.type = SDL_KEYDOWN; event.key.state = SDL_PRESSED; handleKeyEvent(&event.key); @@ -134,5 +176,15 @@ void SdlInputHandler::sendText(const char* text) event.type = SDL_KEYUP; event.key.state = SDL_RELEASED; handleKeyEvent(&event.key); + + if (event.key.keysym.mod & KMOD_SHIFT) { + SDL_Event modifierEvent = {}; + modifierEvent.type = SDL_KEYUP; + modifierEvent.key.state = SDL_RELEASED; + modifierEvent.key.keysym.scancode = SDL_SCANCODE_LSHIFT; + handleKeyEvent(&modifierEvent.key); + + SDL_Delay(10); + } } } diff --git a/app/streaming/input/input.cpp b/app/streaming/input/input.cpp index 19018770..83a643c9 100644 --- a/app/streaming/input/input.cpp +++ b/app/streaming/input/input.cpp @@ -34,7 +34,8 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s m_RightButtonReleaseTimer(0), m_DragTimer(0), m_DragButton(0), - m_NumFingersDown(0) + m_NumFingersDown(0), + m_ClipboardData() { // Allow gamepad input when the app doesn't have focus if requested SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, prefs.backgroundGamepad ? "1" : "0"); @@ -196,6 +197,13 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s } m_MouseMoveTimer = SDL_AddTimer(pollingInterval, SdlInputHandler::mouseMoveTimerCallback, this); + + // Initialize state used by the clipboard thread before we start it + SDL_AtomicSet(&m_ShutdownClipboardThread, 0); + m_ClipboardHasData = SDL_CreateCond(); + m_ClipboardLock = SDL_CreateMutex(); + + m_ClipboardThread = SDL_CreateThread(SdlInputHandler::clipboardThreadProc, "Clipboard Sender", this); } SdlInputHandler::~SdlInputHandler() @@ -221,6 +229,17 @@ SdlInputHandler::~SdlInputHandler() SDL_RemoveTimer(m_RightButtonReleaseTimer); SDL_RemoveTimer(m_DragTimer); + // Wake up the clipboard thread to terminate it + SDL_AtomicSet(&m_ShutdownClipboardThread, 1); + SDL_CondBroadcast(m_ClipboardHasData); + + // Wait for it to terminate + SDL_WaitThread(m_ClipboardThread, nullptr); + + // Now we can safely clean up its resources + SDL_DestroyCond(m_ClipboardHasData); + SDL_DestroyMutex(m_ClipboardLock); + #if !SDL_VERSION_ATLEAST(2, 0, 9) SDL_QuitSubSystem(SDL_INIT_HAPTIC); SDL_assert(!SDL_WasInit(SDL_INIT_HAPTIC)); diff --git a/app/streaming/input/input.h b/app/streaming/input/input.h index f0d13fde..01b997a4 100644 --- a/app/streaming/input/input.h +++ b/app/streaming/input/input.h @@ -65,7 +65,7 @@ public: void handleJoystickArrivalEvent(SDL_JoyDeviceEvent* event); - void sendText(const char* text); + void sendText(QString& string); void rumble(unsigned short controllerNumber, unsigned short lowFreqMotor, unsigned short highFreqMotor); @@ -136,6 +136,9 @@ private: static Uint32 dragTimerCallback(Uint32 interval, void* param); + static + int clipboardThreadProc(void *ptr); + SDL_Window* m_Window; bool m_MultiController; bool m_GamepadMouse; @@ -187,5 +190,11 @@ private: char m_DragButton; int m_NumFingersDown; + SDL_Thread* m_ClipboardThread; + SDL_atomic_t m_ShutdownClipboardThread; + QString m_ClipboardData; + SDL_cond* m_ClipboardHasData; + SDL_mutex* m_ClipboardLock; + static const int k_ButtonMap[]; }; diff --git a/app/streaming/input/keyboard.cpp b/app/streaming/input/keyboard.cpp index bf4d7942..cecd9a08 100644 --- a/app/streaming/input/keyboard.cpp +++ b/app/streaming/input/keyboard.cpp @@ -94,12 +94,13 @@ void SdlInputHandler::performPendingSpecialKeyCombo() "Detected paste text combo"); const char* text; if (SDL_HasClipboardText() && (text = SDL_GetClipboardText()) != nullptr) { - // Reset pending key combo before pasting, - // otherwise it will ignore our keypresses. - m_PendingKeyCombo = KeyComboMax; + // Append this data to the clipboard data string for the thread to process + SDL_LockMutex(m_ClipboardLock); + m_ClipboardData.append(text); + SDL_CondSignal(m_ClipboardHasData); + SDL_UnlockMutex(m_ClipboardLock); - // Send the text and free it as required by SDL - sendText(text); + // SDL_GetClipboardText() allocates, so we must free SDL_free((void*)text); } else { @@ -117,13 +118,17 @@ void SdlInputHandler::performPendingSpecialKeyCombo() m_PendingKeyCombo = KeyComboMax; } +#define IS_SYNTHETIC_KEY_EVENT(x) ((x)->timestamp == 0) + void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event) { short keyCode; char modifiers; // Check for our special key combos - if ((event->state == SDL_PRESSED) && + // Ignore timestamp == 0 which are sent from our keyboard code. + if (!IS_SYNTHETIC_KEY_EVENT(event) && + (event->state == SDL_PRESSED) && (event->keysym.mod & KMOD_CTRL) && (event->keysym.mod & KMOD_ALT) && (event->keysym.mod & KMOD_SHIFT)) { @@ -157,7 +162,7 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event) } } - if (event->state == SDL_PRESSED && m_PendingKeyCombo != KeyComboMax) { + if (!IS_SYNTHETIC_KEY_EVENT(event) && event->state == SDL_PRESSED && m_PendingKeyCombo != KeyComboMax) { // Ignore further key presses until the special combo is raised return; } @@ -401,12 +406,16 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event) } } - // Track the key state so we always know which keys are down - if (event->state == SDL_PRESSED) { - m_KeysDown.insert(keyCode); - } - else { - m_KeysDown.remove(keyCode); + // If this is a synthetic keypress from the clipboard code, + // this will be on a non-main thread, so don't touch m_KeysDown. + if (!IS_SYNTHETIC_KEY_EVENT(event)) { + // Track the key state so we always know which keys are down + if (event->state == SDL_PRESSED) { + m_KeysDown.insert(keyCode); + } + else { + m_KeysDown.remove(keyCode); + } } LiSendKeyboardEvent(0x8000 | keyCode, @@ -414,7 +423,7 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event) KEY_ACTION_DOWN : KEY_ACTION_UP, modifiers); - if (m_PendingKeyCombo != KeyComboMax && m_KeysDown.isEmpty()) { + if (!IS_SYNTHETIC_KEY_EVENT(event) && m_PendingKeyCombo != KeyComboMax && m_KeysDown.isEmpty()) { int keys; const Uint8 *keyState = SDL_GetKeyboardState(&keys);