diff --git a/idd/LGCommon/CPipeEndpoint.cpp b/idd/LGCommon/CPipeEndpoint.cpp index 5d9e413b..dee5a9dc 100644 --- a/idd/LGCommon/CPipeEndpoint.cpp +++ b/idd/LGCommon/CPipeEndpoint.cpp @@ -130,15 +130,9 @@ CPipeEndpoint::PipeIoResult CPipeEndpoint::WriteMessage( const void * message, DWORD messageSize) { - HANDLE ioEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); - if (!ioEvent) - { - DEBUG_ERROR_HR(GetLastError(), "Failed to create named pipe write event"); - return PipeIoResult::Error; - } - + ResetEvent(m_writeEvent); OVERLAPPED overlapped = {}; - overlapped.hEvent = ioEvent; + overlapped.hEvent = m_writeEvent; DWORD bytesWritten = 0; PipeIoResult result = PipeIoResult::Success; @@ -148,7 +142,7 @@ CPipeEndpoint::PipeIoResult CPipeEndpoint::WriteMessage( if (error == ERROR_IO_PENDING) result = WaitForOverlapped( pipe, - ioEvent, + m_writeEvent, &overlapped, &bytesWritten, WRITE_TIMEOUT_MS); @@ -174,7 +168,6 @@ CPipeEndpoint::PipeIoResult CPipeEndpoint::WriteMessage( result = PipeIoResult::Error; } - CloseHandle(ioEvent); return result; } @@ -197,9 +190,16 @@ bool CPipeEndpoint::Start( m_mode = mode; m_messageSize = messageSize; m_stopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); - if (!m_stopEvent) + m_writeEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + if (!m_stopEvent || !m_writeEvent) { - DEBUG_ERROR_HR(GetLastError(), "Failed to create named pipe stop event"); + DEBUG_ERROR_HR(GetLastError(), "Failed to create named pipe events"); + if (m_writeEvent) + CloseHandle(m_writeEvent); + if (m_stopEvent) + CloseHandle(m_stopEvent); + m_writeEvent = nullptr; + m_stopEvent = nullptr; return false; } @@ -210,6 +210,8 @@ bool CPipeEndpoint::Start( { CloseHandle(m_stopEvent); m_stopEvent = nullptr; + CloseHandle(m_writeEvent); + m_writeEvent = nullptr; return false; } PublishPipe(pipe); @@ -232,6 +234,8 @@ bool CPipeEndpoint::Start( CloseHandle(m_stopEvent); m_stopEvent = nullptr; + CloseHandle(m_writeEvent); + m_writeEvent = nullptr; return false; } @@ -241,6 +245,7 @@ bool CPipeEndpoint::Start( void CPipeEndpoint::Stop() { m_running.store(false); + m_connected.store(false); if (m_stopEvent) SetEvent(m_stopEvent); @@ -270,6 +275,12 @@ void CPipeEndpoint::Stop() m_stopEvent = nullptr; } + if (m_writeEvent) + { + CloseHandle(m_writeEvent); + m_writeEvent = nullptr; + } + m_connected.store(false); } diff --git a/idd/LGCommon/CPipeEndpoint.h b/idd/LGCommon/CPipeEndpoint.h index d03396eb..409d6768 100644 --- a/idd/LGCommon/CPipeEndpoint.h +++ b/idd/LGCommon/CPipeEndpoint.h @@ -128,4 +128,5 @@ private: HANDLE m_pipe = INVALID_HANDLE_VALUE; HANDLE m_thread = nullptr; HANDLE m_stopEvent = nullptr; + HANDLE m_writeEvent = nullptr; }; diff --git a/idd/LGCommon/InputPipeProtocol.h b/idd/LGCommon/InputPipeProtocol.h index 1d8ce664..4b76f1ae 100644 --- a/idd/LGCommon/InputPipeProtocol.h +++ b/idd/LGCommon/InputPipeProtocol.h @@ -20,72 +20,59 @@ #pragma once +#include "common/KVMFRInput.h" + #include #include static constexpr wchar_t LG_INPUT_PIPE_NAME[] = L"\\\\.\\pipe\\LookingGlassIDDInput"; -static constexpr uint32_t LG_INPUT_PIPE_MAGIC = 0x5049474c; -static constexpr uint16_t LG_INPUT_PIPE_VERSION = 2; -static constexpr size_t LG_INPUT_PIPE_MAX_PAYLOAD_SIZE = 64; -static constexpr uint8_t LG_INPUT_MOUSE_BUTTON_MASK = 0x1f; -static constexpr uint16_t LG_INPUT_MOUSE_ABSOLUTE_MAX = 32767; -static constexpr int8_t LG_INPUT_MOUSE_WHEEL_MIN = -127; -static constexpr size_t LG_INPUT_KEYBOARD_KEY_COUNT = 6; -static constexpr uint8_t LG_INPUT_KEYBOARD_USAGE_MAX = 0xe7; +static constexpr uint32_t LG_INPUT_PIPE_MAGIC = 0x5049474c; +static constexpr uint16_t LG_INPUT_PIPE_VERSION = 3; +static constexpr size_t LG_INPUT_PIPE_MAX_PAYLOAD_SIZE = 64; +static constexpr uint16_t LG_INPUT_MOUSE_ABSOLUTE_MAX = + KVMFR_INPUT_MOUSE_ABSOLUTE_MAX; +static constexpr int8_t LG_INPUT_MOUSE_WHEEL_MIN = -127; +static constexpr int32_t LG_INPUT_MOUSE_DELTA_MAX = INT16_MAX * 4; +static constexpr int32_t LG_INPUT_MOUSE_DELTA_MIN = INT16_MIN * 4; +static constexpr int32_t LG_INPUT_MOUSE_WHEEL_MAX = INT8_MAX * 4; +static constexpr int32_t LG_INPUT_MOUSE_WHEEL_MIN_TOTAL = + LG_INPUT_MOUSE_WHEEL_MIN * 4; +static constexpr size_t LG_INPUT_KEYBOARD_KEY_COUNT = + KVMFR_INPUT_KEYBOARD_KEY_COUNT; +static constexpr uint8_t LG_INPUT_KEYBOARD_USAGE_MAX = + KVMFR_INPUT_KEYBOARD_USAGE_MAX; -enum LGInputMouseButton : uint8_t +enum LGInputMouseButton : uint32_t { - LG_INPUT_MOUSE_BUTTON_LEFT = 1 << 0, - LG_INPUT_MOUSE_BUTTON_RIGHT = 1 << 1, - LG_INPUT_MOUSE_BUTTON_MIDDLE = 1 << 2, - LG_INPUT_MOUSE_BUTTON_BACK = 1 << 3, + LG_INPUT_MOUSE_BUTTON_LEFT = 1 << 0, + LG_INPUT_MOUSE_BUTTON_RIGHT = 1 << 1, + LG_INPUT_MOUSE_BUTTON_MIDDLE = 1 << 2, + LG_INPUT_MOUSE_BUTTON_BACK = 1 << 3, LG_INPUT_MOUSE_BUTTON_FORWARD = 1 << 4, }; enum LGInputKeyboardModifier : uint8_t { - LG_INPUT_KEYBOARD_MODIFIER_LEFT_CONTROL = 1 << 0, - LG_INPUT_KEYBOARD_MODIFIER_LEFT_SHIFT = 1 << 1, - LG_INPUT_KEYBOARD_MODIFIER_LEFT_ALT = 1 << 2, - LG_INPUT_KEYBOARD_MODIFIER_LEFT_GUI = 1 << 3, + LG_INPUT_KEYBOARD_MODIFIER_LEFT_CONTROL = 1 << 0, + LG_INPUT_KEYBOARD_MODIFIER_LEFT_SHIFT = 1 << 1, + LG_INPUT_KEYBOARD_MODIFIER_LEFT_ALT = 1 << 2, + LG_INPUT_KEYBOARD_MODIFIER_LEFT_GUI = 1 << 3, LG_INPUT_KEYBOARD_MODIFIER_RIGHT_CONTROL = 1 << 4, - LG_INPUT_KEYBOARD_MODIFIER_RIGHT_SHIFT = 1 << 5, - LG_INPUT_KEYBOARD_MODIFIER_RIGHT_ALT = 1 << 6, - LG_INPUT_KEYBOARD_MODIFIER_RIGHT_GUI = 1 << 7, + LG_INPUT_KEYBOARD_MODIFIER_RIGHT_SHIFT = 1 << 5, + LG_INPUT_KEYBOARD_MODIFIER_RIGHT_ALT = 1 << 6, + LG_INPUT_KEYBOARD_MODIFIER_RIGHT_GUI = 1 << 7, }; enum LGInputPipeMessageType : uint16_t { LG_INPUT_PIPE_MESSAGE_MOUSE_ABSOLUTE = 1, LG_INPUT_PIPE_MESSAGE_MOUSE_RELATIVE = 2, - LG_INPUT_PIPE_MESSAGE_KEYBOARD = 3, + LG_INPUT_PIPE_MESSAGE_KEYBOARD = 3, }; #pragma pack(push, 1) -struct LGInputPipeMouseRelative -{ - uint8_t buttons; - int16_t deltaX; - int16_t deltaY; - int8_t wheel; -}; - -struct LGInputPipeMouseAbsolute -{ - uint8_t buttons; - uint16_t x; - uint16_t y; - int8_t wheel; -}; - -struct LGInputPipeKeyboard -{ - uint8_t modifiers; - uint8_t keys[LG_INPUT_KEYBOARD_KEY_COUNT]; -}; - struct LGInputPipeMessage { uint32_t magic; @@ -97,11 +84,15 @@ struct LGInputPipeMessage }; #pragma pack(pop) -static_assert(sizeof(LGInputPipeMouseRelative) == 6, +using LGInputPipeMouseRelative = KVMFRInputMouseRelative; +using LGInputPipeMouseAbsolute = KVMFRInputMouseAbsolute; +using LGInputPipeKeyboard = KVMFRInputKeyboard; + +static_assert(sizeof(LGInputPipeMouseRelative) == 16, "LGInputPipeMouseRelative wire layout changed"); -static_assert(sizeof(LGInputPipeMouseAbsolute) == 6, +static_assert(sizeof(LGInputPipeMouseAbsolute) == 16, "LGInputPipeMouseAbsolute wire layout changed"); -static_assert(sizeof(LGInputPipeKeyboard) == 7, +static_assert(sizeof(LGInputPipeKeyboard) == 16, "LGInputPipeKeyboard wire layout changed"); static_assert(sizeof(LGInputPipeMessage) == 84, "LGInputPipeMessage wire layout changed"); diff --git a/idd/LGCommon/LGCommon.vcxproj b/idd/LGCommon/LGCommon.vcxproj index 0cb9c83a..e838bd83 100644 --- a/idd/LGCommon/LGCommon.vcxproj +++ b/idd/LGCommon/LGCommon.vcxproj @@ -61,7 +61,7 @@ MultiThreaded _ITERATOR_DEBUG_LEVEL=0;%(PreprocessorDefinitions) /EHsc /D_ATL_NO_WIN_SUPPORT %(AdditionalOptions) - $(ProjectDir);%(AdditionalIncludeDirectories) + $(ProjectDir);$(SolutionDir)..\common\include;%(AdditionalIncludeDirectories) diff --git a/idd/LGIdd/input/IInputSink.h b/idd/LGIdd/input/IInputSink.h index 6ccda3ce..31649a97 100644 --- a/idd/LGIdd/input/IInputSink.h +++ b/idd/LGIdd/input/IInputSink.h @@ -27,8 +27,8 @@ class IInputSink public: virtual ~IInputSink() = default; - virtual bool IsAvailable() const = 0; - virtual uint64_t GetGeneration() const = 0; + // Odd states are available; a state change invalidates in-flight input. + virtual uint64_t GetState() const = 0; virtual bool SendMouseRelative(int32_t deltaX, int32_t deltaY, int32_t wheel, uint32_t buttons) = 0; virtual bool SendMouseAbsolute(uint16_t x, uint16_t y, diff --git a/idd/LGIdd/ipc/CInputPipeServer.cpp b/idd/LGIdd/ipc/CInputPipeServer.cpp index 87cef5a8..11a9d584 100644 --- a/idd/LGIdd/ipc/CInputPipeServer.cpp +++ b/idd/LGIdd/ipc/CInputPipeServer.cpp @@ -28,39 +28,191 @@ CInputPipeServer g_inputPipeServer; -static constexpr int32_t MAX_SPLIT_REPORTS = 4; -static constexpr int32_t MAX_MOUSE_DELTA = - INT16_MAX * MAX_SPLIT_REPORTS; -static constexpr int32_t MIN_MOUSE_DELTA = - INT16_MIN * MAX_SPLIT_REPORTS; -static constexpr int32_t MAX_MOUSE_WHEEL = - INT8_MAX * MAX_SPLIT_REPORTS; -static constexpr int32_t MIN_MOUSE_WHEEL = - LG_INPUT_MOUSE_WHEEL_MIN * MAX_SPLIT_REPORTS; +static constexpr DWORD WAIT_FIRST_OBJECT_VALUE = 0; bool CInputPipeServer::Init() { + DeInit(); + m_state.store(0, std::memory_order_release); + m_stopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + m_queueEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); + if (!m_stopEvent || !m_queueEvent) { - CSRWExclusiveLock lock(&m_sendLock); - m_sequence = 0; - m_mouseMode = MouseMode::NONE; - m_absoluteValid = false; - m_absoluteX = 0; - m_absoluteY = 0; + DEBUG_ERROR_HR(GetLastError(), + "Failed to create LGInput sender resources"); + DeInit(); + return false; + } + + m_thread = CreateThread(nullptr, 0, ThreadProc, this, 0, nullptr); + if (!m_thread) + { + DEBUG_ERROR_HR(GetLastError(), "Failed to create LGInput sender"); + DeInit(); + return false; } m_endpoint.SetHandler(this); - return m_endpoint.Start( - LG_INPUT_PIPE_NAME, - CPipeEndpoint::Mode::Server, - sizeof(LGInputPipeMessage)); + if (!m_endpoint.Start( + LG_INPUT_PIPE_NAME, + CPipeEndpoint::Mode::Server, + sizeof(LGInputPipeMessage))) + { + DeInit(); + return false; + } + return true; } void CInputPipeServer::DeInit() { - Invalidate(); + Invalidate(0, false); + if (m_stopEvent) + SetEvent(m_stopEvent); + m_endpoint.Stop(); + + if (m_thread) + { + WaitForSingleObject(m_thread, INFINITE); + CloseHandle(m_thread); + m_thread = nullptr; + } + + if (m_queueEvent) + { + CloseHandle(m_queueEvent); + m_queueEvent = nullptr; + } + if (m_stopEvent) + { + CloseHandle(m_stopEvent); + m_stopEvent = nullptr; + } + + CSRWExclusiveLock lock(&m_queueLock); + m_queueHead = 0; + m_queueCount = 0; + m_mouseMode = MouseMode::NONE; + m_absoluteValid = false; + m_relativeButtons = 0; + m_absoluteButtons = 0; +} + +bool CInputPipeServer::QueueLocked( + LGInputPipeMessageType type, + const KVMFRInputPayload& payload, + bool pureMotion) +{ + if (m_queueCount) + { + const size_t tailIndex = + (m_queueHead + m_queueCount - 1) % QUEUE_LENGTH; + QueueItem& tail = m_queue[tailIndex]; + if (tail.type == type) + { + if (type == LG_INPUT_PIPE_MESSAGE_MOUSE_RELATIVE && + m_queueCount >= MOTION_COALESCE_THRESHOLD && + tail.pureMotion && pureMotion && + tail.payload.mouseRelative.wheel == 0 && + payload.mouseRelative.wheel == 0 && + tail.payload.mouseRelative.buttons == + payload.mouseRelative.buttons) + { + const int64_t x = static_cast( + tail.payload.mouseRelative.deltaX) + + payload.mouseRelative.deltaX; + const int64_t y = static_cast( + tail.payload.mouseRelative.deltaY) + + payload.mouseRelative.deltaY; + if (x >= LG_INPUT_MOUSE_DELTA_MIN && + x <= LG_INPUT_MOUSE_DELTA_MAX && + y >= LG_INPUT_MOUSE_DELTA_MIN && + y <= LG_INPUT_MOUSE_DELTA_MAX) + { + tail.payload.mouseRelative.deltaX = static_cast(x); + tail.payload.mouseRelative.deltaY = static_cast(y); + return true; + } + } + else if (type == LG_INPUT_PIPE_MESSAGE_MOUSE_ABSOLUTE && + tail.pureMotion && pureMotion && + tail.payload.mouseAbsolute.wheel == 0 && + payload.mouseAbsolute.wheel == 0 && + tail.payload.mouseAbsolute.buttons == + payload.mouseAbsolute.buttons) + { + tail.payload.mouseAbsolute.x = payload.mouseAbsolute.x; + tail.payload.mouseAbsolute.y = payload.mouseAbsolute.y; + return true; + } + } + } + + return QueueRawLocked(type, payload, pureMotion); +} + +bool CInputPipeServer::QueueRawLocked( + LGInputPipeMessageType type, + const KVMFRInputPayload& payload, + bool pureMotion) +{ + if (m_queueCount == QUEUE_LENGTH) + return false; + + const size_t index = + (m_queueHead + m_queueCount) % QUEUE_LENGTH; + m_queue[index].type = type; + m_queue[index].state = + m_state.load(std::memory_order_relaxed); + m_queue[index].payload = payload; + m_queue[index].pureMotion = pureMotion; + ++m_queueCount; + SetEvent(m_queueEvent); + return true; +} + +bool CInputPipeServer::QueueResetLocked() +{ + KVMFRInputPayload payload = {}; + if (!QueueRawLocked( + LG_INPUT_PIPE_MESSAGE_MOUSE_RELATIVE, payload, false)) + return false; + + if (m_absoluteValid) + { + payload.mouseAbsolute.x = m_absoluteX; + payload.mouseAbsolute.y = m_absoluteY; + if (!QueueRawLocked( + LG_INPUT_PIPE_MESSAGE_MOUSE_ABSOLUTE, payload, false)) + return false; + } + + payload = {}; + if (!QueueRawLocked(LG_INPUT_PIPE_MESSAGE_KEYBOARD, payload, false)) + return false; + + m_mouseMode = MouseMode::NONE; + m_relativeButtons = 0; + m_absoluteButtons = 0; + return true; +} + +void CInputPipeServer::ResyncLocked() +{ + m_queueHead = 0; + m_queueCount = 0; + uint64_t state = m_state.load(std::memory_order_relaxed); + while (state & 1) + { + if (m_state.compare_exchange_weak( + state, state + 2, std::memory_order_acq_rel)) + { + QueueResetLocked(); + return; + } + } } bool CInputPipeServer::SendMouseRelative( @@ -69,66 +221,47 @@ bool CInputPipeServer::SendMouseRelative( int32_t wheel, uint32_t buttons) { - if (deltaX < MIN_MOUSE_DELTA || deltaX > MAX_MOUSE_DELTA || - deltaY < MIN_MOUSE_DELTA || deltaY > MAX_MOUSE_DELTA || - wheel < MIN_MOUSE_WHEEL || wheel > MAX_MOUSE_WHEEL || - (buttons & ~static_cast(LG_INPUT_MOUSE_BUTTON_MASK))) + if (deltaX < LG_INPUT_MOUSE_DELTA_MIN || + deltaX > LG_INPUT_MOUSE_DELTA_MAX || + deltaY < LG_INPUT_MOUSE_DELTA_MIN || + deltaY > LG_INPUT_MOUSE_DELTA_MAX || + wheel < LG_INPUT_MOUSE_WHEEL_MIN_TOTAL || + wheel > LG_INPUT_MOUSE_WHEEL_MAX || + !(m_state.load(std::memory_order_acquire) & 1)) return false; - CSRWExclusiveLock lock(&m_sendLock); - if (!IsAvailable()) - return false; + KVMFRInputPayload payload = {}; + payload.mouseRelative.buttons = buttons; + payload.mouseRelative.deltaX = deltaX; + payload.mouseRelative.deltaY = deltaY; + payload.mouseRelative.wheel = wheel; - if (m_mouseMode == MouseMode::ABSOLUTE) + CSRWExclusiveLock lock(&m_queueLock); + const bool pureMotion = wheel == 0 && buttons == m_relativeButtons; + const bool switching = m_mouseMode == MouseMode::ABSOLUTE_INPUT; + bool queued = (m_state.load(std::memory_order_relaxed) & 1) != 0; + if (queued && switching) { - const LGInputPipeMouseAbsolute neutral = { - 0, - m_absoluteX, - m_absoluteY, - 0, - }; - if (!SendMessageLocked( - LG_INPUT_PIPE_MESSAGE_MOUSE_ABSOLUTE, - &neutral, - sizeof(neutral))) - { - Invalidate(); - return false; - } - m_mouseMode = MouseMode::NONE; + KVMFRInputPayload neutral = {}; + neutral.mouseAbsolute.x = m_absoluteX; + neutral.mouseAbsolute.y = m_absoluteY; + queued = QueueRawLocked( + LG_INPUT_PIPE_MESSAGE_MOUSE_ABSOLUTE, neutral, false); } + if (queued) + queued = QueueLocked( + LG_INPUT_PIPE_MESSAGE_MOUSE_RELATIVE, payload, pureMotion); - do + if (!queued) + ResyncLocked(); + else { - const int16_t x = deltaX > INT16_MAX ? INT16_MAX : - deltaX < INT16_MIN ? INT16_MIN : static_cast(deltaX); - const int16_t y = deltaY > INT16_MAX ? INT16_MAX : - deltaY < INT16_MIN ? INT16_MIN : static_cast(deltaY); - const int8_t wheelDelta = wheel > INT8_MAX ? INT8_MAX : - wheel < LG_INPUT_MOUSE_WHEEL_MIN ? LG_INPUT_MOUSE_WHEEL_MIN : - static_cast(wheel); - - const LGInputPipeMouseRelative payload = { - static_cast(buttons), - x, - y, - wheelDelta, - }; - if (!SendMessageLocked( - LG_INPUT_PIPE_MESSAGE_MOUSE_RELATIVE, &payload, sizeof(payload))) - { - Invalidate(); - return false; - } - - m_mouseMode = MouseMode::RELATIVE; - deltaX -= x; - deltaY -= y; - wheel -= wheelDelta; + if (switching) + m_absoluteButtons = 0; + m_mouseMode = MouseMode::RELATIVE_INPUT; + m_relativeButtons = buttons; } - while (deltaX || deltaY || wheel); - - return true; + return queued; } bool CInputPipeServer::SendMouseAbsolute( @@ -139,170 +272,206 @@ bool CInputPipeServer::SendMouseAbsolute( { if (x > LG_INPUT_MOUSE_ABSOLUTE_MAX || y > LG_INPUT_MOUSE_ABSOLUTE_MAX || - wheel < MIN_MOUSE_WHEEL || wheel > MAX_MOUSE_WHEEL || - (buttons & ~static_cast(LG_INPUT_MOUSE_BUTTON_MASK))) + wheel < LG_INPUT_MOUSE_WHEEL_MIN_TOTAL || + wheel > LG_INPUT_MOUSE_WHEEL_MAX || + !(m_state.load(std::memory_order_acquire) & 1)) return false; - CSRWExclusiveLock lock(&m_sendLock); - if (!IsAvailable()) - return false; + KVMFRInputPayload payload = {}; + payload.mouseAbsolute.buttons = buttons; + payload.mouseAbsolute.x = x; + payload.mouseAbsolute.y = y; + payload.mouseAbsolute.wheel = wheel; - if (m_mouseMode == MouseMode::RELATIVE) + CSRWExclusiveLock lock(&m_queueLock); + const bool pureMotion = wheel == 0 && buttons == m_absoluteButtons; + const bool switching = m_mouseMode == MouseMode::RELATIVE_INPUT; + bool queued = (m_state.load(std::memory_order_relaxed) & 1) != 0; + if (queued && switching) { - const LGInputPipeMouseRelative neutral = {}; - if (!SendMessageLocked( - LG_INPUT_PIPE_MESSAGE_MOUSE_RELATIVE, - &neutral, - sizeof(neutral))) - { - Invalidate(); - return false; - } - m_mouseMode = MouseMode::NONE; + const KVMFRInputPayload neutral = {}; + queued = QueueRawLocked( + LG_INPUT_PIPE_MESSAGE_MOUSE_RELATIVE, neutral, false); } + if (queued) + queued = QueueLocked( + LG_INPUT_PIPE_MESSAGE_MOUSE_ABSOLUTE, payload, pureMotion); - do + if (!queued) + ResyncLocked(); + else { - const int8_t wheelDelta = wheel > INT8_MAX ? INT8_MAX : - wheel < LG_INPUT_MOUSE_WHEEL_MIN ? LG_INPUT_MOUSE_WHEEL_MIN : - static_cast(wheel); - const LGInputPipeMouseAbsolute payload = { - static_cast(buttons), - x, - y, - wheelDelta, - }; - if (!SendMessageLocked( - LG_INPUT_PIPE_MESSAGE_MOUSE_ABSOLUTE, &payload, sizeof(payload))) - { - Invalidate(); - return false; - } - m_mouseMode = MouseMode::ABSOLUTE; - m_absoluteValid = true; - m_absoluteX = x; - m_absoluteY = y; - wheel -= wheelDelta; + if (switching) + m_relativeButtons = 0; + m_mouseMode = MouseMode::ABSOLUTE_INPUT; + m_absoluteValid = true; + m_absoluteX = x; + m_absoluteY = y; + m_absoluteButtons = buttons; } - while (wheel); - - return true; + return queued; } bool CInputPipeServer::SendKeyboard( uint8_t modifiers, const uint8_t * keys) { - if (!keys) + if (!keys || !(m_state.load(std::memory_order_acquire) & 1)) return false; - LGInputPipeKeyboard payload = {}; - payload.modifiers = modifiers; + KVMFRInputPayload payload = {}; + payload.keyboard.modifiers = modifiers; for (size_t i = 0; i < LG_INPUT_KEYBOARD_KEY_COUNT; ++i) { if (keys[i] > LG_INPUT_KEYBOARD_USAGE_MAX) return false; - payload.keys[i] = keys[i]; + payload.keyboard.keys[i] = keys[i]; } - CSRWExclusiveLock lock(&m_sendLock); - const bool sent = IsAvailable() && - SendMessageLocked( - LG_INPUT_PIPE_MESSAGE_KEYBOARD, - &payload, - sizeof(payload)); - if (!sent) - Invalidate(); - return sent; + CSRWExclusiveLock lock(&m_queueLock); + const bool queued = (m_state.load(std::memory_order_relaxed) & 1) && + QueueLocked(LG_INPUT_PIPE_MESSAGE_KEYBOARD, payload, false); + if (!queued) + ResyncLocked(); + return queued; } bool CInputPipeServer::Reset() { - CSRWExclusiveLock lock(&m_sendLock); - const bool reset = IsAvailable() && - ResetLocked(); - if (!reset) - Invalidate(); - return reset; -} - -bool CInputPipeServer::ResetLocked() -{ - const LGInputPipeMouseRelative relative = {}; - const LGInputPipeMouseAbsolute absolute = { - 0, - m_absoluteX, - m_absoluteY, - 0, - }; - const LGInputPipeKeyboard keyboard = {}; - - if (!SendMessageLocked( - LG_INPUT_PIPE_MESSAGE_MOUSE_RELATIVE, - &relative, - sizeof(relative)) || - (m_absoluteValid && - !SendMessageLocked( - LG_INPUT_PIPE_MESSAGE_MOUSE_ABSOLUTE, - &absolute, - sizeof(absolute))) || - !SendMessageLocked( - LG_INPUT_PIPE_MESSAGE_KEYBOARD, - &keyboard, - sizeof(keyboard))) + if (!(m_state.load(std::memory_order_acquire) & 1)) return false; - m_mouseMode = MouseMode::NONE; + CSRWExclusiveLock lock(&m_queueLock); + bool queued = (m_state.load(std::memory_order_relaxed) & 1) != 0; + if (queued) + queued = QueueResetLocked(); + if (!queued) + ResyncLocked(); + return queued; +} + +bool CInputPipeServer::Pop(QueueItem& item) +{ + CSRWExclusiveLock lock(&m_queueLock); + if (!m_queueCount) + return false; + + item = m_queue[m_queueHead]; + m_queueHead = (m_queueHead + 1) % QUEUE_LENGTH; + --m_queueCount; + if (m_queueCount) + SetEvent(m_queueEvent); return true; } -bool CInputPipeServer::SendMessageLocked( - LGInputPipeMessageType type, - const void * payload, - size_t size) +bool CInputPipeServer::Send(const QueueItem& item) { - if (!payload || !size || size > LG_INPUT_PIPE_MAX_PAYLOAD_SIZE) - return false; - LGInputPipeMessage message = {}; - message.magic = LG_INPUT_PIPE_MAGIC; - message.version = LG_INPUT_PIPE_VERSION; - message.type = type; - message.payloadSize = static_cast(size); - memcpy(message.payload, payload, size); + message.magic = LG_INPUT_PIPE_MAGIC; + message.version = LG_INPUT_PIPE_VERSION; + message.type = item.type; + message.payloadSize = sizeof(KVMFRInputPayload); + memcpy(message.payload, &item.payload, sizeof(item.payload)); - message.sequence = ++m_sequence; - const bool sent = m_endpoint.Send(&message, sizeof(message)); - return sent; + bool current; + bool sent = true; + { + CSRWSharedLock lock(&m_connectionLock); + const uint64_t state = m_state.load(std::memory_order_acquire); + current = (state & 1) && item.state == state; + if (current) + { + message.sequence = ++m_sequence; + sent = m_endpoint.Send(&message, sizeof(message)); + } + } + + if (!sent) + { + Invalidate(item.state, true); + return false; + } + return current; } -void CInputPipeServer::Invalidate() +void CInputPipeServer::Invalidate(uint64_t state, bool requireMatch) { - uint64_t state = m_state.load(std::memory_order_acquire); - while ((state & 1) && !m_state.compare_exchange_weak( - state, state + 1, std::memory_order_acq_rel)) + CSRWExclusiveLock connectionLock(&m_connectionLock); + uint64_t current = m_state.load(std::memory_order_relaxed); + for (;;) { + if (!(current & 1) || (requireMatch && state != current)) + return; + if (m_state.compare_exchange_weak( + current, current + 1, std::memory_order_acq_rel)) + break; } + + CSRWExclusiveLock queueLock(&m_queueLock); + m_queueHead = 0; + m_queueCount = 0; +} + +DWORD WINAPI CInputPipeServer::ThreadProc(void * context) +{ + static_cast(context)->Thread(); + return 0; +} + +void CInputPipeServer::Thread() +{ + const HANDLE handles[] = { m_stopEvent, m_queueEvent }; + for (;;) + { + const DWORD wait = WaitForMultipleObjects( + _countof(handles), handles, FALSE, INFINITE); + if (wait == WAIT_FIRST_OBJECT_VALUE) + break; + if (wait != WAIT_FIRST_OBJECT_VALUE + 1) + { + DEBUG_ERROR_HR(GetLastError(), "LGInput sender wait failed"); + break; + } + + QueueItem item = {}; + while (Pop(item)) + if (!Send(item)) + break; + } + + Invalidate(0, false); } void CInputPipeServer::OnPipeConnected() { - Invalidate(); + CSRWExclusiveLock connectionLock(&m_connectionLock); + CSRWExclusiveLock queueLock(&m_queueLock); - CSRWExclusiveLock lock(&m_sendLock); - const bool ready = ResetLocked(); - if (!ready) + uint64_t state = m_state.load(std::memory_order_relaxed); + if (state & 1) + ++state; + ++state; + m_sequence = 0; + + m_queueHead = 0; + m_queueCount = 0; + m_mouseMode = MouseMode::NONE; + const bool reset = QueueResetLocked(); + for (size_t i = 0; i < m_queueCount; ++i) { - DEBUG_WARN("Failed to neutralize the LGInput endpoint"); - return; + const size_t index = (m_queueHead + i) % QUEUE_LENGTH; + m_queue[index].state = state; } + if (reset) + m_state.store(state, std::memory_order_release); - m_state.fetch_add(1, std::memory_order_acq_rel); + if (!reset) + DEBUG_WARN("Failed to queue LGInput endpoint neutralization"); } void CInputPipeServer::OnPipeDisconnected() { - Invalidate(); + Invalidate(0, false); } bool CInputPipeServer::OnPipeMessage( diff --git a/idd/LGIdd/ipc/CInputPipeServer.h b/idd/LGIdd/ipc/CInputPipeServer.h index 416c2756..dc357c58 100644 --- a/idd/LGIdd/ipc/CInputPipeServer.h +++ b/idd/LGIdd/ipc/CInputPipeServer.h @@ -30,67 +30,90 @@ class CInputPipeServer : public IInputSink, private IPipeEndpointHandler { +private: + static constexpr size_t QUEUE_LENGTH = 128; + static constexpr size_t MOTION_COALESCE_THRESHOLD = QUEUE_LENGTH / 2; + + struct QueueItem + { + KVMFRInputPayload payload; + uint64_t state; + LGInputPipeMessageType type; + bool pureMotion; + }; + + enum class MouseMode + { + NONE, + RELATIVE_INPUT, + ABSOLUTE_INPUT, + }; + + CPipeEndpoint m_endpoint; + + // Odd states are available. Endpoint changes and resyncs advance the state. + std::atomic m_state { 0 }; + + SRWLOCK m_queueLock = SRWLOCK_INIT; + SRWLOCK m_connectionLock = SRWLOCK_INIT; + HANDLE m_stopEvent = nullptr; + HANDLE m_queueEvent = nullptr; + HANDLE m_thread = nullptr; + + QueueItem m_queue[QUEUE_LENGTH] = {}; + size_t m_queueHead = 0; + size_t m_queueCount = 0; + uint64_t m_sequence = 0; + + MouseMode m_mouseMode = MouseMode::NONE; + bool m_absoluteValid = false; + uint16_t m_absoluteX = 0; + uint16_t m_absoluteY = 0; + uint32_t m_relativeButtons = 0; + uint32_t m_absoluteButtons = 0; + + bool QueueLocked(LGInputPipeMessageType type, + const KVMFRInputPayload& payload, bool pureMotion); + bool QueueRawLocked(LGInputPipeMessageType type, + const KVMFRInputPayload& payload, bool pureMotion); + bool QueueResetLocked(); + void ResyncLocked(); + bool Pop(QueueItem& item); + bool Send(const QueueItem& item); + void Invalidate(uint64_t state, bool requireMatch); + void Thread(); + + static DWORD WINAPI ThreadProc(void * context); + + void OnPipeConnected() override; + void OnPipeDisconnected() override; + bool OnPipeMessage(const void * message, size_t size) override; + public: ~CInputPipeServer() { DeInit(); } bool Init(); void DeInit(); - bool IsAvailable() const override - { - return (m_state.load(std::memory_order_acquire) & 1) != 0; - } - uint64_t GetGeneration() const override + uint64_t GetState() const override { return m_state.load(std::memory_order_acquire); } - // Mouse buttons use LGInputMouseButton bits. Values outside the pipe report - // ranges are split into multiple reports without losing motion. bool SendMouseRelative( _In_ int32_t deltaX, _In_ int32_t deltaY, _In_ int32_t wheel, _In_ uint32_t buttons) override; - // Absolute coordinates are normalized to 0..32767 on each axis. bool SendMouseAbsolute( _In_range_(0, LG_INPUT_MOUSE_ABSOLUTE_MAX) uint16_t x, _In_range_(0, LG_INPUT_MOUSE_ABSOLUTE_MAX) uint16_t y, _In_ int32_t wheel, _In_ uint32_t buttons) override; - // Keys are USB HID Keyboard/Keypad usage IDs; zero marks an empty slot. bool SendKeyboard( _In_ uint8_t modifiers, _In_reads_(LG_INPUT_KEYBOARD_KEY_COUNT) const uint8_t * keys) override; bool Reset() override; - bool IsConnected() const { return m_endpoint.IsConnected(); } - -private: - bool SendMessageLocked( - _In_ LGInputPipeMessageType type, - _In_reads_bytes_(size) const void * payload, - _In_ size_t size); - bool ResetLocked(); - void Invalidate(); - void OnPipeConnected() override; - void OnPipeDisconnected() override; - bool OnPipeMessage(const void * message, size_t size) override; - - CPipeEndpoint m_endpoint; - // Odd states are available. Each endpoint transition advances the state. - std::atomic m_state { 0 }; - SRWLOCK m_sendLock = SRWLOCK_INIT; - uint64_t m_sequence = 0; - enum class MouseMode - { - NONE, - RELATIVE, - ABSOLUTE, - }; - MouseMode m_mouseMode = MouseMode::NONE; - bool m_absoluteValid = false; - uint16_t m_absoluteX = 0; - uint16_t m_absoluteY = 0; }; extern CInputPipeServer g_inputPipeServer; diff --git a/idd/LGIdd/transport/lgmp/CLGMPInputTransport.cpp b/idd/LGIdd/transport/lgmp/CLGMPInputTransport.cpp index 43ff60ec..6337a25f 100644 --- a/idd/LGIdd/transport/lgmp/CLGMPInputTransport.cpp +++ b/idd/LGIdd/transport/lgmp/CLGMPInputTransport.cpp @@ -51,6 +51,7 @@ static constexpr int32_t MAX_MOUSE_WHEEL = INT8_MAX * MAX_SPLIT_REPORTS; static constexpr int32_t MIN_MOUSE_WHEEL = -INT8_MAX * MAX_SPLIT_REPORTS; +static constexpr DWORD WAIT_FIRST_OBJECT_VALUE = 0; static bool IsZero(const void * data, size_t size) { @@ -104,7 +105,7 @@ bool CLGMPInputTransport::Start(IInputSink& sink) const DWORD state = WaitForSingleObject(m_thread, 0); if (state == WAIT_TIMEOUT) return true; - if (state != WAIT_OBJECT_0) + if (state != WAIT_FIRST_OBJECT_VALUE) { DEBUG_ERROR_HR(GetLastError(), "Failed to inspect LGMP input worker"); @@ -144,13 +145,13 @@ bool CLGMPInputTransport::Start(IInputSink& sink) } m_sink = &sink; - m_sinkGeneration = sink.GetGeneration(); + m_sinkState = sink.GetState(); m_thread = CreateThread(nullptr, 0, ThreadProc, this, 0, nullptr); if (!m_thread) { DEBUG_ERROR_HR(GetLastError(), "Failed to create LGMP input worker"); m_sink = nullptr; - m_sinkGeneration = 0; + m_sinkState = 0; CloseHandle(m_pollTimer); CloseHandle(m_stopEvent); m_pollTimer = nullptr; @@ -163,18 +164,13 @@ bool CLGMPInputTransport::Start(IInputSink& sink) void CLGMPInputTransport::Stop() { - HANDLE thread; - { - CSRWExclusiveLock lock(&m_lifecycleLock); - thread = m_thread; - if (m_stopEvent) - SetEvent(m_stopEvent); - } - - if (thread) - WaitForSingleObject(thread, INFINITE); - CSRWExclusiveLock lock(&m_lifecycleLock); + + if (m_stopEvent) + SetEvent(m_stopEvent); + if (m_thread) + WaitForSingleObject(m_thread, INFINITE); + if (m_thread) { CloseHandle(m_thread); @@ -195,7 +191,7 @@ void CLGMPInputTransport::Stop() m_ownerGeneration = 0; m_ownerSequence = 0; m_ownerDeadline = 0; - m_sinkGeneration = 0; + m_sinkState = 0; } bool CLGMPInputTransport::IsOwner( @@ -208,13 +204,12 @@ bool CLGMPInputTransport::IsOwner( bool CLGMPInputTransport::Claim( uint32_t sourceClientID, const KVMFRInputMessage& message) { - if (message.sequence != 1 || !m_sink || !m_sink->IsAvailable()) + if (message.sequence != 1 || !m_sink) return false; - const uint64_t sinkGeneration = m_sink->GetGeneration(); - if (sinkGeneration != m_sinkGeneration || !m_sink->Reset() || - !m_sink->IsAvailable() || - m_sink->GetGeneration() != sinkGeneration) + const uint64_t sinkState = m_sink->GetState(); + if (!(sinkState & 1) || sinkState != m_sinkState || + !m_sink->Reset() || m_sink->GetState() != sinkState) return false; m_ownerClientID = sourceClientID; @@ -255,10 +250,10 @@ void CLGMPInputTransport::CheckOwner() if (!m_sink) return; - const uint64_t generation = m_sink->GetGeneration(); - if (generation != m_sinkGeneration) + const uint64_t state = m_sink->GetState(); + if (state != m_sinkState) { - m_sinkGeneration = generation; + m_sinkState = state; ReleaseOwner(true, "input endpoint changed"); return; } @@ -266,7 +261,7 @@ void CLGMPInputTransport::CheckOwner() if (!m_ownerClientID) return; - if (!m_sink->IsAvailable()) + if (!(state & 1)) { ReleaseOwner(true, "input unavailable"); return; @@ -323,11 +318,11 @@ bool CLGMPInputTransport::ProcessMessage( uint32_t sourceClientID, const KVMFRInputMessage& message) { const bool owner = IsOwner(sourceClientID, message.generation); - const uint64_t sinkGeneration = m_sink ? - m_sink->GetGeneration() : m_sinkGeneration; - if (sinkGeneration != m_sinkGeneration) + const uint64_t sinkState = m_sink ? + m_sink->GetState() : m_sinkState; + if (sinkState != m_sinkState) { - m_sinkGeneration = sinkGeneration; + m_sinkState = sinkState; if (m_ownerClientID) { ReleaseOwner(true, "input endpoint changed"); @@ -417,10 +412,10 @@ bool CLGMPInputTransport::ProcessMessage( return false; } - const uint64_t deliveredGeneration = m_sink->GetGeneration(); - if (deliveredGeneration != m_sinkGeneration) + const uint64_t deliveredState = m_sink->GetState(); + if (deliveredState != m_sinkState) { - m_sinkGeneration = deliveredGeneration; + m_sinkState = deliveredState; ReleaseOwner(true, "input endpoint changed"); return false; } @@ -503,9 +498,9 @@ void CLGMPInputTransport::Thread() const DWORD wait = WaitForMultipleObjects( _countof(waitHandles), waitHandles, FALSE, INFINITE); - if (wait == WAIT_OBJECT_0) + if (wait == WAIT_FIRST_OBJECT_VALUE) break; - if (wait != WAIT_OBJECT_0 + 1) + if (wait != WAIT_FIRST_OBJECT_VALUE + 1) { DEBUG_ERROR_HR(GetLastError(), "LGMP input worker wait failed"); break; diff --git a/idd/LGIdd/transport/lgmp/CLGMPInputTransport.h b/idd/LGIdd/transport/lgmp/CLGMPInputTransport.h index 96894c1f..c402707e 100644 --- a/idd/LGIdd/transport/lgmp/CLGMPInputTransport.h +++ b/idd/LGIdd/transport/lgmp/CLGMPInputTransport.h @@ -54,7 +54,7 @@ private: uint32_t m_ownerGeneration = 0; uint32_t m_ownerSequence = 0; ULONGLONG m_ownerDeadline = 0; - uint64_t m_sinkGeneration = 0; + uint64_t m_sinkState = 0; bool Initialize(); void DeInit(); diff --git a/idd/LGInput/CHIDDevice.cpp b/idd/LGInput/CHIDDevice.cpp index 317c0fdd..16d2f670 100644 --- a/idd/LGInput/CHIDDevice.cpp +++ b/idd/LGInput/CHIDDevice.cpp @@ -26,16 +26,21 @@ #include "ipc/CInputPipeClient.h" #include +#include #include +#include -static constexpr USHORT LG_INPUT_VENDOR_ID = 0x0000; -static constexpr USHORT LG_INPUT_PRODUCT_ID = 0x0000; -static constexpr USHORT LG_INPUT_VERSION = 0x0001; -static constexpr size_t REPORT_QUEUE_LENGTH = 64; +static constexpr USHORT LG_INPUT_VENDOR_ID = 0x0000; +static constexpr USHORT LG_INPUT_PRODUCT_ID = 0x0000; +static constexpr USHORT LG_INPUT_VERSION = 0x0001; +static constexpr size_t REPORT_QUEUE_LENGTH = 64; +static constexpr size_t MOTION_COALESCE_THRESHOLD = + REPORT_QUEUE_LENGTH / 2; struct HIDQueuedReport { - UCHAR data[sizeof(HIDKeyboardReport)]; + UCHAR data[HID_MAX_INPUT_REPORT_SIZE]; + bool pureMotion; size_t size; }; @@ -49,8 +54,14 @@ struct HIDDeviceContext SRWLOCK reportLock; bool active; bool stopping; + UCHAR mouseMode; size_t reportHead; size_t reportCount; + uint32_t relativeButtons; + uint32_t absoluteButtons; + bool absoluteValid; + uint16_t absoluteX; + uint16_t absoluteY; HIDQueuedReport reports[REPORT_QUEUE_LENGTH]; CInputPipeClient * inputPipe; }; @@ -151,19 +162,114 @@ static void PopReport( --context->reportCount; } +static HIDQueuedReport& GetQueuedReport( + _Inout_ HIDDeviceContext * context, + _In_ size_t position) +{ + return context->reports[ + (context->reportHead + position) % REPORT_QUEUE_LENGTH]; +} + +static void RemoveQueuedReport( + _Inout_ HIDDeviceContext * context, + _In_ size_t position) +{ + for (size_t i = position; i + 1 < context->reportCount; ++i) + GetQueuedReport(context, i) = GetQueuedReport(context, i + 1); + --context->reportCount; +} + +static bool CompactStaleMotion( + _Inout_ HIDDeviceContext * context, + _In_ UCHAR incomingReportId) +{ + for (size_t i = 0; i < context->reportCount; ++i) + { + HIDQueuedReport& report = GetQueuedReport(context, i); + if (!report.pureMotion || + report.data[0] != HID_REPORT_ID_MOUSE_ABSOLUTE) + continue; + + bool superseded = incomingReportId == HID_REPORT_ID_MOUSE_ABSOLUTE; + for (size_t j = i + 1; !superseded && j < context->reportCount; ++j) + superseded = GetQueuedReport(context, j).data[0] == + HID_REPORT_ID_MOUSE_ABSOLUTE; + + if (superseded) + { + RemoveQueuedReport(context, i); + return true; + } + } + return false; +} + static NTSTATUS QueueReport( _Inout_ HIDDeviceContext * context, _In_reads_bytes_(size) const void * data, - _In_ size_t size) + _In_ size_t size, + _In_ bool pureMotion) { + const UCHAR reportId = *static_cast(data); + if (context->reportCount) + { + HIDQueuedReport& tail = + GetQueuedReport(context, context->reportCount - 1); + if (tail.data[0] == reportId) + { + if (reportId == HID_REPORT_ID_MOUSE_RELATIVE && + context->reportCount >= MOTION_COALESCE_THRESHOLD && + tail.pureMotion && pureMotion) + { + HIDMouseRelativeReport * previous = + reinterpret_cast(tail.data); + const HIDMouseRelativeReport * current = + static_cast(data); + const int32_t x = static_cast(previous->x) + + current->x; + const int32_t y = static_cast(previous->y) + + current->y; + if (x >= INT16_MIN && x <= INT16_MAX && + y >= INT16_MIN && y <= INT16_MAX && + previous->buttons == current->buttons) + { + previous->x = static_cast(x); + previous->y = static_cast(y); + return STATUS_SUCCESS; + } + } + else if (reportId == HID_REPORT_ID_MOUSE_ABSOLUTE && + tail.pureMotion && pureMotion) + { + const HIDMouseAbsoluteReport * previous = + reinterpret_cast(tail.data); + const HIDMouseAbsoluteReport * current = + static_cast(data); + if (previous->buttons == current->buttons) + { + CopyMemory(tail.data, data, size); + tail.size = size; + return STATUS_SUCCESS; + } + } + else if (reportId == HID_REPORT_ID_KEYBOARD && + tail.size == size && memcmp(tail.data, data, size) == 0) + return STATUS_SUCCESS; + } + } + if (context->reportCount == REPORT_QUEUE_LENGTH) - return STATUS_BUFFER_OVERFLOW; + { + if (!CompactStaleMotion(context, reportId)) + return STATUS_BUFFER_OVERFLOW; + } const size_t index = (context->reportHead + context->reportCount) % REPORT_QUEUE_LENGTH; HIDQueuedReport * report = &context->reports[index]; CopyMemory(report->data, data, size); - report->size = size; + report->size = size; + report->pureMotion = pureMotion; ++context->reportCount; return STATUS_SUCCESS; } @@ -343,38 +449,136 @@ NTSTATUS CHIDDevice::SubmitReport( status = STATUS_DEVICE_NOT_READY; else { + bool pureMotion = false; + switch (reportId) + { + case HID_REPORT_ID_MOUSE_RELATIVE: + { + const HIDMouseRelativeReport * mouse = + static_cast(report); + pureMotion = context->mouseMode == reportId && + (mouse->x != 0 || mouse->y != 0 || mouse->buttons != 0) && + mouse->wheel == 0 && + mouse->buttons == context->relativeButtons; + break; + } + + case HID_REPORT_ID_MOUSE_ABSOLUTE: + { + const HIDMouseAbsoluteReport * mouse = + static_cast(report); + pureMotion = context->mouseMode == reportId && + (mouse->x != context->absoluteX || + mouse->y != context->absoluteY || mouse->buttons != 0) && + mouse->wheel == 0 && + mouse->buttons == context->absoluteButtons; + break; + } + + default: + break; + } + status = WdfIoQueueRetrieveNextRequest(context->reportQueue, &request); if (status == STATUS_NO_MORE_ENTRIES) { request = nullptr; - status = QueueReport(context, report, size); + status = QueueReport(context, report, size, pureMotion); + } + else if (NT_SUCCESS(status)) + status = CopyToRequest(request, report, size); + + if (NT_SUCCESS(status)) + { + switch (reportId) + { + case HID_REPORT_ID_MOUSE_RELATIVE: + context->mouseMode = reportId; + context->relativeButtons = + static_cast(report)->buttons; + break; + + case HID_REPORT_ID_MOUSE_ABSOLUTE: + { + const HIDMouseAbsoluteReport * mouse = + static_cast(report); + context->mouseMode = reportId; + context->absoluteButtons = mouse->buttons; + context->absoluteValid = true; + context->absoluteX = mouse->x; + context->absoluteY = mouse->y; + break; + } + + default: + break; + } } } } if (request) - { - status = CopyToRequest(request, report, size); WdfRequestComplete(request, status); - } return status; } -NTSTATUS CHIDDevice::ClearReports() +NTSTATUS CHIDDevice::ResetReports() { - CSRWSharedLock deviceLock(&s_deviceLock); - HIDDeviceContext * context = s_device; - if (!context) - return STATUS_DEVICE_NOT_READY; + uint16_t absoluteX = 0; + uint16_t absoluteY = 0; + bool absoluteValid = false; + { + CSRWSharedLock deviceLock(&s_deviceLock); + HIDDeviceContext * context = s_device; + if (!context) + return STATUS_DEVICE_NOT_READY; - CSRWExclusiveLock reportLock(&context->reportLock); - if (context->stopping) - return STATUS_DEVICE_NOT_READY; + CSRWExclusiveLock reportLock(&context->reportLock); + if (context->stopping) + return STATUS_DEVICE_NOT_READY; - context->reportHead = 0; - context->reportCount = 0; - return STATUS_SUCCESS; + absoluteValid = context->absoluteValid; + absoluteX = context->absoluteX; + absoluteY = context->absoluteY; + context->reportHead = 0; + context->reportCount = 0; + context->mouseMode = 0; + context->relativeButtons = 0; + context->absoluteButtons = 0; + } + + const HIDMouseRelativeReport relative = { + HID_REPORT_ID_MOUSE_RELATIVE, + 0, + 0, + 0, + 0, + }; + const HIDKeyboardReport keyboard = { + HID_REPORT_ID_KEYBOARD, + }; + + NTSTATUS status = SubmitReport(&relative, sizeof(relative)); + if (absoluteValid) + { + const HIDMouseAbsoluteReport absolute = { + HID_REPORT_ID_MOUSE_ABSOLUTE, + 0, + absoluteX, + absoluteY, + 0, + }; + const NTSTATUS absoluteStatus = + SubmitReport(&absolute, sizeof(absolute)); + if (NT_SUCCESS(status)) + status = absoluteStatus; + } + const NTSTATUS keyboardStatus = + SubmitReport(&keyboard, sizeof(keyboard)); + if (NT_SUCCESS(status)) + status = keyboardStatus; + return status; } VOID HIDEvtIoDeviceControl( diff --git a/idd/LGInput/CHIDDevice.h b/idd/LGInput/CHIDDevice.h index 5f5f8ece..068fdbcf 100644 --- a/idd/LGInput/CHIDDevice.h +++ b/idd/LGInput/CHIDDevice.h @@ -30,5 +30,5 @@ public: static NTSTATUS SubmitReport( _In_reads_bytes_(size) const void * report, _In_ size_t size); - static NTSTATUS ClearReports(); + static NTSTATUS ResetReports(); }; diff --git a/idd/LGInput/HIDReports.cpp b/idd/LGInput/HIDReports.cpp index 824eaf07..472570e1 100644 --- a/idd/LGInput/HIDReports.cpp +++ b/idd/LGInput/HIDReports.cpp @@ -31,15 +31,12 @@ static const uint8_t REPORT_DESCRIPTOR[] = 0xA1, 0x00, // Collection (Physical) 0x05, 0x09, // Usage Page (Button) 0x19, 0x01, // Usage Minimum (Button 1) - 0x29, 0x05, // Usage Maximum (Button 5) + 0x29, 0x20, // Usage Maximum (Button 32) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x01, // Logical Maximum (1) 0x75, 0x01, // Report Size (1) - 0x95, 0x05, // Report Count (5) + 0x95, 0x20, // Report Count (32) 0x81, 0x02, // Input (Data, Variable, Absolute) - 0x75, 0x03, // Report Size (3) - 0x95, 0x01, // Report Count (1) - 0x81, 0x03, // Input (Constant) 0x05, 0x01, // Usage Page (Generic Desktop) 0x09, 0x30, // Usage (X) 0x09, 0x31, // Usage (Y) @@ -66,15 +63,12 @@ static const uint8_t REPORT_DESCRIPTOR[] = 0xA1, 0x00, // Collection (Physical) 0x05, 0x09, // Usage Page (Button) 0x19, 0x01, // Usage Minimum (Button 1) - 0x29, 0x05, // Usage Maximum (Button 5) + 0x29, 0x20, // Usage Maximum (Button 32) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x01, // Logical Maximum (1) 0x75, 0x01, // Report Size (1) - 0x95, 0x05, // Report Count (5) + 0x95, 0x20, // Report Count (32) 0x81, 0x02, // Input (Data, Variable, Absolute) - 0x75, 0x03, // Report Size (3) - 0x95, 0x01, // Report Count (1) - 0x81, 0x03, // Input (Constant) 0x05, 0x01, // Usage Page (Generic Desktop) 0x09, 0x30, // Usage (X) 0x09, 0x31, // Usage (Y) diff --git a/idd/LGInput/HIDReports.h b/idd/LGInput/HIDReports.h index 0ec5ae2e..8781c142 100644 --- a/idd/LGInput/HIDReports.h +++ b/idd/LGInput/HIDReports.h @@ -35,7 +35,7 @@ enum HIDReportId : uint8_t struct HIDMouseAbsoluteReport { uint8_t reportId; - uint8_t buttons; + uint32_t buttons; uint16_t x; uint16_t y; int8_t wheel; @@ -44,7 +44,7 @@ struct HIDMouseAbsoluteReport struct HIDMouseRelativeReport { uint8_t reportId; - uint8_t buttons; + uint32_t buttons; int16_t x; int16_t y; int8_t wheel; @@ -66,11 +66,14 @@ struct HIDKeyboardLedsReport #pragma pack(pop) -static_assert(sizeof(HIDMouseAbsoluteReport) == 7); -static_assert(sizeof(HIDMouseRelativeReport) == 7); +static_assert(sizeof(HIDMouseAbsoluteReport) == 10); +static_assert(sizeof(HIDMouseRelativeReport) == 10); static_assert(sizeof(HIDKeyboardReport) == 9); static_assert(sizeof(HIDKeyboardLedsReport) == 2); +static constexpr size_t HID_MAX_INPUT_REPORT_SIZE = + sizeof(HIDMouseAbsoluteReport); + const uint8_t * HIDGetReportDescriptor(); size_t HIDGetReportDescriptorSize(); size_t HIDGetInputReportSize(uint8_t reportId); diff --git a/idd/LGInput/LGInput.vcxproj b/idd/LGInput/LGInput.vcxproj index 51659b0d..a0bb35ee 100644 --- a/idd/LGInput/LGInput.vcxproj +++ b/idd/LGInput/LGInput.vcxproj @@ -80,7 +80,7 @@ true Trace.h /EHsc /D_ATL_NO_WIN_SUPPORT %(AdditionalOptions) - $(ProjectDir)..\LGCommon;$(DDK_INC_PATH);%(AdditionalIncludeDirectories) + $(ProjectDir)..\LGCommon;$(ProjectDir)..\..\common\include;$(DDK_INC_PATH);%(AdditionalIncludeDirectories) %(AdditionalDependencies);OneCoreUAP.lib diff --git a/idd/LGInput/ipc/CInputPipeClient.cpp b/idd/LGInput/ipc/CInputPipeClient.cpp index a829383a..54857c12 100644 --- a/idd/LGInput/ipc/CInputPipeClient.cpp +++ b/idd/LGInput/ipc/CInputPipeClient.cpp @@ -43,7 +43,7 @@ void CInputPipeClient::Stop() m_endpoint.Stop(); m_lastSequence = 0; if (wasRunning) - CHIDDevice::ClearReports(); + CHIDDevice::ResetReports(); } void CInputPipeClient::OnPipeConnected() @@ -55,7 +55,7 @@ void CInputPipeClient::OnPipeConnected() void CInputPipeClient::OnPipeDisconnected() { m_lastSequence = 0; - CHIDDevice::ClearReports(); + CHIDDevice::ResetReports(); DEBUG_INFO("Disconnected from the LGIdd input transport; reconnecting"); } @@ -78,6 +78,7 @@ bool CInputPipeClient::OnPipeMessage( (m_lastSequence && message.sequence != m_lastSequence + 1)) { DEBUG_WARN("LGInput pipe report sequence changed unexpectedly"); + CHIDDevice::ResetReports(); return false; } bool handled = false; @@ -117,18 +118,41 @@ bool CInputPipeClient::HandleMouseRelative( LGInputPipeMouseRelative input = {}; memcpy(&input, payload, sizeof(input)); - if (input.wheel < LG_INPUT_MOUSE_WHEEL_MIN || - (input.buttons & ~LG_INPUT_MOUSE_BUTTON_MASK)) + if (input.deltaX < LG_INPUT_MOUSE_DELTA_MIN || + input.deltaX > LG_INPUT_MOUSE_DELTA_MAX || + input.deltaY < LG_INPUT_MOUSE_DELTA_MIN || + input.deltaY > LG_INPUT_MOUSE_DELTA_MAX || + input.wheel < LG_INPUT_MOUSE_WHEEL_MIN_TOTAL || + input.wheel > LG_INPUT_MOUSE_WHEEL_MAX) return false; - const HIDMouseRelativeReport report = { - HID_REPORT_ID_MOUSE_RELATIVE, - input.buttons, - input.deltaX, - input.deltaY, - input.wheel, - }; - return SubmitReport(&report, sizeof(report)); + int32_t x = input.deltaX; + int32_t y = input.deltaY; + int32_t wheel = input.wheel; + do + { + const int16_t reportX = x > INT16_MAX ? INT16_MAX : + x < INT16_MIN ? INT16_MIN : static_cast(x); + const int16_t reportY = y > INT16_MAX ? INT16_MAX : + y < INT16_MIN ? INT16_MIN : static_cast(y); + const int8_t reportWheel = wheel > INT8_MAX ? INT8_MAX : + wheel < LG_INPUT_MOUSE_WHEEL_MIN ? LG_INPUT_MOUSE_WHEEL_MIN : + static_cast(wheel); + const HIDMouseRelativeReport report = { + HID_REPORT_ID_MOUSE_RELATIVE, + input.buttons, + reportX, + reportY, + reportWheel, + }; + if (!SubmitReport(&report, sizeof(report))) + return false; + x -= reportX; + y -= reportY; + wheel -= reportWheel; + } + while (x || y || wheel); + return true; } bool CInputPipeClient::HandleMouseAbsolute( @@ -142,18 +166,30 @@ bool CInputPipeClient::HandleMouseAbsolute( memcpy(&input, payload, sizeof(input)); if (input.x > LG_INPUT_MOUSE_ABSOLUTE_MAX || input.y > LG_INPUT_MOUSE_ABSOLUTE_MAX || - input.wheel < LG_INPUT_MOUSE_WHEEL_MIN || - (input.buttons & ~LG_INPUT_MOUSE_BUTTON_MASK)) + input.wheel < LG_INPUT_MOUSE_WHEEL_MIN_TOTAL || + input.wheel > LG_INPUT_MOUSE_WHEEL_MAX || + input.reserved) return false; - const HIDMouseAbsoluteReport report = { - HID_REPORT_ID_MOUSE_ABSOLUTE, - input.buttons, - input.x, - input.y, - input.wheel, - }; - return SubmitReport(&report, sizeof(report)); + int32_t wheel = input.wheel; + do + { + const int8_t reportWheel = wheel > INT8_MAX ? INT8_MAX : + wheel < LG_INPUT_MOUSE_WHEEL_MIN ? LG_INPUT_MOUSE_WHEEL_MIN : + static_cast(wheel); + const HIDMouseAbsoluteReport report = { + HID_REPORT_ID_MOUSE_ABSOLUTE, + input.buttons, + input.x, + input.y, + reportWheel, + }; + if (!SubmitReport(&report, sizeof(report))) + return false; + wheel -= reportWheel; + } + while (wheel); + return true; } bool CInputPipeClient::HandleKeyboard( @@ -166,6 +202,10 @@ bool CInputPipeClient::HandleKeyboard( LGInputPipeKeyboard input = {}; memcpy(&input, payload, sizeof(input)); + for (size_t i = 0; i < sizeof(input.reserved); ++i) + if (input.reserved[i]) + return false; + HIDKeyboardReport report = {}; report.reportId = HID_REPORT_ID_KEYBOARD; report.modifiers = input.modifiers; @@ -189,8 +229,16 @@ bool CInputPipeClient::SubmitReport( return false; if (status == STATUS_BUFFER_OVERFLOW) - DEBUG_WARN("LGInput HID report queue is full; dropping a report"); - else if (!NT_SUCCESS(status) && status != STATUS_DEVICE_NOT_READY) - DEBUG_WARN_HR(status, "Failed to submit an LGInput HID report"); + { + DEBUG_WARN("LGInput HID report queue overflowed; resetting input state"); + CHIDDevice::ResetReports(); + return false; + } + if (!NT_SUCCESS(status)) + { + if (status != STATUS_DEVICE_NOT_READY) + DEBUG_WARN_HR(status, "Failed to submit an LGInput HID report"); + return false; + } return true; } diff --git a/idd/LGInput/ipc/CInputPipeClient.h b/idd/LGInput/ipc/CInputPipeClient.h index 7324ac3e..79f70aff 100644 --- a/idd/LGInput/ipc/CInputPipeClient.h +++ b/idd/LGInput/ipc/CInputPipeClient.h @@ -32,7 +32,6 @@ public: bool Start(); void Stop(); - bool IsConnected() const { return m_endpoint.IsConnected(); } private: void OnPipeConnected() override;