[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

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

View File

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

View File

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

View File

@@ -2096,6 +2096,8 @@ void evdev_dispatch(void * opaque)
evdev_barrierMatches(&output.barrier, desiredState))
{
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.activate, true);
const uint64_t currentState = atomic_load_explicit(
@@ -2103,6 +2105,7 @@ void evdev_dispatch(void * opaque)
if (output.barrier.activate &&
!evdev_barrierMatches(&output.barrier, currentState))
evdev_publishActive(output.barrier.activate, false);
core_updateKeyboardLEDsSync();
}
atomic_store_explicit(&state.barrierAck,
output.barrier.serial, memory_order_release);

View File

@@ -35,6 +35,7 @@ struct InputBinding
bool keyboardLEDsValid;
uint8_t keyboardLEDs;
uint32_t epoch;
uint32_t generation;
};
static struct
@@ -55,6 +56,9 @@ static struct
struct InputBinding active;
bool useTransport;
bool notifiedAvailable;
bool keyboardLEDsSyncEnabled;
bool desiredKeyboardLEDsValid;
uint8_t desiredKeyboardLEDs;
_Atomic(bool) keys[KEY_MAX];
_Atomic(uint32_t) buttons;
@@ -191,6 +195,29 @@ static void clearStateNL(void)
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)
{
const struct InputBinding next =
@@ -210,6 +237,7 @@ static bool updateActiveNL(bool dropActive)
const bool keyboardLEDsChanged =
!keyboardLEDsEqual(&next, &l_input.active);
resetKeyboardLEDsNL(&l_input.active);
if (dropActive)
clearStateNL();
else
@@ -220,6 +248,7 @@ static bool updateActiveNL(bool dropActive)
{
if (l_input.active.ops->reset)
l_input.active.ops->reset(l_input.active.opaque);
syncKeyboardLEDsNL();
DEBUG_INFO("Using Input: %s", l_input.active.ops->name);
}
else
@@ -230,24 +259,32 @@ static bool updateActiveNL(bool dropActive)
static bool updateStatusNL(struct InputBinding * binding,
const LG_InputStatus * status)
{
const struct InputBinding oldActive = l_input.active;
const bool wasActive = bindingEqual(&l_input.active, binding);
const struct InputBinding oldActive = l_input.active;
const bool wasActive =
bindingEqual(&l_input.active, binding);
const bool generationChanged = wasActive &&
binding->generation && status->available &&
binding->generation != status->generation;
if (wasActive && !status->available)
{
resetKeyboardLEDsNL(&l_input.active);
resetActiveNL();
l_input.active = (struct InputBinding) { 0 };
}
binding->available = status->available;
binding->mouseAbsolute = status->available &&
binding->available = status->available;
binding->mouseAbsolute = status->available &&
binding->ops->mousePosition &&
binding->ops->supports(binding->opaque,
LG_INPUT_SUPPORT_MOUSE_ABSOLUTE);
binding->keyboardLEDsValid = status->available &&
status->keyboardLEDsValid;
binding->keyboardLEDs = status->keyboardLEDs;
binding->generation = status->generation;
updateActiveNL(false);
if (generationChanged)
syncKeyboardLEDsNL();
return !keyboardLEDsEqual(&oldActive, &l_input.active);
}
@@ -291,11 +328,14 @@ static void transportStatusChanged(void * opaque,
void lgInput_init(void)
{
l_input.fallback = (struct InputBinding) { 0 };
l_input.transport = (struct InputBinding) { 0 };
l_input.active = (struct InputBinding) { 0 };
l_input.useTransport = true;
l_input.notifiedAvailable = false;
l_input.fallback = (struct InputBinding) { 0 };
l_input.transport = (struct InputBinding) { 0 };
l_input.active = (struct InputBinding) { 0 };
l_input.useTransport = true;
l_input.notifiedAvailable = false;
l_input.keyboardLEDsSyncEnabled = false;
l_input.desiredKeyboardLEDsValid = false;
l_input.desiredKeyboardLEDs = 0;
for (int key = 0; key < KEY_MAX; ++key)
atomic_init(&l_input.keys[key], false);
@@ -316,6 +356,7 @@ void lgInput_free(void)
LG_LOCK(l_input.keyboardLEDsDispatch);
LG_LOCK(l_input.availabilityDispatch);
LG_LOCK_EXCLUSIVE(l_input.activeLock);
resetKeyboardLEDsNL(&l_input.active);
resetActiveNL();
fallback = l_input.fallback;
transport = l_input.transport;
@@ -532,15 +573,41 @@ bool lgInput_keyUp(int key)
bool lgInput_keyboardLEDs(bool numLock, bool capsLock, bool scrollLock)
{
LG_LOCK_SHARED(l_input.activeLock);
const bool result = l_input.active.ops &&
(!l_input.active.ops->keyboardLEDs ||
l_input.active.ops->keyboardLEDs(l_input.active.opaque,
numLock, capsLock, scrollLock));
LG_UNLOCK_SHARED(l_input.activeLock);
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);
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;
}
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)
{
LG_LOCK_EXCLUSIVE(l_input.activeLock);