[client/idd] input: report endpoint status

This commit is contained in:
Geoffrey McRae
2026-08-09 01:36:54 +10:00
parent 3e38c62050
commit 5b90384123
8 changed files with 607 additions and 55 deletions

View File

@@ -30,6 +30,16 @@ typedef enum LG_InputSupport
} }
LG_InputSupport; LG_InputSupport;
typedef struct LG_InputStatus
{
bool available;
uint32_t generation;
}
LG_InputStatus;
typedef void (*LG_InputStatusFn)(void * opaque,
const LG_InputStatus * status);
typedef struct LG_InputOps typedef struct LG_InputOps
{ {
const char * name; const char * name;
@@ -37,6 +47,11 @@ typedef struct LG_InputOps
/* Operations must fail safely if a remote endpoint disappears. */ /* Operations must fail safely if a remote endpoint disappears. */
bool (*supports)(void * opaque, LG_InputSupport support); bool (*supports)(void * opaque, LG_InputSupport support);
/* Registration must synchronously report the current status after releasing
* any backend locks. Passing NULL unregisters the listener. */
void (*setStatusListener)(void * opaque, LG_InputStatusFn callback,
void * callbackOpaque);
/* Keyboard codes use the Linux input-event KEY_* values. */ /* Keyboard codes use the Linux input-event KEY_* values. */
bool (*keyDown)(void * opaque, int key); bool (*keyDown)(void * opaque, int key);
bool (*keyUp)(void * opaque, int key); bool (*keyUp)(void * opaque, int key);

View File

@@ -30,7 +30,9 @@ struct InputBinding
{ {
const LG_InputOps * ops; const LG_InputOps * ops;
void * opaque; void * opaque;
bool available;
bool mouseAbsolute; bool mouseAbsolute;
uint32_t generation;
}; };
static struct static struct
@@ -63,12 +65,15 @@ static bool validOps(const LG_InputOps * ops)
static struct InputBinding makeBinding(const LG_InputOps * ops, static struct InputBinding makeBinding(const LG_InputOps * ops,
void * opaque) void * opaque)
{ {
const bool available = ops && !ops->setStatusListener;
return (struct InputBinding) return (struct InputBinding)
{ {
.ops = ops, .ops = ops,
.opaque = opaque, .opaque = opaque,
.mouseAbsolute = ops && ops->mousePosition && .available = available,
.mouseAbsolute = available && ops->mousePosition &&
ops->supports(opaque, LG_INPUT_SUPPORT_MOUSE_ABSOLUTE), ops->supports(opaque, LG_INPUT_SUPPORT_MOUSE_ABSOLUTE),
.generation = 0,
}; };
} }
@@ -112,12 +117,17 @@ static void clearStateNL(void)
static void updateActiveNL(void) static void updateActiveNL(void)
{ {
const struct InputBinding next = const struct InputBinding next =
l_input.useTransport && l_input.transport.ops ? l_input.useTransport && l_input.transport.available ?
l_input.transport : l_input.fallback; l_input.transport :
l_input.fallback.available ? l_input.fallback :
(struct InputBinding) { 0 };
if (next.ops == l_input.active.ops && if (next.ops == l_input.active.ops &&
next.opaque == l_input.active.opaque) next.opaque == l_input.active.opaque)
{
l_input.active = next;
return; return;
}
resetActiveNL(); resetActiveNL();
l_input.active = next; l_input.active = next;
@@ -132,6 +142,52 @@ static void updateActiveNL(void)
DEBUG_INFO("Input is unavailable"); DEBUG_INFO("Input is unavailable");
} }
static void updateStatusNL(struct InputBinding * binding,
const LG_InputStatus * status)
{
const bool wasActive =
l_input.active.ops == binding->ops &&
l_input.active.opaque == binding->opaque;
if (wasActive && !status->available)
{
resetActiveNL();
l_input.active = (struct InputBinding) { 0 };
}
binding->available = status->available;
binding->mouseAbsolute = status->available &&
binding->ops->mousePosition &&
binding->ops->supports(binding->opaque,
LG_INPUT_SUPPORT_MOUSE_ABSOLUTE);
binding->generation = status->generation;
updateActiveNL();
}
static void fallbackStatusChanged(void * opaque,
const LG_InputStatus * status)
{
if (!status)
return;
LG_LOCK_EXCLUSIVE(l_input.activeLock);
if (l_input.fallback.ops && l_input.fallback.opaque == opaque)
updateStatusNL(&l_input.fallback, status);
LG_UNLOCK_EXCLUSIVE(l_input.activeLock);
}
static void transportStatusChanged(void * opaque,
const LG_InputStatus * status)
{
if (!status)
return;
LG_LOCK_EXCLUSIVE(l_input.activeLock);
if (l_input.transport.ops && l_input.transport.opaque == opaque)
updateStatusNL(&l_input.transport, status);
LG_UNLOCK_EXCLUSIVE(l_input.activeLock);
}
void lgInput_init(void) void lgInput_init(void)
{ {
l_input.fallback = (struct InputBinding) { 0 }; l_input.fallback = (struct InputBinding) { 0 };
@@ -148,12 +204,23 @@ void lgInput_init(void)
void lgInput_free(void) void lgInput_free(void)
{ {
struct InputBinding fallback;
struct InputBinding transport;
LG_LOCK_EXCLUSIVE(l_input.activeLock); LG_LOCK_EXCLUSIVE(l_input.activeLock);
resetActiveNL(); resetActiveNL();
fallback = l_input.fallback;
transport = l_input.transport;
l_input.active = (struct InputBinding) { 0 }; l_input.active = (struct InputBinding) { 0 };
l_input.fallback = (struct InputBinding) { 0 }; l_input.fallback = (struct InputBinding) { 0 };
l_input.transport = (struct InputBinding) { 0 }; l_input.transport = (struct InputBinding) { 0 };
LG_UNLOCK_EXCLUSIVE(l_input.activeLock); LG_UNLOCK_EXCLUSIVE(l_input.activeLock);
if (fallback.ops && fallback.ops->setStatusListener)
fallback.ops->setStatusListener(fallback.opaque, NULL, NULL);
if (transport.ops && transport.ops->setStatusListener)
transport.ops->setStatusListener(transport.opaque, NULL, NULL);
LG_RWLOCK_FREE(l_input.activeLock); LG_RWLOCK_FREE(l_input.activeLock);
} }
@@ -166,10 +233,20 @@ void lgInput_setFallback(const LG_InputOps * ops, void * opaque)
opaque = NULL; opaque = NULL;
} }
const struct InputBinding next = makeBinding(ops, opaque);
struct InputBinding old;
LG_LOCK_EXCLUSIVE(l_input.activeLock); LG_LOCK_EXCLUSIVE(l_input.activeLock);
l_input.fallback = makeBinding(ops, opaque); old = l_input.fallback;
l_input.fallback = next;
updateActiveNL(); updateActiveNL();
LG_UNLOCK_EXCLUSIVE(l_input.activeLock); LG_UNLOCK_EXCLUSIVE(l_input.activeLock);
if (old.ops && old.ops->setStatusListener)
old.ops->setStatusListener(old.opaque, NULL, NULL);
if (next.ops && next.ops->setStatusListener)
next.ops->setStatusListener(next.opaque,
fallbackStatusChanged, next.opaque);
} }
void lgInput_setTransport(const LG_InputOps * ops, void * opaque) void lgInput_setTransport(const LG_InputOps * ops, void * opaque)
@@ -181,15 +258,28 @@ void lgInput_setTransport(const LG_InputOps * ops, void * opaque)
opaque = NULL; opaque = NULL;
} }
const struct InputBinding next = makeBinding(ops, opaque);
struct InputBinding old;
LG_LOCK_EXCLUSIVE(l_input.activeLock); LG_LOCK_EXCLUSIVE(l_input.activeLock);
l_input.transport = makeBinding(ops, opaque); old = l_input.transport;
l_input.transport = next;
updateActiveNL(); updateActiveNL();
LG_UNLOCK_EXCLUSIVE(l_input.activeLock); LG_UNLOCK_EXCLUSIVE(l_input.activeLock);
if (old.ops && old.ops->setStatusListener)
old.ops->setStatusListener(old.opaque, NULL, NULL);
if (next.ops && next.ops->setStatusListener)
next.ops->setStatusListener(next.opaque,
transportStatusChanged, next.opaque);
} }
void lgInput_dropTransport(void) void lgInput_dropTransport(void)
{ {
struct InputBinding old;
LG_LOCK_EXCLUSIVE(l_input.activeLock); LG_LOCK_EXCLUSIVE(l_input.activeLock);
old = l_input.transport;
if (l_input.useTransport && l_input.active.ops == l_input.transport.ops && if (l_input.useTransport && l_input.active.ops == l_input.transport.ops &&
l_input.active.opaque == l_input.transport.opaque) l_input.active.opaque == l_input.transport.opaque)
{ {
@@ -200,6 +290,9 @@ void lgInput_dropTransport(void)
l_input.transport = (struct InputBinding) { 0 }; l_input.transport = (struct InputBinding) { 0 };
updateActiveNL(); updateActiveNL();
LG_UNLOCK_EXCLUSIVE(l_input.activeLock); LG_UNLOCK_EXCLUSIVE(l_input.activeLock);
if (old.ops && old.ops->setStatusListener)
old.ops->setStatusListener(old.opaque, NULL, NULL);
} }
void lgInput_useTransport(bool enable) void lgInput_useTransport(bool enable)

View File

@@ -2362,7 +2362,7 @@ restart:
g_state.transportOps->getInputOps(g_state.transport, &inputOpaque) : NULL; g_state.transportOps->getInputOps(g_state.transport, &inputOpaque) : NULL;
lgInput_setTransport(inputOps, inputOpaque); lgInput_setTransport(inputOps, inputOpaque);
if (lgInput_available()) if (inputOps || lgInput_available())
keybind_inputRegister(); keybind_inputRegister();
checkUUID(); checkUUID();
DEBUG_INFO("Starting session"); DEBUG_INFO("Starting session");

View File

@@ -42,7 +42,7 @@
#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_WORKER_IDLE_MS 50 #define INPUT_WORKER_IDLE_MS 10
#define INPUT_WORKER_RETRY_MS 1 #define INPUT_WORKER_RETRY_MS 1
#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 (INT16_MIN * INPUT_MAX_SPLIT_REPORTS)
@@ -71,6 +71,8 @@ struct LGMPInput
atomic_bool stop; atomic_bool stop;
bool connected; bool connected;
bool claimed; bool claimed;
bool available;
bool ownerBlocked;
uint32_t generation; uint32_t generation;
uint32_t sequence; uint32_t sequence;
@@ -89,8 +91,27 @@ struct LGMPInput
uint16_t absoluteY; uint16_t absoluteY;
uint8_t keyState[KVMFR_INPUT_KEYBOARD_USAGE_MAX + 1]; uint8_t keyState[KVMFR_INPUT_KEYBOARD_USAGE_MAX + 1];
uint64_t lastInput; uint64_t lastInput;
uint32_t clientID;
uint32_t capabilities;
uint32_t endpointGeneration;
uint32_t statusSerial;
uint32_t statusOwnerClientID;
uint32_t statusOwnerGeneration;
bool ownerConfirmed;
bool statusValid;
bool notifyStatus;
LG_InputStatusFn statusCallback;
void * statusOpaque;
}; };
static void buildKeyboardPayload(const LGMPInput * input,
KVMFRInputPayload * payload);
static bool queueMouse(LGMPInput * input, enum LGMPInputMouseMode mode,
int32_t x, int32_t y, int32_t wheel, uint32_t buttons,
bool pureMotion, bool * wake);
static struct LGMPInputPending * pendingAt( static struct LGMPInputPending * pendingAt(
LGMPInput * input, unsigned position) LGMPInput * input, unsigned position)
{ {
@@ -98,6 +119,38 @@ static struct LGMPInputPending * pendingAt(
(input->pendingHead + position) % INPUT_PENDING_LENGTH]; (input->pendingHead + position) % INPUT_PENDING_LENGTH];
} }
static LG_InputStatus inputStatus(const LGMPInput * input)
{
return (LG_InputStatus)
{
.available = input->available,
.generation = input->endpointGeneration,
};
}
static void notifyInputStatus(LGMPInput * input)
{
LG_InputStatusFn callback;
void * opaque;
LG_InputStatus status;
LG_LOCK(input->lock);
if (!input->notifyStatus)
{
LG_UNLOCK(input->lock);
return;
}
input->notifyStatus = false;
callback = input->statusCallback;
opaque = input->statusOpaque;
status = inputStatus(input);
LG_UNLOCK(input->lock);
if (callback)
callback(opaque, &status);
}
static void published(LGMPInput * input, static void published(LGMPInput * input,
const KVMFRInputMessage * message) const KVMFRInputMessage * message)
{ {
@@ -122,14 +175,21 @@ static void connectionFailed(LGMPInput * input, LGMP_STATUS status)
if (input->connected) if (input->connected)
DEBUG_WARN("LGMP input transport failed: %s", lgmpStatusString(status)); DEBUG_WARN("LGMP input transport failed: %s", lgmpStatusString(status));
if (input->available || input->endpointGeneration)
input->notifyStatus = true;
input->connected = false; input->connected = false;
input->claimed = false; input->claimed = false;
input->available = false;
input->ownerBlocked = false;
input->pendingHead = 0; input->pendingHead = 0;
input->pendingCount = 0; input->pendingCount = 0;
input->mouseMode = LGMP_INPUT_MOUSE_NONE; input->mouseMode = LGMP_INPUT_MOUSE_NONE;
input->mouseButtons = 0; input->mouseButtons = 0;
input->publishedClaimed = false; input->publishedClaimed = false;
input->lastInput = 0; input->lastInput = 0;
input->capabilities = 0;
input->statusValid = false;
input->ownerConfirmed = false;
memset(input->keyState, 0, sizeof(input->keyState)); memset(input->keyState, 0, sizeof(input->keyState));
atomic_store_explicit(&input->stop, true, memory_order_release); atomic_store_explicit(&input->stop, true, memory_order_release);
} }
@@ -276,11 +336,14 @@ static bool claim(LGMPInput * input, bool * wake)
{ {
if (input->claimed) if (input->claimed)
return true; return true;
if (!input->available || input->ownerBlocked)
return false;
if (++input->generation == 0) if (++input->generation == 0)
++input->generation; ++input->generation;
input->sequence = 0; input->sequence = 0;
input->claimed = true; input->claimed = true;
input->ownerConfirmed = false;
const KVMFRInputPayload payload = { 0 }; const KVMFRInputPayload payload = { 0 };
if (queuePayload(input, KVMFR_INPUT_MESSAGE_CLAIM, if (queuePayload(input, KVMFR_INPUT_MESSAGE_CLAIM,
@@ -312,6 +375,187 @@ static bool inputStateHeld(const LGMPInput * input)
return false; return false;
} }
static bool inputStateActive(const LGMPInput * input)
{
return input->mouseMode == LGMP_INPUT_MOUSE_ABSOLUTE ||
inputStateHeld(input);
}
static void discardProtocolState(LGMPInput * input)
{
input->pendingHead = 0;
input->pendingCount = 0;
input->claimed = false;
input->sequence = 0;
input->publishedGeneration = 0;
input->publishedSequence = 0;
input->publishedClaimed = false;
input->ownerConfirmed = false;
}
static bool restoreInputState(LGMPInput * input, bool * wake)
{
if (!inputStateActive(input))
return true;
if (!claim(input, wake))
return false;
KVMFRInputPayload keyboard = { 0 };
buildKeyboardPayload(input, &keyboard);
if (!queuePayload(input, KVMFR_INPUT_MESSAGE_KEYBOARD,
&keyboard, false, wake))
return false;
if (input->mouseMode != LGMP_INPUT_MOUSE_NONE &&
!queueMouse(input, input->mouseMode, 0, 0, 0,
input->mouseButtons, false, wake))
return false;
return true;
}
static bool validInputStatus(const KVMFRInputStatus * status)
{
static const uint32_t capabilities =
KVMFR_INPUT_CAP_MOUSE_RELATIVE |
KVMFR_INPUT_CAP_MOUSE_ABSOLUTE |
KVMFR_INPUT_CAP_KEYBOARD;
static const uint32_t flags =
KVMFR_INPUT_STATUS_AVAILABLE |
KVMFR_INPUT_STATUS_HAS_OWNER;
if (status->version != KVMFR_INPUT_VERSION ||
status->capabilities & ~capabilities ||
status->flags & ~flags || !status->generation ||
!status->lease || !status->maxButtons ||
status->maxButtons > KVMFR_INPUT_MOUSE_BUTTON_COUNT)
return false;
const bool available =
(status->flags & KVMFR_INPUT_STATUS_AVAILABLE) != 0;
const bool hasOwner =
(status->flags & KVMFR_INPUT_STATUS_HAS_OWNER) != 0;
if (!available && (status->capabilities || hasOwner))
return false;
if (available &&
(status->capabilities &
(KVMFR_INPUT_CAP_MOUSE_RELATIVE | KVMFR_INPUT_CAP_KEYBOARD)) !=
(KVMFR_INPUT_CAP_MOUSE_RELATIVE | KVMFR_INPUT_CAP_KEYBOARD))
return false;
return hasOwner ?
status->ownerClientID && status->ownerGeneration :
!status->ownerClientID && !status->ownerGeneration;
}
static void applyInputStatus(LGMPInput * input,
const KVMFRInputStatus * status, uint32_t serial, bool * wake)
{
const bool wasValid = input->statusValid;
const bool wasAvailable = input->available;
const uint32_t oldCapabilities = input->capabilities;
const uint32_t oldGeneration = input->endpointGeneration;
const bool available =
(status->flags & KVMFR_INPUT_STATUS_AVAILABLE) != 0;
const bool endpointChanged = wasValid &&
oldGeneration != status->generation;
bool restore = false;
input->statusValid = true;
input->statusSerial = serial;
input->available = available;
input->capabilities = status->capabilities;
input->endpointGeneration = status->generation;
input->statusOwnerClientID = status->ownerClientID;
input->statusOwnerGeneration = status->ownerGeneration;
if (endpointChanged || !available)
{
discardProtocolState(input);
restore = endpointChanged && available;
}
if (status->flags & KVMFR_INPUT_STATUS_HAS_OWNER)
{
if (input->claimed &&
status->ownerClientID == input->clientID &&
status->ownerGeneration == input->generation)
{
input->ownerBlocked = false;
input->ownerConfirmed = true;
}
else
{
discardProtocolState(input);
input->ownerBlocked = true;
restore = false;
}
}
else
{
if (input->ownerBlocked ||
(input->claimed && input->ownerConfirmed))
{
discardProtocolState(input);
restore = available;
}
input->ownerBlocked = false;
}
if (restore && !restoreInputState(input, wake))
discardProtocolState(input);
if (!wasValid || wasAvailable != available ||
oldCapabilities != status->capabilities ||
oldGeneration != status->generation)
input->notifyStatus = true;
}
static void processInputStatus(LGMPInput * input, bool * wake)
{
LGMP_STATUS result = lgmpClientAdvanceToLast(input->queue);
if (result == LGMP_ERR_QUEUE_EMPTY)
return;
if (result != LGMP_OK)
{
connectionFailed(input, result);
return;
}
LGMPMessage message;
result = lgmpClientProcess(input->queue, &message);
if (result == LGMP_ERR_QUEUE_EMPTY)
return;
if (result != LGMP_OK)
{
connectionFailed(input, result);
return;
}
KVMFRInputStatus status = { 0 };
const bool valid = message.udata && message.udata <= UINT32_MAX &&
message.size == sizeof(status);
if (valid)
memcpy(&status, message.mem, sizeof(status));
result = lgmpClientMessageDone(input->queue);
if (result != LGMP_OK)
{
connectionFailed(input, result);
return;
}
const uint32_t serial = (uint32_t)message.udata;
if (!valid || !validInputStatus(&status))
{
DEBUG_WARN("Ignoring invalid LGMP input status");
return;
}
if (input->statusValid &&
(int32_t)(serial - input->statusSerial) <= 0)
return;
applyInputStatus(input, &status, serial, wake);
}
static bool release(LGMPInput * input, bool * wake) static bool release(LGMPInput * input, bool * wake)
{ {
input->pendingHead = 0; input->pendingHead = 0;
@@ -409,7 +653,9 @@ static int inputThread(void * opaque)
while (!atomic_load_explicit(&input->stop, memory_order_acquire)) while (!atomic_load_explicit(&input->stop, memory_order_acquire))
{ {
unsigned timeout = INPUT_WORKER_IDLE_MS; unsigned timeout = INPUT_WORKER_IDLE_MS;
bool wake = false;
LG_LOCK(input->lock); LG_LOCK(input->lock);
processInputStatus(input, &wake);
flushPending(input); flushPending(input);
const uint64_t now = microtime(); const uint64_t now = microtime();
@@ -418,7 +664,6 @@ static int inputThread(void * opaque)
input->publishedGeneration == input->generation && input->publishedGeneration == input->generation &&
now - input->lastInput >= INPUT_IDLE_RELEASE_US) now - input->lastInput >= INPUT_IDLE_RELEASE_US)
{ {
bool wake = false;
release(input, &wake); release(input, &wake);
} }
else if (input->connected && input->claimed && else if (input->connected && input->claimed &&
@@ -426,7 +671,6 @@ static int inputThread(void * opaque)
input->publishedGeneration == input->generation && input->publishedGeneration == input->generation &&
now - input->lastSend >= INPUT_KEEPALIVE_US) now - input->lastSend >= INPUT_KEEPALIVE_US)
{ {
bool wake = false;
const KVMFRInputPayload payload = { 0 }; const KVMFRInputPayload payload = { 0 };
queuePayload(input, KVMFR_INPUT_MESSAGE_KEEPALIVE, queuePayload(input, KVMFR_INPUT_MESSAGE_KEEPALIVE,
&payload, false, &wake); &payload, false, &wake);
@@ -436,10 +680,15 @@ static int inputThread(void * opaque)
timeout = INPUT_WORKER_RETRY_MS; timeout = INPUT_WORKER_RETRY_MS;
LG_UNLOCK(input->lock); LG_UNLOCK(input->lock);
notifyInputStatus(input);
if (wake)
lgSignalEvent(input->event);
if (atomic_load_explicit(&input->stop, memory_order_acquire)) if (atomic_load_explicit(&input->stop, memory_order_acquire))
break; break;
lgWaitEvent(input->event, timeout); lgWaitEvent(input->event, timeout);
} }
notifyInputStatus(input);
return 0; return 0;
} }
@@ -467,13 +716,17 @@ void lgmpInput_destroy(LGMPInput ** input)
*input = NULL; *input = NULL;
} }
bool lgmpInput_connect(LGMPInput * input) bool lgmpInput_connect(LGMPInput * input, uint32_t clientID)
{ {
if (!clientID)
return false;
LG_LOCK(input->lock); LG_LOCK(input->lock);
if (input->connected) if (input->connected)
{ {
const bool sameClient = input->clientID == clientID;
LG_UNLOCK(input->lock); LG_UNLOCK(input->lock);
return true; return sameClient;
} }
if (input->thread || input->queue) if (input->thread || input->queue)
{ {
@@ -501,13 +754,24 @@ bool lgmpInput_connect(LGMPInput * input)
input->connected = true; input->connected = true;
input->claimed = false; input->claimed = false;
input->available = false;
input->ownerBlocked = false;
input->pendingHead = 0; input->pendingHead = 0;
input->pendingCount = 0; input->pendingCount = 0;
input->clientID = clientID;
input->capabilities = 0;
input->endpointGeneration = 0;
input->statusSerial = 0;
input->statusOwnerClientID = 0;
input->statusOwnerGeneration = 0;
input->ownerConfirmed = false;
input->statusValid = false;
input->publishedGeneration = 0; input->publishedGeneration = 0;
input->publishedSequence = 0; input->publishedSequence = 0;
input->publishedClaimed = false; input->publishedClaimed = false;
input->lastSend = 0; input->lastSend = 0;
input->lastInput = 0; input->lastInput = 0;
input->generation = 0;
clearInputState(input); clearInputState(input);
atomic_store_explicit(&input->stop, false, memory_order_release); atomic_store_explicit(&input->stop, false, memory_order_release);
LGThread * thread; LGThread * thread;
@@ -545,6 +809,13 @@ void lgmpInput_disconnect(LGMPInput * input)
input->pendingCount = 0; input->pendingCount = 0;
input->connected = false; input->connected = false;
input->claimed = false; input->claimed = false;
if (input->available || input->endpointGeneration)
input->notifyStatus = true;
input->available = false;
input->ownerBlocked = false;
input->capabilities = 0;
input->statusValid = false;
input->ownerConfirmed = false;
clearInputState(input); clearInputState(input);
atomic_store_explicit(&input->stop, true, memory_order_release); atomic_store_explicit(&input->stop, true, memory_order_release);
LGThread * thread = input->thread; LGThread * thread = input->thread;
@@ -580,16 +851,40 @@ void lgmpInput_disconnect(LGMPInput * input)
static bool inputSupports(void * opaque, LG_InputSupport support) static bool inputSupports(void * opaque, LG_InputSupport support)
{ {
(void)opaque; LGMPInput * input = opaque;
LG_LOCK(input->lock);
bool result;
switch (support) switch (support)
{ {
case LG_INPUT_SUPPORT_MOUSE_ABSOLUTE: case LG_INPUT_SUPPORT_MOUSE_ABSOLUTE:
return true; result = input->available &&
(input->capabilities & KVMFR_INPUT_CAP_MOUSE_ABSOLUTE) != 0;
break;
default: default:
return false; result = false;
break;
} }
LG_UNLOCK(input->lock);
return result;
}
static void inputSetStatusListener(void * opaque,
LG_InputStatusFn callback, void * callbackOpaque)
{
LGMPInput * input = opaque;
LG_InputStatus status;
LG_LOCK(input->lock);
input->statusCallback = callback;
input->statusOpaque = callbackOpaque;
input->notifyStatus = false;
status = inputStatus(input);
LG_UNLOCK(input->lock);
if (callback)
callback(callbackOpaque, &status);
} }
static void buildKeyboardPayload(const LGMPInput * input, static void buildKeyboardPayload(const LGMPInput * input,
@@ -626,11 +921,9 @@ static bool updateKey(void * opaque, int key, bool pressed)
LGMPInput * input = opaque; LGMPInput * input = opaque;
bool wake = false; bool wake = false;
LG_LOCK(input->lock); LG_LOCK(input->lock);
if (!input->connected || !claim(input, &wake)) if (!input->connected || !input->available)
{ {
LG_UNLOCK(input->lock); LG_UNLOCK(input->lock);
if (wake)
lgSignalEvent(input->event);
return false; return false;
} }
@@ -644,6 +937,20 @@ static bool updateKey(void * opaque, int key, bool pressed)
else if (*state) else if (*state)
--*state; --*state;
if (input->ownerBlocked)
{
LG_UNLOCK(input->lock);
return true;
}
if (!claim(input, &wake))
{
*state = previous;
LG_UNLOCK(input->lock);
if (wake)
lgSignalEvent(input->event);
return false;
}
KVMFRInputPayload payload = { 0 }; KVMFRInputPayload payload = { 0 };
buildKeyboardPayload(input, &payload); buildKeyboardPayload(input, &payload);
bool result = queuePayload(input, KVMFR_INPUT_MESSAGE_KEYBOARD, bool result = queuePayload(input, KVMFR_INPUT_MESSAGE_KEYBOARD,
@@ -704,7 +1011,15 @@ static bool inputMouseMotion(void * opaque, int32_t x, int32_t y)
LGMPInput * input = opaque; LGMPInput * input = opaque;
bool wake = false; bool wake = false;
LG_LOCK(input->lock); LG_LOCK(input->lock);
bool result = input->connected && claim(input, &wake) && if (input->connected && input->available && input->ownerBlocked)
{
input->mouseMode = LGMP_INPUT_MOUSE_RELATIVE;
LG_UNLOCK(input->lock);
return true;
}
bool result = input->connected && input->available &&
claim(input, &wake) &&
queueMouse(input, LGMP_INPUT_MOUSE_RELATIVE, queueMouse(input, LGMP_INPUT_MOUSE_RELATIVE,
x, y, 0, input->mouseButtons, true, &wake); x, y, 0, input->mouseButtons, true, &wake);
if (result) if (result)
@@ -732,10 +1047,24 @@ static bool inputMousePosition(void * opaque, uint32_t x, uint32_t y,
LGMPInput * input = opaque; LGMPInput * input = opaque;
bool wake = false; bool wake = false;
LG_LOCK(input->lock); LG_LOCK(input->lock);
if (!input->available ||
!(input->capabilities & KVMFR_INPUT_CAP_MOUSE_ABSOLUTE))
{
LG_UNLOCK(input->lock);
return false;
}
const uint16_t previousX = input->absoluteX; const uint16_t previousX = input->absoluteX;
const uint16_t previousY = input->absoluteY; const uint16_t previousY = input->absoluteY;
input->absoluteX = absoluteX; input->absoluteX = absoluteX;
input->absoluteY = absoluteY; input->absoluteY = absoluteY;
if (input->ownerBlocked)
{
input->mouseMode = LGMP_INPUT_MOUSE_ABSOLUTE;
LG_UNLOCK(input->lock);
return true;
}
bool result = input->connected && claim(input, &wake) && bool result = input->connected && claim(input, &wake) &&
queueMouse(input, LGMP_INPUT_MOUSE_ABSOLUTE, queueMouse(input, LGMP_INPUT_MOUSE_ABSOLUTE,
0, 0, 0, input->mouseButtons, true, &wake); 0, 0, 0, input->mouseButtons, true, &wake);
@@ -784,10 +1113,17 @@ static bool updateMouseButton(void * opaque, unsigned int button,
LGMPInput * input = opaque; LGMPInput * input = opaque;
bool wake = false; bool wake = false;
LG_LOCK(input->lock); LG_LOCK(input->lock);
if (input->connected && input->available && input->ownerBlocked)
{
LG_UNLOCK(input->lock);
return true;
}
const enum LGMPInputMouseMode mode = const enum LGMPInputMouseMode mode =
input->mouseMode == LGMP_INPUT_MOUSE_ABSOLUTE ? input->mouseMode == LGMP_INPUT_MOUSE_ABSOLUTE ?
LGMP_INPUT_MOUSE_ABSOLUTE : LGMP_INPUT_MOUSE_RELATIVE; LGMP_INPUT_MOUSE_ABSOLUTE : LGMP_INPUT_MOUSE_RELATIVE;
const bool result = input->connected && claim(input, &wake) && const bool result = input->connected && input->available &&
claim(input, &wake) &&
queueMouse(input, mode, 0, 0, button == 4 ? 1 : -1, queueMouse(input, mode, 0, 0, button == 4 ? 1 : -1,
input->mouseButtons, false, &wake); input->mouseButtons, false, &wake);
LG_UNLOCK(input->lock); LG_UNLOCK(input->lock);
@@ -808,7 +1144,16 @@ static bool updateMouseButton(void * opaque, unsigned int button,
const enum LGMPInputMouseMode mode = const enum LGMPInputMouseMode mode =
input->mouseMode == LGMP_INPUT_MOUSE_ABSOLUTE ? input->mouseMode == LGMP_INPUT_MOUSE_ABSOLUTE ?
LGMP_INPUT_MOUSE_ABSOLUTE : LGMP_INPUT_MOUSE_RELATIVE; LGMP_INPUT_MOUSE_ABSOLUTE : LGMP_INPUT_MOUSE_RELATIVE;
bool result = input->connected && claim(input, &wake) && if (input->connected && input->available && input->ownerBlocked)
{
input->mouseButtons = buttons;
input->mouseMode = mode;
LG_UNLOCK(input->lock);
return true;
}
bool result = input->connected && input->available &&
claim(input, &wake) &&
queueMouse(input, mode, 0, 0, 0, buttons, false, &wake); queueMouse(input, mode, 0, 0, 0, buttons, false, &wake);
bool reset = false; bool reset = false;
if (!result && !pressed && input->connected) if (!result && !pressed && input->connected)
@@ -854,6 +1199,7 @@ static const LG_InputOps INPUT_OPS =
{ {
.name = "LGMP", .name = "LGMP",
.supports = inputSupports, .supports = inputSupports,
.setStatusListener = inputSetStatusListener,
.keyDown = inputKeyDown, .keyDown = inputKeyDown,
.keyUp = inputKeyUp, .keyUp = inputKeyUp,
.keyboardLEDs = NULL, .keyboardLEDs = NULL,

View File

@@ -26,13 +26,14 @@
#include <lgmp/client.h> #include <lgmp/client.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h>
typedef struct LGMPInput LGMPInput; typedef struct LGMPInput LGMPInput;
bool lgmpInput_create(PLGMPClient client, LGMPInput ** result); bool lgmpInput_create(PLGMPClient client, LGMPInput ** result);
void lgmpInput_destroy(LGMPInput ** input); void lgmpInput_destroy(LGMPInput ** input);
bool lgmpInput_connect(LGMPInput * input); bool lgmpInput_connect(LGMPInput * input, uint32_t clientID);
void lgmpInput_disconnect(LGMPInput * input); void lgmpInput_disconnect(LGMPInput * input);
const LG_InputOps * lgmpInput_getOps(void); const LG_InputOps * lgmpInput_getOps(void);

View File

@@ -1353,7 +1353,7 @@ static const LG_InputOps * lgmp_getInputOps(LG_Transport * this,
{ {
*opaque = NULL; *opaque = NULL;
if (!this->connected || !this->inputSupported || if (!this->connected || !this->inputSupported ||
!lgmpInput_connect(this->input)) !lgmpInput_connect(this->input, this->clientID))
return NULL; return NULL;
*opaque = this->input; *opaque = this->input;

View File

@@ -79,7 +79,7 @@ bool CLGMPInputTransport::Initialize()
if (m_queue) if (m_queue)
return true; return true;
const LGMP_STATUS status = m_host.CreateQueue( LGMP_STATUS status = m_host.CreateQueue(
INPUT_QUEUE_CONFIG, &m_queue); INPUT_QUEUE_CONFIG, &m_queue);
if (status != LGMP_OK) if (status != LGMP_OK)
{ {
@@ -88,15 +88,95 @@ bool CLGMPInputTransport::Initialize()
return false; return false;
} }
for (PLGMPMemory& memory : m_statusMemory)
{
status = m_host.Allocate(sizeof(KVMFRInputStatus), &memory);
if (status != LGMP_OK)
{
DEBUG_ERROR("lgmpHostMemAlloc Failed (Input Status): %s",
lgmpStatusString(status));
DeInit();
return false;
}
memset(lgmpHostMemPtr(memory), 0, sizeof(KVMFRInputStatus));
}
m_statusDirty = true;
return true; return true;
} }
void CLGMPInputTransport::DeInit() void CLGMPInputTransport::DeInit()
{ {
Stop(); Stop();
for (PLGMPMemory& memory : m_statusMemory)
lgmpHostMemFree(&memory);
m_queue = nullptr; m_queue = nullptr;
} }
void CLGMPInputTransport::UpdateSinkState(uint64_t state)
{
if (state == m_sinkState)
return;
m_sinkState = state;
if (++m_endpointGeneration == 0)
++m_endpointGeneration;
m_statusDirty = true;
}
void CLGMPInputTransport::PublishStatus()
{
if (!m_queue)
return;
if (lgmpHostQueueNewSubs(m_queue))
m_statusDirty = true;
if (!m_statusDirty || !lgmpHostQueueHasSubs(m_queue))
return;
PLGMPMemory memory = nullptr;
for (PLGMPMemory candidate : m_statusMemory)
if (!lgmpHostQueuePayloadPending(m_queue, candidate))
{
memory = candidate;
break;
}
if (!memory)
return;
const bool available = (m_sinkState & 1) != 0;
KVMFRInputStatus status = {};
status.version = KVMFR_INPUT_VERSION;
status.capabilities = available ?
KVMFR_INPUT_CAP_MOUSE_RELATIVE |
KVMFR_INPUT_CAP_MOUSE_ABSOLUTE |
KVMFR_INPUT_CAP_KEYBOARD : 0;
status.flags = available ? KVMFR_INPUT_STATUS_AVAILABLE : 0;
if (m_ownerClientID)
{
status.flags |= KVMFR_INPUT_STATUS_HAS_OWNER;
status.ownerClientID = m_ownerClientID;
status.ownerGeneration = m_ownerGeneration;
}
status.generation = m_endpointGeneration;
status.lease = static_cast<uint32_t>(OWNER_LEASE_MS);
status.maxButtons = KVMFR_INPUT_MOUSE_BUTTON_COUNT;
memcpy(lgmpHostMemPtr(memory), &status, sizeof(status));
uint32_t serial = m_statusSerial + 1;
if (!serial)
++serial;
const LGMP_STATUS result = lgmpHostQueuePost(m_queue, serial, memory);
if (result == LGMP_OK)
{
m_statusSerial = serial;
m_statusDirty = false;
}
else if (result != LGMP_ERR_QUEUE_FULL)
DEBUG_WARN("lgmpHostQueuePost Failed (Input Status): %s",
lgmpStatusString(result));
}
bool CLGMPInputTransport::Start(IInputSink& sink) bool CLGMPInputTransport::Start(IInputSink& sink)
{ {
CSRWExclusiveLock lock(&m_lifecycleLock); CSRWExclusiveLock lock(&m_lifecycleLock);
@@ -145,7 +225,12 @@ bool CLGMPInputTransport::Start(IInputSink& sink)
} }
m_sink = &sink; m_sink = &sink;
m_sinkState = sink.GetState(); UpdateSinkState(sink.GetState());
if (!m_endpointGeneration)
{
m_endpointGeneration = 1;
m_statusDirty = true;
}
m_thread = CreateThread(nullptr, 0, ThreadProc, this, 0, nullptr); m_thread = CreateThread(nullptr, 0, ThreadProc, this, 0, nullptr);
if (!m_thread) if (!m_thread)
{ {
@@ -216,6 +301,7 @@ bool CLGMPInputTransport::Claim(
m_ownerGeneration = message.generation; m_ownerGeneration = message.generation;
m_ownerSequence = message.sequence; m_ownerSequence = message.sequence;
RenewLease(); RenewLease();
m_statusDirty = true;
DEBUG_INFO("Input owner %u generation %u acquired", DEBUG_INFO("Input owner %u generation %u acquired",
m_ownerClientID, m_ownerGeneration); m_ownerClientID, m_ownerGeneration);
return true; return true;
@@ -241,6 +327,7 @@ void CLGMPInputTransport::ReleaseOwner(
m_ownerGeneration = 0; m_ownerGeneration = 0;
m_ownerSequence = 0; m_ownerSequence = 0;
m_ownerDeadline = 0; m_ownerDeadline = 0;
m_statusDirty = true;
DEBUG_INFO("Input owner %u generation %u released (%s)", DEBUG_INFO("Input owner %u generation %u released (%s)",
clientID, generation, reason); clientID, generation, reason);
} }
@@ -253,7 +340,7 @@ void CLGMPInputTransport::CheckOwner()
const uint64_t state = m_sink->GetState(); const uint64_t state = m_sink->GetState();
if (state != m_sinkState) if (state != m_sinkState)
{ {
m_sinkState = state; UpdateSinkState(state);
ReleaseOwner(true, "input endpoint changed"); ReleaseOwner(true, "input endpoint changed");
return; return;
} }
@@ -322,7 +409,7 @@ bool CLGMPInputTransport::ProcessMessage(
m_sink->GetState() : m_sinkState; m_sink->GetState() : m_sinkState;
if (sinkState != m_sinkState) if (sinkState != m_sinkState)
{ {
m_sinkState = sinkState; UpdateSinkState(sinkState);
if (m_ownerClientID) if (m_ownerClientID)
{ {
ReleaseOwner(true, "input endpoint changed"); ReleaseOwner(true, "input endpoint changed");
@@ -415,7 +502,7 @@ bool CLGMPInputTransport::ProcessMessage(
const uint64_t deliveredState = m_sink->GetState(); const uint64_t deliveredState = m_sink->GetState();
if (deliveredState != m_sinkState) if (deliveredState != m_sinkState)
{ {
m_sinkState = deliveredState; UpdateSinkState(deliveredState);
ReleaseOwner(true, "input endpoint changed"); ReleaseOwner(true, "input endpoint changed");
return false; return false;
} }
@@ -486,6 +573,7 @@ void CLGMPInputTransport::Thread()
CheckOwner(); CheckOwner();
if (DrainMessages()) if (DrainMessages())
activeUntil = GetTickCount64() + ACTIVE_POLL_MS; activeUntil = GetTickCount64() + ACTIVE_POLL_MS;
PublishStatus();
const bool active = GetTickCount64() < activeUntil; const bool active = GetTickCount64() < activeUntil;
if (!ArmPollTimer(m_pollTimer, active)) if (!ArmPollTimer(m_pollTimer, active))
@@ -508,6 +596,8 @@ void CLGMPInputTransport::Thread()
} }
ReleaseOwner(true, "transport stopped"); ReleaseOwner(true, "transport stopped");
UpdateSinkState(0);
PublishStatus();
if (avTaskHandle) if (avTaskHandle)
AvRevertMmThreadCharacteristics(avTaskHandle); AvRevertMmThreadCharacteristics(avTaskHandle);
} }

View File

@@ -21,6 +21,7 @@
#pragma once #pragma once
#include "transport/IInputTransport.h" #include "transport/IInputTransport.h"
#include "common/LGMPConfig.h"
#include <Windows.h> #include <Windows.h>
@@ -43,6 +44,7 @@ private:
CLGMPHost& m_host; CLGMPHost& m_host;
PLGMPHostQueue m_queue = nullptr; PLGMPHostQueue m_queue = nullptr;
PLGMPMemory m_statusMemory[LGMP_Q_INPUT_LEN] = {};
IInputSink * m_sink = nullptr; IInputSink * m_sink = nullptr;
SRWLOCK m_lifecycleLock = SRWLOCK_INIT; SRWLOCK m_lifecycleLock = SRWLOCK_INIT;
@@ -55,9 +57,14 @@ private:
uint32_t m_ownerSequence = 0; uint32_t m_ownerSequence = 0;
ULONGLONG m_ownerDeadline = 0; ULONGLONG m_ownerDeadline = 0;
uint64_t m_sinkState = 0; uint64_t m_sinkState = 0;
uint32_t m_endpointGeneration = 0;
uint32_t m_statusSerial = 0;
bool m_statusDirty = false;
bool Initialize(); bool Initialize();
void DeInit(); void DeInit();
void UpdateSinkState(uint64_t state);
void PublishStatus();
bool DrainMessages(); bool DrainMessages();
bool ProcessMessage(uint32_t sourceClientID, bool ProcessMessage(uint32_t sourceClientID,
const KVMFRInputMessage& message); const KVMFRInputMessage& message);