mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-09 08:41:31 +00:00
1217 lines
32 KiB
C
1217 lines
32 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 "common/KVMFRInput.h"
|
|
#include "common/LGMPConfig.h"
|
|
#include "common/debug.h"
|
|
#include "common/event.h"
|
|
#include "common/locking.h"
|
|
#include "common/thread.h"
|
|
#include "common/time.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_WORKER_IDLE_MS 10
|
|
#define INPUT_WORKER_RETRY_MS 1
|
|
#define INPUT_MAX_SPLIT_REPORTS 4
|
|
#define INPUT_MOUSE_DELTA_MIN (INT16_MIN * INPUT_MAX_SPLIT_REPORTS)
|
|
#define INPUT_MOUSE_DELTA_MAX (INT16_MAX * INPUT_MAX_SPLIT_REPORTS)
|
|
|
|
enum LGMPInputMouseMode
|
|
{
|
|
LGMP_INPUT_MOUSE_NONE,
|
|
LGMP_INPUT_MOUSE_RELATIVE,
|
|
LGMP_INPUT_MOUSE_ABSOLUTE,
|
|
};
|
|
|
|
struct LGMPInputPending
|
|
{
|
|
KVMFRInputMessage message;
|
|
bool pureMotion;
|
|
};
|
|
|
|
struct LGMPInput
|
|
{
|
|
PLGMPClient client;
|
|
PLGMPClientQueue queue;
|
|
LG_Lock lock;
|
|
LGEvent * event;
|
|
LGThread * thread;
|
|
atomic_bool stop;
|
|
bool connected;
|
|
bool claimed;
|
|
bool available;
|
|
bool ownerBlocked;
|
|
|
|
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;
|
|
uint32_t endpointGeneration;
|
|
uint32_t statusSerial;
|
|
uint32_t statusOwnerClientID;
|
|
uint32_t statusOwnerGeneration;
|
|
bool ownerConfirmed;
|
|
bool statusValid;
|
|
bool notifyStatus;
|
|
|
|
LG_InputStatusFn statusCallback;
|
|
void * statusOpaque;
|
|
};
|
|
|
|
static void buildKeyboardPayload(const LGMPInput * input,
|
|
KVMFRInputPayload * payload);
|
|
static bool queueMouse(LGMPInput * input, enum LGMPInputMouseMode mode,
|
|
int32_t x, int32_t y, int32_t wheel, uint32_t buttons,
|
|
bool pureMotion, bool * wake);
|
|
|
|
static struct LGMPInputPending * pendingAt(
|
|
LGMPInput * input, unsigned position)
|
|
{
|
|
return &input->pending[
|
|
(input->pendingHead + position) % INPUT_PENDING_LENGTH];
|
|
}
|
|
|
|
static LG_InputStatus inputStatus(const LGMPInput * input)
|
|
{
|
|
return (LG_InputStatus)
|
|
{
|
|
.available = input->available,
|
|
.generation = input->endpointGeneration,
|
|
};
|
|
}
|
|
|
|
static void notifyInputStatus(LGMPInput * input)
|
|
{
|
|
LG_InputStatusFn callback;
|
|
void * opaque;
|
|
LG_InputStatus status;
|
|
|
|
LG_LOCK(input->lock);
|
|
if (!input->notifyStatus)
|
|
{
|
|
LG_UNLOCK(input->lock);
|
|
return;
|
|
}
|
|
|
|
input->notifyStatus = false;
|
|
callback = input->statusCallback;
|
|
opaque = input->statusOpaque;
|
|
status = inputStatus(input);
|
|
LG_UNLOCK(input->lock);
|
|
|
|
if (callback)
|
|
callback(opaque, &status);
|
|
}
|
|
|
|
static void published(LGMPInput * input,
|
|
const KVMFRInputMessage * message)
|
|
{
|
|
input->publishedGeneration = message->generation;
|
|
input->publishedSequence = message->sequence;
|
|
input->lastSend = microtime();
|
|
|
|
switch (message->type)
|
|
{
|
|
case KVMFR_INPUT_MESSAGE_CLAIM:
|
|
input->publishedClaimed = true;
|
|
break;
|
|
|
|
case KVMFR_INPUT_MESSAGE_RELEASE:
|
|
input->publishedClaimed = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void connectionFailed(LGMPInput * input, LGMP_STATUS status)
|
|
{
|
|
if (input->connected)
|
|
DEBUG_WARN("LGMP input transport failed: %s", lgmpStatusString(status));
|
|
|
|
if (input->available || input->endpointGeneration)
|
|
input->notifyStatus = true;
|
|
input->connected = false;
|
|
input->claimed = false;
|
|
input->available = false;
|
|
input->ownerBlocked = false;
|
|
input->pendingHead = 0;
|
|
input->pendingCount = 0;
|
|
input->mouseMode = LGMP_INPUT_MOUSE_NONE;
|
|
input->mouseButtons = 0;
|
|
input->publishedClaimed = false;
|
|
input->lastInput = 0;
|
|
input->capabilities = 0;
|
|
input->statusValid = false;
|
|
input->ownerConfirmed = false;
|
|
memset(input->keyState, 0, sizeof(input->keyState));
|
|
atomic_store_explicit(&input->stop, true, memory_order_release);
|
|
}
|
|
|
|
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;
|
|
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;
|
|
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;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool queuePayload(LGMPInput * input, KVMFRInputMessageType type,
|
|
const KVMFRInputPayload * payload, bool pureMotion, bool * wake)
|
|
{
|
|
if (!input->connected || !input->queue)
|
|
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)
|
|
return false;
|
|
if (!pureMotion && input->pendingCount == INPUT_PENDING_LENGTH &&
|
|
!discardPendingMotion(input))
|
|
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 = lgmpClientTrySendData(input->queue,
|
|
&message, sizeof(message), NULL);
|
|
if (status == LGMP_OK)
|
|
{
|
|
published(input, &message);
|
|
if (inputMessage)
|
|
input->lastInput = microtime();
|
|
return true;
|
|
}
|
|
|
|
if (status != LGMP_ERR_QUEUE_BUSY && status != LGMP_ERR_QUEUE_FULL)
|
|
{
|
|
input->sequence = previousSequence;
|
|
connectionFailed(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->pureMotion = pureMotion;
|
|
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)
|
|
return false;
|
|
|
|
if (++input->generation == 0)
|
|
++input->generation;
|
|
input->sequence = 0;
|
|
input->claimed = true;
|
|
input->ownerConfirmed = false;
|
|
|
|
const KVMFRInputPayload payload = { 0 };
|
|
if (queuePayload(input, KVMFR_INPUT_MESSAGE_CLAIM,
|
|
&payload, false, wake))
|
|
return true;
|
|
|
|
input->claimed = false;
|
|
return false;
|
|
}
|
|
|
|
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 bool inputStateActive(const LGMPInput * input)
|
|
{
|
|
return input->mouseMode == LGMP_INPUT_MOUSE_ABSOLUTE ||
|
|
inputStateHeld(input);
|
|
}
|
|
|
|
static void discardProtocolState(LGMPInput * input)
|
|
{
|
|
input->pendingHead = 0;
|
|
input->pendingCount = 0;
|
|
input->claimed = false;
|
|
input->sequence = 0;
|
|
input->publishedGeneration = 0;
|
|
input->publishedSequence = 0;
|
|
input->publishedClaimed = false;
|
|
input->ownerConfirmed = false;
|
|
}
|
|
|
|
static bool restoreInputState(LGMPInput * input, bool * wake)
|
|
{
|
|
if (!inputStateActive(input))
|
|
return true;
|
|
if (!claim(input, wake))
|
|
return false;
|
|
|
|
KVMFRInputPayload keyboard = { 0 };
|
|
buildKeyboardPayload(input, &keyboard);
|
|
if (!queuePayload(input, KVMFR_INPUT_MESSAGE_KEYBOARD,
|
|
&keyboard, false, wake))
|
|
return false;
|
|
|
|
if (input->mouseMode != LGMP_INPUT_MOUSE_NONE &&
|
|
!queueMouse(input, input->mouseMode, 0, 0, 0,
|
|
input->mouseButtons, false, wake))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
static bool validInputStatus(const KVMFRInputStatus * status)
|
|
{
|
|
static const uint32_t capabilities =
|
|
KVMFR_INPUT_CAP_MOUSE_RELATIVE |
|
|
KVMFR_INPUT_CAP_MOUSE_ABSOLUTE |
|
|
KVMFR_INPUT_CAP_KEYBOARD;
|
|
static const uint32_t flags =
|
|
KVMFR_INPUT_STATUS_AVAILABLE |
|
|
KVMFR_INPUT_STATUS_HAS_OWNER;
|
|
|
|
if (status->version != KVMFR_INPUT_VERSION ||
|
|
status->capabilities & ~capabilities ||
|
|
status->flags & ~flags || !status->generation ||
|
|
!status->lease || !status->maxButtons ||
|
|
status->maxButtons > KVMFR_INPUT_MOUSE_BUTTON_COUNT)
|
|
return false;
|
|
|
|
const bool available =
|
|
(status->flags & KVMFR_INPUT_STATUS_AVAILABLE) != 0;
|
|
const bool hasOwner =
|
|
(status->flags & KVMFR_INPUT_STATUS_HAS_OWNER) != 0;
|
|
if (!available && (status->capabilities || hasOwner))
|
|
return false;
|
|
if (available &&
|
|
(status->capabilities &
|
|
(KVMFR_INPUT_CAP_MOUSE_RELATIVE | KVMFR_INPUT_CAP_KEYBOARD)) !=
|
|
(KVMFR_INPUT_CAP_MOUSE_RELATIVE | KVMFR_INPUT_CAP_KEYBOARD))
|
|
return false;
|
|
return hasOwner ?
|
|
status->ownerClientID && status->ownerGeneration :
|
|
!status->ownerClientID && !status->ownerGeneration;
|
|
}
|
|
|
|
static void applyInputStatus(LGMPInput * input,
|
|
const KVMFRInputStatus * status, uint32_t serial, bool * wake)
|
|
{
|
|
const bool wasValid = input->statusValid;
|
|
const bool wasAvailable = input->available;
|
|
const uint32_t oldCapabilities = input->capabilities;
|
|
const uint32_t oldGeneration = input->endpointGeneration;
|
|
const bool available =
|
|
(status->flags & KVMFR_INPUT_STATUS_AVAILABLE) != 0;
|
|
const bool endpointChanged = wasValid &&
|
|
oldGeneration != status->generation;
|
|
bool restore = false;
|
|
|
|
input->statusValid = true;
|
|
input->statusSerial = serial;
|
|
input->available = available;
|
|
input->capabilities = status->capabilities;
|
|
input->endpointGeneration = status->generation;
|
|
input->statusOwnerClientID = status->ownerClientID;
|
|
input->statusOwnerGeneration = status->ownerGeneration;
|
|
|
|
if (endpointChanged || !available)
|
|
{
|
|
discardProtocolState(input);
|
|
restore = endpointChanged && available;
|
|
}
|
|
|
|
if (status->flags & KVMFR_INPUT_STATUS_HAS_OWNER)
|
|
{
|
|
if (input->claimed &&
|
|
status->ownerClientID == input->clientID &&
|
|
status->ownerGeneration == input->generation)
|
|
{
|
|
input->ownerBlocked = false;
|
|
input->ownerConfirmed = true;
|
|
}
|
|
else
|
|
{
|
|
discardProtocolState(input);
|
|
input->ownerBlocked = true;
|
|
restore = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (input->ownerBlocked ||
|
|
(input->claimed && input->ownerConfirmed))
|
|
{
|
|
discardProtocolState(input);
|
|
restore = available;
|
|
}
|
|
input->ownerBlocked = false;
|
|
}
|
|
|
|
if (restore && !restoreInputState(input, wake))
|
|
discardProtocolState(input);
|
|
|
|
if (!wasValid || wasAvailable != available ||
|
|
oldCapabilities != status->capabilities ||
|
|
oldGeneration != status->generation)
|
|
input->notifyStatus = true;
|
|
}
|
|
|
|
static void processInputStatus(LGMPInput * input, bool * wake)
|
|
{
|
|
LGMP_STATUS result = lgmpClientAdvanceToLast(input->queue);
|
|
if (result == LGMP_ERR_QUEUE_EMPTY)
|
|
return;
|
|
if (result != LGMP_OK)
|
|
{
|
|
connectionFailed(input, result);
|
|
return;
|
|
}
|
|
|
|
LGMPMessage message;
|
|
result = lgmpClientProcess(input->queue, &message);
|
|
if (result == LGMP_ERR_QUEUE_EMPTY)
|
|
return;
|
|
if (result != LGMP_OK)
|
|
{
|
|
connectionFailed(input, result);
|
|
return;
|
|
}
|
|
|
|
KVMFRInputStatus status = { 0 };
|
|
const bool valid = message.udata && message.udata <= UINT32_MAX &&
|
|
message.size == sizeof(status);
|
|
if (valid)
|
|
memcpy(&status, message.mem, sizeof(status));
|
|
|
|
result = lgmpClientMessageDone(input->queue);
|
|
if (result != LGMP_OK)
|
|
{
|
|
connectionFailed(input, result);
|
|
return;
|
|
}
|
|
|
|
const uint32_t serial = (uint32_t)message.udata;
|
|
if (!valid || !validInputStatus(&status))
|
|
{
|
|
DEBUG_WARN("Ignoring invalid LGMP input status");
|
|
return;
|
|
}
|
|
if (input->statusValid &&
|
|
(int32_t)(serial - input->statusSerial) <= 0)
|
|
return;
|
|
|
|
applyInputStatus(input, &status, serial, wake);
|
|
}
|
|
|
|
static bool release(LGMPInput * input, bool * wake)
|
|
{
|
|
input->pendingHead = 0;
|
|
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 queuePayload(input, KVMFR_INPUT_MESSAGE_RELEASE,
|
|
&payload, false, wake);
|
|
}
|
|
|
|
static void flushPending(LGMPInput * input)
|
|
{
|
|
while (input->connected && input->pendingCount)
|
|
{
|
|
struct LGMPInputPending * item = pendingAt(input, 0);
|
|
const LGMP_STATUS status = lgmpClientTrySendData(input->queue,
|
|
&item->message, sizeof(item->message), NULL);
|
|
if (status == LGMP_ERR_QUEUE_BUSY || status == LGMP_ERR_QUEUE_FULL)
|
|
return;
|
|
if (status != LGMP_OK)
|
|
{
|
|
connectionFailed(input, status);
|
|
return;
|
|
}
|
|
|
|
published(input, &item->message);
|
|
input->pendingHead =
|
|
(input->pendingHead + 1) % INPUT_PENDING_LENGTH;
|
|
--input->pendingCount;
|
|
}
|
|
}
|
|
|
|
static void releaseOnDisconnect(LGMPInput * input)
|
|
{
|
|
if (!input->queue || !input->publishedGeneration)
|
|
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;
|
|
uint32_t serial = 0;
|
|
LGMP_STATUS status;
|
|
do
|
|
{
|
|
status = lgmpClientTrySendData(input->queue,
|
|
&message, sizeof(message), &serial);
|
|
if (status == LGMP_OK)
|
|
break;
|
|
if (status != LGMP_ERR_QUEUE_BUSY && status != LGMP_ERR_QUEUE_FULL)
|
|
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 ownership");
|
|
return;
|
|
}
|
|
|
|
do
|
|
{
|
|
uint32_t processed;
|
|
status = lgmpClientGetSerial(input->queue, &processed);
|
|
if (status != LGMP_OK)
|
|
return;
|
|
if ((int32_t)(processed - serial) >= 0)
|
|
return;
|
|
if (input->event)
|
|
lgWaitEvent(input->event, INPUT_WORKER_RETRY_MS);
|
|
}
|
|
while (microtime() < deadline);
|
|
|
|
DEBUG_WARN("Timed out waiting for LGMP input release");
|
|
}
|
|
|
|
static int inputThread(void * opaque)
|
|
{
|
|
LGMPInput * input = opaque;
|
|
while (!atomic_load_explicit(&input->stop, memory_order_acquire))
|
|
{
|
|
unsigned timeout = INPUT_WORKER_IDLE_MS;
|
|
bool wake = false;
|
|
LG_LOCK(input->lock);
|
|
processInputStatus(input, &wake);
|
|
flushPending(input);
|
|
|
|
const uint64_t now = microtime();
|
|
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 };
|
|
queuePayload(input, KVMFR_INPUT_MESSAGE_KEEPALIVE,
|
|
&payload, false, &wake);
|
|
}
|
|
|
|
if (input->pendingCount)
|
|
timeout = INPUT_WORKER_RETRY_MS;
|
|
LG_UNLOCK(input->lock);
|
|
|
|
notifyInputStatus(input);
|
|
if (wake)
|
|
lgSignalEvent(input->event);
|
|
|
|
if (atomic_load_explicit(&input->stop, memory_order_acquire))
|
|
break;
|
|
lgWaitEvent(input->event, timeout);
|
|
}
|
|
notifyInputStatus(input);
|
|
return 0;
|
|
}
|
|
|
|
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 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->pendingHead = 0;
|
|
input->pendingCount = 0;
|
|
input->clientID = clientID;
|
|
input->capabilities = 0;
|
|
input->endpointGeneration = 0;
|
|
input->statusSerial = 0;
|
|
input->statusOwnerClientID = 0;
|
|
input->statusOwnerGeneration = 0;
|
|
input->ownerConfirmed = false;
|
|
input->statusValid = false;
|
|
input->publishedGeneration = 0;
|
|
input->publishedSequence = 0;
|
|
input->publishedClaimed = false;
|
|
input->lastSend = 0;
|
|
input->lastInput = 0;
|
|
input->generation = 0;
|
|
clearInputState(input);
|
|
atomic_store_explicit(&input->stop, false, memory_order_release);
|
|
LGThread * thread;
|
|
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->notifyStatus = true;
|
|
input->available = false;
|
|
input->ownerBlocked = false;
|
|
input->capabilities = 0;
|
|
input->statusValid = false;
|
|
input->ownerConfirmed = false;
|
|
clearInputState(input);
|
|
atomic_store_explicit(&input->stop, true, memory_order_release);
|
|
LGThread * thread = input->thread;
|
|
LGEvent * event = input->event;
|
|
LG_UNLOCK(input->lock);
|
|
|
|
if (event)
|
|
lgSignalEvent(event);
|
|
if (thread)
|
|
lgJoinThread(thread, NULL);
|
|
|
|
LG_LOCK(input->lock);
|
|
releaseOnDisconnect(input);
|
|
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 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 void buildKeyboardPayload(const LGMPInput * input,
|
|
KVMFRInputPayload * payload)
|
|
{
|
|
for (unsigned usage = 224; usage <= 231; ++usage)
|
|
if (input->keyState[usage])
|
|
payload->keyboard.modifiers |= 1U << (usage - 224);
|
|
|
|
unsigned count = 0;
|
|
for (unsigned usage = 1; usage < 224; ++usage)
|
|
{
|
|
if (!input->keyState[usage])
|
|
continue;
|
|
if (count == KVMFR_INPUT_KEYBOARD_KEY_COUNT)
|
|
{
|
|
memset(payload->keyboard.keys, 1,
|
|
sizeof(payload->keyboard.keys));
|
|
return;
|
|
}
|
|
payload->keyboard.keys[count++] = (uint8_t)usage;
|
|
}
|
|
}
|
|
|
|
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);
|
|
bool result = queuePayload(input, KVMFR_INPUT_MESSAGE_KEYBOARD,
|
|
&payload, false, &wake);
|
|
if (!result && !pressed && input->connected)
|
|
result = release(input, &wake);
|
|
else if (!result && input->connected)
|
|
*state = previous;
|
|
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 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 queuePayload(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;
|
|
}
|
|
|
|
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)
|
|
{
|
|
reset = release(input, &wake);
|
|
result = reset;
|
|
}
|
|
if (result && !reset)
|
|
input->mouseButtons = buttons;
|
|
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 = NULL,
|
|
.mouseMotion = inputMouseMotion,
|
|
.mousePosition = inputMousePosition,
|
|
.mousePress = inputMousePress,
|
|
.mouseRelease = inputMouseRelease,
|
|
.reset = inputReset,
|
|
};
|
|
|
|
const LG_InputOps * lgmpInput_getOps(void)
|
|
{
|
|
return &INPUT_OPS;
|
|
}
|