/** * Looking Glass * Copyright © 2017-2026 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "input.h" #include "kb.h" #include "common/event.h" #include "common/locking.h" #include "common/time.h" #include #include #include #include #include #define NETWORK_INPUT_TIMER_MS 50U #define NETWORK_INPUT_LEASE_MS 500U #define NETWORK_INPUT_KEEPALIVE_MS 200U #define NETWORK_INPUT_CLAIM_RETRY_MS 500U #define NETWORK_INPUT_LED_SYNC_DELAY_MS 20U #define NETWORK_INPUT_LED_ACK_TIMEOUT_MS 1000U #define NETWORK_INPUT_USAGE_PAGE_KEYBOARD 0x07U #define NETWORK_INPUT_KEYBOARD_USAGE_MAX 231U #define NETWORK_INPUT_CLAIM_FLAGS \ (LG_NET_INPUT_CLAIM_KEYBOARD | LG_NET_INPUT_CLAIM_POINTER | \ LG_NET_INPUT_CLAIM_EXCLUSIVE) #define NETWORK_INPUT_LED_MASK \ (LG_NET_KEYBOARD_LED_NUM_LOCK | LG_NET_KEYBOARD_LED_CAPS_LOCK | \ LG_NET_KEYBOARD_LED_SCROLL_LOCK) struct NetworkInputLockKey { uint32_t led; uint16_t usage; }; static const struct NetworkInputLockKey NETWORK_INPUT_LOCK_KEYS[] = { { LG_NET_KEYBOARD_LED_NUM_LOCK, 83 }, { LG_NET_KEYBOARD_LED_CAPS_LOCK, 57 }, { LG_NET_KEYBOARD_LED_SCROLL_LOCK, 71 }, }; struct NetworkInput { NetworkConnection * connection; LGTimer * timer; LGEvent * statusIdle; LG_Lock stateLock; bool destroying; bool connected; bool serviceAvailable; bool claimPending; bool claimed; bool available; uint64_t claimantID; uint64_t claimEpoch; uint64_t sequence; uint64_t nextClaimNs; uint64_t nextKeepaliveNs; uint32_t claimFlags; uint16_t serviceVersion; uint32_t mouseButtons; bool mouseAbsolute; uint32_t absoluteX; uint32_t absoluteY; uint32_t absoluteWidth; uint32_t absoluteHeight; /* Preserve every held usage. The receiver reconstructs a six-key boot * report and emits ErrorRollOver while more than six keys are held. */ bool keyState[NETWORK_INPUT_KEYBOARD_USAGE_MAX + 1U]; uint8_t modifiers; bool keyboardLEDsValid; uint8_t keyboardLEDs; uint32_t keyboardLEDsValidMask; uint32_t pendingKeyboardLEDs; uint16_t pendingKeyboardLEDCounts[ sizeof(NETWORK_INPUT_LOCK_KEYS) / sizeof(*NETWORK_INPUT_LOCK_KEYS)]; uint64_t pendingKeyboardLEDsUntil; uint64_t keyboardLEDsSyncAfter; uint32_t staleKeyboardLEDs; uint64_t staleKeyboardLEDsUntil; uint64_t keyboardLEDSequence; bool desiredKeyboardLEDsValid; uint8_t desiredKeyboardLEDs; uint32_t statusGeneration; bool statusNotifyPending; bool statusDispatching; LG_InputStatusFn statusCallback; void * statusOpaque; }; static _Thread_local NetworkInput * l_networkInputCallback; static uint64_t networkInputNext64(uint64_t value) { ++value; return value ? value : 1U; } static uint32_t networkInputNext32(uint32_t value) { ++value; return value ? value : 1U; } static uint64_t networkInputDeadline(uint32_t milliseconds) { return nanotime() + (uint64_t)milliseconds * UINT64_C(1000000); } static bool networkInputKeyboardHeldNL(const NetworkInput * input) { for (size_t usage = 1; usage <= NETWORK_INPUT_KEYBOARD_USAGE_MAX; ++usage) if (input->keyState[usage]) return true; return false; } static bool networkInputLEDTransitionsNL(const NetworkInput * input) { for (size_t i = 0; i < sizeof(NETWORK_INPUT_LOCK_KEYS) / sizeof(*NETWORK_INPUT_LOCK_KEYS); ++i) if (input->pendingKeyboardLEDCounts[i]) return true; return false; } static uint32_t networkInputProjectedLEDsNL(const NetworkInput * input) { return input->keyboardLEDs ^ input->pendingKeyboardLEDs; } static void networkInputClearLEDProjectionNL(NetworkInput * input) { input->pendingKeyboardLEDs = 0; input->pendingKeyboardLEDsUntil = 0; memset(input->pendingKeyboardLEDCounts, 0, sizeof(input->pendingKeyboardLEDCounts)); } static void networkInputInvalidateLEDsNL(NetworkInput * input) { input->keyboardLEDsValid = false; input->keyboardLEDs = 0; input->keyboardLEDsValidMask = 0; input->keyboardLEDSequence = 0; input->keyboardLEDsSyncAfter = 0; input->staleKeyboardLEDs = 0; input->staleKeyboardLEDsUntil = 0; networkInputClearLEDProjectionNL(input); } static void networkInputRecordLEDToggleNL(NetworkInput * input, uint32_t led, bool physical) { for (size_t i = 0; i < sizeof(NETWORK_INPUT_LOCK_KEYS) / sizeof(*NETWORK_INPUT_LOCK_KEYS); ++i) { if (NETWORK_INPUT_LOCK_KEYS[i].led != led) continue; if (input->pendingKeyboardLEDCounts[i] != UINT16_MAX) ++input->pendingKeyboardLEDCounts[i]; input->pendingKeyboardLEDs ^= led; input->pendingKeyboardLEDsUntil = networkInputDeadline( NETWORK_INPUT_LED_ACK_TIMEOUT_MS); break; } if (physical) { if (!input->desiredKeyboardLEDsValid || ((networkInputProjectedLEDsNL(input) ^ input->desiredKeyboardLEDs) & led)) input->staleKeyboardLEDs |= led; else input->staleKeyboardLEDs &= ~led; input->staleKeyboardLEDsUntil = input->staleKeyboardLEDs ? networkInputDeadline(NETWORK_INPUT_LED_ACK_TIMEOUT_MS) : 0; } input->keyboardLEDsSyncAfter = networkInputDeadline( NETWORK_INPUT_LED_SYNC_DELAY_MS); } static void networkInputAcknowledgeLEDsNL(NetworkInput * input, uint32_t changed) { for (size_t i = 0; i < sizeof(NETWORK_INPUT_LOCK_KEYS) / sizeof(*NETWORK_INPUT_LOCK_KEYS); ++i) { const uint32_t led = NETWORK_INPUT_LOCK_KEYS[i].led; if (!(changed & led) || !input->pendingKeyboardLEDCounts[i]) continue; --input->pendingKeyboardLEDCounts[i]; input->pendingKeyboardLEDs ^= led; } if (!networkInputLEDTransitionsNL(input)) input->pendingKeyboardLEDsUntil = 0; } static LG_InputStatus networkInputStatusNL(const NetworkInput * input) { return (LG_InputStatus) { .available = input->available, .generation = input->statusGeneration, .keyboardLEDsValid = input->available && input->keyboardLEDsValid, .keyboardLEDs = input->keyboardLEDs, }; } static void networkInputDispatchStatus(NetworkInput * input, LG_InputStatusFn callback, void * opaque, LG_InputStatus status) { while (callback) { NetworkInput * previous = l_networkInputCallback; l_networkInputCallback = input; callback(opaque, &status); l_networkInputCallback = previous; LG_LOCK(input->stateLock); if (input->statusNotifyPending && input->statusCallback) { input->statusNotifyPending = false; callback = input->statusCallback; opaque = input->statusOpaque; status = networkInputStatusNL(input); LG_UNLOCK(input->stateLock); continue; } input->statusDispatching = false; lgSignalEvent(input->statusIdle); LG_UNLOCK(input->stateLock); break; } } static void networkInputNotifyStatus(NetworkInput * input) { LG_InputStatusFn callback; void * opaque; LG_InputStatus status; LG_LOCK(input->stateLock); if (!input->statusNotifyPending || input->statusDispatching) { LG_UNLOCK(input->stateLock); return; } input->statusNotifyPending = false; callback = input->statusCallback; opaque = input->statusOpaque; status = networkInputStatusNL(input); if (!callback) { LG_UNLOCK(input->stateLock); return; } input->statusDispatching = true; lgResetEvent(input->statusIdle); LG_UNLOCK(input->stateLock); networkInputDispatchStatus(input, callback, opaque, status); } static bool networkInputSetAvailableNL(NetworkInput * input, bool available) { if (input->available == available) return false; input->available = available; input->statusGeneration = networkInputNext32(input->statusGeneration); input->statusNotifyPending = true; return true; } static void networkInputClearStateNL(NetworkInput * input) { input->mouseButtons = 0; input->mouseAbsolute = false; input->absoluteX = 0; input->absoluteY = 0; input->absoluteWidth = 0; input->absoluteHeight = 0; memset(input->keyState, 0, sizeof(input->keyState)); input->modifiers = 0; networkInputClearLEDProjectionNL(input); } static LG_TransportStatus networkInputSendReliableNL(NetworkInput * input, uint16_t messageType, LGNetEnvelopeFlags flags, uint64_t requestID, const void * payload, uint32_t payloadLength) { return networkConnectionSend(input->connection, LG_NET_SERVICE_INPUT, messageType, input->serviceVersion, flags | LG_NET_ENVELOPE_FINAL, input->claimEpoch, requestID, payload, payloadLength); } static bool networkInputSendClaimNL(NetworkInput * input) { if (!input->connected || !input->serviceAvailable || input->destroying) return false; networkInputClearStateNL(input); networkInputInvalidateLEDsNL(input); const uint64_t claimEpoch = networkInputNext64(input->claimEpoch); const LGNetInputClaim claim = { .claimantID = input->claimantID, .claimEpoch = claimEpoch, .leaseMs = NETWORK_INPUT_LEASE_MS, .flags = NETWORK_INPUT_CLAIM_FLAGS, }; uint8_t payload[LG_NET_INPUT_CLAIM_WIRE_SIZE]; if (!lgNetInputClaimEncode(payload, sizeof(payload), &claim)) return false; input->claimEpoch = claimEpoch; input->sequence = 0; if (networkInputSendReliableNL(input, LG_NET_INPUT_MESSAGE_CLAIM, LG_NET_ENVELOPE_ACK_REQUIRED, claimEpoch, payload, sizeof(payload)) != LG_TRANSPORT_OK) { input->nextClaimNs = networkInputDeadline( NETWORK_INPUT_CLAIM_RETRY_MS); return false; } input->claimPending = true; input->claimed = false; input->claimFlags = 0; input->nextClaimNs = networkInputDeadline(NETWORK_INPUT_LEASE_MS); input->nextKeepaliveNs = 0; return true; } static bool networkInputSendControlNL(NetworkInput * input, uint16_t messageType, uint32_t flags, uint32_t leaseMs) { const uint64_t sequence = networkInputNext64(input->sequence); const LGNetInputControl control = { .claimantID = input->claimantID, .claimEpoch = input->claimEpoch, .sequence = sequence, .flags = flags, .leaseMs = leaseMs, }; uint8_t payload[LG_NET_INPUT_CONTROL_WIRE_SIZE]; if (!lgNetInputControlEncode(payload, sizeof(payload), &control) || networkInputSendReliableNL(input, messageType, 0, 0, payload, sizeof(payload)) != LG_TRANSPORT_OK) return false; input->sequence = sequence; return true; } static bool networkInputAbandonClaimNL(NetworkInput * input, bool tryReset) { if (input->claimed && networkConnectionValid(input->connection)) { if (tryReset) networkInputSendControlNL(input, LG_NET_INPUT_MESSAGE_RESET, LG_NET_INPUT_CLAIM_KEYBOARD | LG_NET_INPUT_CLAIM_POINTER, 0); networkInputSendControlNL(input, LG_NET_INPUT_MESSAGE_RELEASE, input->claimFlags, 0); } input->claimPending = false; input->claimed = false; input->claimFlags = 0; input->claimEpoch = networkInputNext64(input->claimEpoch); input->nextKeepaliveNs = 0; input->nextClaimNs = networkInputDeadline( NETWORK_INPUT_CLAIM_RETRY_MS); networkInputClearStateNL(input); networkInputInvalidateLEDsNL(input); return networkInputSetAvailableNL(input, false); } static bool networkInputSendResetNL(NetworkInput * input) { const bool failed = input->claimed && (!networkConnectionValid(input->connection) || !networkInputSendControlNL(input, LG_NET_INPUT_MESSAGE_RESET, LG_NET_INPUT_CLAIM_KEYBOARD | LG_NET_INPUT_CLAIM_POINTER, 0)); networkInputClearStateNL(input); return failed && networkInputAbandonClaimNL(input, false); } static bool networkInputSendReleaseNL(NetworkInput * input) { bool sent = true; if ((input->claimed || input->claimPending) && networkConnectionValid(input->connection)) sent = networkInputSendControlNL(input, LG_NET_INPUT_MESSAGE_RELEASE, input->claimFlags, 0); input->claimPending = false; input->claimed = false; input->claimFlags = 0; input->nextKeepaliveNs = 0; return sent; } static bool networkInputNextSequenceNL(NetworkInput * input, uint64_t * sequence) { if (!input->available || !input->claimed) return false; *sequence = networkInputNext64(input->sequence); return true; } static bool networkInputSendKeyboardNL(NetworkInput * input, uint16_t usage, bool down, bool repeat) { uint64_t sequence; if (!networkInputNextSequenceNL(input, &sequence)) return false; LGNetInputKeyboard keyboard = { .sequence = sequence, .timestampNs = nanotime(), .usagePage = NETWORK_INPUT_USAGE_PAGE_KEYBOARD, .usage = usage, .scanCode = 0, .flags = (down ? LG_NET_KEYBOARD_DOWN : 0) | (repeat ? LG_NET_KEYBOARD_REPEAT : 0), .modifiers = input->modifiers, }; if (!keyboard.timestampNs) keyboard.timestampNs = 1; uint8_t payload[LG_NET_INPUT_KEYBOARD_WIRE_SIZE]; if (!lgNetInputKeyboardEncode(payload, sizeof(payload), &keyboard) || networkInputSendReliableNL(input, LG_NET_INPUT_MESSAGE_KEYBOARD, 0, 0, payload, sizeof(payload)) != LG_TRANSPORT_OK) return false; input->sequence = sequence; return true; } static uint32_t networkInputLEDForUsage(uint16_t usage) { switch (usage) { case 83: return LG_NET_KEYBOARD_LED_NUM_LOCK; case 57: return LG_NET_KEYBOARD_LED_CAPS_LOCK; case 71: return LG_NET_KEYBOARD_LED_SCROLL_LOCK; default: return 0; } } static bool networkInputTapLockKeyNL(NetworkInput * input, const struct NetworkInputLockKey * key, bool * notify) { if (key->usage > NETWORK_INPUT_KEYBOARD_USAGE_MAX || input->keyState[key->usage]) return false; if (!networkInputSendKeyboardNL(input, key->usage, true, false)) return false; networkInputRecordLEDToggleNL(input, key->led, false); if (networkInputSendKeyboardNL(input, key->usage, false, false)) return true; *notify |= networkInputSendResetNL(input); input->keyboardLEDsSyncAfter = networkInputDeadline( NETWORK_INPUT_LED_ACK_TIMEOUT_MS); return false; } static void networkInputSyncKeyboardLEDsNL(NetworkInput * input, bool * notify) { const uint64_t now = nanotime(); if (networkInputLEDTransitionsNL(input) && now >= input->pendingKeyboardLEDsUntil) { networkInputClearLEDProjectionNL(input); input->keyboardLEDsSyncAfter = now + (uint64_t)NETWORK_INPUT_LED_SYNC_DELAY_MS * UINT64_C(1000000); } if (input->staleKeyboardLEDs && now >= input->staleKeyboardLEDsUntil) { input->desiredKeyboardLEDsValid = false; input->staleKeyboardLEDs = 0; input->staleKeyboardLEDsUntil = 0; } if (!input->available || !input->keyboardLEDsValid || !input->desiredKeyboardLEDsValid || networkInputLEDTransitionsNL(input) || now < input->keyboardLEDsSyncAfter) return; const uint32_t difference = (networkInputProjectedLEDsNL(input) ^ input->desiredKeyboardLEDs) & NETWORK_INPUT_LED_MASK & ~input->staleKeyboardLEDs; if (!difference) { if (!input->staleKeyboardLEDs) input->desiredKeyboardLEDsValid = false; return; } if (networkInputKeyboardHeldNL(input)) return; for (size_t i = 0; i < sizeof(NETWORK_INPUT_LOCK_KEYS) / sizeof(*NETWORK_INPUT_LOCK_KEYS); ++i) { if (!(difference & NETWORK_INPUT_LOCK_KEYS[i].led)) continue; if (!networkInputTapLockKeyNL(input, &NETWORK_INPUT_LOCK_KEYS[i], notify)) break; } } static bool networkInputTimer(void * opaque) { NetworkInput * input = opaque; bool notify = false; LG_LOCK(input->stateLock); if (input->destroying) { LG_UNLOCK(input->stateLock); return false; } const uint64_t now = nanotime(); if (input->connected && input->serviceAvailable) { if (input->claimPending && now >= input->nextClaimNs) input->claimPending = false; if (!input->claimPending && !input->claimed && now >= input->nextClaimNs) networkInputSendClaimNL(input); else if (input->claimed && now >= input->nextKeepaliveNs) { if (networkInputSendControlNL(input, LG_NET_INPUT_MESSAGE_KEEPALIVE, input->claimFlags, NETWORK_INPUT_LEASE_MS)) input->nextKeepaliveNs = networkInputDeadline( NETWORK_INPUT_KEEPALIVE_MS); else notify |= networkInputAbandonClaimNL(input, true); } networkInputSyncKeyboardLEDsNL(input, ¬ify); } LG_UNLOCK(input->stateLock); networkInputNotifyStatus(input); return true; } static bool networkInputSupports(void * opaque, LG_InputSupport support) { NetworkInput * input = opaque; if (support != LG_INPUT_SUPPORT_MOUSE_ABSOLUTE) return false; LG_LOCK(input->stateLock); const bool supported = input->available && (input->claimFlags & LG_NET_INPUT_CLAIM_POINTER); LG_UNLOCK(input->stateLock); return supported; } static void networkInputSetStatusListener(void * opaque, LG_InputStatusFn callback, void * callbackOpaque) { NetworkInput * input = opaque; const bool reentrant = l_networkInputCallback == input; LG_LOCK(input->stateLock); input->statusCallback = NULL; input->statusOpaque = NULL; input->statusNotifyPending = false; const bool dispatching = input->statusDispatching; LG_UNLOCK(input->stateLock); if (dispatching && !reentrant) lgWaitEvent(input->statusIdle, TIMEOUT_INFINITE); if (!callback) return; LG_LOCK(input->stateLock); input->statusCallback = callback; input->statusOpaque = callbackOpaque; input->statusNotifyPending = false; const LG_InputStatus status = networkInputStatusNL(input); const bool startDispatch = !input->statusDispatching; if (startDispatch) { input->statusDispatching = true; lgResetEvent(input->statusIdle); } LG_UNLOCK(input->stateLock); if (startDispatch) networkInputDispatchStatus(input, callback, callbackOpaque, status); else { NetworkInput * previous = l_networkInputCallback; l_networkInputCallback = input; callback(callbackOpaque, &status); l_networkInputCallback = previous; } } static bool networkInputUpdateKey(void * opaque, int key, bool down) { if (key < 0 || key >= LG_KEY_MAX) return false; const uint16_t usage = lg_keycode_to_hid[key]; if (!usage || usage > NETWORK_INPUT_KEYBOARD_USAGE_MAX) return true; NetworkInput * input = opaque; bool notify = false; LG_LOCK(input->stateLock); if (!input->available || !(input->claimFlags & LG_NET_INPUT_CLAIM_KEYBOARD)) { LG_UNLOCK(input->stateLock); return false; } const bool previous = input->keyState[usage]; if (previous == down) { LG_UNLOCK(input->stateLock); return true; } input->keyState[usage] = down; if (usage >= 224 && usage <= 231) { const uint8_t mask = UINT8_C(1) << (usage - 224); if (down) input->modifiers |= mask; else input->modifiers &= (uint8_t)~mask; } if (!networkInputSendKeyboardNL(input, usage, down, false)) { input->keyState[usage] = previous; if (usage >= 224 && usage <= 231) { const uint8_t mask = UINT8_C(1) << (usage - 224); if (previous) input->modifiers |= mask; else input->modifiers &= (uint8_t)~mask; } if (!down) notify = networkInputAbandonClaimNL(input, true); LG_UNLOCK(input->stateLock); if (notify) networkInputNotifyStatus(input); return false; } const uint32_t led = networkInputLEDForUsage(usage); if (down && led) { if (input->keyboardLEDsValid) networkInputRecordLEDToggleNL(input, led, true); else { input->desiredKeyboardLEDsValid = false; input->keyboardLEDsSyncAfter = networkInputDeadline( NETWORK_INPUT_LED_SYNC_DELAY_MS); } } LG_UNLOCK(input->stateLock); return true; } static bool networkInputKeyDown(void * opaque, int key) { return networkInputUpdateKey(opaque, key, true); } static bool networkInputKeyUp(void * opaque, int key) { return networkInputUpdateKey(opaque, key, false); } static bool networkInputKeyboardLEDs(void * opaque, bool numLock, bool capsLock, bool scrollLock) { NetworkInput * input = opaque; bool notify = false; const uint8_t desired = (numLock ? LG_NET_KEYBOARD_LED_NUM_LOCK : 0) | (capsLock ? LG_NET_KEYBOARD_LED_CAPS_LOCK : 0) | (scrollLock ? LG_NET_KEYBOARD_LED_SCROLL_LOCK : 0); LG_LOCK(input->stateLock); input->desiredKeyboardLEDs = desired; input->desiredKeyboardLEDsValid = true; input->staleKeyboardLEDs &= networkInputProjectedLEDsNL(input) ^ desired; if (!input->staleKeyboardLEDs) input->staleKeyboardLEDsUntil = 0; const uint64_t syncAfter = networkInputDeadline( NETWORK_INPUT_LED_SYNC_DELAY_MS); if (input->keyboardLEDsSyncAfter < syncAfter) input->keyboardLEDsSyncAfter = syncAfter; networkInputSyncKeyboardLEDsNL(input, ¬ify); const bool available = input->available; LG_UNLOCK(input->stateLock); if (notify) networkInputNotifyStatus(input); return available; } static void networkInputKeyboardLEDsReset(void * opaque) { NetworkInput * input = opaque; LG_LOCK(input->stateLock); input->desiredKeyboardLEDsValid = false; input->keyboardLEDsSyncAfter = 0; input->staleKeyboardLEDs = 0; input->staleKeyboardLEDsUntil = 0; LG_UNLOCK(input->stateLock); } static bool networkInputSendRelativeNL(NetworkInput * input, int32_t x, int32_t y, int32_t wheelX, int32_t wheelY, uint32_t buttons, uint32_t changedButtons, bool motion) { uint64_t sequence; if (!networkInputNextSequenceNL(input, &sequence)) return false; LGNetInputRelative relative = { .sequence = sequence, .timestampNs = nanotime(), .deltaX = x, .deltaY = y, .wheelX = wheelX, .wheelY = wheelY, .buttons = buttons, .changedButtons = changedButtons, }; if (!relative.timestampNs) relative.timestampNs = 1; uint8_t payload[LG_NET_INPUT_RELATIVE_WIRE_SIZE]; if (!lgNetInputRelativeEncode(payload, sizeof(payload), &relative)) return false; LG_TransportStatus status = LG_TRANSPORT_UNAVAILABLE; if (motion && networkConnectionServiceAvailable(input->connection, LG_NET_SERVICE_INPUT, LG_NET_CAPABILITY_DATAGRAM)) status = networkConnectionSendDatagram(input->connection, LG_NET_SERVICE_INPUT, LG_NET_INPUT_MESSAGE_MOUSE_RELATIVE, input->serviceVersion, 0, input->claimEpoch, 0, payload, sizeof(payload), true); if (status != LG_TRANSPORT_OK) status = networkInputSendReliableNL(input, LG_NET_INPUT_MESSAGE_MOUSE_RELATIVE, 0, 0, payload, sizeof(payload)); if (status != LG_TRANSPORT_OK) return false; input->sequence = sequence; input->mouseAbsolute = false; return true; } static bool networkInputSendAbsoluteNL(NetworkInput * input, uint32_t x, uint32_t y, uint32_t width, uint32_t height, int32_t wheelX, int32_t wheelY, uint32_t buttons, uint32_t changedButtons, bool motion) { uint64_t sequence; if (!networkInputNextSequenceNL(input, &sequence)) return false; LGNetInputAbsolute absolute = { .sequence = sequence, .timestampNs = nanotime(), .x = (int32_t)x, .y = (int32_t)y, .width = width, .height = height, .wheelX = wheelX, .wheelY = wheelY, .buttons = buttons, .changedButtons = changedButtons, }; if (!absolute.timestampNs) absolute.timestampNs = 1; uint8_t payload[LG_NET_INPUT_ABSOLUTE_WIRE_SIZE]; if (!lgNetInputAbsoluteEncode(payload, sizeof(payload), &absolute)) return false; LG_TransportStatus status = LG_TRANSPORT_UNAVAILABLE; if (motion && networkConnectionServiceAvailable(input->connection, LG_NET_SERVICE_INPUT, LG_NET_CAPABILITY_DATAGRAM)) status = networkConnectionSendDatagram(input->connection, LG_NET_SERVICE_INPUT, LG_NET_INPUT_MESSAGE_MOUSE_ABSOLUTE, input->serviceVersion, 0, input->claimEpoch, 0, payload, sizeof(payload), true); if (status != LG_TRANSPORT_OK) status = networkInputSendReliableNL(input, LG_NET_INPUT_MESSAGE_MOUSE_ABSOLUTE, 0, 0, payload, sizeof(payload)); if (status != LG_TRANSPORT_OK) return false; input->sequence = sequence; input->mouseAbsolute = true; input->absoluteX = x; input->absoluteY = y; input->absoluteWidth = width; input->absoluteHeight = height; return true; } static bool networkInputMouseMotion(void * opaque, int32_t x, int32_t y) { NetworkInput * input = opaque; LG_LOCK(input->stateLock); const bool result = input->available && (input->claimFlags & LG_NET_INPUT_CLAIM_POINTER) && networkInputSendRelativeNL(input, x, y, 0, 0, input->mouseButtons, 0, true); LG_UNLOCK(input->stateLock); return result; } static bool networkInputMousePosition(void * opaque, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { if (!width || !height || width > LG_NET_INPUT_ABSOLUTE_MAX_WIDTH || height > LG_NET_INPUT_ABSOLUTE_MAX_HEIGHT || x >= width || y >= height) return false; NetworkInput * input = opaque; LG_LOCK(input->stateLock); const bool result = input->available && (input->claimFlags & LG_NET_INPUT_CLAIM_POINTER) && networkInputSendAbsoluteNL(input, x, y, width, height, 0, 0, input->mouseButtons, 0, true); LG_UNLOCK(input->stateLock); return result; } static uint32_t networkInputButtonMask(unsigned int button) { switch (button) { case 1: return LG_NET_POINTER_BUTTON_LEFT; case 2: return LG_NET_POINTER_BUTTON_MIDDLE; case 3: return LG_NET_POINTER_BUTTON_RIGHT; case 6: return LG_NET_POINTER_BUTTON_BACK; case 7: return LG_NET_POINTER_BUTTON_FORWARD; default: if (button >= 8 && button < 32) return UINT32_C(1) << (button - 3U); return 0; } } static bool networkInputUpdateButton(void * opaque, unsigned int button, bool down) { NetworkInput * input = opaque; bool notify = false; LG_LOCK(input->stateLock); if (!input->available || !(input->claimFlags & LG_NET_INPUT_CLAIM_POINTER)) { LG_UNLOCK(input->stateLock); return false; } int32_t wheelY = 0; if (button == 4 || button == 5) { if (!down) { LG_UNLOCK(input->stateLock); return true; } wheelY = button == 4 ? 1 : -1; } const uint32_t mask = networkInputButtonMask(button); if (!mask && !wheelY) { LG_UNLOCK(input->stateLock); return false; } const uint32_t buttons = down ? input->mouseButtons | mask : input->mouseButtons & ~mask; bool result; if (input->mouseAbsolute && input->absoluteWidth && input->absoluteHeight) result = networkInputSendAbsoluteNL(input, input->absoluteX, input->absoluteY, input->absoluteWidth, input->absoluteHeight, 0, wheelY, buttons, mask, false); else result = networkInputSendRelativeNL(input, 0, 0, 0, wheelY, buttons, mask, false); if (result && mask) input->mouseButtons = buttons; else if (!result && !down && mask) notify = networkInputAbandonClaimNL(input, true); LG_UNLOCK(input->stateLock); if (notify) networkInputNotifyStatus(input); return result; } static bool networkInputMousePress(void * opaque, unsigned int button) { return networkInputUpdateButton(opaque, button, true); } static bool networkInputMouseRelease(void * opaque, unsigned int button) { return networkInputUpdateButton(opaque, button, false); } static void networkInputReset(void * opaque) { NetworkInput * input = opaque; bool notify = false; LG_LOCK(input->stateLock); notify = networkInputSendResetNL(input); LG_UNLOCK(input->stateLock); if (notify) networkInputNotifyStatus(input); } static const LG_InputOps NETWORK_INPUT_OPS = { .name = "Network", .supports = networkInputSupports, .setStatusListener = networkInputSetStatusListener, .keyDown = networkInputKeyDown, .keyUp = networkInputKeyUp, .keyboardLEDs = networkInputKeyboardLEDs, .keyboardLEDsReset = networkInputKeyboardLEDsReset, .mouseMotion = networkInputMouseMotion, .mousePosition = networkInputMousePosition, .mousePress = networkInputMousePress, .mouseRelease = networkInputMouseRelease, .reset = networkInputReset, }; bool networkInputCreate(NetworkInput ** result, NetworkConnection * connection) { if (!result || !connection) return false; NetworkInput * input = calloc(1, sizeof(*input)); if (!input) return false; LG_LOCK_INIT(input->stateLock); input->connection = connection; input->claimantID = nanotime() ^ ((uint64_t)(uintptr_t)input << 17); input->statusGeneration = 1; if (!input->claimantID) input->claimantID = 1; input->statusIdle = lgCreateEvent(false, 0); if (!input->statusIdle) { LG_LOCK_FREE(input->stateLock); free(input); return false; } lgSignalEvent(input->statusIdle); if (!lgCreateTimer(NETWORK_INPUT_TIMER_MS, networkInputTimer, input, &input->timer)) { lgFreeEvent(input->statusIdle); LG_LOCK_FREE(input->stateLock); free(input); return false; } *result = input; return true; } void networkInputDestroy(NetworkInput ** handle) { if (!handle || !*handle) return; NetworkInput * input = *handle; LG_LOCK(input->stateLock); input->destroying = true; LG_UNLOCK(input->stateLock); lgTimerDestroy(input->timer); networkInputSetStatusListener(input, NULL, NULL); LG_LOCK(input->stateLock); networkInputSendResetNL(input); networkInputSendReleaseNL(input); input->connected = false; input->serviceAvailable = false; networkInputSetAvailableNL(input, false); LG_UNLOCK(input->stateLock); lgFreeEvent(input->statusIdle); LG_LOCK_FREE(input->stateLock); free(input); *handle = NULL; } const LG_InputOps * networkInputGetOps(void) { return &NETWORK_INPUT_OPS; } void networkInputConnectionState(NetworkInput * input, bool connected) { if (!input) return; uint16_t serviceVersion = 0; const bool serviceAvailable = connected && networkConnectionServiceAvailable(input->connection, LG_NET_SERVICE_INPUT, LG_NET_CAPABILITY_RELIABLE) && networkConnectionServiceVersion(input->connection, LG_NET_SERVICE_INPUT, &serviceVersion); bool notify = false; LG_LOCK(input->stateLock); if (!connected || !serviceAvailable) { notify |= networkInputSendResetNL(input); networkInputSendReleaseNL(input); input->connected = connected; input->serviceAvailable = false; input->serviceVersion = 0; networkInputInvalidateLEDsNL(input); notify |= networkInputSetAvailableNL(input, false); } else { input->connected = true; input->serviceAvailable = true; input->serviceVersion = serviceVersion; input->claimPending = false; input->claimed = false; input->claimFlags = 0; input->sequence = 0; input->nextClaimNs = 0; networkInputClearStateNL(input); networkInputInvalidateLEDsNL(input); notify |= networkInputSetAvailableNL(input, false); networkInputSendClaimNL(input); } LG_UNLOCK(input->stateLock); if (notify) networkInputNotifyStatus(input); } static bool networkInputHandleStatus(NetworkInput * input, const LGNetPacketView * packet) { LGNetInputStatus status; if (lgNetInputStatusDecode(&status, packet->payload, packet->payloadLength) != LG_NET_PARSE_OK) return false; if (packet->envelope.componentEpoch != status.claimEpoch) return false; bool notify = false; LG_LOCK(input->stateLock); if (!input->connected || status.claimEpoch != input->claimEpoch) { LG_UNLOCK(input->stateLock); return true; } switch (status.status) { case LG_NET_INPUT_STATUS_ACCEPTED: input->claimPending = false; input->claimed = (status.flags & NETWORK_INPUT_CLAIM_FLAGS) == NETWORK_INPUT_CLAIM_FLAGS; input->claimFlags = input->claimed ? status.flags : 0; input->nextKeepaliveNs = networkInputDeadline( status.leaseRemainingMs > NETWORK_INPUT_KEEPALIVE_MS ? NETWORK_INPUT_KEEPALIVE_MS : status.leaseRemainingMs > 1U ? status.leaseRemainingMs / 2U : 1U); if (!input->claimed) { input->nextClaimNs = networkInputDeadline( NETWORK_INPUT_CLAIM_RETRY_MS); networkInputClearStateNL(input); networkInputInvalidateLEDsNL(input); } notify = networkInputSetAvailableNL(input, input->claimed); break; case LG_NET_INPUT_STATUS_UNSUPPORTED: input->claimPending = false; input->claimed = false; input->claimFlags = 0; input->serviceAvailable = false; networkInputClearStateNL(input); networkInputInvalidateLEDsNL(input); notify = networkInputSetAvailableNL(input, false); break; case LG_NET_INPUT_STATUS_REJECTED: case LG_NET_INPUT_STATUS_RELEASED: case LG_NET_INPUT_STATUS_EXPIRED: case LG_NET_INPUT_STATUS_ERROR: input->claimPending = false; input->claimed = false; input->claimFlags = 0; networkInputClearStateNL(input); networkInputInvalidateLEDsNL(input); input->nextClaimNs = networkInputDeadline( NETWORK_INPUT_CLAIM_RETRY_MS); notify = networkInputSetAvailableNL(input, false); break; } LG_UNLOCK(input->stateLock); if (notify) networkInputNotifyStatus(input); return true; } static bool networkInputHandleLEDs(NetworkInput * input, const LGNetPacketView * packet) { LGNetInputLEDs leds; if (lgNetInputLEDsDecode(&leds, packet->payload, packet->payloadLength) != LG_NET_PARSE_OK) return false; bool notify = false; LG_LOCK(input->stateLock); if (!input->connected || !input->claimed || packet->envelope.componentEpoch != input->claimEpoch || leds.sequence <= input->keyboardLEDSequence) { LG_UNLOCK(input->stateLock); return true; } const bool oldValid = input->keyboardLEDsValid; const uint8_t oldState = input->keyboardLEDs; const uint32_t oldValidMask = input->keyboardLEDsValidMask; const uint32_t mergedState = (input->keyboardLEDs & ~leds.validMask) | leds.state; const uint32_t changed = (input->keyboardLEDs ^ mergedState) & leds.validMask & oldValidMask; input->keyboardLEDSequence = leds.sequence; input->keyboardLEDsValidMask |= leds.validMask; input->keyboardLEDs = (uint8_t)mergedState; input->keyboardLEDsValid = (input->keyboardLEDsValidMask & NETWORK_INPUT_LED_MASK) == NETWORK_INPUT_LED_MASK; networkInputAcknowledgeLEDsNL(input, changed); networkInputSyncKeyboardLEDsNL(input, ¬ify); notify |= oldValid != input->keyboardLEDsValid || oldState != input->keyboardLEDs; if (notify) input->statusNotifyPending = true; LG_UNLOCK(input->stateLock); if (notify) networkInputNotifyStatus(input); return true; } bool networkInputPacket(NetworkInput * input, const LGNetPacketView * packet, bool datagram) { if (!input || !packet || datagram || packet->envelope.service != LG_NET_SERVICE_INPUT) return false; switch (packet->envelope.messageType) { case LG_NET_INPUT_MESSAGE_CLAIM_RESULT: if (!(packet->envelope.flags & LG_NET_ENVELOPE_RESPONSE) || packet->envelope.requestID != packet->envelope.componentEpoch) return false; return networkInputHandleStatus(input, packet); case LG_NET_INPUT_MESSAGE_STATUS: return networkInputHandleStatus(input, packet); case LG_NET_INPUT_MESSAGE_KEYBOARD_LEDS: return networkInputHandleLEDs(input, packet); default: return false; } }