mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 15:11:31 +00:00
[client/idd] input: synchronize keyboard LEDs
Report guest HID keyboard LED state from LGInput to LGIdd and publish it with the LGMP input endpoint status. Forward SPICE modifier updates through the same provider state so evdev can synchronize physical keyboard LEDs across transport changes and reconnects. Bump the versioned pipe and KVMFR input status contracts for the new feedback state. Accept earlier LGMP input status versions without LED feedback so the client retains input while the guest driver is being upgraded.
This commit is contained in:
@@ -29,7 +29,7 @@ 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 = 3;
|
||||
static constexpr uint16_t LG_INPUT_PIPE_VERSION = 4;
|
||||
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;
|
||||
@@ -67,9 +67,12 @@ enum LGInputKeyboardModifier : uint8_t
|
||||
|
||||
enum LGInputPipeMessageType : uint16_t
|
||||
{
|
||||
// LGIdd to LGInput.
|
||||
LG_INPUT_PIPE_MESSAGE_MOUSE_ABSOLUTE = 1,
|
||||
LG_INPUT_PIPE_MESSAGE_MOUSE_RELATIVE = 2,
|
||||
LG_INPUT_PIPE_MESSAGE_KEYBOARD = 3,
|
||||
// LGInput to LGIdd.
|
||||
LG_INPUT_PIPE_MESSAGE_KEYBOARD_LEDS = 4,
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
@@ -88,11 +91,18 @@ using LGInputPipeMouseRelative = KVMFRInputMouseRelative;
|
||||
using LGInputPipeMouseAbsolute = KVMFRInputMouseAbsolute;
|
||||
using LGInputPipeKeyboard = KVMFRInputKeyboard;
|
||||
|
||||
struct LGInputPipeKeyboardLEDs
|
||||
{
|
||||
KVMFRInputKeyboardLEDFlags leds;
|
||||
};
|
||||
|
||||
static_assert(sizeof(LGInputPipeMouseRelative) == 16,
|
||||
"LGInputPipeMouseRelative wire layout changed");
|
||||
static_assert(sizeof(LGInputPipeMouseAbsolute) == 16,
|
||||
"LGInputPipeMouseAbsolute wire layout changed");
|
||||
static_assert(sizeof(LGInputPipeKeyboard) == 16,
|
||||
"LGInputPipeKeyboard wire layout changed");
|
||||
static_assert(sizeof(LGInputPipeKeyboardLEDs) == 1,
|
||||
"LGInputPipeKeyboardLEDs wire layout changed");
|
||||
static_assert(sizeof(LGInputPipeMessage) == 84,
|
||||
"LGInputPipeMessage wire layout changed");
|
||||
|
||||
@@ -29,6 +29,8 @@ public:
|
||||
|
||||
// Odd states are available; a state change invalidates in-flight input.
|
||||
virtual uint64_t GetState() const = 0;
|
||||
// Returns false until the guest endpoint has reported its HID LED state.
|
||||
virtual bool GetKeyboardLEDs(uint8_t& leds) 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,
|
||||
|
||||
@@ -38,6 +38,8 @@ bool CInputPipeServer::Init()
|
||||
DeInit();
|
||||
|
||||
Atomic::Store(m_state, 0, std::memory_order_release);
|
||||
Atomic::Store(m_keyboardLEDs, 0, std::memory_order_release);
|
||||
Atomic::Store(m_keyboardLEDsValid, false, std::memory_order_release);
|
||||
m_performanceFrequency.QuadPart = 0;
|
||||
if (!QueryPerformanceFrequency(&m_performanceFrequency))
|
||||
m_performanceFrequency.QuadPart = 0;
|
||||
@@ -551,6 +553,7 @@ void CInputPipeServer::Invalidate(uint64_t state, bool requireMatch)
|
||||
CSRWExclusiveLock queueLock(m_queueLock);
|
||||
m_queueHead = 0;
|
||||
m_queueCount = 0;
|
||||
Atomic::Store(m_keyboardLEDsValid, false, std::memory_order_release);
|
||||
}
|
||||
|
||||
DWORD WINAPI CInputPipeServer::ThreadProc(void * context)
|
||||
@@ -597,6 +600,8 @@ void CInputPipeServer::OnPipeConnected()
|
||||
++state;
|
||||
++state;
|
||||
m_sequence = 0;
|
||||
m_feedbackSequence = 0;
|
||||
Atomic::Store(m_keyboardLEDsValid, false, std::memory_order_release);
|
||||
|
||||
m_queueHead = 0;
|
||||
m_queueCount = 0;
|
||||
@@ -623,8 +628,40 @@ bool CInputPipeServer::OnPipeMessage(
|
||||
const void * message,
|
||||
size_t size)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(message);
|
||||
UNREFERENCED_PARAMETER(size);
|
||||
DEBUG_WARN("LGInput sent an unexpected message");
|
||||
return false;
|
||||
if (size != sizeof(LGInputPipeMessage))
|
||||
{
|
||||
DEBUG_WARN("Received a malformed LGInput feedback message");
|
||||
return false;
|
||||
}
|
||||
|
||||
const LGInputPipeMessage& frame =
|
||||
*static_cast<const LGInputPipeMessage *>(message);
|
||||
if (frame.magic != LG_INPUT_PIPE_MAGIC ||
|
||||
frame.version != LG_INPUT_PIPE_VERSION ||
|
||||
frame.type != LG_INPUT_PIPE_MESSAGE_KEYBOARD_LEDS ||
|
||||
frame.payloadSize != sizeof(LGInputPipeKeyboardLEDs) ||
|
||||
!frame.sequence || frame.sequence != m_feedbackSequence + 1)
|
||||
{
|
||||
DEBUG_WARN("Received a malformed LGInput feedback message");
|
||||
return false;
|
||||
}
|
||||
|
||||
LGInputPipeKeyboardLEDs feedback = {};
|
||||
memcpy(&feedback, frame.payload, sizeof(feedback));
|
||||
const uint8_t mask =
|
||||
KVMFR_INPUT_KEYBOARD_LED_NUM_LOCK |
|
||||
KVMFR_INPUT_KEYBOARD_LED_CAPS_LOCK |
|
||||
KVMFR_INPUT_KEYBOARD_LED_SCROLL_LOCK |
|
||||
KVMFR_INPUT_KEYBOARD_LED_COMPOSE |
|
||||
KVMFR_INPUT_KEYBOARD_LED_KANA;
|
||||
if (feedback.leds & ~mask)
|
||||
{
|
||||
DEBUG_WARN("Received invalid LGInput keyboard LEDs");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_feedbackSequence = frame.sequence;
|
||||
Atomic::Store(m_keyboardLEDs, feedback.leds, std::memory_order_release);
|
||||
Atomic::Store(m_keyboardLEDsValid, true, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -66,6 +66,10 @@ private:
|
||||
size_t m_queueHead = 0;
|
||||
size_t m_queueCount = 0;
|
||||
uint64_t m_sequence = 0;
|
||||
uint64_t m_feedbackSequence = 0;
|
||||
|
||||
std::atomic<uint8_t> m_keyboardLEDs { 0 };
|
||||
std::atomic<bool> m_keyboardLEDsValid { false };
|
||||
|
||||
MouseMode m_mouseMode = MouseMode::NONE;
|
||||
uint16_t m_absoluteX = 0;
|
||||
@@ -118,6 +122,14 @@ public:
|
||||
return Atomic::Load(m_state, std::memory_order_acquire);
|
||||
}
|
||||
|
||||
bool GetKeyboardLEDs(uint8_t& leds) const override
|
||||
{
|
||||
if (!Atomic::Load(m_keyboardLEDsValid, std::memory_order_acquire))
|
||||
return false;
|
||||
leds = Atomic::Load(m_keyboardLEDs, std::memory_order_acquire);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SendMouseRelative(
|
||||
_In_ int32_t deltaX,
|
||||
_In_ int32_t deltaY,
|
||||
|
||||
@@ -551,6 +551,8 @@ InputTargetState CInputHub::GetState(const SourceKey& source)
|
||||
return result;
|
||||
if (!BindingValid(source))
|
||||
return result;
|
||||
result.keyboardLEDsValid = m_sink->GetKeyboardLEDs(
|
||||
result.keyboardLEDs);
|
||||
if (!m_owner.backend)
|
||||
result.available = true;
|
||||
else if (source.client && source.generation && m_owner == source)
|
||||
|
||||
@@ -34,6 +34,8 @@ struct InputTargetState
|
||||
InputSourceId owner;
|
||||
bool available = false;
|
||||
bool owned = false;
|
||||
bool keyboardLEDsValid = false;
|
||||
uint8_t keyboardLEDs = 0;
|
||||
};
|
||||
|
||||
enum class InputResult
|
||||
|
||||
@@ -330,11 +330,13 @@ void CLGMPInputTransport::UpdateTargetState(
|
||||
const InputTargetState& state)
|
||||
{
|
||||
CSRWExclusiveLock lock(m_statusLock);
|
||||
if (state.state == m_targetState.state &&
|
||||
state.available == m_targetState.available &&
|
||||
state.owned == m_targetState.owned &&
|
||||
state.owner.client == m_targetState.owner.client &&
|
||||
state.owner.generation == m_targetState.owner.generation)
|
||||
if (state.state == m_targetState.state &&
|
||||
state.available == m_targetState.available &&
|
||||
state.owned == m_targetState.owned &&
|
||||
state.keyboardLEDsValid == m_targetState.keyboardLEDsValid &&
|
||||
state.keyboardLEDs == m_targetState.keyboardLEDs &&
|
||||
state.owner.client == m_targetState.owner.client &&
|
||||
state.owner.generation == m_targetState.owner.generation)
|
||||
return;
|
||||
|
||||
if (state.state != m_targetState.state)
|
||||
@@ -389,6 +391,11 @@ bool CLGMPInputTransport::PublishStatus()
|
||||
status.ownerClientID = m_targetState.owner.client;
|
||||
status.ownerGeneration = m_targetState.owner.generation;
|
||||
}
|
||||
if (available && m_targetState.keyboardLEDsValid)
|
||||
{
|
||||
status.flags |= KVMFR_INPUT_STATUS_KEYBOARD_LEDS_VALID;
|
||||
status.keyboardLEDs = m_targetState.keyboardLEDs;
|
||||
}
|
||||
status.generation = m_endpointGeneration;
|
||||
status.lease = static_cast<uint32_t>(OWNER_LEASE_MS);
|
||||
status.maxButtons = KVMFR_INPUT_MOUSE_BUTTON_COUNT;
|
||||
|
||||
@@ -165,7 +165,10 @@ static NTSTATUS SetOutputReport(
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
|
||||
WDFDEVICE device = WdfIoQueueGetDevice(queue);
|
||||
HIDGetDeviceContext(device)->keyboardLeds = report->leds;
|
||||
HIDDeviceContext * context = HIDGetDeviceContext(device);
|
||||
context->keyboardLeds = report->leds;
|
||||
if (context->inputPipe)
|
||||
context->inputPipe->UpdateKeyboardLEDs(report->leds);
|
||||
WdfRequestSetInformation(request, sizeof(*report));
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,15 @@ static constexpr uint16_t HID_CONSUMER_USAGE_VOLUME_DOWN = 0xea;
|
||||
|
||||
bool CInputPipeClient::Start()
|
||||
{
|
||||
{
|
||||
CSRWExclusiveLock lock(m_feedbackLock);
|
||||
m_feedbackReady = false;
|
||||
}
|
||||
m_endpoint.Stop();
|
||||
{
|
||||
CSRWExclusiveLock lock(m_feedbackLock);
|
||||
m_feedbackSequence = 0;
|
||||
}
|
||||
m_lastSequence = 0;
|
||||
m_statReceived = 0;
|
||||
m_statMalformed = 0;
|
||||
@@ -55,6 +63,10 @@ bool CInputPipeClient::Start()
|
||||
void CInputPipeClient::Stop()
|
||||
{
|
||||
const bool wasRunning = m_endpoint.IsRunning();
|
||||
{
|
||||
CSRWExclusiveLock lock(m_feedbackLock);
|
||||
m_feedbackReady = false;
|
||||
}
|
||||
m_endpoint.Stop();
|
||||
m_lastSequence = 0;
|
||||
if (wasRunning)
|
||||
@@ -67,11 +79,22 @@ void CInputPipeClient::Stop()
|
||||
void CInputPipeClient::OnPipeConnected()
|
||||
{
|
||||
m_lastSequence = 0;
|
||||
{
|
||||
CSRWExclusiveLock lock(m_feedbackLock);
|
||||
m_feedbackSequence = 0;
|
||||
m_feedbackReady = SendKeyboardLEDsLocked();
|
||||
if (!m_feedbackReady)
|
||||
DEBUG_WARN("Failed to report LGInput keyboard LEDs");
|
||||
}
|
||||
DEBUG_INFO("Connected to the LGIdd input transport");
|
||||
}
|
||||
|
||||
void CInputPipeClient::OnPipeDisconnected()
|
||||
{
|
||||
{
|
||||
CSRWExclusiveLock lock(m_feedbackLock);
|
||||
m_feedbackReady = false;
|
||||
}
|
||||
m_lastSequence = 0;
|
||||
LogStatistics(true);
|
||||
CHIDDevice::ResetReports();
|
||||
@@ -319,6 +342,46 @@ bool CInputPipeClient::SubmitReport(
|
||||
return true;
|
||||
}
|
||||
|
||||
void CInputPipeClient::UpdateKeyboardLEDs(uint8_t leds)
|
||||
{
|
||||
const uint8_t mask =
|
||||
KVMFR_INPUT_KEYBOARD_LED_NUM_LOCK |
|
||||
KVMFR_INPUT_KEYBOARD_LED_CAPS_LOCK |
|
||||
KVMFR_INPUT_KEYBOARD_LED_SCROLL_LOCK |
|
||||
KVMFR_INPUT_KEYBOARD_LED_COMPOSE |
|
||||
KVMFR_INPUT_KEYBOARD_LED_KANA;
|
||||
leds &= mask;
|
||||
|
||||
CSRWExclusiveLock lock(m_feedbackLock);
|
||||
if (m_keyboardLEDs == leds)
|
||||
return;
|
||||
|
||||
m_keyboardLEDs = leds;
|
||||
if (m_feedbackReady && !SendKeyboardLEDsLocked())
|
||||
{
|
||||
m_feedbackReady = false;
|
||||
DEBUG_WARN("Failed to report LGInput keyboard LEDs");
|
||||
}
|
||||
}
|
||||
|
||||
bool CInputPipeClient::SendKeyboardLEDsLocked()
|
||||
{
|
||||
LGInputPipeMessage message = {};
|
||||
message.magic = LG_INPUT_PIPE_MAGIC;
|
||||
message.version = LG_INPUT_PIPE_VERSION;
|
||||
message.type = LG_INPUT_PIPE_MESSAGE_KEYBOARD_LEDS;
|
||||
message.payloadSize = sizeof(LGInputPipeKeyboardLEDs);
|
||||
message.sequence = ++m_feedbackSequence;
|
||||
|
||||
const LGInputPipeKeyboardLEDs feedback = { m_keyboardLEDs };
|
||||
memcpy(message.payload, &feedback, sizeof(feedback));
|
||||
if (m_endpoint.Send(&message, sizeof(message)))
|
||||
return true;
|
||||
|
||||
--m_feedbackSequence;
|
||||
return false;
|
||||
}
|
||||
|
||||
void CInputPipeClient::LogStatistics(bool force)
|
||||
{
|
||||
const ULONGLONG now = GetTickCount64();
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "CPipeEndpoint.h"
|
||||
#include "CSRWLock.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
@@ -32,6 +33,7 @@ public:
|
||||
|
||||
bool Start();
|
||||
void Stop();
|
||||
void UpdateKeyboardLEDs(uint8_t leds);
|
||||
|
||||
private:
|
||||
static constexpr ULONGLONG STATISTICS_INTERVAL_MS = 5000;
|
||||
@@ -43,9 +45,14 @@ private:
|
||||
bool HandleMouseAbsolute(const void * payload, size_t size);
|
||||
bool HandleKeyboard(const void * payload, size_t size);
|
||||
bool SubmitReport(const void * report, size_t size);
|
||||
bool SendKeyboardLEDsLocked();
|
||||
void LogStatistics(bool force);
|
||||
|
||||
CPipeEndpoint m_endpoint;
|
||||
CSRWLock m_feedbackLock;
|
||||
bool m_feedbackReady = false;
|
||||
uint64_t m_feedbackSequence = 0;
|
||||
uint8_t m_keyboardLEDs = 0;
|
||||
uint64_t m_lastSequence = 0;
|
||||
uint64_t m_statReceived = 0;
|
||||
uint64_t m_statMalformed = 0;
|
||||
|
||||
Reference in New Issue
Block a user