diff --git a/client/include/interface/input.h b/client/include/interface/input.h index 95090ae4..8b491ce9 100644 --- a/client/include/interface/input.h +++ b/client/include/interface/input.h @@ -30,6 +30,16 @@ typedef enum 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 { const char * name; @@ -37,6 +47,11 @@ typedef struct LG_InputOps /* Operations must fail safely if a remote endpoint disappears. */ 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. */ bool (*keyDown)(void * opaque, int key); bool (*keyUp)(void * opaque, int key); diff --git a/client/src/input.c b/client/src/input.c index bba6f824..d793b852 100644 --- a/client/src/input.c +++ b/client/src/input.c @@ -30,7 +30,9 @@ struct InputBinding { const LG_InputOps * ops; void * opaque; + bool available; bool mouseAbsolute; + uint32_t generation; }; static struct @@ -63,12 +65,15 @@ static bool validOps(const LG_InputOps * ops) static struct InputBinding makeBinding(const LG_InputOps * ops, void * opaque) { + const bool available = ops && !ops->setStatusListener; return (struct InputBinding) { .ops = ops, .opaque = opaque, - .mouseAbsolute = ops && ops->mousePosition && + .available = available, + .mouseAbsolute = available && ops->mousePosition && ops->supports(opaque, LG_INPUT_SUPPORT_MOUSE_ABSOLUTE), + .generation = 0, }; } @@ -112,12 +117,17 @@ static void clearStateNL(void) static void updateActiveNL(void) { const struct InputBinding next = - l_input.useTransport && l_input.transport.ops ? - l_input.transport : l_input.fallback; + l_input.useTransport && l_input.transport.available ? + l_input.transport : + l_input.fallback.available ? l_input.fallback : + (struct InputBinding) { 0 }; if (next.ops == l_input.active.ops && next.opaque == l_input.active.opaque) + { + l_input.active = next; return; + } resetActiveNL(); l_input.active = next; @@ -132,6 +142,52 @@ static void updateActiveNL(void) 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) { l_input.fallback = (struct InputBinding) { 0 }; @@ -148,12 +204,23 @@ void lgInput_init(void) void lgInput_free(void) { + struct InputBinding fallback; + struct InputBinding transport; + LG_LOCK_EXCLUSIVE(l_input.activeLock); resetActiveNL(); + fallback = l_input.fallback; + transport = l_input.transport; l_input.active = (struct InputBinding) { 0 }; l_input.fallback = (struct InputBinding) { 0 }; l_input.transport = (struct InputBinding) { 0 }; 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); } @@ -166,10 +233,20 @@ void lgInput_setFallback(const LG_InputOps * ops, void * opaque) opaque = NULL; } + const struct InputBinding next = makeBinding(ops, opaque); + struct InputBinding old; + LG_LOCK_EXCLUSIVE(l_input.activeLock); - l_input.fallback = makeBinding(ops, opaque); + old = l_input.fallback; + l_input.fallback = next; updateActiveNL(); 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) @@ -181,15 +258,28 @@ void lgInput_setTransport(const LG_InputOps * ops, void * opaque) opaque = NULL; } + const struct InputBinding next = makeBinding(ops, opaque); + struct InputBinding old; + LG_LOCK_EXCLUSIVE(l_input.activeLock); - l_input.transport = makeBinding(ops, opaque); + old = l_input.transport; + l_input.transport = next; updateActiveNL(); 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) { + struct InputBinding old; + LG_LOCK_EXCLUSIVE(l_input.activeLock); + old = l_input.transport; if (l_input.useTransport && l_input.active.ops == l_input.transport.ops && l_input.active.opaque == l_input.transport.opaque) { @@ -200,6 +290,9 @@ void lgInput_dropTransport(void) l_input.transport = (struct InputBinding) { 0 }; updateActiveNL(); LG_UNLOCK_EXCLUSIVE(l_input.activeLock); + + if (old.ops && old.ops->setStatusListener) + old.ops->setStatusListener(old.opaque, NULL, NULL); } void lgInput_useTransport(bool enable) diff --git a/client/src/main.c b/client/src/main.c index 95980278..4dd960f7 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -2362,7 +2362,7 @@ restart: g_state.transportOps->getInputOps(g_state.transport, &inputOpaque) : NULL; lgInput_setTransport(inputOps, inputOpaque); - if (lgInput_available()) + if (inputOps || lgInput_available()) keybind_inputRegister(); checkUUID(); DEBUG_INFO("Starting session"); diff --git a/client/transports/LGMP/input.c b/client/transports/LGMP/input.c index e4c08b6b..2384555d 100644 --- a/client/transports/LGMP/input.c +++ b/client/transports/LGMP/input.c @@ -42,7 +42,7 @@ #define INPUT_KEEPALIVE_US 200000 #define INPUT_IDLE_RELEASE_US 400000 #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_MAX_SPLIT_REPORTS 4 #define INPUT_MOUSE_DELTA_MIN (INT16_MIN * INPUT_MAX_SPLIT_REPORTS) @@ -71,6 +71,8 @@ struct LGMPInput atomic_bool stop; bool connected; bool claimed; + bool available; + bool ownerBlocked; uint32_t generation; uint32_t sequence; @@ -89,8 +91,27 @@ struct LGMPInput uint16_t absoluteY; uint8_t keyState[KVMFR_INPUT_KEYBOARD_USAGE_MAX + 1]; 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( LGMPInput * input, unsigned position) { @@ -98,6 +119,38 @@ static struct LGMPInputPending * pendingAt( (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, const KVMFRInputMessage * message) { @@ -122,14 +175,21 @@ static void connectionFailed(LGMPInput * input, LGMP_STATUS status) if (input->connected) DEBUG_WARN("LGMP input transport failed: %s", lgmpStatusString(status)); + if (input->available || input->endpointGeneration) + input->notifyStatus = true; input->connected = false; input->claimed = false; + input->available = false; + input->ownerBlocked = false; input->pendingHead = 0; input->pendingCount = 0; input->mouseMode = LGMP_INPUT_MOUSE_NONE; input->mouseButtons = 0; input->publishedClaimed = false; input->lastInput = 0; + input->capabilities = 0; + input->statusValid = false; + input->ownerConfirmed = false; memset(input->keyState, 0, sizeof(input->keyState)); atomic_store_explicit(&input->stop, true, memory_order_release); } @@ -276,11 +336,14 @@ static bool claim(LGMPInput * input, bool * wake) { if (input->claimed) return true; + if (!input->available || input->ownerBlocked) + return false; if (++input->generation == 0) ++input->generation; - input->sequence = 0; - input->claimed = true; + input->sequence = 0; + input->claimed = true; + input->ownerConfirmed = false; const KVMFRInputPayload payload = { 0 }; if (queuePayload(input, KVMFR_INPUT_MESSAGE_CLAIM, @@ -312,6 +375,187 @@ static bool inputStateHeld(const LGMPInput * input) 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) { input->pendingHead = 0; @@ -409,7 +653,9 @@ static int inputThread(void * opaque) while (!atomic_load_explicit(&input->stop, memory_order_acquire)) { unsigned timeout = INPUT_WORKER_IDLE_MS; + bool wake = false; LG_LOCK(input->lock); + processInputStatus(input, &wake); flushPending(input); const uint64_t now = microtime(); @@ -418,7 +664,6 @@ static int inputThread(void * opaque) input->publishedGeneration == input->generation && now - input->lastInput >= INPUT_IDLE_RELEASE_US) { - bool wake = false; release(input, &wake); } else if (input->connected && input->claimed && @@ -426,7 +671,6 @@ static int inputThread(void * opaque) input->publishedGeneration == input->generation && now - input->lastSend >= INPUT_KEEPALIVE_US) { - bool wake = false; const KVMFRInputPayload payload = { 0 }; queuePayload(input, KVMFR_INPUT_MESSAGE_KEEPALIVE, &payload, false, &wake); @@ -436,10 +680,15 @@ static int inputThread(void * opaque) timeout = INPUT_WORKER_RETRY_MS; LG_UNLOCK(input->lock); + notifyInputStatus(input); + if (wake) + lgSignalEvent(input->event); + if (atomic_load_explicit(&input->stop, memory_order_acquire)) break; lgWaitEvent(input->event, timeout); } + notifyInputStatus(input); return 0; } @@ -467,13 +716,17 @@ void lgmpInput_destroy(LGMPInput ** input) *input = NULL; } -bool lgmpInput_connect(LGMPInput * input) +bool lgmpInput_connect(LGMPInput * input, uint32_t clientID) { + if (!clientID) + return false; + LG_LOCK(input->lock); if (input->connected) { + const bool sameClient = input->clientID == clientID; LG_UNLOCK(input->lock); - return true; + return sameClient; } if (input->thread || input->queue) { @@ -499,15 +752,26 @@ bool lgmpInput_connect(LGMPInput * input) return false; } - input->connected = true; - input->claimed = false; - input->pendingHead = 0; - input->pendingCount = 0; - input->publishedGeneration = 0; - input->publishedSequence = 0; - input->publishedClaimed = false; - input->lastSend = 0; - input->lastInput = 0; + input->connected = true; + input->claimed = false; + input->available = false; + input->ownerBlocked = false; + input->pendingHead = 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->publishedSequence = 0; + input->publishedClaimed = false; + input->lastSend = 0; + input->lastInput = 0; + input->generation = 0; clearInputState(input); atomic_store_explicit(&input->stop, false, memory_order_release); LGThread * thread; @@ -545,6 +809,13 @@ void lgmpInput_disconnect(LGMPInput * input) input->pendingCount = 0; input->connected = 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); atomic_store_explicit(&input->stop, true, memory_order_release); LGThread * thread = input->thread; @@ -580,16 +851,40 @@ void lgmpInput_disconnect(LGMPInput * input) static bool inputSupports(void * opaque, LG_InputSupport support) { - (void)opaque; + LGMPInput * input = opaque; + LG_LOCK(input->lock); + bool result; switch (support) { case LG_INPUT_SUPPORT_MOUSE_ABSOLUTE: - return true; + result = input->available && + (input->capabilities & KVMFR_INPUT_CAP_MOUSE_ABSOLUTE) != 0; + break; 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, @@ -626,11 +921,9 @@ static bool updateKey(void * opaque, int key, bool pressed) LGMPInput * input = opaque; bool wake = false; LG_LOCK(input->lock); - if (!input->connected || !claim(input, &wake)) + if (!input->connected || !input->available) { LG_UNLOCK(input->lock); - if (wake) - lgSignalEvent(input->event); return false; } @@ -644,6 +937,20 @@ static bool updateKey(void * opaque, int key, bool pressed) else if (*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 }; buildKeyboardPayload(input, &payload); 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; bool wake = false; 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, x, y, 0, input->mouseButtons, true, &wake); if (result) @@ -732,10 +1047,24 @@ static bool inputMousePosition(void * opaque, uint32_t x, uint32_t y, LGMPInput * input = opaque; bool wake = false; 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 previousY = input->absoluteY; input->absoluteX = absoluteX; 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) && queueMouse(input, LGMP_INPUT_MOUSE_ABSOLUTE, 0, 0, 0, input->mouseButtons, true, &wake); @@ -784,10 +1113,17 @@ static bool updateMouseButton(void * opaque, unsigned int button, LGMPInput * input = opaque; bool wake = false; LG_LOCK(input->lock); + if (input->connected && input->available && input->ownerBlocked) + { + LG_UNLOCK(input->lock); + return true; + } + const enum LGMPInputMouseMode mode = input->mouseMode == LGMP_INPUT_MOUSE_ABSOLUTE ? 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, input->mouseButtons, false, &wake); LG_UNLOCK(input->lock); @@ -808,7 +1144,16 @@ static bool updateMouseButton(void * opaque, unsigned int button, const enum LGMPInputMouseMode mode = input->mouseMode == LGMP_INPUT_MOUSE_ABSOLUTE ? 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); bool reset = false; if (!result && !pressed && input->connected) @@ -852,16 +1197,17 @@ static void inputReset(void * opaque) static const LG_InputOps INPUT_OPS = { - .name = "LGMP", - .supports = inputSupports, - .keyDown = inputKeyDown, - .keyUp = inputKeyUp, - .keyboardLEDs = NULL, - .mouseMotion = inputMouseMotion, - .mousePosition = inputMousePosition, - .mousePress = inputMousePress, - .mouseRelease = inputMouseRelease, - .reset = inputReset, + .name = "LGMP", + .supports = inputSupports, + .setStatusListener = inputSetStatusListener, + .keyDown = inputKeyDown, + .keyUp = inputKeyUp, + .keyboardLEDs = NULL, + .mouseMotion = inputMouseMotion, + .mousePosition = inputMousePosition, + .mousePress = inputMousePress, + .mouseRelease = inputMouseRelease, + .reset = inputReset, }; const LG_InputOps * lgmpInput_getOps(void) diff --git a/client/transports/LGMP/input.h b/client/transports/LGMP/input.h index accd5055..c773db59 100644 --- a/client/transports/LGMP/input.h +++ b/client/transports/LGMP/input.h @@ -26,13 +26,14 @@ #include #include +#include typedef struct LGMPInput LGMPInput; bool lgmpInput_create(PLGMPClient client, LGMPInput ** result); void lgmpInput_destroy(LGMPInput ** input); -bool lgmpInput_connect(LGMPInput * input); +bool lgmpInput_connect(LGMPInput * input, uint32_t clientID); void lgmpInput_disconnect(LGMPInput * input); const LG_InputOps * lgmpInput_getOps(void); diff --git a/client/transports/LGMP/lgmp.c b/client/transports/LGMP/lgmp.c index bfefdcf9..eea5c612 100644 --- a/client/transports/LGMP/lgmp.c +++ b/client/transports/LGMP/lgmp.c @@ -1353,7 +1353,7 @@ static const LG_InputOps * lgmp_getInputOps(LG_Transport * this, { *opaque = NULL; if (!this->connected || !this->inputSupported || - !lgmpInput_connect(this->input)) + !lgmpInput_connect(this->input, this->clientID)) return NULL; *opaque = this->input; diff --git a/idd/LGIdd/transport/lgmp/CLGMPInputTransport.cpp b/idd/LGIdd/transport/lgmp/CLGMPInputTransport.cpp index 6337a25f..8f72d228 100644 --- a/idd/LGIdd/transport/lgmp/CLGMPInputTransport.cpp +++ b/idd/LGIdd/transport/lgmp/CLGMPInputTransport.cpp @@ -79,7 +79,7 @@ bool CLGMPInputTransport::Initialize() if (m_queue) return true; - const LGMP_STATUS status = m_host.CreateQueue( + LGMP_STATUS status = m_host.CreateQueue( INPUT_QUEUE_CONFIG, &m_queue); if (status != LGMP_OK) { @@ -88,15 +88,95 @@ bool CLGMPInputTransport::Initialize() 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; } void CLGMPInputTransport::DeInit() { Stop(); + for (PLGMPMemory& memory : m_statusMemory) + lgmpHostMemFree(&memory); 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(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) { CSRWExclusiveLock lock(&m_lifecycleLock); @@ -145,7 +225,12 @@ bool CLGMPInputTransport::Start(IInputSink& 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); if (!m_thread) { @@ -216,6 +301,7 @@ bool CLGMPInputTransport::Claim( m_ownerGeneration = message.generation; m_ownerSequence = message.sequence; RenewLease(); + m_statusDirty = true; DEBUG_INFO("Input owner %u generation %u acquired", m_ownerClientID, m_ownerGeneration); return true; @@ -241,6 +327,7 @@ void CLGMPInputTransport::ReleaseOwner( m_ownerGeneration = 0; m_ownerSequence = 0; m_ownerDeadline = 0; + m_statusDirty = true; DEBUG_INFO("Input owner %u generation %u released (%s)", clientID, generation, reason); } @@ -253,7 +340,7 @@ void CLGMPInputTransport::CheckOwner() const uint64_t state = m_sink->GetState(); if (state != m_sinkState) { - m_sinkState = state; + UpdateSinkState(state); ReleaseOwner(true, "input endpoint changed"); return; } @@ -322,7 +409,7 @@ bool CLGMPInputTransport::ProcessMessage( m_sink->GetState() : m_sinkState; if (sinkState != m_sinkState) { - m_sinkState = sinkState; + UpdateSinkState(sinkState); if (m_ownerClientID) { ReleaseOwner(true, "input endpoint changed"); @@ -415,7 +502,7 @@ bool CLGMPInputTransport::ProcessMessage( const uint64_t deliveredState = m_sink->GetState(); if (deliveredState != m_sinkState) { - m_sinkState = deliveredState; + UpdateSinkState(deliveredState); ReleaseOwner(true, "input endpoint changed"); return false; } @@ -486,6 +573,7 @@ void CLGMPInputTransport::Thread() CheckOwner(); if (DrainMessages()) activeUntil = GetTickCount64() + ACTIVE_POLL_MS; + PublishStatus(); const bool active = GetTickCount64() < activeUntil; if (!ArmPollTimer(m_pollTimer, active)) @@ -508,6 +596,8 @@ void CLGMPInputTransport::Thread() } ReleaseOwner(true, "transport stopped"); + UpdateSinkState(0); + PublishStatus(); if (avTaskHandle) AvRevertMmThreadCharacteristics(avTaskHandle); } diff --git a/idd/LGIdd/transport/lgmp/CLGMPInputTransport.h b/idd/LGIdd/transport/lgmp/CLGMPInputTransport.h index c402707e..aad5b617 100644 --- a/idd/LGIdd/transport/lgmp/CLGMPInputTransport.h +++ b/idd/LGIdd/transport/lgmp/CLGMPInputTransport.h @@ -21,6 +21,7 @@ #pragma once #include "transport/IInputTransport.h" +#include "common/LGMPConfig.h" #include @@ -42,22 +43,28 @@ private: CLGMPHost& m_host; - PLGMPHostQueue m_queue = nullptr; - IInputSink * m_sink = nullptr; + PLGMPHostQueue m_queue = nullptr; + PLGMPMemory m_statusMemory[LGMP_Q_INPUT_LEN] = {}; + IInputSink * m_sink = nullptr; SRWLOCK m_lifecycleLock = SRWLOCK_INIT; HANDLE m_stopEvent = nullptr; HANDLE m_pollTimer = nullptr; HANDLE m_thread = nullptr; - uint32_t m_ownerClientID = 0; - uint32_t m_ownerGeneration = 0; - uint32_t m_ownerSequence = 0; - ULONGLONG m_ownerDeadline = 0; - uint64_t m_sinkState = 0; + uint32_t m_ownerClientID = 0; + uint32_t m_ownerGeneration = 0; + uint32_t m_ownerSequence = 0; + ULONGLONG m_ownerDeadline = 0; + uint64_t m_sinkState = 0; + uint32_t m_endpointGeneration = 0; + uint32_t m_statusSerial = 0; + bool m_statusDirty = false; bool Initialize(); void DeInit(); + void UpdateSinkState(uint64_t state); + void PublishStatus(); bool DrainMessages(); bool ProcessMessage(uint32_t sourceClientID, const KVMFRInputMessage& message);