[client] input: synchronize host lock state over LGMP
Some checks failed
build / client (Debug, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / module (push) Has been cancelled
build / host-linux (push) Has been cancelled
build / host-windows-cross (push) Has been cancelled
build / host-windows-native (push) Has been cancelled
build / idd (push) Has been cancelled
build / obs (clang) (push) Has been cancelled
build / obs (gcc) (push) Has been cancelled
build / docs (push) Has been cancelled

Cache native keyboard lock state across input-provider transitions and
reconcile it with HID LED feedback over LGMP.

Project raw and corrective lock-key transitions through the reliable
input FIFO, defer correction until outstanding transitions settle, and
recover from delayed or missing feedback without double toggling.
This commit is contained in:
Geoffrey McRae
2026-08-21 15:03:57 +10:00
parent efb397bbaf
commit 53917c4918
9 changed files with 612 additions and 105 deletions

View File

@@ -53,6 +53,7 @@ void lgInput_setAvailabilityListener(
bool lgInput_keyDown(int key); bool lgInput_keyDown(int key);
bool lgInput_keyUp(int key); bool lgInput_keyUp(int key);
bool lgInput_keyboardLEDs(bool numLock, bool capsLock, bool scrollLock); bool lgInput_keyboardLEDs(bool numLock, bool capsLock, bool scrollLock);
void lgInput_setKeyboardLEDsSync(bool enable);
void lgInput_releaseKeys(void); void lgInput_releaseKeys(void);
void lgInput_releaseButtons(void); void lgInput_releaseButtons(void);
/* Release all tracked keyboard and mouse button state under one active-input /* Release all tracked keyboard and mouse button state under one active-input

View File

@@ -69,6 +69,8 @@ typedef struct LG_InputOps
bool (*keyUp)(void * opaque, int key); bool (*keyUp)(void * opaque, int key);
bool (*keyboardLEDs)(void * opaque, bool numLock, bool capsLock, bool (*keyboardLEDs)(void * opaque, bool numLock, bool capsLock,
bool scrollLock); bool scrollLock);
/* Cancel any queued host-to-guest lock-state reconciliation. */
void (*keyboardLEDsReset)(void * opaque);
bool (*mouseMotion)(void * opaque, int32_t x, int32_t y); bool (*mouseMotion)(void * opaque, int32_t x, int32_t y);
/* Position is in guest pixels and must be within the supplied dimensions. */ /* Position is in guest pixels and must be within the supplied dimensions. */

View File

@@ -655,9 +655,6 @@ void app_handleKeyboardLEDs(bool numLock, bool capsLock, bool scrollLock)
if (evdev_isExclusive()) if (evdev_isExclusive())
return; return;
if (!core_inputEnabled())
return;
if (!lgInput_keyboardLEDs(numLock, capsLock, scrollLock)) if (!lgInput_keyboardLEDs(numLock, capsLock, scrollLock))
DEBUG_ERROR("app_handleKeyboardLEDs: failed to send message"); DEBUG_ERROR("app_handleKeyboardLEDs: failed to send message");
} }

View File

@@ -132,10 +132,22 @@ static bool moveExit(double ex, double ey)
return inside; return inside;
} }
static bool inputPolicyEnabled(void)
{
return !g_state.ignoreInput &&
((g_cursor.grab && g_params.captureInputOnly) ||
!g_params.captureInputOnly);
}
bool core_inputEnabled(void) bool core_inputEnabled(void)
{ {
return lgInput_available() && !g_state.ignoreInput && return lgInput_available() && inputPolicyEnabled();
((g_cursor.grab && g_params.captureInputOnly) || !g_params.captureInputOnly); }
void core_updateKeyboardLEDsSync(void)
{
lgInput_setKeyboardLEDsSync(
inputPolicyEnabled() && !evdev_isExclusive());
} }
void core_updateKeyboardGrab(void) void core_updateKeyboardGrab(void)
@@ -154,6 +166,7 @@ void core_updateKeyboardGrab(void)
(capture || (inputEnabled && (view || automatic))); (capture || (inputEnabled && (view || automatic)));
evdev_setGrab(active, localActive && g_cursor.grab); evdev_setGrab(active, localActive && g_cursor.grab);
core_updateKeyboardLEDsSync();
if (active) if (active)
g_state.ds->grabKeyboard(); g_state.ds->grabKeyboard();
else else

View File

@@ -24,6 +24,7 @@
#include <stdbool.h> #include <stdbool.h>
bool core_inputEnabled(void); bool core_inputEnabled(void);
void core_updateKeyboardLEDsSync(void);
void core_updateKeyboardGrab(void); void core_updateKeyboardGrab(void);
void core_invalidatePointer(bool detectInView); void core_invalidatePointer(bool detectInView);
void core_setCursorInView(bool enable); void core_setCursorInView(bool enable);

View File

@@ -2096,6 +2096,8 @@ void evdev_dispatch(void * opaque)
evdev_barrierMatches(&output.barrier, desiredState)) evdev_barrierMatches(&output.barrier, desiredState))
{ {
evdev_dispatchReset(output.barrier.lanes); evdev_dispatchReset(output.barrier.lanes);
if (output.barrier.activate & EVDEV_GRAB_KEYBOARD)
lgInput_setKeyboardLEDsSync(false);
evdev_publishActive(output.barrier.deactivate, false); evdev_publishActive(output.barrier.deactivate, false);
evdev_publishActive(output.barrier.activate, true); evdev_publishActive(output.barrier.activate, true);
const uint64_t currentState = atomic_load_explicit( const uint64_t currentState = atomic_load_explicit(
@@ -2103,6 +2105,7 @@ void evdev_dispatch(void * opaque)
if (output.barrier.activate && if (output.barrier.activate &&
!evdev_barrierMatches(&output.barrier, currentState)) !evdev_barrierMatches(&output.barrier, currentState))
evdev_publishActive(output.barrier.activate, false); evdev_publishActive(output.barrier.activate, false);
core_updateKeyboardLEDsSync();
} }
atomic_store_explicit(&state.barrierAck, atomic_store_explicit(&state.barrierAck,
output.barrier.serial, memory_order_release); output.barrier.serial, memory_order_release);

View File

@@ -35,6 +35,7 @@ struct InputBinding
bool keyboardLEDsValid; bool keyboardLEDsValid;
uint8_t keyboardLEDs; uint8_t keyboardLEDs;
uint32_t epoch; uint32_t epoch;
uint32_t generation;
}; };
static struct static struct
@@ -55,6 +56,9 @@ static struct
struct InputBinding active; struct InputBinding active;
bool useTransport; bool useTransport;
bool notifiedAvailable; bool notifiedAvailable;
bool keyboardLEDsSyncEnabled;
bool desiredKeyboardLEDsValid;
uint8_t desiredKeyboardLEDs;
_Atomic(bool) keys[KEY_MAX]; _Atomic(bool) keys[KEY_MAX];
_Atomic(uint32_t) buttons; _Atomic(uint32_t) buttons;
@@ -191,6 +195,29 @@ static void clearStateNL(void)
atomic_store_explicit(&l_input.buttons, 0, memory_order_relaxed); atomic_store_explicit(&l_input.buttons, 0, memory_order_relaxed);
} }
static void resetKeyboardLEDsNL(const struct InputBinding * binding)
{
if (binding->ops && binding->ops->keyboardLEDsReset)
binding->ops->keyboardLEDsReset(binding->opaque);
}
static bool syncKeyboardLEDsNL(void)
{
if (!l_input.keyboardLEDsSyncEnabled ||
!l_input.desiredKeyboardLEDsValid)
return true;
if (!l_input.active.ops)
return false;
if (!l_input.active.ops->keyboardLEDs)
return true;
const uint8_t leds = l_input.desiredKeyboardLEDs;
return l_input.active.ops->keyboardLEDs(l_input.active.opaque,
(leds & LG_KEYBOARD_LED_NUM_LOCK) != 0,
(leds & LG_KEYBOARD_LED_CAPS_LOCK) != 0,
(leds & LG_KEYBOARD_LED_SCROLL_LOCK) != 0);
}
static bool updateActiveNL(bool dropActive) static bool updateActiveNL(bool dropActive)
{ {
const struct InputBinding next = const struct InputBinding next =
@@ -210,6 +237,7 @@ static bool updateActiveNL(bool dropActive)
const bool keyboardLEDsChanged = const bool keyboardLEDsChanged =
!keyboardLEDsEqual(&next, &l_input.active); !keyboardLEDsEqual(&next, &l_input.active);
resetKeyboardLEDsNL(&l_input.active);
if (dropActive) if (dropActive)
clearStateNL(); clearStateNL();
else else
@@ -220,6 +248,7 @@ static bool updateActiveNL(bool dropActive)
{ {
if (l_input.active.ops->reset) if (l_input.active.ops->reset)
l_input.active.ops->reset(l_input.active.opaque); l_input.active.ops->reset(l_input.active.opaque);
syncKeyboardLEDsNL();
DEBUG_INFO("Using Input: %s", l_input.active.ops->name); DEBUG_INFO("Using Input: %s", l_input.active.ops->name);
} }
else else
@@ -231,10 +260,15 @@ static bool updateStatusNL(struct InputBinding * binding,
const LG_InputStatus * status) const LG_InputStatus * status)
{ {
const struct InputBinding oldActive = l_input.active; const struct InputBinding oldActive = l_input.active;
const bool wasActive = bindingEqual(&l_input.active, binding); const bool wasActive =
bindingEqual(&l_input.active, binding);
const bool generationChanged = wasActive &&
binding->generation && status->available &&
binding->generation != status->generation;
if (wasActive && !status->available) if (wasActive && !status->available)
{ {
resetKeyboardLEDsNL(&l_input.active);
resetActiveNL(); resetActiveNL();
l_input.active = (struct InputBinding) { 0 }; l_input.active = (struct InputBinding) { 0 };
} }
@@ -247,7 +281,10 @@ static bool updateStatusNL(struct InputBinding * binding,
binding->keyboardLEDsValid = status->available && binding->keyboardLEDsValid = status->available &&
status->keyboardLEDsValid; status->keyboardLEDsValid;
binding->keyboardLEDs = status->keyboardLEDs; binding->keyboardLEDs = status->keyboardLEDs;
binding->generation = status->generation;
updateActiveNL(false); updateActiveNL(false);
if (generationChanged)
syncKeyboardLEDsNL();
return !keyboardLEDsEqual(&oldActive, &l_input.active); return !keyboardLEDsEqual(&oldActive, &l_input.active);
} }
@@ -296,6 +333,9 @@ void lgInput_init(void)
l_input.active = (struct InputBinding) { 0 }; l_input.active = (struct InputBinding) { 0 };
l_input.useTransport = true; l_input.useTransport = true;
l_input.notifiedAvailable = false; l_input.notifiedAvailable = false;
l_input.keyboardLEDsSyncEnabled = false;
l_input.desiredKeyboardLEDsValid = false;
l_input.desiredKeyboardLEDs = 0;
for (int key = 0; key < KEY_MAX; ++key) for (int key = 0; key < KEY_MAX; ++key)
atomic_init(&l_input.keys[key], false); atomic_init(&l_input.keys[key], false);
@@ -316,6 +356,7 @@ void lgInput_free(void)
LG_LOCK(l_input.keyboardLEDsDispatch); LG_LOCK(l_input.keyboardLEDsDispatch);
LG_LOCK(l_input.availabilityDispatch); LG_LOCK(l_input.availabilityDispatch);
LG_LOCK_EXCLUSIVE(l_input.activeLock); LG_LOCK_EXCLUSIVE(l_input.activeLock);
resetKeyboardLEDsNL(&l_input.active);
resetActiveNL(); resetActiveNL();
fallback = l_input.fallback; fallback = l_input.fallback;
transport = l_input.transport; transport = l_input.transport;
@@ -532,15 +573,41 @@ bool lgInput_keyUp(int key)
bool lgInput_keyboardLEDs(bool numLock, bool capsLock, bool scrollLock) bool lgInput_keyboardLEDs(bool numLock, bool capsLock, bool scrollLock)
{ {
LG_LOCK_SHARED(l_input.activeLock); const uint8_t desired =
const bool result = l_input.active.ops && (numLock ? LG_KEYBOARD_LED_NUM_LOCK : 0) |
(!l_input.active.ops->keyboardLEDs || (capsLock ? LG_KEYBOARD_LED_CAPS_LOCK : 0) |
l_input.active.ops->keyboardLEDs(l_input.active.opaque, (scrollLock ? LG_KEYBOARD_LED_SCROLL_LOCK : 0);
numLock, capsLock, scrollLock));
LG_UNLOCK_SHARED(l_input.activeLock); LG_LOCK_EXCLUSIVE(l_input.activeLock);
l_input.desiredKeyboardLEDs = desired;
l_input.desiredKeyboardLEDsValid = true;
const bool result = syncKeyboardLEDsNL();
LG_UNLOCK_EXCLUSIVE(l_input.activeLock);
return result; return result;
} }
void lgInput_setKeyboardLEDsSync(bool enable)
{
LG_LOCK_EXCLUSIVE(l_input.activeLock);
if (l_input.keyboardLEDsSyncEnabled == enable)
{
LG_UNLOCK_EXCLUSIVE(l_input.activeLock);
return;
}
l_input.keyboardLEDsSyncEnabled = enable;
if (enable)
syncKeyboardLEDsNL();
else
{
resetKeyboardLEDsNL(&l_input.fallback);
if (l_input.transport.ops != l_input.fallback.ops ||
l_input.transport.opaque != l_input.fallback.opaque)
resetKeyboardLEDsNL(&l_input.transport);
}
LG_UNLOCK_EXCLUSIVE(l_input.activeLock);
}
void lgInput_releaseKeys(void) void lgInput_releaseKeys(void)
{ {
LG_LOCK_EXCLUSIVE(l_input.activeLock); LG_LOCK_EXCLUSIVE(l_input.activeLock);

View File

@@ -288,6 +288,11 @@ void evdev_setGrab(bool keyboard, bool pointer)
(void)pointer; (void)pointer;
} }
bool evdev_isExclusive(void)
{
return false;
}
static bool inputSupports(void * opaque, LG_InputSupport support) static bool inputSupports(void * opaque, LG_InputSupport support)
{ {
return support == LG_INPUT_SUPPORT_MOUSE_ABSOLUTE && return support == LG_INPUT_SUPPORT_MOUSE_ABSOLUTE &&

View File

@@ -45,12 +45,16 @@
#define INPUT_KEEPALIVE_US 200000 #define INPUT_KEEPALIVE_US 200000
#define INPUT_IDLE_RELEASE_US 400000 #define INPUT_IDLE_RELEASE_US 400000
#define INPUT_RELEASE_TIMEOUT_US 50000 #define INPUT_RELEASE_TIMEOUT_US 50000
#define INPUT_KEYBOARD_LED_SYNC_DELAY_US 20000
#define INPUT_KEYBOARD_LED_ACK_TIMEOUT_US 500000
#define INPUT_WORKER_IDLE_MS 10 #define INPUT_WORKER_IDLE_MS 10
#define INPUT_WORKER_RETRY_MS 1 #define INPUT_WORKER_RETRY_MS 1
#define INPUT_STATS_INTERVAL_US 5000000 #define INPUT_STATS_INTERVAL_US 5000000
#define INPUT_MAX_SPLIT_REPORTS 4 #define INPUT_MAX_SPLIT_REPORTS 4
#define INPUT_MOUSE_DELTA_MIN (INT16_MIN * INPUT_MAX_SPLIT_REPORTS) #define INPUT_MOUSE_DELTA_MIN \
#define INPUT_MOUSE_DELTA_MAX (INT16_MAX * INPUT_MAX_SPLIT_REPORTS) (INT16_MIN * INPUT_MAX_SPLIT_REPORTS)
#define INPUT_MOUSE_DELTA_MAX \
(INT16_MAX * INPUT_MAX_SPLIT_REPORTS)
_Static_assert( _Static_assert(
(int)LG_KEYBOARD_LED_NUM_LOCK == (int)LG_KEYBOARD_LED_NUM_LOCK ==
@@ -71,10 +75,30 @@ enum LGMPInputMouseMode
LGMP_INPUT_MOUSE_ABSOLUTE, LGMP_INPUT_MOUSE_ABSOLUTE,
}; };
struct LGMPInputLockKey
{
int key;
uint8_t led;
};
static const struct LGMPInputLockKey LOCK_KEYS[] =
{
{ KEY_NUMLOCK, LG_KEYBOARD_LED_NUM_LOCK },
{ KEY_CAPSLOCK, LG_KEYBOARD_LED_CAPS_LOCK },
{ KEY_SCROLLLOCK, LG_KEYBOARD_LED_SCROLL_LOCK },
};
static const uint8_t LOCK_LED_MASK =
LG_KEYBOARD_LED_NUM_LOCK |
LG_KEYBOARD_LED_CAPS_LOCK |
LG_KEYBOARD_LED_SCROLL_LOCK;
struct LGMPInputPending struct LGMPInputPending
{ {
KVMFRInputMessage message; KVMFRInputMessage message;
uint8_t keyboardLEDToggle;
bool pureMotion; bool pureMotion;
bool keyboardLEDSync;
}; };
struct LGMPInputCounters struct LGMPInputCounters
@@ -118,6 +142,14 @@ struct LGMPInput
bool ownerBlocked; bool ownerBlocked;
bool keyboardLEDsValid; bool keyboardLEDsValid;
uint8_t keyboardLEDs; uint8_t keyboardLEDs;
bool desiredKeyboardLEDsValid;
uint8_t desiredKeyboardLEDs;
uint8_t staleKeyboardLEDs;
uint8_t pendingKeyboardLEDs;
uint32_t keyboardLEDTransitions;
uint64_t keyboardLEDsSyncAfter;
uint64_t keyboardLEDsPendingUntil;
uint64_t keyboardLEDsStaleUntil;
uint32_t generation; uint32_t generation;
uint32_t sequence; uint32_t sequence;
@@ -160,8 +192,32 @@ static bool buildKeyboardPayload(const LGMPInput * input,
static bool queueMouse(LGMPInput * input, enum LGMPInputMouseMode mode, static bool queueMouse(LGMPInput * input, enum LGMPInputMouseMode mode,
int32_t x, int32_t y, int32_t wheel, uint32_t buttons, int32_t x, int32_t y, int32_t wheel, uint32_t buttons,
bool pureMotion, bool * wake); bool pureMotion, bool * wake);
static bool release(LGMPInput * input, bool * wake);
static void discardProtocolState(LGMPInput * input); static void discardProtocolState(LGMPInput * input);
static uint8_t keyboardLEDForKey(int key)
{
for (size_t i = 0; i < sizeof(LOCK_KEYS) / sizeof(*LOCK_KEYS); ++i)
if (LOCK_KEYS[i].key == key)
return LOCK_KEYS[i].led;
return 0;
}
static uint8_t projectedKeyboardLEDs(const LGMPInput * input)
{
return input->keyboardLEDs ^ input->pendingKeyboardLEDs;
}
static void invalidateKeyboardLEDProjection(
LGMPInput * input, uint64_t now, uint64_t delay)
{
input->pendingKeyboardLEDs = 0;
input->keyboardLEDTransitions = 0;
input->keyboardLEDsPendingUntil = 0;
input->keyboardLEDsSyncAfter =
now + delay;
}
static struct LGMPInputPending * pendingAt( static struct LGMPInputPending * pendingAt(
LGMPInput * input, unsigned position) LGMPInput * input, unsigned position)
{ {
@@ -169,6 +225,103 @@ static struct LGMPInputPending * pendingAt(
(input->pendingHead + position) % INPUT_PENDING_LENGTH]; (input->pendingHead + position) % INPUT_PENDING_LENGTH];
} }
static bool markPendingKeyboardLEDSync(
LGMPInput * input, uint32_t sequence)
{
if (!input->pendingCount)
return false;
struct LGMPInputPending * item = pendingAt(
input, input->pendingCount - 1);
if (item->message.generation != input->generation ||
item->message.sequence != sequence ||
item->message.type != KVMFR_INPUT_MESSAGE_KEYBOARD)
return false;
item->keyboardLEDSync = true;
return true;
}
static bool markPendingKeyboardLEDToggle(
LGMPInput * input, uint32_t sequence, uint8_t led)
{
if (!input->pendingCount)
return false;
struct LGMPInputPending * item = pendingAt(
input, input->pendingCount - 1);
if (item->message.generation != input->generation ||
item->message.sequence != sequence ||
item->message.type != KVMFR_INPUT_MESSAGE_KEYBOARD)
return false;
item->keyboardLEDToggle ^= led;
return true;
}
static bool pendingKeyboardLEDSync(LGMPInput * input)
{
for (unsigned i = 0; i < input->pendingCount; ++i)
if (pendingAt(input, i)->keyboardLEDSync)
return true;
return false;
}
static uint8_t pendingKeyboardLEDToggles(LGMPInput * input)
{
uint8_t toggles = 0;
for (unsigned i = 0; i < input->pendingCount; ++i)
toggles ^= pendingAt(input, i)->keyboardLEDToggle;
return toggles;
}
static unsigned keyboardLEDToggleCount(uint8_t toggles)
{
unsigned count = 0;
while (toggles)
{
count += toggles & 1U;
toggles >>= 1;
}
return count;
}
static uint32_t queuedKeyboardLEDTransitions(LGMPInput * input)
{
uint32_t count = 0;
for (unsigned i = 0; i < input->pendingCount; ++i)
count += keyboardLEDToggleCount(
pendingAt(input, i)->keyboardLEDToggle);
return count;
}
static uint32_t publishedKeyboardLEDTransitions(LGMPInput * input)
{
const uint32_t queued = queuedKeyboardLEDTransitions(input);
return input->keyboardLEDTransitions > queued ?
input->keyboardLEDTransitions - queued : 0;
}
static void retainQueuedKeyboardLEDProjection(LGMPInput * input)
{
input->pendingKeyboardLEDs = pendingKeyboardLEDToggles(input);
input->keyboardLEDTransitions = queuedKeyboardLEDTransitions(input);
input->keyboardLEDsPendingUntil = 0;
}
static uint8_t publishedKeyboardLEDToggles(LGMPInput * input)
{
return input->pendingKeyboardLEDs ^ pendingKeyboardLEDToggles(input);
}
static void updateKeyboardLEDPendingDeadline(
LGMPInput * input, uint64_t now)
{
input->keyboardLEDsPendingUntil =
publishedKeyboardLEDTransitions(input) ?
now + INPUT_KEYBOARD_LED_ACK_TIMEOUT_US : 0;
}
static LG_InputStatus inputStatus(const LGMPInput * input) static LG_InputStatus inputStatus(const LGMPInput * input)
{ {
return (LG_InputStatus) return (LG_InputStatus)
@@ -252,6 +405,12 @@ static void connectionFailed(LGMPInput * input, LGMP_STATUS status)
input->ownerBlocked = false; input->ownerBlocked = false;
input->keyboardLEDsValid = false; input->keyboardLEDsValid = false;
input->keyboardLEDs = 0; input->keyboardLEDs = 0;
input->pendingKeyboardLEDs = 0;
input->keyboardLEDTransitions = 0;
input->staleKeyboardLEDs = 0;
input->keyboardLEDsSyncAfter = 0;
input->keyboardLEDsPendingUntil = 0;
input->keyboardLEDsStaleUntil = 0;
input->pendingHead = 0; input->pendingHead = 0;
input->pendingCount = 0; input->pendingCount = 0;
input->mouseMode = LGMP_INPUT_MOUSE_NONE; input->mouseMode = LGMP_INPUT_MOUSE_NONE;
@@ -477,7 +636,9 @@ static bool submitPayload(LGMPInput * input, KVMFRInputMessageType type,
struct LGMPInputPending * item = pendingAt(input, input->pendingCount++); struct LGMPInputPending * item = pendingAt(input, input->pendingCount++);
item->message = message; item->message = message;
item->keyboardLEDToggle = 0;
item->pureMotion = pureMotion; item->pureMotion = pureMotion;
item->keyboardLEDSync = false;
++input->stats.counters.localEnqueues; ++input->stats.counters.localEnqueues;
if (input->pendingCount > input->stats.counters.pendingHighWater) if (input->pendingCount > input->stats.counters.pendingHighWater)
input->stats.counters.pendingHighWater = input->pendingCount; input->stats.counters.pendingHighWater = input->pendingCount;
@@ -510,6 +671,143 @@ static bool claim(LGMPInput * input, bool * wake)
return false; return false;
} }
static bool keyboardStateHeld(const LGMPInput * input)
{
for (unsigned usage = 1;
usage <= KVMFR_INPUT_KEYBOARD_USAGE_MAX; ++usage)
if (input->keyState[usage])
return true;
return false;
}
static void recordKeyboardToggle(LGMPInput * input, int key)
{
const uint8_t led = keyboardLEDForKey(key);
if (!led)
return;
const uint64_t now = microtime();
if (input->keyboardLEDsValid)
{
input->pendingKeyboardLEDs ^= led;
if (input->keyboardLEDTransitions != UINT32_MAX)
++input->keyboardLEDTransitions;
markPendingKeyboardLEDToggle(input, input->sequence, led);
updateKeyboardLEDPendingDeadline(input, now);
}
if (!input->desiredKeyboardLEDsValid ||
((projectedKeyboardLEDs(input) ^ input->desiredKeyboardLEDs) & led))
{
input->staleKeyboardLEDs |= led;
input->keyboardLEDsStaleUntil =
now + INPUT_KEYBOARD_LED_ACK_TIMEOUT_US;
}
else
{
input->staleKeyboardLEDs &= ~led;
if (!input->staleKeyboardLEDs)
input->keyboardLEDsStaleUntil = 0;
}
input->keyboardLEDsSyncAfter = now + INPUT_KEYBOARD_LED_SYNC_DELAY_US;
}
static bool queueKeyboardTap(LGMPInput * input,
const struct LGMPInputLockKey * lockKey, bool * wake)
{
const uint8_t usage = linux_to_hid[lockKey->key];
if (!usage || input->keyState[usage] || !claim(input, wake))
return false;
input->keyState[usage] = 1;
KVMFRInputPayload payload = { 0 };
buildKeyboardPayload(input, &payload);
if (!submitPayload(input, KVMFR_INPUT_MESSAGE_KEYBOARD,
&payload, false, wake))
{
input->keyState[usage] = 0;
return false;
}
markPendingKeyboardLEDSync(input, input->sequence);
markPendingKeyboardLEDToggle(input, input->sequence, lockKey->led);
input->keyState[usage] = 0;
payload = (KVMFRInputPayload) { 0 };
buildKeyboardPayload(input, &payload);
if (!submitPayload(input, KVMFR_INPUT_MESSAGE_KEYBOARD,
&payload, false, wake))
{
invalidateKeyboardLEDProjection(input, microtime(),
INPUT_KEYBOARD_LED_ACK_TIMEOUT_US);
if (input->connected)
release(input, wake);
return false;
}
markPendingKeyboardLEDSync(input, input->sequence);
input->pendingKeyboardLEDs ^= lockKey->led;
if (input->keyboardLEDTransitions != UINT32_MAX)
++input->keyboardLEDTransitions;
updateKeyboardLEDPendingDeadline(input, microtime());
return true;
}
static void syncKeyboardLEDs(LGMPInput * input,
uint64_t now, bool * wake)
{
if (publishedKeyboardLEDTransitions(input) &&
now >= input->keyboardLEDsPendingUntil)
{
input->pendingKeyboardLEDs = pendingKeyboardLEDToggles(input);
input->keyboardLEDTransitions =
queuedKeyboardLEDTransitions(input);
input->keyboardLEDsPendingUntil = 0;
input->keyboardLEDsSyncAfter =
now + INPUT_KEYBOARD_LED_SYNC_DELAY_US;
}
if (input->staleKeyboardLEDs &&
now >= input->keyboardLEDsStaleUntil)
{
input->desiredKeyboardLEDsValid = false;
input->staleKeyboardLEDs = 0;
input->keyboardLEDsStaleUntil = 0;
}
if (!input->connected || !input->available || input->ownerBlocked ||
!input->keyboardLEDsValid || !input->desiredKeyboardLEDsValid ||
input->keyboardLEDTransitions ||
now < input->keyboardLEDsSyncAfter)
return;
const uint8_t mismatch =
(projectedKeyboardLEDs(input) ^ input->desiredKeyboardLEDs) &
LOCK_LED_MASK & ~input->staleKeyboardLEDs;
if (!mismatch)
{
if (!input->keyboardLEDTransitions && !input->staleKeyboardLEDs)
input->desiredKeyboardLEDsValid = false;
return;
}
if (keyboardStateHeld(input))
return;
unsigned messageCount = input->claimed ? 0 : 1;
for (size_t i = 0; i < sizeof(LOCK_KEYS) / sizeof(*LOCK_KEYS); ++i)
if (mismatch & LOCK_KEYS[i].led)
messageCount += 2;
if (messageCount > INPUT_PENDING_LENGTH - input->pendingCount)
return;
for (size_t i = 0; i < sizeof(LOCK_KEYS) / sizeof(*LOCK_KEYS); ++i)
if ((mismatch & LOCK_KEYS[i].led) &&
!queueKeyboardTap(input, &LOCK_KEYS[i], wake))
break;
input->keyboardLEDsSyncAfter =
now + INPUT_KEYBOARD_LED_SYNC_DELAY_US;
}
static void clearInputState(LGMPInput * input) static void clearInputState(LGMPInput * input)
{ {
input->mouseMode = LGMP_INPUT_MOUSE_NONE; input->mouseMode = LGMP_INPUT_MOUSE_NONE;
@@ -533,6 +831,8 @@ static bool inputStateHeld(const LGMPInput * input)
static void discardProtocolState(LGMPInput * input) static void discardProtocolState(LGMPInput * input)
{ {
invalidateKeyboardLEDProjection(input, microtime(),
INPUT_KEYBOARD_LED_ACK_TIMEOUT_US);
input->pendingHead = 0; input->pendingHead = 0;
input->pendingCount = 0; input->pendingCount = 0;
input->claimed = false; input->claimed = false;
@@ -555,6 +855,13 @@ static bool restoreInputState(LGMPInput * input, bool * wake)
if (keyboardHeld && !submitPayload(input, KVMFR_INPUT_MESSAGE_KEYBOARD, if (keyboardHeld && !submitPayload(input, KVMFR_INPUT_MESSAGE_KEYBOARD,
&keyboard, false, wake)) &keyboard, false, wake))
return false; return false;
if (keyboardHeld)
for (size_t i = 0; i < sizeof(LOCK_KEYS) / sizeof(*LOCK_KEYS); ++i)
{
const uint8_t usage = linux_to_hid[LOCK_KEYS[i].key];
if (usage && input->keyState[usage])
recordKeyboardToggle(input, LOCK_KEYS[i].key);
}
if (input->mouseButtons && if (input->mouseButtons &&
input->mouseMode != LGMP_INPUT_MOUSE_NONE && input->mouseMode != LGMP_INPUT_MOUSE_NONE &&
@@ -765,10 +1072,14 @@ static void applyInputStatus(LGMPInput * input,
const uint32_t oldGeneration = input->endpointGeneration; const uint32_t oldGeneration = input->endpointGeneration;
const bool oldKeyboardLEDsValid = input->keyboardLEDsValid; const bool oldKeyboardLEDsValid = input->keyboardLEDsValid;
const uint8_t oldKeyboardLEDs = input->keyboardLEDs; const uint8_t oldKeyboardLEDs = input->keyboardLEDs;
const bool keyboardLEDsValid =
(status->flags & KVMFR_INPUT_STATUS_KEYBOARD_LEDS_VALID) != 0;
const uint8_t keyboardLEDs = status->keyboardLEDs;
const bool targetAvailable = const bool targetAvailable =
(status->flags & KVMFR_INPUT_STATUS_AVAILABLE) != 0; (status->flags & KVMFR_INPUT_STATUS_AVAILABLE) != 0;
const bool endpointChanged = wasValid && const bool endpointChanged = wasValid &&
oldGeneration != status->generation; oldGeneration != status->generation;
const uint64_t now = microtime();
bool restore = false; bool restore = false;
input->statusValid = true; input->statusValid = true;
@@ -777,9 +1088,45 @@ static void applyInputStatus(LGMPInput * input,
input->endpointGeneration = status->generation; input->endpointGeneration = status->generation;
input->statusOwnerClientID = status->ownerClientID; input->statusOwnerClientID = status->ownerClientID;
input->statusOwnerGeneration = status->ownerGeneration; input->statusOwnerGeneration = status->ownerGeneration;
input->keyboardLEDsValid = if (!keyboardLEDsValid)
(status->flags & KVMFR_INPUT_STATUS_KEYBOARD_LEDS_VALID) != 0; retainQueuedKeyboardLEDProjection(input);
input->keyboardLEDs = status->keyboardLEDs; else if (!oldKeyboardLEDsValid)
{
retainQueuedKeyboardLEDProjection(input);
input->staleKeyboardLEDs = 0;
input->keyboardLEDsStaleUntil = 0;
}
else if (endpointChanged)
{
input->pendingKeyboardLEDs = 0;
input->keyboardLEDTransitions = 0;
input->staleKeyboardLEDs = 0;
input->keyboardLEDsPendingUntil = 0;
input->keyboardLEDsStaleUntil = 0;
}
else
{
const uint8_t acknowledged =
(oldKeyboardLEDs ^ keyboardLEDs) &
publishedKeyboardLEDToggles(input);
if (acknowledged)
{
input->pendingKeyboardLEDs ^= acknowledged;
const unsigned count = keyboardLEDToggleCount(acknowledged);
input->keyboardLEDTransitions =
input->keyboardLEDTransitions > count ?
input->keyboardLEDTransitions - count : 0;
updateKeyboardLEDPendingDeadline(input, now);
}
}
input->keyboardLEDsValid = keyboardLEDsValid;
input->keyboardLEDs = keyboardLEDs;
if (keyboardLEDsValid && input->desiredKeyboardLEDsValid &&
((projectedKeyboardLEDs(input) ^ input->desiredKeyboardLEDs) &
LOCK_LED_MASK & ~input->staleKeyboardLEDs))
input->keyboardLEDsSyncAfter =
now + INPUT_KEYBOARD_LED_SYNC_DELAY_US;
const bool streamChanged = reconcileInputStream(input, status); const bool streamChanged = reconcileInputStream(input, status);
const bool available = targetAvailable && const bool available = targetAvailable &&
input->streamEndpointBound; input->streamEndpointBound;
@@ -878,6 +1225,9 @@ static void processInputStatus(LGMPInput * input, bool * wake)
static bool release(LGMPInput * input, bool * wake) static bool release(LGMPInput * input, bool * wake)
{ {
if (input->pendingCount)
invalidateKeyboardLEDProjection(input, microtime(),
INPUT_KEYBOARD_LED_ACK_TIMEOUT_US);
input->pendingHead = 0; input->pendingHead = 0;
input->pendingCount = 0; input->pendingCount = 0;
input->claimed = false; input->claimed = false;
@@ -910,9 +1260,12 @@ static bool flushPending(LGMPInput * input)
progress = true; progress = true;
published(input, &item->message); published(input, &item->message);
const uint8_t keyboardLEDToggle = item->keyboardLEDToggle;
input->pendingHead = input->pendingHead =
(input->pendingHead + 1) % INPUT_PENDING_LENGTH; (input->pendingHead + 1) % INPUT_PENDING_LENGTH;
--input->pendingCount; --input->pendingCount;
if (keyboardLEDToggle)
updateKeyboardLEDPendingDeadline(input, microtime());
} }
return progress; return progress;
} }
@@ -1037,7 +1390,9 @@ static int inputThread(void * opaque)
processInputStatus(input, &wake); processInputStatus(input, &wake);
const bool streamProgress = flushPending(input); const bool streamProgress = flushPending(input);
const uint64_t now = microtime(); uint64_t now = microtime();
syncKeyboardLEDs(input, now, &wake);
now = microtime();
if (input->connected && input->claimed && !inputStateHeld(input) && if (input->connected && input->claimed && !inputStateHeld(input) &&
!input->pendingCount && !input->pendingCount &&
input->publishedGeneration == input->generation && input->publishedGeneration == input->generation &&
@@ -1166,6 +1521,12 @@ bool lgmpInput_connect(LGMPInput * input, uint32_t clientID)
input->ownerBlocked = false; input->ownerBlocked = false;
input->keyboardLEDsValid = false; input->keyboardLEDsValid = false;
input->keyboardLEDs = 0; input->keyboardLEDs = 0;
input->pendingKeyboardLEDs = 0;
input->keyboardLEDTransitions = 0;
input->staleKeyboardLEDs = 0;
input->keyboardLEDsSyncAfter = 0;
input->keyboardLEDsPendingUntil = 0;
input->keyboardLEDsStaleUntil = 0;
input->pendingHead = 0; input->pendingHead = 0;
input->pendingCount = 0; input->pendingCount = 0;
input->clientID = clientID; input->clientID = clientID;
@@ -1231,6 +1592,12 @@ void lgmpInput_disconnect(LGMPInput * input)
input->ownerBlocked = false; input->ownerBlocked = false;
input->keyboardLEDsValid = false; input->keyboardLEDsValid = false;
input->keyboardLEDs = 0; input->keyboardLEDs = 0;
input->pendingKeyboardLEDs = 0;
input->keyboardLEDTransitions = 0;
input->staleKeyboardLEDs = 0;
input->keyboardLEDsSyncAfter = 0;
input->keyboardLEDsPendingUntil = 0;
input->keyboardLEDsStaleUntil = 0;
input->capabilities = 0; input->capabilities = 0;
input->streamEndpointBound = false; input->streamEndpointBound = false;
memset(&input->streamEndpoint, 0, memset(&input->streamEndpoint, 0,
@@ -1255,6 +1622,12 @@ void lgmpInput_disconnect(LGMPInput * input)
input->ownerBlocked = false; input->ownerBlocked = false;
input->keyboardLEDsValid = false; input->keyboardLEDsValid = false;
input->keyboardLEDs = 0; input->keyboardLEDs = 0;
input->pendingKeyboardLEDs = 0;
input->keyboardLEDTransitions = 0;
input->staleKeyboardLEDs = 0;
input->keyboardLEDsSyncAfter = 0;
input->keyboardLEDsPendingUntil = 0;
input->keyboardLEDsStaleUntil = 0;
input->statusValid = false; input->statusValid = false;
input->ownerConfirmed = false; input->ownerConfirmed = false;
input->notifyStatus = false; input->notifyStatus = false;
@@ -1405,6 +1778,8 @@ static bool updateKey(void * opaque, int key, bool pressed)
} }
else if (!result && input->connected) else if (!result && input->connected)
*state = previous; *state = previous;
if (result && pressed && !previous)
recordKeyboardToggle(input, key);
LG_UNLOCK(input->lock); LG_UNLOCK(input->lock);
if (wake) if (wake)
@@ -1422,6 +1797,48 @@ static bool inputKeyUp(void * opaque, int key)
return updateKey(opaque, key, false); return updateKey(opaque, key, false);
} }
static bool inputKeyboardLEDs(void * opaque, bool numLock, bool capsLock,
bool scrollLock)
{
LGMPInput * input = opaque;
const uint8_t desired =
(numLock ? LG_KEYBOARD_LED_NUM_LOCK : 0) |
(capsLock ? LG_KEYBOARD_LED_CAPS_LOCK : 0) |
(scrollLock ? LG_KEYBOARD_LED_SCROLL_LOCK : 0);
const uint64_t syncAfter =
microtime() + INPUT_KEYBOARD_LED_SYNC_DELAY_US;
LG_LOCK(input->lock);
input->desiredKeyboardLEDs = desired;
input->desiredKeyboardLEDsValid = true;
input->staleKeyboardLEDs &= ~LOCK_LED_MASK;
input->keyboardLEDsStaleUntil = 0;
if (input->keyboardLEDsSyncAfter < syncAfter)
input->keyboardLEDsSyncAfter = syncAfter;
const bool result = input->connected && input->available;
if (input->event)
lgSignalEvent(input->event);
LG_UNLOCK(input->lock);
return result;
}
static void inputKeyboardLEDsReset(void * opaque)
{
LGMPInput * input = opaque;
bool wake = false;
LG_LOCK(input->lock);
input->desiredKeyboardLEDsValid = false;
input->staleKeyboardLEDs = 0;
input->keyboardLEDsStaleUntil = 0;
if (input->connected && pendingKeyboardLEDSync(input))
release(input, &wake);
if (input->event)
lgSignalEvent(input->event);
LG_UNLOCK(input->lock);
}
static bool queueMouse(LGMPInput * input, enum LGMPInputMouseMode mode, static bool queueMouse(LGMPInput * input, enum LGMPInputMouseMode mode,
int32_t x, int32_t y, int32_t wheel, uint32_t buttons, int32_t x, int32_t y, int32_t wheel, uint32_t buttons,
bool pureMotion, bool * wake) bool pureMotion, bool * wake)
@@ -1656,7 +2073,8 @@ static const LG_InputOps INPUT_OPS =
.setStatusListener = inputSetStatusListener, .setStatusListener = inputSetStatusListener,
.keyDown = inputKeyDown, .keyDown = inputKeyDown,
.keyUp = inputKeyUp, .keyUp = inputKeyUp,
.keyboardLEDs = NULL, .keyboardLEDs = inputKeyboardLEDs,
.keyboardLEDsReset = inputKeyboardLEDsReset,
.mouseMotion = inputMouseMotion, .mouseMotion = inputMouseMotion,
.mousePosition = inputMousePosition, .mousePosition = inputMousePosition,
.mousePress = inputMousePress, .mousePress = inputMousePress,