mirror of
https://github.com/moonlight-stream/moonlight-qt
synced 2024-11-10 05:34:17 +00:00
Send UTF-8 clipboard text directly rather than emulating keystrokes
This commit is contained in:
parent
c7121516c1
commit
97a09e0571
6 changed files with 20 additions and 242 deletions
|
@ -136,7 +136,6 @@ SOURCES += \
|
|||
settings/mappingfetcher.cpp \
|
||||
settings/streamingpreferences.cpp \
|
||||
streaming/input/abstouch.cpp \
|
||||
streaming/input/clipboard.cpp \
|
||||
streaming/input/gamepad.cpp \
|
||||
streaming/input/input.cpp \
|
||||
streaming/input/keyboard.cpp \
|
||||
|
|
|
@ -1,190 +0,0 @@
|
|||
#include "input.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
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; \
|
||||
break
|
||||
|
||||
#define MAP_KEY_SHIFT(c, sc) \
|
||||
case c: \
|
||||
event.key.keysym.scancode = sc; \
|
||||
event.key.keysym.mod = KMOD_SHIFT; \
|
||||
break
|
||||
|
||||
void SdlInputHandler::sendText(QString& string)
|
||||
{
|
||||
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;
|
||||
}
|
||||
else if (c >= 'a' && c <= 'z') {
|
||||
event.key.keysym.scancode = (SDL_Scancode)((c - 'a') + SDL_SCANCODE_A);
|
||||
}
|
||||
else if (c >= '1' && c <= '9') {
|
||||
event.key.keysym.scancode = (SDL_Scancode)((c - '1') + SDL_SCANCODE_1);
|
||||
}
|
||||
else {
|
||||
// Fixup some Unicode characters to be expressible in ASCII
|
||||
switch (c) {
|
||||
case u'\U0000201C': // Left "
|
||||
case u'\U0000201D': // Right "
|
||||
c = '"';
|
||||
break;
|
||||
|
||||
case u'\U00002018': // Left '
|
||||
case u'\U00002019': // Right '
|
||||
c = '\'';
|
||||
break;
|
||||
|
||||
case u'\U00002013': // Smart -
|
||||
c = '-';
|
||||
break;
|
||||
|
||||
case u'\U00002026': // Ellipsis (...)
|
||||
// Convert this character into 3 periods
|
||||
string.insert(i+1, '.');
|
||||
string.insert(i+1, '.');
|
||||
c = '.';
|
||||
break;
|
||||
|
||||
case u'\U000000A0': // Non-breaking space
|
||||
c = ' ';
|
||||
break;
|
||||
|
||||
default:
|
||||
// Nothing
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
switch (c) {
|
||||
|
||||
// Handle CRLF separately to avoid duplicate newlines
|
||||
case '\r':
|
||||
if (string[i + 1] == '\n') {
|
||||
i++;
|
||||
}
|
||||
event.key.keysym.scancode = SDL_SCANCODE_RETURN;
|
||||
break;
|
||||
|
||||
MAP_KEY('\b', SDL_SCANCODE_BACKSPACE);
|
||||
MAP_KEY('\n', SDL_SCANCODE_RETURN);
|
||||
MAP_KEY('\t', SDL_SCANCODE_TAB);
|
||||
|
||||
MAP_KEY(' ', SDL_SCANCODE_SPACE);
|
||||
MAP_KEY_SHIFT('!', SDL_SCANCODE_1);
|
||||
MAP_KEY_SHIFT('"', SDL_SCANCODE_APOSTROPHE);
|
||||
MAP_KEY_SHIFT('#', SDL_SCANCODE_3);
|
||||
MAP_KEY_SHIFT('$', SDL_SCANCODE_4);
|
||||
MAP_KEY_SHIFT('%', SDL_SCANCODE_5);
|
||||
MAP_KEY_SHIFT('&', SDL_SCANCODE_7);
|
||||
MAP_KEY('\'', SDL_SCANCODE_APOSTROPHE);
|
||||
MAP_KEY_SHIFT('(', SDL_SCANCODE_9);
|
||||
MAP_KEY_SHIFT(')', SDL_SCANCODE_0);
|
||||
MAP_KEY_SHIFT('*', SDL_SCANCODE_8);
|
||||
MAP_KEY_SHIFT('+', SDL_SCANCODE_EQUALS);
|
||||
MAP_KEY(',', SDL_SCANCODE_COMMA);
|
||||
MAP_KEY('-', SDL_SCANCODE_MINUS);
|
||||
MAP_KEY('.', SDL_SCANCODE_PERIOD);
|
||||
MAP_KEY('/', SDL_SCANCODE_SLASH);
|
||||
MAP_KEY('0', SDL_SCANCODE_0);
|
||||
|
||||
MAP_KEY_SHIFT(':', SDL_SCANCODE_SEMICOLON);
|
||||
MAP_KEY(';', SDL_SCANCODE_SEMICOLON);
|
||||
MAP_KEY_SHIFT('<', SDL_SCANCODE_COMMA);
|
||||
MAP_KEY('=', SDL_SCANCODE_EQUALS);
|
||||
MAP_KEY_SHIFT('>', SDL_SCANCODE_PERIOD);
|
||||
MAP_KEY_SHIFT('?', SDL_SCANCODE_SLASH);
|
||||
MAP_KEY_SHIFT('@', SDL_SCANCODE_2);
|
||||
|
||||
MAP_KEY('[', SDL_SCANCODE_LEFTBRACKET);
|
||||
MAP_KEY('\\', SDL_SCANCODE_BACKSLASH);
|
||||
MAP_KEY(']', SDL_SCANCODE_RIGHTBRACKET);
|
||||
MAP_KEY_SHIFT('^', SDL_SCANCODE_6);
|
||||
MAP_KEY_SHIFT('_', SDL_SCANCODE_MINUS);
|
||||
MAP_KEY('`', SDL_SCANCODE_GRAVE);
|
||||
|
||||
MAP_KEY_SHIFT('{', SDL_SCANCODE_LEFTBRACKET);
|
||||
MAP_KEY_SHIFT('|', SDL_SCANCODE_BACKSLASH);
|
||||
MAP_KEY_SHIFT('}', SDL_SCANCODE_RIGHTBRACKET);
|
||||
MAP_KEY_SHIFT('~', SDL_SCANCODE_GRAVE);
|
||||
|
||||
default:
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Pasting text - non-ASCII character value 'U+%x' ignored",
|
||||
c);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
SDL_Delay(10);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -33,8 +33,7 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
|
|||
m_RightButtonReleaseTimer(0),
|
||||
m_DragTimer(0),
|
||||
m_DragButton(0),
|
||||
m_NumFingersDown(0),
|
||||
m_ClipboardData()
|
||||
m_NumFingersDown(0)
|
||||
{
|
||||
// System keys are always captured when running without a DE
|
||||
if (!WMUtils::isRunningDesktopEnvironment()) {
|
||||
|
@ -198,13 +197,6 @@ 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()
|
||||
|
@ -230,17 +222,6 @@ 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));
|
||||
|
|
|
@ -138,9 +138,6 @@ private:
|
|||
static
|
||||
Uint32 dragTimerCallback(Uint32 interval, void* param);
|
||||
|
||||
static
|
||||
int clipboardThreadProc(void *ptr);
|
||||
|
||||
SDL_Window* m_Window;
|
||||
bool m_MultiController;
|
||||
bool m_GamepadMouse;
|
||||
|
@ -191,11 +188,5 @@ 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[];
|
||||
};
|
||||
|
|
|
@ -101,13 +101,18 @@ void SdlInputHandler::performSpecialKeyCombo(KeyCombo combo)
|
|||
// with the text we're going to type.
|
||||
raiseAllKeys();
|
||||
|
||||
const char* text;
|
||||
char* text;
|
||||
if (SDL_HasClipboardText() && (text = SDL_GetClipboardText()) != nullptr) {
|
||||
// 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);
|
||||
// Sending both CR and LF will lead to two newlines in the destination for
|
||||
// each newline in the source, so we fix up any CRLFs into just a single LF.
|
||||
for (char* c = text; *c != 0; c++) {
|
||||
if (*c == '\r' && *(c + 1) == '\n') {
|
||||
memmove(c, c + 1, strlen(c) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Send this text to the PC
|
||||
LiSendUtf8TextEvent(text, strlen(text));
|
||||
|
||||
// SDL_GetClipboardText() allocates, so we must free
|
||||
SDL_free((void*)text);
|
||||
|
@ -124,8 +129,6 @@ void SdlInputHandler::performSpecialKeyCombo(KeyCombo combo)
|
|||
}
|
||||
}
|
||||
|
||||
#define IS_SYNTHETIC_KEY_EVENT(x) ((x)->timestamp == 0)
|
||||
|
||||
void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
||||
{
|
||||
short keyCode;
|
||||
|
@ -138,9 +141,7 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
|||
}
|
||||
|
||||
// Check for our special key combos
|
||||
// Ignore timestamp == 0 which are sent from our keyboard code.
|
||||
if (!IS_SYNTHETIC_KEY_EVENT(event) &&
|
||||
(event->state == SDL_PRESSED) &&
|
||||
if ((event->state == SDL_PRESSED) &&
|
||||
(event->keysym.mod & KMOD_CTRL) &&
|
||||
(event->keysym.mod & KMOD_ALT) &&
|
||||
(event->keysym.mod & KMOD_SHIFT)) {
|
||||
|
@ -403,16 +404,12 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
|||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
// 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,
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 3b9d8a31763be77c921bd2581b5e75f4d40a1b11
|
||||
Subproject commit 94d439e5c3aa388a2cf98a26ad1a4be5d7563763
|
Loading…
Reference in a new issue