mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-09-01 19:53:54 +00:00
Some checks failed
build / client (Debug, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / module (push) Has been cancelled
build / host-linux (push) Has been cancelled
build / host-windows-cross (push) Has been cancelled
build / host-windows-native (push) Has been cancelled
build / idd (push) Has been cancelled
build / obs (clang) (push) Has been cancelled
build / obs (gcc) (push) Has been cancelled
build / docs (push) Has been cancelled
Add LGProtocol as a pinned submodule and consume its KVMFR protocol definitions throughout the client, host, IDD, OBS, and profiler. Keep Looking Glass framebuffer helpers local while removing duplicated protocol headers and migrating users to KVMFR-scoped types.
2089 lines
59 KiB
C
2089 lines
59 KiB
C
/**
|
|
* 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 <LGProtocol/KVMFRInput.h>
|
|
#include <LGProtocol/LGMPConfig.h>
|
|
#include "common/debug.h"
|
|
#include "common/event.h"
|
|
#include "common/locking.h"
|
|
#include "common/thread.h"
|
|
#include "common/time.h"
|
|
|
|
#include <lgmp/stream.h>
|
|
|
|
#include <inttypes.h>
|
|
#include <limits.h>
|
|
#include <stdatomic.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define INPUT_PENDING_LENGTH 128
|
|
#define INPUT_MOTION_COALESCE_THRESHOLD (INPUT_PENDING_LENGTH / 2)
|
|
#define INPUT_PENDING_RESERVED (INPUT_PENDING_LENGTH / 2)
|
|
#define INPUT_KEEPALIVE_US 200000
|
|
#define INPUT_IDLE_RELEASE_US 400000
|
|
#define INPUT_RELEASE_TIMEOUT_US 50000
|
|
#define INPUT_KEYBOARD_LED_SYNC_DELAY_US 20000
|
|
#define INPUT_KEYBOARD_LED_ACK_TIMEOUT_US 500000
|
|
#define INPUT_WORKER_IDLE_MS 10
|
|
#define INPUT_WORKER_RETRY_MS 1
|
|
#define INPUT_STATS_INTERVAL_US 5000000
|
|
#define INPUT_MAX_SPLIT_REPORTS 4
|
|
#define INPUT_MOUSE_DELTA_MIN \
|
|
(INT16_MIN * INPUT_MAX_SPLIT_REPORTS)
|
|
#define INPUT_MOUSE_DELTA_MAX \
|
|
(INT16_MAX * INPUT_MAX_SPLIT_REPORTS)
|
|
|
|
_Static_assert(
|
|
(int)LG_KEYBOARD_LED_NUM_LOCK ==
|
|
(int)KVMFR_INPUT_KEYBOARD_LED_NUM_LOCK &&
|
|
(int)LG_KEYBOARD_LED_CAPS_LOCK ==
|
|
(int)KVMFR_INPUT_KEYBOARD_LED_CAPS_LOCK &&
|
|
(int)LG_KEYBOARD_LED_SCROLL_LOCK ==
|
|
(int)KVMFR_INPUT_KEYBOARD_LED_SCROLL_LOCK &&
|
|
(int)LG_KEYBOARD_LED_COMPOSE ==
|
|
(int)KVMFR_INPUT_KEYBOARD_LED_COMPOSE &&
|
|
(int)LG_KEYBOARD_LED_KANA == (int)KVMFR_INPUT_KEYBOARD_LED_KANA,
|
|
"Client and KVMFR keyboard LED bits differ");
|
|
|
|
enum LGMPInputMouseMode
|
|
{
|
|
LGMP_INPUT_MOUSE_NONE,
|
|
LGMP_INPUT_MOUSE_RELATIVE,
|
|
LGMP_INPUT_MOUSE_ABSOLUTE,
|
|
};
|
|
|
|
struct LGMPInputLockKey
|
|
{
|
|
int key;
|
|
uint8_t led;
|
|
};
|
|
|
|
static const struct LGMPInputLockKey LOCK_KEYS[] =
|
|
{
|
|
{ KEY_NUMLOCK, LG_KEYBOARD_LED_NUM_LOCK },
|
|
{ KEY_CAPSLOCK, LG_KEYBOARD_LED_CAPS_LOCK },
|
|
{ KEY_SCROLLLOCK, LG_KEYBOARD_LED_SCROLL_LOCK },
|
|
};
|
|
|
|
static const uint8_t LOCK_LED_MASK =
|
|
LG_KEYBOARD_LED_NUM_LOCK |
|
|
LG_KEYBOARD_LED_CAPS_LOCK |
|
|
LG_KEYBOARD_LED_SCROLL_LOCK;
|
|
|
|
struct LGMPInputPending
|
|
{
|
|
KVMFRInputMessage message;
|
|
uint8_t keyboardLEDToggle;
|
|
bool pureMotion;
|
|
bool keyboardLEDSync;
|
|
};
|
|
|
|
struct LGMPInputCounters
|
|
{
|
|
uint64_t immediateSends;
|
|
uint64_t deferredSends;
|
|
uint64_t localEnqueues;
|
|
uint64_t initialFull;
|
|
uint64_t retryFull;
|
|
uint64_t relativeCoalesces;
|
|
uint64_t absoluteCoalesces;
|
|
uint64_t reservedRejects;
|
|
uint64_t motionEvictions;
|
|
uint64_t discreteOverflowFailures;
|
|
uint64_t discreteOverflowResets;
|
|
uint64_t claims;
|
|
uint64_t releases;
|
|
uint64_t keepalives;
|
|
uint64_t terminalFailures;
|
|
unsigned pendingHighWater;
|
|
};
|
|
|
|
struct LGMPInputStats
|
|
{
|
|
struct LGMPInputCounters counters;
|
|
uint64_t lastReport;
|
|
};
|
|
|
|
struct LGMPInput
|
|
{
|
|
PLGMPClient client;
|
|
PLGMPClientQueue queue;
|
|
PLGMPClientStream stream;
|
|
LG_Lock lock;
|
|
LGEvent * event;
|
|
LGThread * thread;
|
|
atomic_bool stop;
|
|
bool connected;
|
|
bool claimed;
|
|
bool available;
|
|
bool ownerBlocked;
|
|
bool keyboardLEDsValid;
|
|
uint8_t keyboardLEDs;
|
|
bool desiredKeyboardLEDsValid;
|
|
uint8_t desiredKeyboardLEDs;
|
|
uint8_t staleKeyboardLEDs;
|
|
uint8_t pendingKeyboardLEDs;
|
|
uint32_t keyboardLEDTransitions;
|
|
uint64_t keyboardLEDsSyncAfter;
|
|
uint64_t keyboardLEDsPendingUntil;
|
|
uint64_t keyboardLEDsStaleUntil;
|
|
|
|
uint32_t generation;
|
|
uint32_t sequence;
|
|
uint32_t publishedGeneration;
|
|
uint32_t publishedSequence;
|
|
bool publishedClaimed;
|
|
uint64_t lastSend;
|
|
|
|
struct LGMPInputPending pending[INPUT_PENDING_LENGTH];
|
|
unsigned pendingHead;
|
|
unsigned pendingCount;
|
|
|
|
enum LGMPInputMouseMode mouseMode;
|
|
uint32_t mouseButtons;
|
|
uint16_t absoluteX;
|
|
uint16_t absoluteY;
|
|
uint8_t keyState[KVMFR_INPUT_KEYBOARD_USAGE_MAX + 1];
|
|
uint64_t lastInput;
|
|
|
|
uint32_t clientID;
|
|
uint32_t capabilities;
|
|
KVMFRInputStreamEndpoint streamEndpoint;
|
|
uint32_t endpointGeneration;
|
|
uint32_t statusSerial;
|
|
uint32_t statusOwnerClientID;
|
|
uint32_t statusOwnerGeneration;
|
|
bool ownerConfirmed;
|
|
bool streamEndpointBound;
|
|
bool statusValid;
|
|
bool notifyStatus;
|
|
|
|
LG_InputStatusFn statusCallback;
|
|
void * statusOpaque;
|
|
|
|
struct LGMPInputStats stats;
|
|
};
|
|
|
|
static bool 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 bool release(LGMPInput * input, bool * wake);
|
|
static void discardProtocolState(LGMPInput * input);
|
|
|
|
static uint8_t keyboardLEDForKey(int key)
|
|
{
|
|
for (size_t i = 0; i < sizeof(LOCK_KEYS) / sizeof(*LOCK_KEYS); ++i)
|
|
if (LOCK_KEYS[i].key == key)
|
|
return LOCK_KEYS[i].led;
|
|
return 0;
|
|
}
|
|
|
|
static uint8_t projectedKeyboardLEDs(const LGMPInput * input)
|
|
{
|
|
return input->keyboardLEDs ^ input->pendingKeyboardLEDs;
|
|
}
|
|
|
|
static void invalidateKeyboardLEDProjection(
|
|
LGMPInput * input, uint64_t now, uint64_t delay)
|
|
{
|
|
input->pendingKeyboardLEDs = 0;
|
|
input->keyboardLEDTransitions = 0;
|
|
input->keyboardLEDsPendingUntil = 0;
|
|
input->keyboardLEDsSyncAfter =
|
|
now + delay;
|
|
}
|
|
|
|
static struct LGMPInputPending * pendingAt(
|
|
LGMPInput * input, unsigned position)
|
|
{
|
|
return &input->pending[
|
|
(input->pendingHead + position) % INPUT_PENDING_LENGTH];
|
|
}
|
|
|
|
static bool markPendingKeyboardLEDSync(
|
|
LGMPInput * input, uint32_t sequence)
|
|
{
|
|
if (!input->pendingCount)
|
|
return false;
|
|
|
|
struct LGMPInputPending * item = pendingAt(
|
|
input, input->pendingCount - 1);
|
|
if (item->message.generation != input->generation ||
|
|
item->message.sequence != sequence ||
|
|
item->message.type != KVMFR_INPUT_MESSAGE_KEYBOARD)
|
|
return false;
|
|
|
|
item->keyboardLEDSync = true;
|
|
return true;
|
|
}
|
|
|
|
static bool markPendingKeyboardLEDToggle(
|
|
LGMPInput * input, uint32_t sequence, uint8_t led)
|
|
{
|
|
if (!input->pendingCount)
|
|
return false;
|
|
|
|
struct LGMPInputPending * item = pendingAt(
|
|
input, input->pendingCount - 1);
|
|
if (item->message.generation != input->generation ||
|
|
item->message.sequence != sequence ||
|
|
item->message.type != KVMFR_INPUT_MESSAGE_KEYBOARD)
|
|
return false;
|
|
|
|
item->keyboardLEDToggle ^= led;
|
|
return true;
|
|
}
|
|
|
|
static bool pendingKeyboardLEDSync(LGMPInput * input)
|
|
{
|
|
for (unsigned i = 0; i < input->pendingCount; ++i)
|
|
if (pendingAt(input, i)->keyboardLEDSync)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static uint8_t pendingKeyboardLEDToggles(LGMPInput * input)
|
|
{
|
|
uint8_t toggles = 0;
|
|
for (unsigned i = 0; i < input->pendingCount; ++i)
|
|
toggles ^= pendingAt(input, i)->keyboardLEDToggle;
|
|
return toggles;
|
|
}
|
|
|
|
static unsigned keyboardLEDToggleCount(uint8_t toggles)
|
|
{
|
|
unsigned count = 0;
|
|
while (toggles)
|
|
{
|
|
count += toggles & 1U;
|
|
toggles >>= 1;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static uint32_t queuedKeyboardLEDTransitions(LGMPInput * input)
|
|
{
|
|
uint32_t count = 0;
|
|
for (unsigned i = 0; i < input->pendingCount; ++i)
|
|
count += keyboardLEDToggleCount(
|
|
pendingAt(input, i)->keyboardLEDToggle);
|
|
return count;
|
|
}
|
|
|
|
static uint32_t publishedKeyboardLEDTransitions(LGMPInput * input)
|
|
{
|
|
const uint32_t queued = queuedKeyboardLEDTransitions(input);
|
|
return input->keyboardLEDTransitions > queued ?
|
|
input->keyboardLEDTransitions - queued : 0;
|
|
}
|
|
|
|
static void retainQueuedKeyboardLEDProjection(LGMPInput * input)
|
|
{
|
|
input->pendingKeyboardLEDs = pendingKeyboardLEDToggles(input);
|
|
input->keyboardLEDTransitions = queuedKeyboardLEDTransitions(input);
|
|
input->keyboardLEDsPendingUntil = 0;
|
|
}
|
|
|
|
static uint8_t publishedKeyboardLEDToggles(LGMPInput * input)
|
|
{
|
|
return input->pendingKeyboardLEDs ^ pendingKeyboardLEDToggles(input);
|
|
}
|
|
|
|
static void updateKeyboardLEDPendingDeadline(
|
|
LGMPInput * input, uint64_t now)
|
|
{
|
|
input->keyboardLEDsPendingUntil =
|
|
publishedKeyboardLEDTransitions(input) ?
|
|
now + INPUT_KEYBOARD_LED_ACK_TIMEOUT_US : 0;
|
|
}
|
|
|
|
static LG_InputStatus inputStatus(const LGMPInput * input)
|
|
{
|
|
return (LG_InputStatus)
|
|
{
|
|
.available = input->available,
|
|
.generation = input->endpointGeneration,
|
|
.keyboardLEDsValid = input->available && input->keyboardLEDsValid,
|
|
.keyboardLEDs = input->keyboardLEDs,
|
|
};
|
|
}
|
|
|
|
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)
|
|
{
|
|
input->publishedGeneration = message->generation;
|
|
input->publishedSequence = message->sequence;
|
|
input->lastSend = microtime();
|
|
|
|
switch (message->type)
|
|
{
|
|
case KVMFR_INPUT_MESSAGE_CLAIM:
|
|
input->publishedClaimed = true;
|
|
++input->stats.counters.claims;
|
|
break;
|
|
|
|
case KVMFR_INPUT_MESSAGE_RELEASE:
|
|
input->publishedClaimed = false;
|
|
++input->stats.counters.releases;
|
|
break;
|
|
|
|
case KVMFR_INPUT_MESSAGE_KEEPALIVE:
|
|
++input->stats.counters.keepalives;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void detachInputStream(LGMPInput * input)
|
|
{
|
|
if (input->stream)
|
|
lgmpClientStreamDetach(&input->stream);
|
|
input->streamEndpointBound = false;
|
|
}
|
|
|
|
static void connectionFailed(LGMPInput * input, LGMP_STATUS status)
|
|
{
|
|
if (input->connected)
|
|
{
|
|
DEBUG_WARN("LGMP input transport failed: %s", lgmpStatusString(status));
|
|
++input->stats.counters.terminalFailures;
|
|
}
|
|
|
|
if (input->available || input->endpointGeneration ||
|
|
input->keyboardLEDsValid)
|
|
input->notifyStatus = true;
|
|
input->connected = false;
|
|
input->claimed = false;
|
|
input->available = false;
|
|
input->ownerBlocked = false;
|
|
input->keyboardLEDsValid = false;
|
|
input->keyboardLEDs = 0;
|
|
input->pendingKeyboardLEDs = 0;
|
|
input->keyboardLEDTransitions = 0;
|
|
input->staleKeyboardLEDs = 0;
|
|
input->keyboardLEDsSyncAfter = 0;
|
|
input->keyboardLEDsPendingUntil = 0;
|
|
input->keyboardLEDsStaleUntil = 0;
|
|
input->pendingHead = 0;
|
|
input->pendingCount = 0;
|
|
input->mouseMode = LGMP_INPUT_MOUSE_NONE;
|
|
input->mouseButtons = 0;
|
|
input->publishedClaimed = false;
|
|
input->lastInput = 0;
|
|
input->capabilities = 0;
|
|
input->streamEndpointBound = false;
|
|
detachInputStream(input);
|
|
memset(&input->streamEndpoint, 0,
|
|
sizeof(input->streamEndpoint));
|
|
input->statusValid = false;
|
|
input->ownerConfirmed = false;
|
|
memset(input->keyState, 0, sizeof(input->keyState));
|
|
atomic_store_explicit(&input->stop, true, memory_order_release);
|
|
}
|
|
|
|
static LGMP_STATUS trySend(LGMPInput * input,
|
|
const KVMFRInputMessage * message, bool deferred)
|
|
{
|
|
if (!input->stream)
|
|
return LGMP_ERR_STREAM_UNBOUND;
|
|
|
|
LGMPStreamBuffer buffer = { 0 };
|
|
LGMP_STATUS status = lgmpClientStreamWriteAcquire(
|
|
input->stream, &buffer);
|
|
if (status == LGMP_OK)
|
|
{
|
|
if (buffer.capacity < sizeof(*message))
|
|
{
|
|
lgmpClientStreamWriteCancel(input->stream, &buffer);
|
|
status = LGMP_ERR_INVALID_SIZE;
|
|
}
|
|
else
|
|
{
|
|
memcpy(buffer.data, message, sizeof(*message));
|
|
status = lgmpClientStreamWriteCommit(input->stream,
|
|
&buffer, sizeof(*message));
|
|
if (status != LGMP_OK)
|
|
lgmpClientStreamWriteCancel(input->stream, &buffer);
|
|
}
|
|
}
|
|
|
|
if (status == LGMP_OK)
|
|
{
|
|
if (deferred)
|
|
++input->stats.counters.deferredSends;
|
|
else
|
|
++input->stats.counters.immediateSends;
|
|
return status;
|
|
}
|
|
|
|
if (status == LGMP_ERR_STREAM_FULL)
|
|
{
|
|
if (deferred)
|
|
++input->stats.counters.retryFull;
|
|
else
|
|
++input->stats.counters.initialFull;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
static bool retryableSendStatus(LGMP_STATUS status)
|
|
{
|
|
return status == LGMP_ERR_STREAM_FULL;
|
|
}
|
|
|
|
static void sendFailed(LGMPInput * input, LGMP_STATUS status)
|
|
{
|
|
if (status == LGMP_ERR_STREAM_UNBOUND ||
|
|
status == LGMP_ERR_STREAM_STALE)
|
|
{
|
|
DEBUG_WARN("LGMP input stream binding was lost: %s",
|
|
lgmpStatusString(status));
|
|
const bool notify = input->available || input->endpointGeneration;
|
|
detachInputStream(input);
|
|
discardProtocolState(input);
|
|
input->available = false;
|
|
if (notify)
|
|
input->notifyStatus = true;
|
|
return;
|
|
}
|
|
|
|
connectionFailed(input, status);
|
|
}
|
|
|
|
static bool coalesceMotion(LGMPInput * input,
|
|
KVMFRInputMessageType type, const KVMFRInputPayload * payload)
|
|
{
|
|
if (input->pendingCount < INPUT_MOTION_COALESCE_THRESHOLD)
|
|
return false;
|
|
|
|
struct LGMPInputPending * tail = pendingAt(
|
|
input, input->pendingCount - 1);
|
|
if (!tail->pureMotion || tail->message.type != type ||
|
|
tail->message.generation != input->generation)
|
|
return false;
|
|
|
|
if (type == KVMFR_INPUT_MESSAGE_MOUSE_ABSOLUTE)
|
|
{
|
|
tail->message.payload = *payload;
|
|
++input->stats.counters.absoluteCoalesces;
|
|
return true;
|
|
}
|
|
|
|
if (type != KVMFR_INPUT_MESSAGE_MOUSE_RELATIVE ||
|
|
tail->message.payload.mouseRelative.buttons !=
|
|
payload->mouseRelative.buttons)
|
|
return false;
|
|
|
|
const int64_t x =
|
|
(int64_t)tail->message.payload.mouseRelative.deltaX +
|
|
payload->mouseRelative.deltaX;
|
|
const int64_t y =
|
|
(int64_t)tail->message.payload.mouseRelative.deltaY +
|
|
payload->mouseRelative.deltaY;
|
|
if (x < INPUT_MOUSE_DELTA_MIN || x > INPUT_MOUSE_DELTA_MAX ||
|
|
y < INPUT_MOUSE_DELTA_MIN || y > INPUT_MOUSE_DELTA_MAX)
|
|
return false;
|
|
|
|
tail->message.payload.mouseRelative.deltaX = (int32_t)x;
|
|
tail->message.payload.mouseRelative.deltaY = (int32_t)y;
|
|
++input->stats.counters.relativeCoalesces;
|
|
return true;
|
|
}
|
|
|
|
static bool discardPendingMotion(LGMPInput * input)
|
|
{
|
|
for (unsigned i = input->pendingCount; i > 0; --i)
|
|
{
|
|
const unsigned position = i - 1;
|
|
struct LGMPInputPending * item = pendingAt(input, position);
|
|
if (!item->pureMotion)
|
|
continue;
|
|
|
|
const uint32_t generation = item->message.generation;
|
|
for (unsigned j = position; j + 1 < input->pendingCount; ++j)
|
|
*pendingAt(input, j) = *pendingAt(input, j + 1);
|
|
--input->pendingCount;
|
|
|
|
for (unsigned j = position; j < input->pendingCount; ++j)
|
|
{
|
|
item = pendingAt(input, j);
|
|
if (item->message.generation == generation)
|
|
item->message.sequence = item->message.sequence == 1 ?
|
|
UINT32_MAX : item->message.sequence - 1;
|
|
}
|
|
|
|
if (generation == input->generation)
|
|
input->sequence = input->sequence == 1 ?
|
|
UINT32_MAX : input->sequence - 1;
|
|
++input->stats.counters.motionEvictions;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool submitPayload(LGMPInput * input, KVMFRInputMessageType type,
|
|
const KVMFRInputPayload * payload, bool pureMotion, bool * wake)
|
|
{
|
|
if (!input->connected || !input->stream)
|
|
return false;
|
|
|
|
const bool inputMessage =
|
|
type == KVMFR_INPUT_MESSAGE_MOUSE_RELATIVE ||
|
|
type == KVMFR_INPUT_MESSAGE_MOUSE_ABSOLUTE ||
|
|
type == KVMFR_INPUT_MESSAGE_KEYBOARD;
|
|
|
|
if (pureMotion && coalesceMotion(input, type, payload))
|
|
{
|
|
input->lastInput = microtime();
|
|
return true;
|
|
}
|
|
if (pureMotion && input->pendingCount >=
|
|
INPUT_PENDING_LENGTH - INPUT_PENDING_RESERVED)
|
|
{
|
|
++input->stats.counters.reservedRejects;
|
|
return false;
|
|
}
|
|
if (!pureMotion && input->pendingCount == INPUT_PENDING_LENGTH &&
|
|
!discardPendingMotion(input))
|
|
{
|
|
++input->stats.counters.discreteOverflowFailures;
|
|
return false;
|
|
}
|
|
|
|
const uint32_t previousSequence = input->sequence;
|
|
if (++input->sequence == 0)
|
|
input->sequence = 1;
|
|
|
|
KVMFRInputMessage message =
|
|
{
|
|
.type = type,
|
|
.generation = input->generation,
|
|
.sequence = input->sequence,
|
|
.payload = *payload,
|
|
};
|
|
|
|
if (!input->pendingCount)
|
|
{
|
|
const LGMP_STATUS status = trySend(input, &message, false);
|
|
if (status == LGMP_OK)
|
|
{
|
|
published(input, &message);
|
|
if (inputMessage)
|
|
input->lastInput = microtime();
|
|
return true;
|
|
}
|
|
|
|
if (!retryableSendStatus(status))
|
|
{
|
|
input->sequence = previousSequence;
|
|
sendFailed(input, status);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (input->pendingCount == INPUT_PENDING_LENGTH)
|
|
{
|
|
input->sequence = previousSequence;
|
|
return false;
|
|
}
|
|
|
|
struct LGMPInputPending * item = pendingAt(input, input->pendingCount++);
|
|
item->message = message;
|
|
item->keyboardLEDToggle = 0;
|
|
item->pureMotion = pureMotion;
|
|
item->keyboardLEDSync = false;
|
|
++input->stats.counters.localEnqueues;
|
|
if (input->pendingCount > input->stats.counters.pendingHighWater)
|
|
input->stats.counters.pendingHighWater = input->pendingCount;
|
|
if (inputMessage)
|
|
input->lastInput = microtime();
|
|
*wake = true;
|
|
return true;
|
|
}
|
|
|
|
static bool claim(LGMPInput * input, bool * wake)
|
|
{
|
|
if (input->claimed)
|
|
return true;
|
|
if (!input->available || input->ownerBlocked ||
|
|
!input->stream || !input->streamEndpointBound)
|
|
return false;
|
|
|
|
if (++input->generation == 0)
|
|
++input->generation;
|
|
input->sequence = 0;
|
|
input->claimed = true;
|
|
input->ownerConfirmed = false;
|
|
|
|
const KVMFRInputPayload payload = { 0 };
|
|
if (submitPayload(input, KVMFR_INPUT_MESSAGE_CLAIM,
|
|
&payload, false, wake))
|
|
return true;
|
|
|
|
input->claimed = false;
|
|
return false;
|
|
}
|
|
|
|
static bool keyboardStateHeld(const LGMPInput * input)
|
|
{
|
|
for (unsigned usage = 1;
|
|
usage <= KVMFR_INPUT_KEYBOARD_USAGE_MAX; ++usage)
|
|
if (input->keyState[usage])
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static void recordKeyboardToggle(LGMPInput * input, int key)
|
|
{
|
|
const uint8_t led = keyboardLEDForKey(key);
|
|
if (!led)
|
|
return;
|
|
|
|
const uint64_t now = microtime();
|
|
if (input->keyboardLEDsValid)
|
|
{
|
|
input->pendingKeyboardLEDs ^= led;
|
|
if (input->keyboardLEDTransitions != UINT32_MAX)
|
|
++input->keyboardLEDTransitions;
|
|
markPendingKeyboardLEDToggle(input, input->sequence, led);
|
|
updateKeyboardLEDPendingDeadline(input, now);
|
|
}
|
|
|
|
if (!input->desiredKeyboardLEDsValid ||
|
|
((projectedKeyboardLEDs(input) ^ input->desiredKeyboardLEDs) & led))
|
|
{
|
|
input->staleKeyboardLEDs |= led;
|
|
input->keyboardLEDsStaleUntil =
|
|
now + INPUT_KEYBOARD_LED_ACK_TIMEOUT_US;
|
|
}
|
|
else
|
|
{
|
|
input->staleKeyboardLEDs &= ~led;
|
|
if (!input->staleKeyboardLEDs)
|
|
input->keyboardLEDsStaleUntil = 0;
|
|
}
|
|
input->keyboardLEDsSyncAfter = now + INPUT_KEYBOARD_LED_SYNC_DELAY_US;
|
|
}
|
|
|
|
static bool queueKeyboardTap(LGMPInput * input,
|
|
const struct LGMPInputLockKey * lockKey, bool * wake)
|
|
{
|
|
const uint8_t usage = linux_to_hid[lockKey->key];
|
|
if (!usage || input->keyState[usage] || !claim(input, wake))
|
|
return false;
|
|
|
|
input->keyState[usage] = 1;
|
|
KVMFRInputPayload payload = { 0 };
|
|
buildKeyboardPayload(input, &payload);
|
|
if (!submitPayload(input, KVMFR_INPUT_MESSAGE_KEYBOARD,
|
|
&payload, false, wake))
|
|
{
|
|
input->keyState[usage] = 0;
|
|
return false;
|
|
}
|
|
markPendingKeyboardLEDSync(input, input->sequence);
|
|
markPendingKeyboardLEDToggle(input, input->sequence, lockKey->led);
|
|
|
|
input->keyState[usage] = 0;
|
|
payload = (KVMFRInputPayload) { 0 };
|
|
buildKeyboardPayload(input, &payload);
|
|
if (!submitPayload(input, KVMFR_INPUT_MESSAGE_KEYBOARD,
|
|
&payload, false, wake))
|
|
{
|
|
invalidateKeyboardLEDProjection(input, microtime(),
|
|
INPUT_KEYBOARD_LED_ACK_TIMEOUT_US);
|
|
if (input->connected)
|
|
release(input, wake);
|
|
return false;
|
|
}
|
|
markPendingKeyboardLEDSync(input, input->sequence);
|
|
|
|
input->pendingKeyboardLEDs ^= lockKey->led;
|
|
if (input->keyboardLEDTransitions != UINT32_MAX)
|
|
++input->keyboardLEDTransitions;
|
|
updateKeyboardLEDPendingDeadline(input, microtime());
|
|
return true;
|
|
}
|
|
|
|
static void syncKeyboardLEDs(LGMPInput * input,
|
|
uint64_t now, bool * wake)
|
|
{
|
|
if (publishedKeyboardLEDTransitions(input) &&
|
|
now >= input->keyboardLEDsPendingUntil)
|
|
{
|
|
input->pendingKeyboardLEDs = pendingKeyboardLEDToggles(input);
|
|
input->keyboardLEDTransitions =
|
|
queuedKeyboardLEDTransitions(input);
|
|
input->keyboardLEDsPendingUntil = 0;
|
|
input->keyboardLEDsSyncAfter =
|
|
now + INPUT_KEYBOARD_LED_SYNC_DELAY_US;
|
|
}
|
|
|
|
if (input->staleKeyboardLEDs &&
|
|
now >= input->keyboardLEDsStaleUntil)
|
|
{
|
|
input->desiredKeyboardLEDsValid = false;
|
|
input->staleKeyboardLEDs = 0;
|
|
input->keyboardLEDsStaleUntil = 0;
|
|
}
|
|
|
|
if (!input->connected || !input->available || input->ownerBlocked ||
|
|
!input->keyboardLEDsValid || !input->desiredKeyboardLEDsValid ||
|
|
input->keyboardLEDTransitions ||
|
|
now < input->keyboardLEDsSyncAfter)
|
|
return;
|
|
|
|
const uint8_t mismatch =
|
|
(projectedKeyboardLEDs(input) ^ input->desiredKeyboardLEDs) &
|
|
LOCK_LED_MASK & ~input->staleKeyboardLEDs;
|
|
if (!mismatch)
|
|
{
|
|
if (!input->keyboardLEDTransitions && !input->staleKeyboardLEDs)
|
|
input->desiredKeyboardLEDsValid = false;
|
|
return;
|
|
}
|
|
if (keyboardStateHeld(input))
|
|
return;
|
|
|
|
unsigned messageCount = input->claimed ? 0 : 1;
|
|
for (size_t i = 0; i < sizeof(LOCK_KEYS) / sizeof(*LOCK_KEYS); ++i)
|
|
if (mismatch & LOCK_KEYS[i].led)
|
|
messageCount += 2;
|
|
if (messageCount > INPUT_PENDING_LENGTH - input->pendingCount)
|
|
return;
|
|
|
|
for (size_t i = 0; i < sizeof(LOCK_KEYS) / sizeof(*LOCK_KEYS); ++i)
|
|
if ((mismatch & LOCK_KEYS[i].led) &&
|
|
!queueKeyboardTap(input, &LOCK_KEYS[i], wake))
|
|
break;
|
|
|
|
input->keyboardLEDsSyncAfter =
|
|
now + INPUT_KEYBOARD_LED_SYNC_DELAY_US;
|
|
}
|
|
|
|
static void clearInputState(LGMPInput * input)
|
|
{
|
|
input->mouseMode = LGMP_INPUT_MOUSE_NONE;
|
|
input->mouseButtons = 0;
|
|
input->absoluteX = 0;
|
|
input->absoluteY = 0;
|
|
memset(input->keyState, 0, sizeof(input->keyState));
|
|
}
|
|
|
|
static bool inputStateHeld(const LGMPInput * input)
|
|
{
|
|
if (input->mouseButtons)
|
|
return true;
|
|
|
|
for (unsigned usage = 1;
|
|
usage <= KVMFR_INPUT_KEYBOARD_USAGE_MAX; ++usage)
|
|
if (input->keyState[usage])
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static void discardProtocolState(LGMPInput * input)
|
|
{
|
|
invalidateKeyboardLEDProjection(input, microtime(),
|
|
INPUT_KEYBOARD_LED_ACK_TIMEOUT_US);
|
|
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 (!inputStateHeld(input))
|
|
return true;
|
|
if (!claim(input, wake))
|
|
return false;
|
|
|
|
KVMFRInputPayload keyboard = { 0 };
|
|
const bool keyboardHeld = buildKeyboardPayload(input, &keyboard);
|
|
if (keyboardHeld && !submitPayload(input, KVMFR_INPUT_MESSAGE_KEYBOARD,
|
|
&keyboard, false, wake))
|
|
return false;
|
|
if (keyboardHeld)
|
|
for (size_t i = 0; i < sizeof(LOCK_KEYS) / sizeof(*LOCK_KEYS); ++i)
|
|
{
|
|
const uint8_t usage = linux_to_hid[LOCK_KEYS[i].key];
|
|
if (usage && input->keyState[usage])
|
|
recordKeyboardToggle(input, LOCK_KEYS[i].key);
|
|
}
|
|
|
|
if (input->mouseButtons &&
|
|
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 commonFlags =
|
|
KVMFR_INPUT_STATUS_AVAILABLE |
|
|
KVMFR_INPUT_STATUS_HAS_OWNER;
|
|
static const uint8_t keyboardLEDs =
|
|
KVMFR_INPUT_KEYBOARD_LED_NUM_LOCK |
|
|
KVMFR_INPUT_KEYBOARD_LED_CAPS_LOCK |
|
|
KVMFR_INPUT_KEYBOARD_LED_SCROLL_LOCK |
|
|
KVMFR_INPUT_KEYBOARD_LED_COMPOSE |
|
|
KVMFR_INPUT_KEYBOARD_LED_KANA;
|
|
|
|
const bool supportedVersion = status->version > 0 &&
|
|
status->version <= KVMFR_INPUT_VERSION;
|
|
const bool keyboardLEDsVersion =
|
|
status->version >= KVMFR_INPUT_KEYBOARD_LEDS_VERSION;
|
|
const uint32_t flags = commonFlags |
|
|
(keyboardLEDsVersion ?
|
|
KVMFR_INPUT_STATUS_KEYBOARD_LEDS_VALID : 0);
|
|
|
|
if (!supportedVersion ||
|
|
status->capabilities & ~capabilities ||
|
|
status->flags & ~flags ||
|
|
!status->generation ||
|
|
!status->lease || !status->maxButtons ||
|
|
status->maxButtons > KVMFR_INPUT_MOUSE_BUTTON_COUNT ||
|
|
status->streamVersion != KVMFR_INPUT_STREAM_VERSION ||
|
|
status->streamEndpointCount !=
|
|
KVMFR_INPUT_STREAM_ENDPOINT_COUNT ||
|
|
!status->streamGeneration)
|
|
return false;
|
|
|
|
if (status->keyboardLEDs & ~keyboardLEDs)
|
|
return false;
|
|
for (size_t i = 0; i < sizeof(status->statusReserved); ++i)
|
|
if (status->statusReserved[i])
|
|
return false;
|
|
|
|
for (size_t i = 0; i < sizeof(status->streamReserved) /
|
|
sizeof(status->streamReserved[0]); ++i)
|
|
if (status->streamReserved[i])
|
|
return false;
|
|
|
|
for (unsigned i = 0;
|
|
i < KVMFR_INPUT_STREAM_ENDPOINT_COUNT; ++i)
|
|
{
|
|
const KVMFRInputStreamEndpoint * endpoint =
|
|
&status->streamEndpoint[i];
|
|
const uint32_t endpointFlags =
|
|
KVMFR_INPUT_STREAM_ENDPOINT_AVAILABLE |
|
|
KVMFR_INPUT_STREAM_ENDPOINT_BOUND;
|
|
if (endpoint->flags & ~endpointFlags || endpoint->reserved ||
|
|
!(endpoint->flags &
|
|
KVMFR_INPUT_STREAM_ENDPOINT_AVAILABLE))
|
|
return false;
|
|
|
|
const bool endpointBound =
|
|
(endpoint->flags & KVMFR_INPUT_STREAM_ENDPOINT_BOUND) != 0;
|
|
if (endpoint->stream.magic != LGMP_STREAM_DESCRIPTOR_MAGIC ||
|
|
endpoint->stream.size != sizeof(endpoint->stream) ||
|
|
endpoint->stream.version != LGMP_STREAM_DESCRIPTOR_VERSION ||
|
|
!endpoint->stream.offset ||
|
|
!endpoint->stream.regionSize ||
|
|
endpoint->stream.direction != LGMP_STREAM_CLIENT_TO_HOST ||
|
|
endpoint->stream.policy != LGMP_STREAM_RELIABLE_FIFO ||
|
|
endpoint->stream.slotCount !=
|
|
KVMFR_INPUT_STREAM_SLOT_COUNT ||
|
|
endpoint->stream.slotSize != KVMFR_INPUT_STREAM_SLOT_SIZE ||
|
|
(endpointBound ?
|
|
!endpoint->boundClientID || !endpoint->bindingGeneration :
|
|
endpoint->boundClientID || endpoint->bindingGeneration))
|
|
return false;
|
|
|
|
for (unsigned j = 0; j < i; ++j)
|
|
{
|
|
const KVMFRInputStreamEndpoint * previous =
|
|
&status->streamEndpoint[j];
|
|
if (previous->stream.offset == endpoint->stream.offset ||
|
|
(endpointBound &&
|
|
previous->boundClientID == endpoint->boundClientID))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const bool available =
|
|
(status->flags & KVMFR_INPUT_STATUS_AVAILABLE) != 0;
|
|
const bool hasOwner =
|
|
(status->flags & KVMFR_INPUT_STATUS_HAS_OWNER) != 0;
|
|
const bool keyboardLEDsValid = keyboardLEDsVersion &&
|
|
(status->flags & KVMFR_INPUT_STATUS_KEYBOARD_LEDS_VALID) != 0;
|
|
if (!available && (status->capabilities || hasOwner))
|
|
return false;
|
|
if (!keyboardLEDsValid && status->keyboardLEDs)
|
|
return false;
|
|
if (!available && keyboardLEDsValid)
|
|
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 struct LGMPStreamDescriptor importStreamDescriptor(
|
|
const KVMFRStreamDescriptor * source)
|
|
{
|
|
return (struct LGMPStreamDescriptor)
|
|
{
|
|
.magic = source->magic,
|
|
.version = source->version,
|
|
.size = source->size,
|
|
.offset = source->offset,
|
|
.regionSize = source->regionSize,
|
|
.direction = source->direction,
|
|
.policy = source->policy,
|
|
.slotCount = source->slotCount,
|
|
.slotSize = source->slotSize,
|
|
};
|
|
}
|
|
|
|
static bool reconcileInputStream(LGMPInput * input,
|
|
const KVMFRInputStatus * status)
|
|
{
|
|
const KVMFRInputStreamEndpoint * desired = NULL;
|
|
for (unsigned i = 0; i < status->streamEndpointCount; ++i)
|
|
if ((status->streamEndpoint[i].flags &
|
|
KVMFR_INPUT_STREAM_ENDPOINT_BOUND) &&
|
|
status->streamEndpoint[i].boundClientID == input->clientID)
|
|
{
|
|
desired = &status->streamEndpoint[i];
|
|
break;
|
|
}
|
|
|
|
const bool oldBound = input->streamEndpointBound;
|
|
const KVMFRInputStreamEndpoint oldEndpoint = input->streamEndpoint;
|
|
if (desired && input->stream && oldBound &&
|
|
memcmp(&oldEndpoint, desired, sizeof(*desired)) == 0)
|
|
{
|
|
uint32_t clientID = 0;
|
|
uint32_t epoch = 0;
|
|
const LGMP_STATUS bindingStatus = lgmpClientStreamGetBinding(
|
|
input->stream, &clientID, &epoch);
|
|
if (bindingStatus == LGMP_OK && clientID == input->clientID &&
|
|
epoch == desired->bindingGeneration)
|
|
return false;
|
|
}
|
|
|
|
detachInputStream(input);
|
|
memset(&input->streamEndpoint, 0, sizeof(input->streamEndpoint));
|
|
if (!desired)
|
|
return oldBound;
|
|
|
|
input->streamEndpoint = *desired;
|
|
const struct LGMPStreamDescriptor descriptor =
|
|
importStreamDescriptor(&desired->stream);
|
|
LGMP_STATUS attachStatus = lgmpClientStreamAttach(
|
|
input->client, &descriptor, &input->stream);
|
|
if (attachStatus != LGMP_OK)
|
|
{
|
|
DEBUG_WARN("Failed to attach LGMP input stream: %s",
|
|
lgmpStatusString(attachStatus));
|
|
return oldBound;
|
|
}
|
|
|
|
uint32_t clientID = 0;
|
|
uint32_t epoch = 0;
|
|
attachStatus = lgmpClientStreamGetBinding(
|
|
input->stream, &clientID, &epoch);
|
|
if (attachStatus != LGMP_OK || clientID != input->clientID ||
|
|
epoch != desired->bindingGeneration)
|
|
{
|
|
if (attachStatus != LGMP_OK)
|
|
DEBUG_WARN("Failed to inspect LGMP input stream binding: %s",
|
|
lgmpStatusString(attachStatus));
|
|
else
|
|
DEBUG_WARN("LGMP input stream binding changed during attach");
|
|
detachInputStream(input);
|
|
return oldBound;
|
|
}
|
|
|
|
input->streamEndpointBound = true;
|
|
return !oldBound ||
|
|
memcmp(&oldEndpoint, desired, sizeof(*desired)) != 0;
|
|
}
|
|
|
|
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 oldKeyboardLEDsValid = input->keyboardLEDsValid;
|
|
const uint8_t oldKeyboardLEDs = input->keyboardLEDs;
|
|
const bool keyboardLEDsValid =
|
|
(status->flags & KVMFR_INPUT_STATUS_KEYBOARD_LEDS_VALID) != 0;
|
|
const uint8_t keyboardLEDs = status->keyboardLEDs;
|
|
const bool targetAvailable =
|
|
(status->flags & KVMFR_INPUT_STATUS_AVAILABLE) != 0;
|
|
const bool endpointChanged = wasValid &&
|
|
oldGeneration != status->generation;
|
|
const uint64_t now = microtime();
|
|
bool restore = false;
|
|
|
|
input->statusValid = true;
|
|
input->statusSerial = serial;
|
|
input->capabilities = status->capabilities;
|
|
input->endpointGeneration = status->generation;
|
|
input->statusOwnerClientID = status->ownerClientID;
|
|
input->statusOwnerGeneration = status->ownerGeneration;
|
|
if (!keyboardLEDsValid)
|
|
retainQueuedKeyboardLEDProjection(input);
|
|
else if (!oldKeyboardLEDsValid)
|
|
{
|
|
retainQueuedKeyboardLEDProjection(input);
|
|
input->staleKeyboardLEDs = 0;
|
|
input->keyboardLEDsStaleUntil = 0;
|
|
}
|
|
else if (endpointChanged)
|
|
{
|
|
input->pendingKeyboardLEDs = 0;
|
|
input->keyboardLEDTransitions = 0;
|
|
input->staleKeyboardLEDs = 0;
|
|
input->keyboardLEDsPendingUntil = 0;
|
|
input->keyboardLEDsStaleUntil = 0;
|
|
}
|
|
else
|
|
{
|
|
const uint8_t acknowledged =
|
|
(oldKeyboardLEDs ^ keyboardLEDs) &
|
|
publishedKeyboardLEDToggles(input);
|
|
if (acknowledged)
|
|
{
|
|
input->pendingKeyboardLEDs ^= acknowledged;
|
|
const unsigned count = keyboardLEDToggleCount(acknowledged);
|
|
input->keyboardLEDTransitions =
|
|
input->keyboardLEDTransitions > count ?
|
|
input->keyboardLEDTransitions - count : 0;
|
|
updateKeyboardLEDPendingDeadline(input, now);
|
|
}
|
|
}
|
|
|
|
input->keyboardLEDsValid = keyboardLEDsValid;
|
|
input->keyboardLEDs = keyboardLEDs;
|
|
if (keyboardLEDsValid && input->desiredKeyboardLEDsValid &&
|
|
((projectedKeyboardLEDs(input) ^ input->desiredKeyboardLEDs) &
|
|
LOCK_LED_MASK & ~input->staleKeyboardLEDs))
|
|
input->keyboardLEDsSyncAfter =
|
|
now + INPUT_KEYBOARD_LED_SYNC_DELAY_US;
|
|
const bool streamChanged = reconcileInputStream(input, status);
|
|
const bool available = targetAvailable &&
|
|
input->streamEndpointBound;
|
|
input->available = available;
|
|
|
|
if (endpointChanged || !available || streamChanged)
|
|
{
|
|
discardProtocolState(input);
|
|
restore = available && (endpointChanged || streamChanged);
|
|
}
|
|
|
|
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 ||
|
|
oldKeyboardLEDsValid != input->keyboardLEDsValid ||
|
|
oldKeyboardLEDs != input->keyboardLEDs)
|
|
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)
|
|
{
|
|
if (input->pendingCount)
|
|
invalidateKeyboardLEDProjection(input, microtime(),
|
|
INPUT_KEYBOARD_LED_ACK_TIMEOUT_US);
|
|
input->pendingHead = 0;
|
|
input->pendingCount = 0;
|
|
input->claimed = false;
|
|
clearInputState(input);
|
|
|
|
if (!input->publishedClaimed)
|
|
return true;
|
|
|
|
input->generation = input->publishedGeneration;
|
|
input->sequence = input->publishedSequence;
|
|
const KVMFRInputPayload payload = { 0 };
|
|
return submitPayload(input, KVMFR_INPUT_MESSAGE_RELEASE,
|
|
&payload, false, wake);
|
|
}
|
|
|
|
static bool flushPending(LGMPInput * input)
|
|
{
|
|
bool progress = false;
|
|
while (input->connected && input->pendingCount)
|
|
{
|
|
struct LGMPInputPending * item = pendingAt(input, 0);
|
|
const LGMP_STATUS status = trySend(input, &item->message, true);
|
|
if (retryableSendStatus(status))
|
|
return progress;
|
|
if (status != LGMP_OK)
|
|
{
|
|
sendFailed(input, status);
|
|
return progress;
|
|
}
|
|
|
|
progress = true;
|
|
published(input, &item->message);
|
|
const uint8_t keyboardLEDToggle = item->keyboardLEDToggle;
|
|
input->pendingHead =
|
|
(input->pendingHead + 1) % INPUT_PENDING_LENGTH;
|
|
--input->pendingCount;
|
|
if (keyboardLEDToggle)
|
|
updateKeyboardLEDPendingDeadline(input, microtime());
|
|
}
|
|
return progress;
|
|
}
|
|
|
|
static bool collectStats(LGMPInput * input, uint64_t now, bool force,
|
|
struct LGMPInputCounters * result)
|
|
{
|
|
if (!force && now - input->stats.lastReport < INPUT_STATS_INTERVAL_US)
|
|
return false;
|
|
|
|
input->stats.lastReport = now;
|
|
*result = input->stats.counters;
|
|
memset(&input->stats.counters, 0, sizeof(input->stats.counters));
|
|
input->stats.counters.pendingHighWater = input->pendingCount;
|
|
|
|
return result->immediateSends || result->deferredSends ||
|
|
result->localEnqueues || result->initialFull || result->retryFull ||
|
|
result->relativeCoalesces || result->absoluteCoalesces ||
|
|
result->reservedRejects || result->motionEvictions ||
|
|
result->discreteOverflowFailures ||
|
|
result->discreteOverflowResets || result->claims ||
|
|
result->releases || result->keepalives ||
|
|
result->terminalFailures;
|
|
}
|
|
|
|
static void logStats(const struct LGMPInputCounters * stats)
|
|
{
|
|
DEBUG_TRACE("LGMP input: sent immediate/deferred %lu/%lu"
|
|
", queued %lu (high %u), initial/retry full %lu/%lu"
|
|
", coalesced relative/absolute %lu/%lu"
|
|
", motion rejected/evicted %lu/%lu"
|
|
", overflow failures/resets %lu/%lu"
|
|
", published claim/release/keepalive %lu/%lu"
|
|
"/%lu, terminal failures %lu",
|
|
stats->immediateSends, stats->deferredSends,
|
|
stats->localEnqueues, stats->pendingHighWater,
|
|
stats->initialFull, stats->retryFull, stats->relativeCoalesces,
|
|
stats->absoluteCoalesces, stats->reservedRejects,
|
|
stats->motionEvictions, stats->discreteOverflowFailures,
|
|
stats->discreteOverflowResets, stats->claims, stats->releases,
|
|
stats->keepalives, stats->terminalFailures);
|
|
}
|
|
|
|
static void releaseOnDisconnect(LGMPInput * input)
|
|
{
|
|
if (!input->queue || !input->stream || !input->publishedClaimed ||
|
|
!input->publishedGeneration ||
|
|
!lgmpClientSessionValid(input->client))
|
|
return;
|
|
|
|
KVMFRInputMessage message =
|
|
{
|
|
.type = KVMFR_INPUT_MESSAGE_RELEASE,
|
|
.generation = input->publishedGeneration,
|
|
.sequence = input->publishedSequence + 1,
|
|
};
|
|
if (!message.sequence)
|
|
message.sequence = 1;
|
|
|
|
const uint64_t deadline = microtime() + INPUT_RELEASE_TIMEOUT_US;
|
|
const uint32_t statusSerial = input->statusSerial;
|
|
LGMP_STATUS status;
|
|
do
|
|
{
|
|
status = trySend(input, &message, true);
|
|
if (status == LGMP_OK)
|
|
break;
|
|
if (!retryableSendStatus(status))
|
|
return;
|
|
if (input->event)
|
|
lgWaitEvent(input->event, INPUT_WORKER_RETRY_MS);
|
|
}
|
|
while (microtime() < deadline);
|
|
|
|
if (status != LGMP_OK)
|
|
{
|
|
DEBUG_WARN("Timed out releasing LGMP input stream ownership");
|
|
return;
|
|
}
|
|
|
|
do
|
|
{
|
|
bool wake = false;
|
|
processInputStatus(input, &wake);
|
|
if (input->statusValid &&
|
|
(int32_t)(input->statusSerial - statusSerial) > 0 &&
|
|
(input->statusOwnerClientID != input->clientID ||
|
|
input->statusOwnerGeneration != message.generation))
|
|
return;
|
|
if (input->event)
|
|
lgWaitEvent(input->event, INPUT_WORKER_RETRY_MS);
|
|
}
|
|
while (microtime() < deadline);
|
|
|
|
DEBUG_WARN("Timed out waiting for LGMP input stream release");
|
|
}
|
|
|
|
static int inputThread(void * opaque)
|
|
{
|
|
LGMPInput * input = opaque;
|
|
LGMPStreamPollState streamPoll;
|
|
const LGMP_STATUS pollStatus = lgmpStreamPollInit(&streamPoll,
|
|
(struct LGMPStreamPollConfig)
|
|
{
|
|
.spinCount = 32U,
|
|
.minWaitUs = 50U,
|
|
.maxWaitUs = 1000U,
|
|
});
|
|
if (pollStatus != LGMP_OK)
|
|
{
|
|
DEBUG_ERROR("Failed to initialize LGMP input polling: %s",
|
|
lgmpStatusString(pollStatus));
|
|
return -1;
|
|
}
|
|
|
|
while (!atomic_load_explicit(&input->stop, memory_order_acquire))
|
|
{
|
|
struct LGMPInputCounters stats = { 0 };
|
|
unsigned timeout = INPUT_WORKER_IDLE_MS;
|
|
bool wake = false;
|
|
LG_LOCK(input->lock);
|
|
processInputStatus(input, &wake);
|
|
const bool streamProgress = flushPending(input);
|
|
|
|
uint64_t now = microtime();
|
|
syncKeyboardLEDs(input, now, &wake);
|
|
now = microtime();
|
|
if (input->connected && input->claimed && !inputStateHeld(input) &&
|
|
!input->pendingCount &&
|
|
input->publishedGeneration == input->generation &&
|
|
now - input->lastInput >= INPUT_IDLE_RELEASE_US)
|
|
{
|
|
release(input, &wake);
|
|
}
|
|
else if (input->connected && input->claimed &&
|
|
input->publishedClaimed && !input->pendingCount &&
|
|
input->publishedGeneration == input->generation &&
|
|
now - input->lastSend >= INPUT_KEEPALIVE_US)
|
|
{
|
|
const KVMFRInputPayload payload = { 0 };
|
|
submitPayload(input, KVMFR_INPUT_MESSAGE_KEEPALIVE,
|
|
&payload, false, &wake);
|
|
}
|
|
|
|
if (input->pendingCount)
|
|
timeout = INPUT_WORKER_RETRY_MS;
|
|
const bool streamPending = input->pendingCount != 0;
|
|
const bool report = collectStats(input, now, false, &stats);
|
|
LG_UNLOCK(input->lock);
|
|
|
|
if (report)
|
|
logStats(&stats);
|
|
notifyInputStatus(input);
|
|
if (wake)
|
|
lgSignalEvent(input->event);
|
|
|
|
if (atomic_load_explicit(&input->stop, memory_order_acquire))
|
|
break;
|
|
|
|
bool signaled;
|
|
if (streamPending)
|
|
{
|
|
if (streamProgress)
|
|
lgmpStreamPollActivity(&streamPoll);
|
|
const uint32_t waitUs = lgmpStreamPollIdle(&streamPoll);
|
|
if (!waitUs)
|
|
continue;
|
|
signaled = lgWaitEventNS(input->event, waitUs * 1000U);
|
|
}
|
|
else
|
|
{
|
|
if (streamProgress)
|
|
lgmpStreamPollActivity(&streamPoll);
|
|
signaled = lgWaitEvent(input->event, timeout);
|
|
}
|
|
if (signaled)
|
|
lgmpStreamPollActivity(&streamPoll);
|
|
}
|
|
|
|
struct LGMPInputCounters stats = { 0 };
|
|
LG_LOCK(input->lock);
|
|
const bool report = collectStats(input, microtime(), true, &stats);
|
|
LG_UNLOCK(input->lock);
|
|
if (report)
|
|
logStats(&stats);
|
|
notifyInputStatus(input);
|
|
return 0;
|
|
}
|
|
|
|
bool lgmpInput_create(PLGMPClient client, LGMPInput ** result)
|
|
{
|
|
LGMPInput * input = calloc(1, sizeof(*input));
|
|
if (!input)
|
|
return false;
|
|
|
|
input->client = client;
|
|
LG_LOCK_INIT(input->lock);
|
|
atomic_init(&input->stop, false);
|
|
*result = input;
|
|
return true;
|
|
}
|
|
|
|
void lgmpInput_destroy(LGMPInput ** input)
|
|
{
|
|
if (!input || !*input)
|
|
return;
|
|
|
|
lgmpInput_disconnect(*input);
|
|
LG_LOCK_FREE((*input)->lock);
|
|
free(*input);
|
|
*input = NULL;
|
|
}
|
|
|
|
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 sameClient;
|
|
}
|
|
if (input->thread || input->queue)
|
|
{
|
|
LG_UNLOCK(input->lock);
|
|
return false;
|
|
}
|
|
|
|
LGMP_STATUS status = lgmpClientSubscribe(
|
|
input->client, LGMP_Q_INPUT, &input->queue);
|
|
if (status != LGMP_OK)
|
|
{
|
|
LG_UNLOCK(input->lock);
|
|
DEBUG_WARN("Failed to subscribe to LGMP input status queue: %s",
|
|
lgmpStatusString(status));
|
|
return false;
|
|
}
|
|
|
|
input->event = lgCreateEvent(true, 0);
|
|
if (!input->event)
|
|
{
|
|
lgmpClientUnsubscribe(&input->queue);
|
|
LG_UNLOCK(input->lock);
|
|
return false;
|
|
}
|
|
|
|
input->connected = true;
|
|
input->claimed = false;
|
|
input->available = false;
|
|
input->ownerBlocked = false;
|
|
input->keyboardLEDsValid = false;
|
|
input->keyboardLEDs = 0;
|
|
input->pendingKeyboardLEDs = 0;
|
|
input->keyboardLEDTransitions = 0;
|
|
input->staleKeyboardLEDs = 0;
|
|
input->keyboardLEDsSyncAfter = 0;
|
|
input->keyboardLEDsPendingUntil = 0;
|
|
input->keyboardLEDsStaleUntil = 0;
|
|
input->pendingHead = 0;
|
|
input->pendingCount = 0;
|
|
input->clientID = clientID;
|
|
input->capabilities = 0;
|
|
input->streamEndpointBound = false;
|
|
memset(&input->streamEndpoint, 0,
|
|
sizeof(input->streamEndpoint));
|
|
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;
|
|
memset(&input->stats, 0, sizeof(input->stats));
|
|
input->stats.lastReport = microtime();
|
|
clearInputState(input);
|
|
atomic_store_explicit(&input->stop, false, memory_order_release);
|
|
LGThread * thread;
|
|
if (lgCreateThread("lgmpInput", inputThread, input, &thread))
|
|
{
|
|
input->thread = thread;
|
|
LG_UNLOCK(input->lock);
|
|
return true;
|
|
}
|
|
|
|
input->connected = false;
|
|
LGEvent * event = input->event;
|
|
PLGMPClientQueue queue = input->queue;
|
|
input->event = NULL;
|
|
input->queue = NULL;
|
|
LG_UNLOCK(input->lock);
|
|
lgmpClientUnsubscribe(&queue);
|
|
lgFreeEvent(event);
|
|
return false;
|
|
}
|
|
|
|
void lgmpInput_disconnect(LGMPInput * input)
|
|
{
|
|
if (!input)
|
|
return;
|
|
|
|
LG_LOCK(input->lock);
|
|
if (!input->thread && !input->queue)
|
|
{
|
|
LG_UNLOCK(input->lock);
|
|
return;
|
|
}
|
|
|
|
input->pendingHead = 0;
|
|
input->pendingCount = 0;
|
|
input->connected = false;
|
|
input->claimed = false;
|
|
if (input->available || input->endpointGeneration ||
|
|
input->keyboardLEDsValid)
|
|
input->notifyStatus = true;
|
|
input->available = false;
|
|
input->ownerBlocked = false;
|
|
input->keyboardLEDsValid = false;
|
|
input->keyboardLEDs = 0;
|
|
input->pendingKeyboardLEDs = 0;
|
|
input->keyboardLEDTransitions = 0;
|
|
input->staleKeyboardLEDs = 0;
|
|
input->keyboardLEDsSyncAfter = 0;
|
|
input->keyboardLEDsPendingUntil = 0;
|
|
input->keyboardLEDsStaleUntil = 0;
|
|
input->capabilities = 0;
|
|
input->streamEndpointBound = false;
|
|
memset(&input->streamEndpoint, 0,
|
|
sizeof(input->streamEndpoint));
|
|
input->statusValid = false;
|
|
input->ownerConfirmed = false;
|
|
clearInputState(input);
|
|
atomic_store_explicit(&input->stop, true, memory_order_release);
|
|
LGThread * thread = input->thread;
|
|
LGEvent * event = input->event;
|
|
LG_UNLOCK(input->lock);
|
|
|
|
if (event)
|
|
lgSignalEvent(event);
|
|
if (thread)
|
|
lgJoinThread(thread, NULL);
|
|
|
|
LG_LOCK(input->lock);
|
|
releaseOnDisconnect(input);
|
|
detachInputStream(input);
|
|
input->available = false;
|
|
input->ownerBlocked = false;
|
|
input->keyboardLEDsValid = false;
|
|
input->keyboardLEDs = 0;
|
|
input->pendingKeyboardLEDs = 0;
|
|
input->keyboardLEDTransitions = 0;
|
|
input->staleKeyboardLEDs = 0;
|
|
input->keyboardLEDsSyncAfter = 0;
|
|
input->keyboardLEDsPendingUntil = 0;
|
|
input->keyboardLEDsStaleUntil = 0;
|
|
input->statusValid = false;
|
|
input->ownerConfirmed = false;
|
|
input->notifyStatus = false;
|
|
input->statusOwnerClientID = 0;
|
|
input->statusOwnerGeneration = 0;
|
|
memset(&input->streamEndpoint, 0,
|
|
sizeof(input->streamEndpoint));
|
|
PLGMPClientQueue queue = input->queue;
|
|
input->queue = NULL;
|
|
input->thread = NULL;
|
|
input->event = NULL;
|
|
input->publishedClaimed = false;
|
|
LG_UNLOCK(input->lock);
|
|
|
|
if (queue)
|
|
{
|
|
const LGMP_STATUS status = lgmpClientUnsubscribe(&queue);
|
|
if (status != LGMP_OK && status != LGMP_ERR_INVALID_SESSION &&
|
|
status != LGMP_ERR_QUEUE_TIMEOUT &&
|
|
status != LGMP_ERR_QUEUE_UNSUBSCRIBED)
|
|
DEBUG_WARN("Failed to unsubscribe from LGMP input status queue: %s",
|
|
lgmpStatusString(status));
|
|
}
|
|
if (event)
|
|
lgFreeEvent(event);
|
|
}
|
|
|
|
static bool inputSupports(void * opaque, LG_InputSupport support)
|
|
{
|
|
LGMPInput * input = opaque;
|
|
LG_LOCK(input->lock);
|
|
|
|
bool result;
|
|
switch (support)
|
|
{
|
|
case LG_INPUT_SUPPORT_MOUSE_ABSOLUTE:
|
|
result = input->available &&
|
|
(input->capabilities & KVMFR_INPUT_CAP_MOUSE_ABSOLUTE) != 0;
|
|
break;
|
|
|
|
default:
|
|
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 bool buildKeyboardPayload(const LGMPInput * input,
|
|
KVMFRInputPayload * payload)
|
|
{
|
|
bool held = false;
|
|
for (unsigned usage = 224; usage <= 231; ++usage)
|
|
if (input->keyState[usage])
|
|
{
|
|
payload->keyboard.modifiers |= 1U << (usage - 224);
|
|
held = true;
|
|
}
|
|
|
|
unsigned count = 0;
|
|
for (unsigned usage = 1; usage < 224; ++usage)
|
|
{
|
|
if (!input->keyState[usage])
|
|
continue;
|
|
held = true;
|
|
if (count == KVMFR_INPUT_KEYBOARD_KEY_COUNT)
|
|
{
|
|
memset(payload->keyboard.keys, 1,
|
|
sizeof(payload->keyboard.keys));
|
|
return true;
|
|
}
|
|
payload->keyboard.keys[count++] = (uint8_t)usage;
|
|
}
|
|
return held;
|
|
}
|
|
|
|
static bool updateKey(void * opaque, int key, bool pressed)
|
|
{
|
|
if (key < 0 || key >= KEY_MAX)
|
|
return false;
|
|
|
|
const uint8_t usage = linux_to_hid[key];
|
|
if (!usage)
|
|
return true;
|
|
|
|
LGMPInput * input = opaque;
|
|
bool wake = false;
|
|
LG_LOCK(input->lock);
|
|
if (!input->connected || !input->available)
|
|
{
|
|
LG_UNLOCK(input->lock);
|
|
return false;
|
|
}
|
|
|
|
uint8_t * state = &input->keyState[usage];
|
|
const uint8_t previous = *state;
|
|
if (pressed)
|
|
{
|
|
if (*state != UINT8_MAX)
|
|
++*state;
|
|
}
|
|
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);
|
|
const uint64_t overflowFailures =
|
|
input->stats.counters.discreteOverflowFailures;
|
|
bool result = submitPayload(input, KVMFR_INPUT_MESSAGE_KEYBOARD,
|
|
&payload, false, &wake);
|
|
if (!result && !pressed && input->connected)
|
|
{
|
|
if (input->stats.counters.discreteOverflowFailures !=
|
|
overflowFailures)
|
|
++input->stats.counters.discreteOverflowResets;
|
|
result = release(input, &wake);
|
|
}
|
|
else if (!result && input->connected)
|
|
*state = previous;
|
|
if (result && pressed && !previous)
|
|
recordKeyboardToggle(input, key);
|
|
LG_UNLOCK(input->lock);
|
|
|
|
if (wake)
|
|
lgSignalEvent(input->event);
|
|
return result;
|
|
}
|
|
|
|
static bool inputKeyDown(void * opaque, int key)
|
|
{
|
|
return updateKey(opaque, key, true);
|
|
}
|
|
|
|
static bool inputKeyUp(void * opaque, int key)
|
|
{
|
|
return updateKey(opaque, key, false);
|
|
}
|
|
|
|
static bool inputKeyboardLEDs(void * opaque, bool numLock, bool capsLock,
|
|
bool scrollLock)
|
|
{
|
|
LGMPInput * input = opaque;
|
|
|
|
const uint8_t desired =
|
|
(numLock ? LG_KEYBOARD_LED_NUM_LOCK : 0) |
|
|
(capsLock ? LG_KEYBOARD_LED_CAPS_LOCK : 0) |
|
|
(scrollLock ? LG_KEYBOARD_LED_SCROLL_LOCK : 0);
|
|
const uint64_t syncAfter =
|
|
microtime() + INPUT_KEYBOARD_LED_SYNC_DELAY_US;
|
|
|
|
LG_LOCK(input->lock);
|
|
input->desiredKeyboardLEDs = desired;
|
|
input->desiredKeyboardLEDsValid = true;
|
|
input->staleKeyboardLEDs &= ~LOCK_LED_MASK;
|
|
input->keyboardLEDsStaleUntil = 0;
|
|
if (input->keyboardLEDsSyncAfter < syncAfter)
|
|
input->keyboardLEDsSyncAfter = syncAfter;
|
|
const bool result = input->connected && input->available;
|
|
if (input->event)
|
|
lgSignalEvent(input->event);
|
|
LG_UNLOCK(input->lock);
|
|
return result;
|
|
}
|
|
|
|
static void inputKeyboardLEDsReset(void * opaque)
|
|
{
|
|
LGMPInput * input = opaque;
|
|
bool wake = false;
|
|
|
|
LG_LOCK(input->lock);
|
|
input->desiredKeyboardLEDsValid = false;
|
|
input->staleKeyboardLEDs = 0;
|
|
input->keyboardLEDsStaleUntil = 0;
|
|
if (input->connected && pendingKeyboardLEDSync(input))
|
|
release(input, &wake);
|
|
if (input->event)
|
|
lgSignalEvent(input->event);
|
|
LG_UNLOCK(input->lock);
|
|
}
|
|
|
|
static bool queueMouse(LGMPInput * input, enum LGMPInputMouseMode mode,
|
|
int32_t x, int32_t y, int32_t wheel, uint32_t buttons,
|
|
bool pureMotion, bool * wake)
|
|
{
|
|
KVMFRInputPayload payload = { 0 };
|
|
KVMFRInputMessageType type;
|
|
if (mode == LGMP_INPUT_MOUSE_ABSOLUTE)
|
|
{
|
|
type = KVMFR_INPUT_MESSAGE_MOUSE_ABSOLUTE;
|
|
payload.mouseAbsolute.buttons = buttons;
|
|
payload.mouseAbsolute.x = input->absoluteX;
|
|
payload.mouseAbsolute.y = input->absoluteY;
|
|
payload.mouseAbsolute.wheel = wheel;
|
|
}
|
|
else
|
|
{
|
|
type = KVMFR_INPUT_MESSAGE_MOUSE_RELATIVE;
|
|
payload.mouseRelative.buttons = buttons;
|
|
payload.mouseRelative.deltaX = x;
|
|
payload.mouseRelative.deltaY = y;
|
|
payload.mouseRelative.wheel = wheel;
|
|
}
|
|
|
|
return submitPayload(input, type, &payload, pureMotion, wake);
|
|
}
|
|
|
|
static bool inputMouseMotion(void * opaque, int32_t x, int32_t y)
|
|
{
|
|
if (x < INPUT_MOUSE_DELTA_MIN || x > INPUT_MOUSE_DELTA_MAX ||
|
|
y < INPUT_MOUSE_DELTA_MIN || y > INPUT_MOUSE_DELTA_MAX)
|
|
return false;
|
|
|
|
LGMPInput * input = opaque;
|
|
bool wake = false;
|
|
LG_LOCK(input->lock);
|
|
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)
|
|
input->mouseMode = LGMP_INPUT_MOUSE_RELATIVE;
|
|
LG_UNLOCK(input->lock);
|
|
|
|
if (wake)
|
|
lgSignalEvent(input->event);
|
|
return result;
|
|
}
|
|
|
|
static bool inputMousePosition(void * opaque, uint32_t x, uint32_t y,
|
|
uint32_t width, uint32_t height)
|
|
{
|
|
if (!width || !height || x >= width || y >= height)
|
|
return false;
|
|
|
|
const uint16_t absoluteX = width == 1 ? 0 :
|
|
(uint16_t)((uint64_t)x * KVMFR_INPUT_MOUSE_ABSOLUTE_MAX /
|
|
(width - 1));
|
|
const uint16_t absoluteY = height == 1 ? 0 :
|
|
(uint16_t)((uint64_t)y * KVMFR_INPUT_MOUSE_ABSOLUTE_MAX /
|
|
(height - 1));
|
|
|
|
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);
|
|
if (result)
|
|
input->mouseMode = LGMP_INPUT_MOUSE_ABSOLUTE;
|
|
else
|
|
{
|
|
input->absoluteX = previousX;
|
|
input->absoluteY = previousY;
|
|
}
|
|
LG_UNLOCK(input->lock);
|
|
|
|
if (wake)
|
|
lgSignalEvent(input->event);
|
|
return result;
|
|
}
|
|
|
|
static uint32_t mouseButtonMask(unsigned int button)
|
|
{
|
|
switch (button)
|
|
{
|
|
case 1:
|
|
return UINT32_C(1) << 0;
|
|
|
|
case 2:
|
|
return UINT32_C(1) << 2;
|
|
|
|
case 3:
|
|
return UINT32_C(1) << 1;
|
|
|
|
default:
|
|
if (button >= 6 && button < 32)
|
|
return UINT32_C(1) << (button - 3);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static bool updateMouseButton(void * opaque, unsigned int button,
|
|
bool pressed)
|
|
{
|
|
if (button == 4 || button == 5)
|
|
{
|
|
if (!pressed)
|
|
return true;
|
|
|
|
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 && input->available &&
|
|
claim(input, &wake) &&
|
|
queueMouse(input, mode, 0, 0, button == 4 ? 1 : -1,
|
|
input->mouseButtons, false, &wake);
|
|
LG_UNLOCK(input->lock);
|
|
if (wake)
|
|
lgSignalEvent(input->event);
|
|
return result;
|
|
}
|
|
|
|
const uint32_t mask = mouseButtonMask(button);
|
|
if (!mask)
|
|
return false;
|
|
|
|
LGMPInput * input = opaque;
|
|
bool wake = false;
|
|
LG_LOCK(input->lock);
|
|
const uint32_t buttons = pressed ?
|
|
input->mouseButtons | mask : input->mouseButtons & ~mask;
|
|
const enum LGMPInputMouseMode mode =
|
|
input->mouseMode == LGMP_INPUT_MOUSE_ABSOLUTE ?
|
|
LGMP_INPUT_MOUSE_ABSOLUTE : LGMP_INPUT_MOUSE_RELATIVE;
|
|
if (input->connected && input->available && input->ownerBlocked)
|
|
{
|
|
input->mouseButtons = buttons;
|
|
input->mouseMode = mode;
|
|
LG_UNLOCK(input->lock);
|
|
return true;
|
|
}
|
|
|
|
const uint64_t overflowFailures =
|
|
input->stats.counters.discreteOverflowFailures;
|
|
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)
|
|
{
|
|
if (input->stats.counters.discreteOverflowFailures !=
|
|
overflowFailures)
|
|
++input->stats.counters.discreteOverflowResets;
|
|
reset = release(input, &wake);
|
|
result = reset;
|
|
}
|
|
if (result && !reset)
|
|
{
|
|
input->mouseButtons = buttons;
|
|
input->mouseMode = mode;
|
|
}
|
|
LG_UNLOCK(input->lock);
|
|
|
|
if (wake)
|
|
lgSignalEvent(input->event);
|
|
return result;
|
|
}
|
|
|
|
static bool inputMousePress(void * opaque, unsigned int button)
|
|
{
|
|
return updateMouseButton(opaque, button, true);
|
|
}
|
|
|
|
static bool inputMouseRelease(void * opaque, unsigned int button)
|
|
{
|
|
return updateMouseButton(opaque, button, false);
|
|
}
|
|
|
|
static void inputReset(void * opaque)
|
|
{
|
|
LGMPInput * input = opaque;
|
|
bool wake = false;
|
|
LG_LOCK(input->lock);
|
|
if (input->connected)
|
|
release(input, &wake);
|
|
else
|
|
clearInputState(input);
|
|
LG_UNLOCK(input->lock);
|
|
|
|
if (wake)
|
|
lgSignalEvent(input->event);
|
|
}
|
|
|
|
static const LG_InputOps INPUT_OPS =
|
|
{
|
|
.name = "LGMP",
|
|
.supports = inputSupports,
|
|
.setStatusListener = inputSetStatusListener,
|
|
.keyDown = inputKeyDown,
|
|
.keyUp = inputKeyUp,
|
|
.keyboardLEDs = inputKeyboardLEDs,
|
|
.keyboardLEDsReset = inputKeyboardLEDsReset,
|
|
.mouseMotion = inputMouseMotion,
|
|
.mousePosition = inputMousePosition,
|
|
.mousePress = inputMousePress,
|
|
.mouseRelease = inputMouseRelease,
|
|
.reset = inputReset,
|
|
};
|
|
|
|
const LG_InputOps * lgmpInput_getOps(void)
|
|
{
|
|
return &INPUT_OPS;
|
|
}
|