[idd] input: preserve reports under backpressure

Move named pipe writes off the LGMP input worker so a stalled LGInput
endpoint cannot block queue draining or lease maintenance.

Coalesce motion only under queue pressure while preserving mode, button,
wheel, and keyboard transitions. Reset HID state after discontinuities
and carry all 32 mouse button bits through the pipe and HID reports.
This commit is contained in:
Geoffrey McRae
2026-08-08 23:31:44 +10:00
parent 83c552fb9d
commit ced2fb531e
16 changed files with 820 additions and 382 deletions

View File

@@ -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,

View File

@@ -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<int64_t>(
tail.payload.mouseRelative.deltaX) +
payload.mouseRelative.deltaX;
const int64_t y = static_cast<int64_t>(
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<int32_t>(x);
tail.payload.mouseRelative.deltaY = static_cast<int32_t>(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<uint32_t>(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<int16_t>(deltaX);
const int16_t y = deltaY > INT16_MAX ? INT16_MAX :
deltaY < INT16_MIN ? INT16_MIN : static_cast<int16_t>(deltaY);
const int8_t wheelDelta = wheel > INT8_MAX ? INT8_MAX :
wheel < LG_INPUT_MOUSE_WHEEL_MIN ? LG_INPUT_MOUSE_WHEEL_MIN :
static_cast<int8_t>(wheel);
const LGInputPipeMouseRelative payload = {
static_cast<uint8_t>(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<uint32_t>(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<int8_t>(wheel);
const LGInputPipeMouseAbsolute payload = {
static_cast<uint8_t>(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<uint32_t>(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<CInputPipeServer *>(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(

View File

@@ -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<uint64_t> 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<uint64_t> 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;

View File

@@ -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;

View File

@@ -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();